#!/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