Files
juc500/scripts/sync_peer.sh
T
2026-07-21 21:11:19 +09:00

291 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# juc500 — 수정한 파일을 USB 연결된 상대편으로 전송 (동기화만, 재시작은 수동)
#
# 개발 절차:
# 1) 소스 수정
# 2) ./scripts/sync_peer.sh ← 이 스크립트 (상대로 파일 전송)
# Windows 배포 산출물이 있으면 dist-windows/도 함께 전송
# 3) 이 PC에서 ./start.sh
# 4) 상대 PC (Void/Git Bash): cd C:/dev/juc500 && ./start.sh
#
# 상대 경로 (절대 경로, 슬래시 사용):
# Windows 기본: C:/dev/juc500
# Mac Studio: /Users/aidev/dev/juc500/juc500 (USE_MAC_PEER_PATHS=1)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
GUI_URL="${JUC500_GUI_URL:-http://127.0.0.1:8765}"
# Dev-only manual sync. Runtime does not auto-sync; set JUC500_AUTO_SYNC_PEER=1 to enable on connect.
# Windows peer at C:\dev\juc500 (Void에서 ./start.sh 실행 경로)
PEER_ROOT="${JUC500_PEER_ROOT:-C:/dev/juc500}"
PEER_ROOT_ALT="${JUC500_PEER_ROOT_ALT:-C:/dev/juc500/juc500}"
# Mac peer (browse 홈 기준 상대경로) — JUC500_PEER_ROOT 비우고 USE_MAC_PEER_PATHS=1
MAC_PEER_XFER="dev/juc500/juc500/juc500_xfer"
MAC_PEER_STATIC="dev/juc500/juc500/juc500_xfer/static"
MAC_PEER_SCRIPTS="dev/juc500/juc500/scripts"
MAC_PEER_ROOT_DIR="dev/juc500/juc500"
if [[ "${USE_MAC_PEER_PATHS:-0}" == "1" ]]; then
PEER_XFER="$MAC_PEER_XFER"
PEER_STATIC="$MAC_PEER_STATIC"
PEER_SCRIPTS="$MAC_PEER_SCRIPTS"
PEER_ROOT_DIR="$MAC_PEER_ROOT_DIR"
else
PEER_XFER="${PEER_ROOT}/juc500_xfer"
PEER_STATIC="${PEER_ROOT}/juc500_xfer/static"
PEER_SCRIPTS="${PEER_ROOT}/scripts"
PEER_ROOT_DIR="${PEER_ROOT}"
if [[ "$PEER_ROOT_ALT" != "$PEER_ROOT" ]]; then
PEER_XFER_ALT="${PEER_ROOT_ALT}/juc500_xfer"
PEER_STATIC_ALT="${PEER_ROOT_ALT}/juc500_xfer/static"
PEER_SCRIPTS_ALT="${PEER_ROOT_ALT}/scripts"
PEER_ROOT_DIR_ALT="${PEER_ROOT_ALT}"
fi
fi
PY=""
if [[ -x "$ROOT/.venv/bin/python" ]]; then
PY="$ROOT/.venv/bin/python"
elif command -v python3 >/dev/null 2>&1; then
PY="$(command -v python3)"
else
echo "python3 없음" >&2
exit 1
fi
send_files() {
local dest="$1"
shift
"$PY" - "$dest" "$GUI_URL" "$@" <<'PY'
import json, sys, urllib.request
dest, gui_url = sys.argv[1], sys.argv[2]
paths = sys.argv[3:]
body = json.dumps({"paths": paths, "dest_dir": dest}).encode()
req = urllib.request.Request(
f"{gui_url}/api/local/send",
data=body,
method="POST",
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as r:
print(r.read().decode())
PY
}
if ! curl -sf -m3 "$GUI_URL/api/status" >/dev/null 2>&1; then
echo "GUI가 떠 있지 않습니다. 먼저 ./start.sh 로 실행하세요." >&2
exit 1
fi
CONNECTED=$("$PY" -c "
import json, urllib.request
with urllib.request.urlopen('$GUI_URL/api/status', timeout=3) as r:
print(json.load(r).get('connected'))
" 2>/dev/null || echo "False")
if [[ "$CONNECTED" != "True" ]]; then
echo "상대편에 연결되지 않았습니다. 양쪽 GUI에서 「연결」을 누르세요." >&2
exit 1
fi
if [[ $# -gt 0 ]]; then
for f in "$@"; do
if [[ ! -f "$f" ]]; then
echo "파일 없음: $f" >&2
exit 1
fi
done
send_files "$PEER_XFER" "$@"
else
echo "=== 기본 파일 전송 → ${PEER_XFER} ==="
XFER_FILES=(
"$ROOT/juc500_xfer/gui_server.py"
"$ROOT/juc500_xfer/usb_link.py"
"$ROOT/juc500_xfer/inputshare.py"
"$ROOT/juc500_xfer/protocol.py"
"$ROOT/juc500_xfer/peer_sync.py"
"$ROOT/juc500_xfer/clipboard_files.py"
"$ROOT/juc500_xfer/__init__.py"
)
STATIC_FILES=(
"$ROOT/juc500_xfer/static/app.js"
"$ROOT/juc500_xfer/static/index.html"
"$ROOT/juc500_xfer/static/styles.css"
)
send_files "$PEER_XFER" "${XFER_FILES[@]}"
send_files "$PEER_STATIC" "${STATIC_FILES[@]}"
if [[ -n "${PEER_XFER_ALT:-}" ]]; then
echo "=== legacy editable 경로 → ${PEER_XFER_ALT} ==="
send_files "$PEER_XFER_ALT" "${XFER_FILES[@]}"
send_files "$PEER_STATIC_ALT" "${STATIC_FILES[@]}"
fi
if [[ -f "$ROOT/scripts/peer_restart.sh" ]]; then
send_files "$PEER_SCRIPTS" "$ROOT/scripts/peer_restart.sh"
if [[ -n "${PEER_SCRIPTS_ALT:-}" ]]; then
send_files "$PEER_SCRIPTS_ALT" "$ROOT/scripts/peer_restart.sh"
fi
fi
if [[ -f "$ROOT/start.sh" ]]; then
send_files "$PEER_ROOT_DIR" "$ROOT/start.sh"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "$PEER_ROOT_DIR_ALT" "$ROOT/start.sh"
fi
fi
if [[ -f "$ROOT/start.bat" ]]; then
send_files "$PEER_ROOT_DIR" "$ROOT/start.bat"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "$PEER_ROOT_DIR_ALT" "$ROOT/start.bat"
fi
fi
if [[ -f "$ROOT/start_win.sh" ]]; then
send_files "$PEER_ROOT_DIR" "$ROOT/start_win.sh"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "$PEER_ROOT_DIR_ALT" "$ROOT/start_win.sh"
fi
fi
if [[ -f "$ROOT/start_both.sh" ]]; then
send_files "$PEER_ROOT_DIR" "$ROOT/start_both.sh"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "$PEER_ROOT_DIR_ALT" "$ROOT/start_both.sh"
fi
fi
if [[ -f "$ROOT/README.md" ]]; then
send_files "$PEER_ROOT_DIR" "$ROOT/README.md"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "$PEER_ROOT_DIR_ALT" "$ROOT/README.md"
fi
fi
WINDOWS_BUILD_ROOT_FILES=()
for build_file in \
build-installer-windows.bat \
build-installer-windows.sh \
juc500-windows.spec \
juc500-windows-onefile.spec \
juc500_app.py \
requirements.txt \
pyproject.toml; do
if [[ -f "$ROOT/$build_file" ]]; then
WINDOWS_BUILD_ROOT_FILES+=("$ROOT/$build_file")
fi
done
if [[ ${#WINDOWS_BUILD_ROOT_FILES[@]} -gt 0 ]]; then
echo "=== Windows 빌드 파일 → ${PEER_ROOT_DIR} ==="
send_files "$PEER_ROOT_DIR" "${WINDOWS_BUILD_ROOT_FILES[@]}"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "$PEER_ROOT_DIR_ALT" "${WINDOWS_BUILD_ROOT_FILES[@]}"
fi
fi
WINDOWS_PACKAGING_FILES=()
for packaging_file in "$ROOT"/packaging/windows/*; do
if [[ -f "$packaging_file" ]]; then
WINDOWS_PACKAGING_FILES+=("$packaging_file")
fi
done
if [[ ${#WINDOWS_PACKAGING_FILES[@]} -gt 0 ]]; then
echo "=== Windows 패키징 파일 → ${PEER_ROOT_DIR}/packaging/windows ==="
send_files "${PEER_ROOT_DIR}/packaging/windows" "${WINDOWS_PACKAGING_FILES[@]}"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "${PEER_ROOT_DIR_ALT}/packaging/windows" "${WINDOWS_PACKAGING_FILES[@]}"
fi
fi
WINDOWS_DIST_FILES=()
for artifact in \
JUC500-Setup-win64.exe \
JUC500-portable.exe \
JUC500-win64-portable.zip \
SHA256SUMS.txt; do
if [[ -f "$ROOT/dist-windows/$artifact" ]]; then
WINDOWS_DIST_FILES+=("$ROOT/dist-windows/$artifact")
fi
done
if [[ ${#WINDOWS_DIST_FILES[@]} -gt 0 ]]; then
echo "=== Windows 설치/포터블 → ${PEER_ROOT_DIR}/dist-windows ==="
send_files "${PEER_ROOT_DIR}/dist-windows" "${WINDOWS_DIST_FILES[@]}"
if [[ -n "${PEER_ROOT_DIR_ALT:-}" ]]; then
send_files "${PEER_ROOT_DIR_ALT}/dist-windows" "${WINDOWS_DIST_FILES[@]}"
fi
else
echo "⚠ dist-windows 산출물 없음 — ./build-installer-windows.sh --ci 실행 필요"
fi
if [[ -f "$ROOT/vendor/libusb-1.0.dll" ]]; then
echo "=== libusb DLL → ${PEER_ROOT}/vendor ==="
if ! send_files "${PEER_ROOT}/vendor" "$ROOT/vendor/libusb-1.0.dll"; then
echo "⚠ libusb DLL 전송 생략 — 상대 앱이 사용 중일 수 있음 (배포 EXE에는 이미 포함됨)" >&2
fi
if [[ -n "${PEER_ROOT_ALT:-}" && "$PEER_ROOT_ALT" != "$PEER_ROOT" ]]; then
if ! send_files "${PEER_ROOT_ALT}/vendor" "$ROOT/vendor/libusb-1.0.dll"; then
echo "⚠ legacy 경로 libusb DLL 전송 생략 — 상대 앱 종료 후 재시도" >&2
fi
fi
fi
for win_script in ensure_libusb_windows.bat setup_windows.bat bootstrap_windows.sh make_windows_bundle.sh make_offline_windows_update.sh apply_offline_update.bat; do
if [[ -f "$ROOT/scripts/$win_script" ]]; then
send_files "$PEER_SCRIPTS" "$ROOT/scripts/$win_script"
if [[ -n "${PEER_SCRIPTS_ALT:-}" ]]; then
send_files "$PEER_SCRIPTS_ALT" "$ROOT/scripts/$win_script"
fi
fi
done
if [[ -f "$ROOT/vendor/tools/zadig.exe" ]]; then
echo "=== zadig → ${PEER_ROOT}/vendor/tools ==="
send_files "${PEER_ROOT}/vendor/tools" "$ROOT/vendor/tools/zadig.exe"
fi
fi
LOCAL_INPUTSHARE=$("$PY" -c "from juc500_xfer.inputshare import INPUTSHARE_BUILD; print(INPUTSHARE_BUILD)" 2>/dev/null || echo "unknown")
LOCAL_XFER=$("$PY" -c "from juc500_xfer.usb_link import XFER_BUILD; print(XFER_BUILD)" 2>/dev/null || echo "unknown")
echo ""
echo "로컬 build — inputshare: $LOCAL_INPUTSHARE · xfer: $LOCAL_XFER"
echo "상대편 build 확인 중 (RPC ping, 재시작 전이면 구버전일 수 있음)…"
read -r PEER_INPUTSHARE PEER_XFER < <(
curl -sf -m10 "$GUI_URL/api/peer/ping" 2>/dev/null | "$PY" -c "
import json,sys
try:
d=json.load(sys.stdin)
print(d.get('inputshare_build') or 'unknown', d.get('xfer_build') or 'unknown')
except Exception:
print('check-failed check-failed')
" 2>/dev/null || echo "check-failed check-failed"
)
echo "상대편 build — inputshare: $PEER_INPUTSHARE · xfer: $PEER_XFER"
if [[ "$PEER_INPUTSHARE" == "$LOCAL_INPUTSHARE" && "$PEER_XFER" == "$LOCAL_XFER" ]]; then
echo "✓ 상대편 build 일치"
else
echo "⚠ 상대편 build 불일치 — 아래 3·4단계에서 양쪽 ./start.sh 로 새 코드 로드"
fi
print_dev_workflow() {
echo ""
echo "=== 개발 적용 절차 (남은 단계) ==="
echo "3) 이 PC: cd $ROOT && ./start.sh"
if [[ "${USE_MAC_PEER_PATHS:-0}" == "1" ]]; then
echo "4) 상대 Mac: cd /Users/aidev/dev/juc500/juc500 && ./start.sh"
else
echo "4) 상대 PC: cd ${PEER_ROOT} && ./start.sh (Void/Git Bash)"
echo " 시작 시 'JUC500 build: ...juc700-win9 | ...\\juc500_xfer\\usb_link.py' 확인"
if [[ -n "${PEER_XFER_ALT:-}" ]]; then
echo " ※ legacy editable 경로(${PEER_XFER_ALT})에도 동기화함"
fi
fi
echo ""
echo "build 확인: curl -s $GUI_URL/api/status | grep xfer_build"
echo " curl -s $GUI_URL/api/peer/ping"
}
# 개발 중에는 RPC 자동 재시작을 쓰지 않음 (루프·경로 오류 방지). 필요 시 JUC500_SYNC_RESTART=1
if [[ "${JUC500_SYNC_RESTART:-0}" == "1" ]]; then
echo ""
echo "=== RPC 자동 재시작 (JUC500_SYNC_RESTART=1) ==="
resp="$(curl -sf -m25 -X POST "$GUI_URL/api/restart/peer" -H 'Content-Type: application/json' -d '{}' || true)"
if [[ -n "$resp" ]]; then
echo "$resp"
else
echo "⚠ 상대 RPC 재시작 실패 — 4단계에서 수동 ./start.sh" >&2
fi
fi
print_dev_workflow