#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \ scripts/run-hypertwist-remote-windows-file-sync.sh \ --file UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp Options: --file Relative repo path to copy into the remote Windows worktree. May be repeated. --remote-root Optional Windows worktree root. Defaults to C:\HyperTwist_worktrees\phase10validate. --dry-run Print the copy plan without transferring files. Environment: HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required password for the reverse-SSH Windows user. HYPERTWIST_REMOTE_TUNNEL_PORT Optional, defaults to 22022. HYPERTWIST_REMOTE_WINDOWS_USER Optional, defaults to "anthracite ace". HYPERTWIST_REMOTE_TUNNEL_HOST Optional, defaults to "localhost". EOF } if ! command -v sshpass >/dev/null 2>&1; then echo "sshpass is required but was not found on PATH." >&2 exit 1 fi if ! command -v sha256sum >/dev/null 2>&1; then echo "sha256sum is required but was not found on PATH." >&2 exit 1 fi if ! command -v scp >/dev/null 2>&1; then echo "scp is required but was not found on PATH." >&2 exit 1 fi if ! command -v python3 >/dev/null 2>&1; then echo "python3 is required but was not found on PATH." >&2 exit 1 fi if [[ -z "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD:-}" ]]; then echo "Set HYPERTWIST_REMOTE_WINDOWS_PASSWORD before using this wrapper." >&2 exit 1 fi remote_root='C:\HyperTwist_worktrees\phase10validate' dry_run="false" files=() while [[ $# -gt 0 ]]; do case "$1" in --file) files+=("${2:-}") shift 2 ;; --remote-root) remote_root="${2:-}" shift 2 ;; --dry-run) dry_run="true" shift ;; -h|--help) usage exit 0 ;; *) usage >&2 exit 1 ;; esac done if [[ ${#files[@]} -eq 0 ]]; then echo "Provide at least one --file." >&2 exit 1 fi remote_port="${HYPERTWIST_REMOTE_TUNNEL_PORT:-22022}" remote_user="${HYPERTWIST_REMOTE_WINDOWS_USER:-anthracite ace}" remote_host="${HYPERTWIST_REMOTE_TUNNEL_HOST:-localhost}" encode_powershell_command() { local script_text="$1" python3 - <<'PY' "$script_text" import base64 import sys script = sys.argv[1] sys.stdout.write(base64.b64encode(script.encode("utf-16le")).decode("ascii")) PY } ps_single_quote() { printf "'%s'" "${1//\'/\'\'}" } ssh_base=( sshpass -p "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD}" ssh -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no -p "${remote_port}" -l "${remote_user}" "${remote_host}" ) scp_base=( sshpass -p "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD}" scp -q -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no -P "${remote_port}" ) for relative_path in "${files[@]}"; do if [[ "$relative_path" = /* ]]; then echo "Use repo-relative paths only: $relative_path" >&2 exit 1 fi if [[ ! -f "$relative_path" ]]; then echo "File not found: $relative_path" >&2 exit 1 fi windows_relative_path="${relative_path//\//\\}" windows_dest_path="${remote_root}\\${windows_relative_path}" local_hash="$(sha256sum "$relative_path" | awk '{print tolower($1)}')" windows_incoming_path="${windows_dest_path}.hypertwist-incoming-${local_hash:0:12}" if [[ "$dry_run" == "true" ]]; then printf '%s -> %s (%s)\n' "$relative_path" "$windows_dest_path" "$local_hash" continue fi remote_prepare_script="$( cat </dev/null incoming_scp_path="${windows_incoming_path//\\//}" "${scp_base[@]}" \ "$relative_path" \ "${remote_user}@${remote_host}:${incoming_scp_path}" remote_finalize_script="$( cat <&2 echo " local : $local_hash" >&2 echo " remote: $remote_hash" >&2 exit 1 fi printf 'Synced %s -> %s (%s)\n' "$relative_path" "$windows_dest_path" "$local_hash" done