juc500 정상동작
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
@echo off
|
||||
REM Offline overlay update for closed-network Windows 11.
|
||||
REM Run from the extracted update folder (or pass DEST as arg 1).
|
||||
setlocal EnableExtensions
|
||||
set "SRC=%~dp0"
|
||||
if "%SRC:~-1%"=="\" set "SRC=%SRC:~0,-1%"
|
||||
REM If this bat lives in scripts\, SRC is package root when called as scripts\apply...
|
||||
if exist "%SRC%\juc500_xfer\usb_link.py" goto :have_src
|
||||
if exist "%SRC%\..\juc500_xfer\usb_link.py" (
|
||||
set "SRC=%SRC%\.."
|
||||
goto :have_src
|
||||
)
|
||||
echo [!] juc500_xfer not found next to this script.
|
||||
echo Extract the offline update zip first, then run this bat from that folder.
|
||||
exit /b 1
|
||||
|
||||
:have_src
|
||||
set "DEST=%~1"
|
||||
if "%DEST%"=="" set "DEST=C:\dev\juc500"
|
||||
echo === JUC500 offline update ===
|
||||
echo SRC = %SRC%
|
||||
echo DEST= %DEST%
|
||||
if not exist "%DEST%" (
|
||||
echo [!] DEST missing: %DEST%
|
||||
echo Create it or pass path: apply_offline_update.bat D:\juc500
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Copying runtime sources...
|
||||
robocopy "%SRC%\juc500_xfer" "%DEST%\juc500_xfer" /E /XD __pycache__ .git /NFL /NDL /NJH /NJS /nc /ns /np
|
||||
if errorlevel 8 exit /b 1
|
||||
|
||||
if exist "%SRC%\scripts" (
|
||||
robocopy "%SRC%\scripts" "%DEST%\scripts" /E /XD __pycache__ /NFL /NDL /NJH /NJS /nc /ns /np
|
||||
)
|
||||
|
||||
for %%F in (start.bat start.sh start_win.sh start_both.sh juc500_app.py requirements.txt README.md) do (
|
||||
if exist "%SRC%\%%F" copy /Y "%SRC%\%%F" "%DEST%\%%F" >nul
|
||||
)
|
||||
|
||||
if exist "%SRC%\vendor\libusb-1.0.dll" (
|
||||
if not exist "%DEST%\vendor" mkdir "%DEST%\vendor"
|
||||
copy /Y "%SRC%\vendor\libusb-1.0.dll" "%DEST%\vendor\libusb-1.0.dll" >nul
|
||||
if exist "%DEST%\.venv\Scripts" copy /Y "%SRC%\vendor\libusb-1.0.dll" "%DEST%\.venv\Scripts\libusb-1.0.dll" >nul
|
||||
)
|
||||
|
||||
if exist "%SRC%\vendor\wheels" (
|
||||
if not exist "%DEST%\vendor\wheels" mkdir "%DEST%\vendor\wheels"
|
||||
robocopy "%SRC%\vendor\wheels" "%DEST%\vendor\wheels" *.whl /NFL /NDL /NJH /NJS /nc /ns /np
|
||||
)
|
||||
|
||||
if exist "%SRC%\vendor\tools\zadig.exe" (
|
||||
if not exist "%DEST%\vendor\tools" mkdir "%DEST%\vendor\tools"
|
||||
copy /Y "%SRC%\vendor\tools\zadig.exe" "%DEST%\vendor\tools\zadig.exe" >nul
|
||||
)
|
||||
|
||||
if exist "%SRC%\docs" (
|
||||
if not exist "%DEST%\docs" mkdir "%DEST%\docs"
|
||||
robocopy "%SRC%\docs" "%DEST%\docs" /E /NFL /NDL /NJH /NJS /nc /ns /np
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Verifying build tag...
|
||||
if exist "%DEST%\.venv\Scripts\python.exe" (
|
||||
"%DEST%\.venv\Scripts\python.exe" -c "from juc500_xfer.usb_link import XFER_BUILD; print('XFER_BUILD=', XFER_BUILD)"
|
||||
) else (
|
||||
echo [.venv missing — run start.bat once to bootstrap from vendor]
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Done. Close JUC500 GUI, then:
|
||||
echo cd /d %DEST%
|
||||
echo start.bat
|
||||
echo GUI should show build tag from BUILD.txt (e.g. 2026-07-21-juc500-only).
|
||||
echo JUC500: Zadig MI_05 WinUSB on 0711:7500 — vendor\tools\zadig.exe
|
||||
endlocal
|
||||
exit /b 0
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
# Windows 오프라인 초기 설정 — Git Bash / MSYS (cmd.exe 경로 변환 없이 직접 실행)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
WHEELS="$ROOT/vendor/wheels"
|
||||
GETPIP="$ROOT/vendor/get-pip.py"
|
||||
LIBUSB="$ROOT/vendor/libusb-1.0.dll"
|
||||
EMBED_PY="$ROOT/vendor/python-windows/python.exe"
|
||||
VENV_PY="$ROOT/.venv/Scripts/python.exe"
|
||||
MAC_VENV="$ROOT/.venv/bin/python"
|
||||
|
||||
if [[ ! -f "$WHEELS/pyusb-1.2.1-py3-none-any.whl" || ! -f "$LIBUSB" ]]; then
|
||||
echo "[!] vendor 번들이 불완전합니다. 압축 시 vendor/ 폴더를 반드시 포함하세요." >&2
|
||||
echo " 필요: vendor/wheels, vendor/libusb-1.0.dll, vendor/get-pip.py" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mac .venv 가 zip 에 섞이면 Windows 에서 깨짐
|
||||
if [[ -f "$MAC_VENV" && ! -f "$VENV_PY" ]]; then
|
||||
echo "[*] Mac .venv 제거 중…"
|
||||
rm -rf "$ROOT/.venv"
|
||||
fi
|
||||
|
||||
venv_ok() {
|
||||
[[ -f "$VENV_PY" ]] || return 1
|
||||
PYTHONPATH="$ROOT${PYTHONPATH:+:$PYTHONPATH}" "$VENV_PY" -c "import usb" 2>/dev/null || return 1
|
||||
PYTHONPATH="$ROOT${PYTHONPATH:+:$PYTHONPATH}" "$VENV_PY" -c "
|
||||
from juc500_xfer.usb_link import libusb_path
|
||||
import sys
|
||||
sys.exit(0 if libusb_path() else 1)
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
if venv_ok; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "============================================"
|
||||
echo " JUC500 Windows 오프라인 초기 설정"
|
||||
echo "============================================"
|
||||
|
||||
pick_boot_py() {
|
||||
if command -v py >/dev/null 2>&1; then
|
||||
if py -3 -c "import sys" 2>/dev/null; then
|
||||
echo "py -3"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if command -v python >/dev/null 2>&1; then
|
||||
if python -c "import sys" 2>/dev/null; then
|
||||
echo "python"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if [[ -f "$EMBED_PY" ]]; then
|
||||
echo "$EMBED_PY"
|
||||
echo "Using vendored Python: vendor/python-windows/python.exe" >&2
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
BOOT_PY="$(pick_boot_py)" || {
|
||||
echo "[!] Python 없음. vendor/python-windows 가 있는지 확인하세요." >&2
|
||||
exit 1
|
||||
}
|
||||
echo "Bootstrap: $BOOT_PY"
|
||||
|
||||
run_boot() {
|
||||
# shellcheck disable=SC2086
|
||||
$BOOT_PY "$@"
|
||||
}
|
||||
|
||||
if ! run_boot -c "import virtualenv" 2>/dev/null; then
|
||||
echo "Installing virtualenv from vendor/wheels …"
|
||||
if ! run_boot -m pip install --no-index --find-links="$WHEELS" virtualenv 2>/dev/null; then
|
||||
if [[ -f "$GETPIP" ]]; then
|
||||
run_boot "$GETPIP" --no-index --find-links="$WHEELS" pip setuptools wheel
|
||||
run_boot -m pip install --no-index --find-links="$WHEELS" virtualenv
|
||||
else
|
||||
echo "[!] virtualenv 설치 실패" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$VENV_PY" ]]; then
|
||||
echo "Creating .venv (offline)…"
|
||||
run_boot -m virtualenv --no-periodic-update --no-setuptools --no-pip "$ROOT/.venv"
|
||||
run_boot "$GETPIP" --no-index --find-links="$WHEELS" pip setuptools wheel packaging
|
||||
elif ! "$VENV_PY" -m pip --version >/dev/null 2>&1; then
|
||||
echo "[*] .venv 손상 — 재생성…"
|
||||
rm -rf "$ROOT/.venv"
|
||||
run_boot -m virtualenv --no-periodic-update --no-setuptools --no-pip "$ROOT/.venv"
|
||||
run_boot "$GETPIP" --no-index --find-links="$WHEELS" pip setuptools wheel packaging
|
||||
fi
|
||||
|
||||
echo "Installing dependencies from vendor/wheels (no PyPI)…"
|
||||
"$VENV_PY" -m pip install --no-index --find-links="$WHEELS" -U pip setuptools wheel packaging
|
||||
"$VENV_PY" -m pip install --no-index --find-links="$WHEELS" -r "$ROOT/requirements.txt"
|
||||
"$VENV_PY" -m pip install --no-index --find-links="$WHEELS" --no-build-isolation -e "$ROOT"
|
||||
|
||||
cp -f "$LIBUSB" "$ROOT/.venv/Scripts/libusb-1.0.dll"
|
||||
echo "libusb-1.0.dll → .venv/Scripts/"
|
||||
|
||||
if ! venv_ok; then
|
||||
echo "[!] 초기 설정 후에도 .venv 검증 실패" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Windows 초기 설정 완료"
|
||||
exit 0
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# 개발 적용: sync_peer (동기화) + 수동 재시작 안내
|
||||
# 1) 소스 수정
|
||||
# 2) 이 스크립트 또는 ./scripts/sync_peer.sh
|
||||
# 3) ./scripts/stop_tauri_watchers.sh (Tauri 케이블 감시 종료)
|
||||
# 4) ./start.sh (이 PC)
|
||||
# 5) 상대 PC: cd C:/dev/juc500 && ./start.sh
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
if [[ -x "$ROOT/scripts/stop_tauri_watchers.sh" ]]; then
|
||||
"$ROOT/scripts/stop_tauri_watchers.sh" || true
|
||||
fi
|
||||
|
||||
export JUC500_SYNC_RESTART=0
|
||||
exec "$ROOT/scripts/sync_peer.sh" "$@"
|
||||
@@ -0,0 +1,22 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
cd /d "%~dp0.."
|
||||
set "ROOT=%CD%"
|
||||
set "SRC=%ROOT%\vendor\libusb-1.0.dll"
|
||||
|
||||
if not exist "%SRC%" (
|
||||
echo [!] Missing vendor\libusb-1.0.dll
|
||||
echo Copy the full repo including vendor\ from the Mac, or run scripts\setup_windows.bat
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\.venv\Scripts\python.exe" (
|
||||
echo [!] .venv missing. Run: scripts\setup_windows.bat
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
copy /Y "%SRC%" "%ROOT%\.venv\Scripts\libusb-1.0.dll" >nul
|
||||
echo libusb-1.0.dll -^> .venv\Scripts\
|
||||
|
||||
"%ROOT%\.venv\Scripts\python.exe" -c "from juc500_xfer.usb_link import libusb_path; p=libusb_path(); print('libusb OK:', p or 'FAILED'); import sys; sys.exit(0 if p else 1)"
|
||||
exit /b %ERRORLEVEL%
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
# macOS 로그인 시 JUC500 Tauri 감시 앱 자동 실행 (케이블 연결 → GUI)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LABEL="com.juc500.desktop"
|
||||
PLIST="$HOME/Library/LaunchAgents/${LABEL}.plist"
|
||||
LAUNCHER="$ROOT/scripts/launch_tauri.sh"
|
||||
BIN="$ROOT/juc500-app/src-tauri/target/release/juc500-app"
|
||||
|
||||
if [[ -x "$BIN" ]]; then
|
||||
RUN_CMD="$BIN"
|
||||
elif [[ -x "$LAUNCHER" ]]; then
|
||||
RUN_CMD="/bin/bash"
|
||||
RUN_ARGS=("$LAUNCHER")
|
||||
else
|
||||
echo "Tauri 앱 없음. 먼저: cd juc500-app && npm install && npm run tauri build" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/Library/LaunchAgents"
|
||||
chmod +x "$ROOT/scripts/launch_tauri.sh"
|
||||
|
||||
if [[ "$RUN_CMD" == "/bin/bash" ]]; then
|
||||
cat >"$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key><string>${LABEL}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>${LAUNCHER}</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>JUC500_ROOT</key><string>${ROOT}</string>
|
||||
<key>JUC500_CABLE_AUTOLAUNCH</key><string>1</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key><true/>
|
||||
<key>KeepAlive</key><true/>
|
||||
<key>StandardOutPath</key><string>${ROOT}/.juc500/tauri-launch.log</string>
|
||||
<key>StandardErrorPath</key><string>${ROOT}/.juc500/tauri-launch.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
else
|
||||
cat >"$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key><string>${LABEL}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${BIN}</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>JUC500_ROOT</key><string>${ROOT}</string>
|
||||
<key>JUC500_CABLE_AUTOLAUNCH</key><string>1</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key><true/>
|
||||
<key>KeepAlive</key><true/>
|
||||
<key>StandardOutPath</key><string>${ROOT}/.juc500/tauri-launch.log</string>
|
||||
<key>StandardErrorPath</key><string>${ROOT}/.juc500/tauri-launch.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
fi
|
||||
|
||||
launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null || true
|
||||
launchctl bootstrap "gui/$(id -u)" "$PLIST"
|
||||
launchctl enable "gui/$(id -u)/${LABEL}"
|
||||
echo "설치 완료: ${PLIST}"
|
||||
echo "로그인 시 트레이 감시 앱이 실행되고, 케이블 연결 시 GUI가 열립니다."
|
||||
@@ -0,0 +1,25 @@
|
||||
@echo off
|
||||
REM Windows 로그인 시 JUC500 Tauri 감시 앱 자동 실행
|
||||
setlocal EnableExtensions
|
||||
cd /d "%~dp0.."
|
||||
set "ROOT=%CD%"
|
||||
|
||||
set "BIN=%ROOT%\juc500-app\src-tauri\target\release\juc500-app.exe"
|
||||
if not exist "%BIN%" (
|
||||
echo [!] Build first: cd juc500-app ^&^& npm install ^&^& npm run tauri build
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "STARTUP=%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup"
|
||||
set "LINK=%STARTUP%\JUC500.lnk"
|
||||
set "LAUNCHER=%ROOT%\scripts\launch_tauri.bat"
|
||||
|
||||
powershell -NoProfile -Command ^
|
||||
"$s=(New-Object -ComObject WScript.Shell).CreateShortcut('%LINK%');" ^
|
||||
"$s.TargetPath='%LAUNCHER%';" ^
|
||||
"$s.WorkingDirectory='%ROOT%';" ^
|
||||
"$s.Description='JUC500 cable auto-launch';" ^
|
||||
"$s.Save()"
|
||||
|
||||
echo Installed startup shortcut: %LINK%
|
||||
echo Tray watcher runs at logon; GUI opens when cable is plugged.
|
||||
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
REM JUC500 Tauri shell — auto-launch on cable connect
|
||||
cd /d "%~dp0.."
|
||||
set "JUC500_ROOT=%CD%"
|
||||
set "JUC500_USE_TAURI=1"
|
||||
|
||||
set "BIN=%CD%\juc500-app\src-tauri\target\release\juc500-app.exe"
|
||||
if exist "%BIN%" goto run
|
||||
set "BIN=%CD%\juc500-app\src-tauri\target\debug\juc500-app.exe"
|
||||
if exist "%BIN%" goto run
|
||||
|
||||
echo [!] Tauri app not built. Run: cd juc500-app ^&^& npm install ^&^& npm run tauri build
|
||||
exit /b 1
|
||||
|
||||
:run
|
||||
start "" "%BIN%"
|
||||
exit /b 0
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# JUC500 Tauri 셸 — USB 케이블 연결 시 Python 백엔드 + GUI 자동 기동
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
export JUC500_ROOT="$ROOT"
|
||||
# 케이블 자동 실행은 install_autolaunch_* 설치 시 JUC500_CABLE_AUTOLAUNCH=1 로 켬
|
||||
export JUC500_CABLE_AUTOLAUNCH="${JUC500_CABLE_AUTOLAUNCH:-0}"
|
||||
export JUC500_USE_TAURI=1
|
||||
|
||||
APP_DIR="$ROOT/juc500-app"
|
||||
if [[ ! -d "$APP_DIR/src-tauri" ]]; then
|
||||
echo "juc500-app 없음. Tauri 프로젝트를 먼저 빌드하세요." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Release 바이너리 우선, 없으면 dev
|
||||
BIN=""
|
||||
for candidate in \
|
||||
"$APP_DIR/src-tauri/target/release/juc500-app" \
|
||||
"$APP_DIR/src-tauri/target/debug/juc500-app"; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
BIN="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$BIN" ]]; then
|
||||
exec "$BIN"
|
||||
fi
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
echo "npm 없음 — Node.js 설치 후: cd juc500-app && npm install && npm run tauri build" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$APP_DIR"
|
||||
if [[ ! -d node_modules ]]; then
|
||||
npm install
|
||||
fi
|
||||
exec npm run tauri -- dev
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build offline Windows update package (USB stick / closed network).
|
||||
# Includes runtime sources + vendor wheels/libusb/zadig + apply bat.
|
||||
# Does NOT require internet on the Windows peer.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
BUILD="$("$ROOT/.venv/bin/python" -c "from juc500_xfer.usb_link import XFER_BUILD; print(XFER_BUILD)" 2>/dev/null || echo unknown)"
|
||||
OUT_DIR="${1:-$ROOT/dist-offline/juc500-offline-update-${STAMP}}"
|
||||
ZIP_OUT="${2:-$ROOT/dist-offline/juc500-offline-update-${STAMP}.zip}"
|
||||
|
||||
mkdir -p "$OUT_DIR" "$ROOT/dist-offline"
|
||||
|
||||
echo "=== Offline Windows update package ==="
|
||||
echo "XFER_BUILD=$BUILD"
|
||||
echo "OUT_DIR=$OUT_DIR"
|
||||
|
||||
rm -rf "$OUT_DIR"
|
||||
mkdir -p "$OUT_DIR/juc500_xfer/static" "$OUT_DIR/scripts" "$OUT_DIR/vendor/wheels" "$OUT_DIR/vendor/tools" "$OUT_DIR/docs"
|
||||
|
||||
# Runtime Python package (JUC500-only usb_link.py)
|
||||
rsync -a --delete \
|
||||
--exclude '__pycache__' --exclude '*.py[cod]' --exclude '.DS_Store' \
|
||||
"$ROOT/juc500_xfer/" "$OUT_DIR/juc500_xfer/"
|
||||
|
||||
# Launchers / entry
|
||||
for f in juc500_app.py start.bat start.sh start_win.sh start_both.sh requirements.txt README.md; do
|
||||
[[ -f "$ROOT/$f" ]] && cp -f "$ROOT/$f" "$OUT_DIR/$f"
|
||||
done
|
||||
|
||||
# Scripts (bootstrap + apply)
|
||||
rsync -a --exclude '__pycache__' "$ROOT/scripts/" "$OUT_DIR/scripts/"
|
||||
cp -f "$ROOT/scripts/apply_offline_update.bat" "$OUT_DIR/apply_offline_update.bat"
|
||||
|
||||
# Vendor offline essentials
|
||||
cp -f "$ROOT/vendor/libusb-1.0.dll" "$OUT_DIR/vendor/libusb-1.0.dll"
|
||||
cp -f "$ROOT/vendor/wheels/"*.whl "$OUT_DIR/vendor/wheels/" 2>/dev/null || true
|
||||
[[ -f "$ROOT/vendor/wheels/README.md" ]] && cp -f "$ROOT/vendor/wheels/README.md" "$OUT_DIR/vendor/wheels/"
|
||||
[[ -f "$ROOT/vendor/README.md" ]] && cp -f "$ROOT/vendor/README.md" "$OUT_DIR/vendor/"
|
||||
[[ -f "$ROOT/vendor/get-pip.py" ]] && cp -f "$ROOT/vendor/get-pip.py" "$OUT_DIR/vendor/"
|
||||
if [[ -f "$ROOT/vendor/tools/zadig.exe" ]]; then
|
||||
cp -f "$ROOT/vendor/tools/zadig.exe" "$OUT_DIR/vendor/tools/zadig.exe"
|
||||
fi
|
||||
|
||||
# Docs
|
||||
for f in OFFLINE-WINDOWS.md WINDOWS.md WINDOWS-ACCESS-DENIED.md; do
|
||||
[[ -f "$ROOT/docs/$f" ]] && cp -f "$ROOT/docs/$f" "$OUT_DIR/docs/$f"
|
||||
done
|
||||
|
||||
cat >"$OUT_DIR/BUILD.txt" <<EOF
|
||||
JUC500 offline update
|
||||
XFER_BUILD=$BUILD
|
||||
created=$STAMP
|
||||
SmartKMLink VID:PID=removed — JUC500 only (0711:7500 IF5)
|
||||
|
||||
Windows closed-network apply:
|
||||
1) Copy this folder (or unzip) to the peer PC via USB stick
|
||||
2) Close JUC500 GUI on Windows
|
||||
3) Double-click apply_offline_update.bat
|
||||
(default DEST=C:\\dev\\juc500 — or: apply_offline_update.bat D:\\path\\juc500)
|
||||
4) cd C:\\dev\\juc500 && start.bat
|
||||
5) GUI version line must show: $BUILD
|
||||
6) JUC500 IF5: vendor\\tools\\zadig.exe → 0711:7500 → MI_05 → WinUSB
|
||||
|
||||
Full fresh install (includes embeddable Python):
|
||||
./scripts/make_windows_bundle.sh
|
||||
EOF
|
||||
|
||||
cat >"$OUT_DIR/APPLY-WINDOWS.txt" <<'EOF'
|
||||
폐쇄망 Windows 11 — 오프라인 업데이트
|
||||
====================================
|
||||
|
||||
1. 이 폴더 전체를 USB로 Windows PC에 복사
|
||||
2. JUC500 GUI 종료
|
||||
3. apply_offline_update.bat 실행 (기본 대상 C:\dev\juc500)
|
||||
4. C:\dev\juc500 에서 start.bat 실행
|
||||
5. 상단 빌드 태그가 BUILD.txt 의 XFER_BUILD 와 같은지 확인
|
||||
6. JUC500(0711:7500): vendor\tools\zadig.exe
|
||||
→ Options → List All Devices → Smart Data Link → Interface 5 (MI_05) → WinUSB
|
||||
|
||||
인터넷·pip 설치 불필요. vendor\wheels · libusb · (선택) zadig 포함.
|
||||
EOF
|
||||
|
||||
rm -f "$ZIP_OUT"
|
||||
(
|
||||
cd "$OUT_DIR"
|
||||
zip -r -q "$ZIP_OUT" .
|
||||
)
|
||||
|
||||
# Also drop a copy on Desktop for easy USB copy
|
||||
DESK="$HOME/Desktop/juc500-offline-update-${STAMP}.zip"
|
||||
cp -f "$ZIP_OUT" "$DESK"
|
||||
|
||||
bytes="$(wc -c <"$ZIP_OUT" | tr -d ' ')"
|
||||
echo "완료: $ZIP_OUT ($bytes bytes)"
|
||||
echo "Desktop: $DESK"
|
||||
echo "폴더: $OUT_DIR"
|
||||
echo "XFER_BUILD=$BUILD"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Mac/Linux → Windows 배포용 zip 생성 (vendor 포함, .venv 제외)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
STAMP="$(date +%Y%m%d)"
|
||||
OUT="${1:-$ROOT/../juc500-windows-${STAMP}.zip}"
|
||||
|
||||
if [[ ! -f "$ROOT/vendor/libusb-1.0.dll" ]]; then
|
||||
echo "[!] vendor/libusb-1.0.dll 없음" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$ROOT/vendor/wheels/pyusb-1.2.1-py3-none-any.whl" ]]; then
|
||||
echo "[!] vendor/wheels 없음" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$ROOT/vendor/tools/zadig.exe" ]]; then
|
||||
echo "[!] vendor/tools/zadig.exe 없음 — WinUSB 오프라인 도구. ./scripts/make_offline_windows_update.sh 전에 준비하세요." >&2
|
||||
# non-fatal: bundle still usable if peer already has Zadig
|
||||
fi
|
||||
|
||||
echo "=== Windows 배포 zip 생성 ==="
|
||||
echo "출력: $OUT"
|
||||
|
||||
# zip: 프로젝트 루트 내용을 아카이브 루트에 넣음 (juc500/ 접두사 없음)
|
||||
rm -f "$OUT"
|
||||
(
|
||||
cd "$ROOT"
|
||||
zip -r -q "$OUT" . \
|
||||
-x ".venv/*" \
|
||||
-x ".venv-build/*" \
|
||||
-x ".venv-build-win/*" \
|
||||
-x ".venv-offline-test/*" \
|
||||
-x ".git/*" \
|
||||
-x "*/__pycache__/*" \
|
||||
-x "*.py[cod]" \
|
||||
-x "*.egg-info/*" \
|
||||
-x "dist/*" \
|
||||
-x "dist-windows/*" \
|
||||
-x "build/*" \
|
||||
-x "juc500-app/node_modules/*" \
|
||||
-x "juc500-app/src-tauri/target/*" \
|
||||
-x ".DS_Store" \
|
||||
-x "*.zip" \
|
||||
-x "*.tar.gz" \
|
||||
-x ".juc500/*" \
|
||||
-x "hub.log"
|
||||
)
|
||||
|
||||
bytes="$(wc -c <"$OUT" | tr -d ' ')"
|
||||
echo "완료: $OUT ($bytes bytes)"
|
||||
echo ""
|
||||
echo "Windows에서:"
|
||||
echo " 1) zip 압축 해제 (예: C:\\dev\\juc500)"
|
||||
echo " 2) Git Bash: cd /c/dev/juc500 && ./start_win.sh"
|
||||
echo " (첫 실행 시 vendor 로 .venv 자동 생성)"
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# juc500 — 이 Mac에서 JUC500 GUI 프로세스를 종료하고 백그라운드로 다시 실행
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
is_windows() {
|
||||
case "$(uname -s 2>/dev/null || echo unknown)" in
|
||||
MINGW*|MSYS*|CYGWIN*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
PY=""
|
||||
if is_windows && [[ -f "$ROOT/.venv/Scripts/python.exe" ]]; then
|
||||
PY="$ROOT/.venv/Scripts/python.exe"
|
||||
elif [[ -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
|
||||
|
||||
LOG_DIR="$ROOT/.juc500"
|
||||
mkdir -p "$LOG_DIR"
|
||||
LOG_FILE="$LOG_DIR/restart.log"
|
||||
HOST_TAG="$(hostname -s 2>/dev/null || hostname)"
|
||||
|
||||
echo "[$HOST_TAG] JUC500 종료 중…"
|
||||
kill_msg="$(
|
||||
"$PY" -c "
|
||||
from juc500_xfer.wormhole_kill import kill_other_juc500_processes
|
||||
r = kill_other_juc500_processes(force=True)
|
||||
print(r.get('message') or '')
|
||||
" 2>/dev/null || true
|
||||
)"
|
||||
if [[ -n "${kill_msg}" ]]; then
|
||||
echo "[$HOST_TAG] $kill_msg"
|
||||
fi
|
||||
sleep 0.6
|
||||
|
||||
echo "[$HOST_TAG] JUC500 시작 중… (로그: $LOG_FILE)"
|
||||
if is_windows && [[ -f "$ROOT/start.sh" ]]; then
|
||||
nohup bash "$ROOT/start.sh" >>"$LOG_FILE" 2>&1 &
|
||||
elif is_windows; then
|
||||
WIN_ROOT="$ROOT"
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
WIN_ROOT="$(cygpath -w "$ROOT")"
|
||||
fi
|
||||
if [[ -f "$ROOT/start.bat" ]]; then
|
||||
nohup cmd.exe /c "ping 127.0.0.1 -n 3 >nul & cd /d \"$WIN_ROOT\" & call \"$WIN_ROOT\\start.bat\"" >>"$LOG_FILE" 2>&1 &
|
||||
elif [[ -f "$ROOT/start_win.sh" ]]; then
|
||||
nohup bash "$ROOT/start_win.sh" >>"$LOG_FILE" 2>&1 &
|
||||
else
|
||||
echo "[$HOST_TAG] start.sh / start.bat 없음" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
nohup "$ROOT/start.sh" >>"$LOG_FILE" 2>&1 &
|
||||
fi
|
||||
echo "[$HOST_TAG] PID $!"
|
||||
@@ -17,7 +17,8 @@ pip install -U pip
|
||||
# vendor wheel 우선 (오프라인 가능)
|
||||
pip install --no-index --find-links=vendor/wheels -r requirements.txt \
|
||||
|| pip install --find-links=vendor/wheels -r requirements.txt
|
||||
pip install -e .
|
||||
pip install --no-index --find-links=vendor/wheels --no-build-isolation -e . \
|
||||
|| pip install --find-links=vendor/wheels -e .
|
||||
|
||||
echo
|
||||
echo "설치 완료. 실행:"
|
||||
|
||||
+108
-34
@@ -1,34 +1,108 @@
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d %~dp0..
|
||||
|
||||
py -3 -m venv .venv
|
||||
call .venv\Scripts\activate.bat
|
||||
python -m pip install -U pip
|
||||
REM PyPI 없이 vendor\wheels 의 pyusb wheel 사용
|
||||
python -m pip install --no-index --find-links=vendor\wheels -r requirements.txt
|
||||
if errorlevel 1 (
|
||||
echo [!] --no-index 실패. find-links + 온라인 허용으로 재시도…
|
||||
python -m pip install --find-links=vendor\wheels -r requirements.txt
|
||||
)
|
||||
python -m pip install --no-index --find-links=vendor\wheels -e .
|
||||
if errorlevel 1 (
|
||||
python -m pip install --find-links=vendor\wheels -e .
|
||||
)
|
||||
|
||||
REM libusb DLL 을 venv Scripts 로 복사 (장치 인식에 필수)
|
||||
if exist vendor\libusb-1.0.dll (
|
||||
copy /Y vendor\libusb-1.0.dll .venv\Scripts\libusb-1.0.dll >nul
|
||||
echo libusb-1.0.dll -^> .venv\Scripts\
|
||||
) else (
|
||||
echo [!] vendor\libusb-1.0.dll 없음 — USB 장치를 못 찾을 수 있습니다.
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Setup done. Next:
|
||||
echo .venv\Scripts\activate
|
||||
echo python -m juc500_xfer doctor
|
||||
echo python -m juc500_xfer info
|
||||
echo python -m juc500_xfer gui
|
||||
echo See docs\WINDOWS.md for Zadig / WinUSB steps.
|
||||
endlocal
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
cd /d "%~dp0.."
|
||||
set "ROOT=%CD%"
|
||||
set "WHEELS=%ROOT%\vendor\wheels"
|
||||
set "GETPIP=%ROOT%\vendor\get-pip.py"
|
||||
set "LIBUSB=%ROOT%\vendor\libusb-1.0.dll"
|
||||
set "EMBED_PY=%ROOT%\vendor\python-windows\python.exe"
|
||||
|
||||
echo ============================================
|
||||
echo JUC500 Windows offline setup
|
||||
echo ============================================
|
||||
|
||||
if not exist "%WHEELS%\pyusb-1.2.1-py3-none-any.whl" (
|
||||
echo [!] Missing vendor\wheels. Copy the full source tree including vendor\.
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%LIBUSB%" (
|
||||
echo [!] Missing vendor\libusb-1.0.dll
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Remove Mac-copied broken .venv
|
||||
if exist "%ROOT%\.venv\bin\python" (
|
||||
if not exist "%ROOT%\.venv\Scripts\python.exe" (
|
||||
echo [!] Mac .venv detected - removing...
|
||||
rmdir /s /q "%ROOT%\.venv"
|
||||
)
|
||||
)
|
||||
|
||||
REM Choose bootstrap Python: system first, else vendored embeddable
|
||||
set "BOOT_PY="
|
||||
where py >nul 2>&1 && set "BOOT_PY=py -3"
|
||||
if not defined BOOT_PY (
|
||||
where python >nul 2>&1 && set "BOOT_PY=python"
|
||||
)
|
||||
if not defined BOOT_PY (
|
||||
if exist "%EMBED_PY%" (
|
||||
set "BOOT_PY=%EMBED_PY%"
|
||||
echo Using vendored Python: vendor\python-windows\python.exe
|
||||
)
|
||||
)
|
||||
if not defined BOOT_PY (
|
||||
echo [!] No Python found. vendor\python-windows is missing.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Bootstrap: %BOOT_PY%
|
||||
|
||||
REM Ensure virtualenv module available (embeddable has no stdlib venv)
|
||||
%BOOT_PY% -c "import virtualenv" 2>nul
|
||||
if errorlevel 1 (
|
||||
echo Installing virtualenv from vendor\wheels ...
|
||||
%BOOT_PY% -m pip install --no-index --find-links="%WHEELS%" virtualenv
|
||||
if errorlevel 1 (
|
||||
if exist "%GETPIP%" (
|
||||
%BOOT_PY% "%GETPIP%" --no-index --find-links="%WHEELS%" pip setuptools wheel
|
||||
%BOOT_PY% -m pip install --no-index --find-links="%WHEELS%" virtualenv
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\.venv\Scripts\python.exe" (
|
||||
echo Creating .venv (fully offline, no pip seed from internet)...
|
||||
%BOOT_PY% -m virtualenv --no-periodic-update --no-setuptools --no-pip "%ROOT%\.venv"
|
||||
if errorlevel 1 (
|
||||
echo [!] virtualenv failed.
|
||||
exit /b 1
|
||||
)
|
||||
"%ROOT%\.venv\Scripts\python.exe" "%GETPIP%" --no-index --find-links="%WHEELS%" pip setuptools wheel packaging
|
||||
if errorlevel 1 (
|
||||
echo [!] get-pip offline bootstrap failed.
|
||||
exit /b 1
|
||||
)
|
||||
goto install_deps
|
||||
)
|
||||
|
||||
"%ROOT%\.venv\Scripts\python.exe" -c "import usb" 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [!] .venv incomplete — reinstalling dependencies...
|
||||
goto install_deps
|
||||
)
|
||||
goto copy_libusb
|
||||
|
||||
:install_deps
|
||||
echo Installing dependencies from vendor\wheels (no PyPI)...
|
||||
"%ROOT%\.venv\Scripts\python.exe" -m pip install --no-index --find-links="%WHEELS%" -U pip setuptools wheel packaging
|
||||
"%ROOT%\.venv\Scripts\python.exe" -m pip install --no-index --find-links="%WHEELS%" -r "%ROOT%\requirements.txt"
|
||||
if errorlevel 1 (
|
||||
echo [!] requirements install failed.
|
||||
exit /b 1
|
||||
)
|
||||
"%ROOT%\.venv\Scripts\python.exe" -m pip install --no-index --find-links="%WHEELS%" --no-build-isolation -e "%ROOT%"
|
||||
if errorlevel 1 (
|
||||
echo [!] editable install failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:copy_libusb
|
||||
copy /Y "%LIBUSB%" "%ROOT%\.venv\Scripts\libusb-1.0.dll" >nul
|
||||
echo libusb-1.0.dll -^> .venv\Scripts\
|
||||
|
||||
echo.
|
||||
echo Setup done (offline). Run:
|
||||
echo .\start.bat
|
||||
echo or Git Bash: ./start_win.sh
|
||||
echo.
|
||||
endlocal
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tauri 케이블 감시 앱 종료 (개발 중 ./start.sh 와 충돌 방지)
|
||||
set -euo pipefail
|
||||
|
||||
stopped=0
|
||||
for pattern in \
|
||||
"juc500-app/src-tauri/target/release/juc500-app" \
|
||||
"juc500-app/src-tauri/target/debug/juc500-app" \
|
||||
"/Applications/JUC500.app/Contents/MacOS/juc500-app" \
|
||||
"juc500-app.exe"; do
|
||||
if pkill -f "$pattern" 2>/dev/null; then
|
||||
echo "종료: $pattern"
|
||||
stopped=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$stopped" == "0" ]]; then
|
||||
echo "실행 중인 Tauri juc500-app 없음"
|
||||
fi
|
||||
Executable
+290
@@ -0,0 +1,290 @@
|
||||
#!/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
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# macOS 로그인 자동 실행(Tauri 케이블 감시) 제거
|
||||
set -euo pipefail
|
||||
LABEL="com.juc500.desktop"
|
||||
PLIST="$HOME/Library/LaunchAgents/${LABEL}.plist"
|
||||
|
||||
launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null || true
|
||||
if [[ -f "$PLIST" ]]; then
|
||||
rm -f "$PLIST"
|
||||
echo "제거: $PLIST"
|
||||
else
|
||||
echo "LaunchAgent 없음: $PLIST"
|
||||
fi
|
||||
|
||||
"$(cd "$(dirname "$0")" && pwd)/stop_tauri_watchers.sh"
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify JUC500 GUI connect + listdir via local API."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
GUI = "http://127.0.0.1:8765"
|
||||
POLL = 1.0
|
||||
CONNECT_DEADLINE = 180.0
|
||||
|
||||
|
||||
def api(path: str, *, method: str = "GET", body: dict | None = None, timeout: float = 15.0):
|
||||
data = None
|
||||
headers = {}
|
||||
if body is not None:
|
||||
data = json.dumps(body).encode()
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(f"{GUI}{path}", data=data, method=method, headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
return json.loads(r.read().decode())
|
||||
|
||||
|
||||
def status() -> dict:
|
||||
return api("/api/status", timeout=8.0)
|
||||
|
||||
|
||||
def wait_connected(deadline: float, *, label: str) -> dict:
|
||||
end = time.monotonic() + deadline
|
||||
last: dict = {}
|
||||
started = time.monotonic()
|
||||
last_retry = started
|
||||
while time.monotonic() < end:
|
||||
now = time.monotonic()
|
||||
try:
|
||||
last = status()
|
||||
except Exception as exc:
|
||||
print(f"[{label}] status error: {exc}", flush=True)
|
||||
time.sleep(POLL)
|
||||
continue
|
||||
conn = bool(last.get("connected"))
|
||||
model = last.get("cable_model")
|
||||
connecting = bool(last.get("connecting"))
|
||||
err = last.get("connect_error")
|
||||
print(
|
||||
f"[{label}] connected={conn} model={model} connecting={connecting} err={err!r}",
|
||||
flush=True,
|
||||
)
|
||||
if conn:
|
||||
return last
|
||||
if err and not connecting:
|
||||
retryable = any(
|
||||
k in (err or "")
|
||||
for k in (
|
||||
"session stopped",
|
||||
"연결 시간 초과",
|
||||
"연결되지 않았습니다",
|
||||
"Access denied",
|
||||
"점유 실패",
|
||||
)
|
||||
)
|
||||
if retryable and now - last_retry >= 5.0:
|
||||
try:
|
||||
api("/api/disconnect", method="POST", body={})
|
||||
time.sleep(1.0)
|
||||
api("/api/connect/auto", method="POST", body={})
|
||||
print(f"[{label}] retry after: {err}", flush=True)
|
||||
except Exception as exc:
|
||||
print(f"[{label}] retry error: {exc}", flush=True)
|
||||
last_retry = now
|
||||
time.sleep(POLL)
|
||||
continue
|
||||
raise RuntimeError(f"{label} failed: {err}")
|
||||
if now - last_retry >= 70.0 and (connecting or now - started >= 25.0):
|
||||
try:
|
||||
api("/api/disconnect", method="POST", body={})
|
||||
time.sleep(1.0)
|
||||
api("/api/connect/auto", method="POST", body={})
|
||||
print(f"[{label}] retry disconnect+connect/auto", flush=True)
|
||||
except Exception as exc:
|
||||
print(f"[{label}] retry error: {exc}", flush=True)
|
||||
last_retry = now
|
||||
time.sleep(POLL)
|
||||
raise TimeoutError(f"{label}: not connected within {deadline:.0f}s — last={last}")
|
||||
|
||||
|
||||
def verify_listdir(label: str) -> int:
|
||||
try:
|
||||
data = api("/api/listdir?path=__roots__", timeout=30.0)
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = exc.read().decode(errors="replace")
|
||||
raise RuntimeError(f"{label} listdir HTTP {exc.code}: {body}") from exc
|
||||
n = len(data.get("entries") or [])
|
||||
print(f"[{label}] listdir roots={n}", flush=True)
|
||||
if n <= 0:
|
||||
raise RuntimeError(f"{label} listdir returned no entries")
|
||||
return n
|
||||
|
||||
|
||||
def assert_juc500(s: dict, label: str) -> None:
|
||||
active = (s.get("cable_model") or "").upper()
|
||||
dev = s.get("active_cable") or s.get("device") or {}
|
||||
pid = (dev.get("pid") or "").lower()
|
||||
if active != "JUC500":
|
||||
raise RuntimeError(f"{label}: cable_model={active!r} expected JUC500")
|
||||
if pid and pid != "7500":
|
||||
raise RuntimeError(f"{label}: unexpected pid={pid} for JUC500")
|
||||
print(
|
||||
f"[{label}] OK model={active} pid={pid or '7500'} host={s.get('hostname')}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("JUC500 connect verification", flush=True)
|
||||
try:
|
||||
s0 = status()
|
||||
except Exception as exc:
|
||||
print(f"GUI not reachable at {GUI}: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
devices = s0.get("devices") or []
|
||||
print(f"Detected devices: {len(devices)}", flush=True)
|
||||
if not devices:
|
||||
print("WARN: no JUC500 detected — plug cable and retry", flush=True)
|
||||
|
||||
if not s0.get("connected"):
|
||||
print("Waiting for autoconnect on JUC500…", flush=True)
|
||||
|
||||
s = wait_connected(CONNECT_DEADLINE, label="JUC500")
|
||||
assert_juc500(s, "JUC500-connected")
|
||||
verify_listdir("JUC500")
|
||||
|
||||
try:
|
||||
ping = api("/api/peer/ping", timeout=15.0)
|
||||
print(f"[JUC500] peer ping: {ping}", flush=True)
|
||||
except Exception as exc:
|
||||
print(f"[JUC500] peer ping skipped: {exc}", flush=True)
|
||||
|
||||
print("\n=== ALL CHECKS PASSED ===", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user