From 48a5e7f7d6638850ceff1765932421a72c6763df Mon Sep 17 00:00:00 2001 From: KellyMichels Date: Wed, 22 Jul 2026 17:46:51 -0500 Subject: [PATCH] fix(bash): translate Windows config paths to WSL/Unix form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A shared zconfig.json (one file used from a Windows checkout and from WSL via the symlink) holds Windows paths like "F:\evomedia.net\app". The bash port passed those to local file ops verbatim, so every path-using command failed on WSL (e.g. zbackup: "Root not found: F:\evomedia.net\evomedia-docs"). Add a z_path helper that converts drive-letter paths to the host's native form (wslpath, with a /mnt fallback) and is a no-op for Unix paths and empty strings — so a bash-native config is unaffected. Route every LOCAL path through it: localRoot (new zproj_root accessor), paths.* (temp, backupsLocal, backupsEc2, scriptsRoot, oneDriveBackups), ec2.pemKey (zec2_pem), and ztokens.dataDir. Server-side paths (remote.path, composeDir, stackRoot, certsSource) are left verbatim. Verified on WSL: all 11 project roots, all paths.*, and the C:/G: pem and OneDrive paths resolve to existing dirs; zbackup evodocs (the failing case) now runs clean end-to-end. --- bash/zbackup | 6 +++--- bash/zbackup_ec2 | 2 +- bash/zdeploy | 14 +++++++------- bash/zhelpers.sh | 32 +++++++++++++++++++++++++++++--- bash/zstart | 8 ++++---- bash/zsync | 6 +++--- 6 files changed, 47 insertions(+), 21 deletions(-) diff --git a/bash/zbackup b/bash/zbackup index 70f2088..68b3d2c 100644 --- a/bash/zbackup +++ b/bash/zbackup @@ -29,7 +29,7 @@ while [ $# -gt 0 ]; do esac done -BACKUP_ROOT="$(zq '.paths.backupsLocal')" +BACKUP_ROOT="$(z_path "$(zq '.paths.backupsLocal')")" include_scripts=0 if [ "${#projects[@]}" -eq 0 ]; then mapfile -t projects < <(zproj_keys) @@ -76,7 +76,7 @@ exit_code=0 backup_project() { # local key="$1" root out_dir zip_path dump_dir db_dump extra="-" zproj_require "$key" - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" out_dir="$BACKUP_ROOT/$key"; mkdir -p "$out_dir" zip_path="$out_dir/$(ts)_${key}$(tag_suffix).zip" @@ -103,7 +103,7 @@ backup_project() { # backup_scripts() { local out_dir zip_path scripts_root - scripts_root="$(zq '.paths.scriptsRoot')" + scripts_root="$(z_path "$(zq '.paths.scriptsRoot')")" out_dir="$BACKUP_ROOT/scripts"; mkdir -p "$out_dir" zip_path="$out_dir/$(ts)_scripts$(tag_suffix).zip" diff --git a/bash/zbackup_ec2 b/bash/zbackup_ec2 index 828c047..ddd54af 100644 --- a/bash/zbackup_ec2 +++ b/bash/zbackup_ec2 @@ -25,7 +25,7 @@ if [ "${#projects[@]}" -eq 0 ]; then mapfile -t projects < <(jq -r '.projects | to_entries[] | select((.key | startswith("_") | not) and .value.remote.path != null) | .key' "$ZCONFIG") fi -EC2_BACKUP_ROOT="$(zq '.paths.backupsEc2')" +EC2_BACKUP_ROOT="$(z_path "$(zq '.paths.backupsEc2')")" REMOTE_HOME="/home/$(zec2_user)" scp_down() { # diff --git a/bash/zdeploy b/bash/zdeploy index 3e09192..1c68cdd 100644 --- a/bash/zdeploy +++ b/bash/zdeploy @@ -40,7 +40,7 @@ EC2_IP="$(zec2_ip)" STACK_ROOT="$(zq '.ec2.stackRoot')" REMOTE_HOME="/home/$(zec2_user)" EC2_USER="$(zec2_user)" -TEMP_ROOT="$(zq '.paths.temp')" +TEMP_ROOT="$(z_path "$(zq '.paths.temp')")" { [ -z "$TEMP_ROOT" ] || [ "$TEMP_ROOT" = "null" ]; } && TEMP_ROOT="${TMPDIR:-/tmp}" mkdir -p "$TEMP_ROOT" @@ -167,7 +167,7 @@ wait_verify_static() { # deploy_git_pull() { # local key="$1" root branch [ "$(zproj "$key" .deploy.gitPull)" = "true" ] || return 0 - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" if [ ! -d "$root/.git" ]; then warn " gitPull set but '$root' is not a git repo - skipping pull." return 0 @@ -187,7 +187,7 @@ deploy_git_pull() { # deploy_python() { # local key="$1" start=$SECONDS root remote_path compose_dir app_svc zip_name zip_local local bv="" has_version_tool=0 excl=() attempt out label domain - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" remote_path="$(zproj "$key" .remote.path)" compose_dir="$(zremote_compose_dir "$key")" app_svc="$(zproj "$key" .remote.appService)"; [ -n "$app_svc" ] || app_svc="app" @@ -272,7 +272,7 @@ deploy_python() { # deploy_vite() { # local key="$1" start=$SECONDS root remote_path zip_name zip_local excl=() local pre_pv="" pre_bn="" label domain edge_pc - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" remote_path="$(zproj "$key" .remote.path)" zip_name="$(deploy_zip_name "$key")" zip_local="$TEMP_ROOT/$zip_name" @@ -328,7 +328,7 @@ deploy_vite() { # deploy_next() { # local key="$1" start=$SECONDS root remote_path app_svc zip_name zip_local excl=() local pre_pv="" pre_bn="" label domain db_user db_name prod_port - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" remote_path="$(zproj "$key" .remote.path)" app_svc="$(zproj "$key" .remote.appService)"; [ -n "$app_svc" ] || app_svc="web" zip_name="$(deploy_zip_name "$key")" @@ -412,7 +412,7 @@ deploy_next() { # deploy_edge() { # local key="$1" root remote_path pc label cert_mount="" rm_stale="" f - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" remote_path="$(zproj "$key" .remote.path)" pc="$(zproj "$key" .proxyContainer)" label="$(zproj "$key" .label)" @@ -452,7 +452,7 @@ deploy_edge() { # deploy_docker() { # local key="$1" root remote_path label f domain - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" remote_path="$(zproj "$key" .remote.path)" label="$(zproj "$key" .label)" domain="$(zproj "$key" .domain)" diff --git a/bash/zhelpers.sh b/bash/zhelpers.sh index b4e2ad8..b470571 100644 --- a/bash/zhelpers.sh +++ b/bash/zhelpers.sh @@ -33,6 +33,30 @@ z_require() { [ "$miss" -eq 0 ] || exit 1 } +# ---- local path translation ------------------------------------------------- +# A shared zconfig.json may hold Windows paths (e.g. "F:\evomedia.net\app") when +# the same file is used from a Windows checkout and from WSL. Translate a +# drive-letter path to the host's native form so local file ops work; leave +# anything else (Unix paths, empty strings) untouched — so this is a no-op on +# native Linux/macOS. Only LOCAL paths get run through this; server-side paths +# (remote.path, composeDir, stackRoot, certsSource) stay verbatim. +z_path() { + local p="$1" + case "$p" in + [A-Za-z]:[\\/]*) + if command -v wslpath >/dev/null 2>&1; then + wslpath -u "$p" 2>/dev/null || printf '%s' "$p" + else + # Fallback when wslpath is absent: F:\a\b -> /mnt/f/a/b + local drive="${p%%:*}" rest="${p#*:}" + rest="${rest//\\//}" + printf '/mnt/%s%s' "${drive,,}" "$rest" + fi + ;; + *) printf '%s' "$p" ;; + esac +} + # ---- config accessors (jq) -------------------------------------------------- z_need_config() { z_require jq @@ -52,6 +76,8 @@ zproj_keys_with_domain() { } # Field of a project: zproj e.g. zproj sp .kind | zproj sp .ports.dev zproj() { jq -r --arg k "$1" ".projects[\$k]$2 // empty" "$ZCONFIG"; } +# A project's local root, translated to the host's native path form. +zproj_root() { z_path "$(zproj "$1" .localRoot)"; } zproj_require() { if [ "$(jq -r --arg k "$1" '(.projects[$k] != null)' "$ZCONFIG")" != "true" ]; then err "Unknown project key '$1'. Available: $(zproj_csv)" @@ -60,7 +86,7 @@ zproj_require() { } zec2_ip() { zq '.ec2.ip'; } zec2_user() { zq '.ec2.user'; } -zec2_pem() { zq '.ec2.pemKey'; } +zec2_pem() { z_path "$(zq '.ec2.pemKey')"; } zec2_target() { printf '%s@%s' "$(zec2_user)" "$(zec2_ip)"; } # Remote compose directory: remote.composeDir if set, else remote.path. zremote_compose_dir() { @@ -124,7 +150,7 @@ json_build_label() { # Local build label by project kind (mirrors Get-LocalVersionLabel). local_version_label() { local key="$1" kind root out - kind="$(zproj "$key" .kind)"; root="$(zproj "$key" .localRoot)" + kind="$(zproj "$key" .kind)"; root="$(zproj_root "$key")" case "$kind" in python) if [ -f "$root/scripts/build_version_tool.py" ]; then @@ -294,7 +320,7 @@ z_record() { # command -v jq >/dev/null 2>&1 || return local lines="$1" chars="$2" est="$3" dir model projects dir="${ZTOKENS_DATA:-}" - [ -z "$dir" ] && [ -f "$ZCONFIG" ] && dir="$(jq -r '.ztokens.dataDir // empty' "$ZCONFIG" 2>/dev/null)" + [ -z "$dir" ] && [ -f "$ZCONFIG" ] && dir="$(z_path "$(jq -r '.ztokens.dataDir // empty' "$ZCONFIG" 2>/dev/null)")" if [ -z "$dir" ]; then if [ -d "$_ZDIR/../../ztokens/data" ]; then dir="$_ZDIR/../../ztokens/data"; else dir="$HOME/.ztokens/data"; fi fi diff --git a/bash/zstart b/bash/zstart index cd78def..51f6f0b 100644 --- a/bash/zstart +++ b/bash/zstart @@ -52,7 +52,7 @@ detach() { # # "start": { "gitPull": true, "env": { "SOME_FLAG": "1" } } start_prep() { # local key="$1" root kv name val - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" while IFS=$'\t' read -r name val; do [ -n "$name" ] || continue export "$name=$val" @@ -68,7 +68,7 @@ start_prep() { # start_python() { # local key="$1" port="$2" root module exe bv="" - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" module="$(zproj "$key" .startModule)" [ -d "$root" ] || { err "Project root not found: $root"; return 1; } [ -n "$module" ] || { err "Project '$key' (kind=python) needs 'startModule' in zconfig.json (e.g. \"startModule\": \"pyapp.main\")."; return 1; } @@ -105,7 +105,7 @@ start_python() { # start_vite() { # local key="$1" port="$2" root - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" [ -d "$root" ] || { err "Project root not found: $root"; return 1; } [ -f "$root/package.json" ] || { err "package.json not found in $root"; return 1; } @@ -133,7 +133,7 @@ start_vite() { # start_next() { # local key="$1" port="$2" root - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" [ -d "$root" ] || { err "Project root not found: $root"; return 1; } [ -f "$root/package.json" ] || { err "package.json not found in $root"; return 1; } diff --git a/bash/zsync b/bash/zsync index 4e365bf..b96b6bc 100644 --- a/bash/zsync +++ b/bash/zsync @@ -29,8 +29,8 @@ done # ---- no-args mode: backups -> offsite, new files only ------------------------ if [ "${#projects[@]}" -eq 0 ]; then - BACKUPS_ROOT="$(dirname "$(zq '.paths.backupsLocal')")" - OFFSITE_ROOT="$(zq '.paths.oneDriveBackups')" + BACKUPS_ROOT="$(dirname "$(z_path "$(zq '.paths.backupsLocal')")")" + OFFSITE_ROOT="$(z_path "$(zq '.paths.oneDriveBackups')")" printf '\n'; info "=== zsync (backups -> offsite) ===" dim " Source: $BACKUPS_ROOT" @@ -73,7 +73,7 @@ for key in "${projects[@]}"; do printf '\n'; warn "zsync project mode only supports vite kind (builds and mirrors dist/). '$key' is kind '$kind'." exit 1 fi - root="$(zproj "$key" .localRoot)" + root="$(zproj_root "$key")" if [ -z "$dest" ]; then printf '\n'; info "=== zsync ($(zproj "$key" .label)) ==="