291 lines
10 KiB
Bash
Executable File
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
|