start_bambuddy.bat 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. @echo off
  2. chcp 65001 >nul 2>&1
  3. setlocal enabledelayedexpansion
  4. title Bambuddy
  5. REM ============================================
  6. REM Bambuddy Portable Launcher for Windows
  7. REM
  8. REM Double-click to start. First run downloads
  9. REM Python and Node.js automatically (portable,
  10. REM no system changes). Everything is stored in
  11. REM the .portable\ folder.
  12. REM
  13. REM Usage:
  14. REM start_bambuddy.bat Launch
  15. REM start_bambuddy.bat update Update deps & rebuild frontend
  16. REM start_bambuddy.bat reset Clean all & fresh start
  17. REM set PORT=9000 & start_bambuddy.bat Change port
  18. REM ============================================
  19. REM If launched from the "install" folder, go up one level; otherwise use current directory.
  20. for %%I in ("%CD%") do set "CURRENT_DIR_NAME=%%~nxI"
  21. if /I "%CURRENT_DIR_NAME%"=="install" (
  22. set "ROOT=%CD%\.."
  23. cd ..
  24. ) else (
  25. set "ROOT=%CD%"
  26. )
  27. if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
  28. set "PORTABLE=%ROOT%\.portable"
  29. set "PYTHON_DIR=%PORTABLE%\python"
  30. set "NODE_DIR=%PORTABLE%\node"
  31. set "FFMPEG_DIR=%PORTABLE%\ffmpeg"
  32. REM NOTE: Python version is intentionally pinned to a specific portable build.
  33. REM If you upgrade the bundled Python runtime, update PYTHON_VER here
  34. REM and make sure it matches the version used in download/installation logic.
  35. if not defined PYTHON_VER set "PYTHON_VER=3.13.1"
  36. REM Default Node.js version for the portable runtime. Override by setting NODE_VER before running this script.
  37. if not defined NODE_VER set "NODE_VER=22.12.0"
  38. REM NOTE: FFmpeg is not downloaded automatically.
  39. REM Install from the official site and add it to PATH:
  40. REM https://ffmpeg.org/download.html
  41. REM Pinned SHA256 hashes for downloads (update when bumping versions)
  42. set "GET_PIP_SHA256=dffc3658baada4ef383f31c3c672d4e5e306a6e376cee8bee5dbdf1385525104"
  43. set "PYTHON_ZIP_HASH_AMD64=7b7923ff0183a8b8fca90f6047184b419b108cb437f75fc1c002f9d2f8bcec16"
  44. set "PYTHON_ZIP_HASH_ARM64=ae8561bf958f77c68cb6c44ced983e5267fe965a7e4168f41ec2291350b81d55"
  45. set "NODE_ZIP_HASH_X64=2b8f2256382f97ad51e29ff71f702961af466c4616393f767455501e6aece9b8"
  46. set "NODE_ZIP_HASH_ARM64=17401720af48976e3f67c41e8968a135fb49ca1f88103a92e0e8c70605763854"
  47. REM Detect system architecture (amd64 or arm64)
  48. set "PYTHON_ARCH=amd64"
  49. set "NODE_ARCH=x64"
  50. if /I "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
  51. set "PYTHON_ARCH=arm64"
  52. set "NODE_ARCH=arm64"
  53. )
  54. if defined PROCESSOR_ARCHITEW6432 (
  55. if /I "%PROCESSOR_ARCHITEW6432%"=="ARM64" (
  56. set "PYTHON_ARCH=arm64"
  57. set "NODE_ARCH=arm64"
  58. )
  59. )
  60. set "PYTHON_ZIP_HASH_EXPECTED=%PYTHON_ZIP_HASH_AMD64%"
  61. if /I "%PYTHON_ARCH%"=="arm64" set "PYTHON_ZIP_HASH_EXPECTED=%PYTHON_ZIP_HASH_ARM64%"
  62. set "NODE_ZIP_HASH_EXPECTED=%NODE_ZIP_HASH_X64%"
  63. if /I "%NODE_ARCH%"=="arm64" set "NODE_ZIP_HASH_EXPECTED=%NODE_ZIP_HASH_ARM64%"
  64. if not defined PORT set "PORT=8000"
  65. REM Validate PORT is a number in the range 1-65535
  66. echo(!PORT!| findstr /R "^[0-9][0-9]*$" >nul
  67. if errorlevel 1 (
  68. echo Invalid PORT value "%PORT%". PORT must be an integer between 1 and 65535.
  69. exit /b 1
  70. )
  71. if %PORT% LSS 1 (
  72. echo Invalid PORT value "%PORT%". PORT must be between 1 and 65535.
  73. exit /b 1
  74. )
  75. if %PORT% GTR 65535 (
  76. echo Invalid PORT value "%PORT%". PORT must be between 1 and 65535.
  77. exit /b 1
  78. )
  79. REM ---- Handle arguments ----
  80. if /i "%~1"=="reset" (
  81. echo Cleaning up portable environment...
  82. call :safe_rmdir "%PORTABLE%" ".portable"
  83. if errorlevel 1 exit /b 1
  84. call :safe_rmdir "%ROOT%\static" "static"
  85. if errorlevel 1 exit /b 1
  86. echo Done. Run again without arguments to set up fresh.
  87. pause
  88. exit /b 0
  89. )
  90. if /i "%~1"=="update" (
  91. echo Forcing dependency update and frontend rebuild...
  92. if exist "%PORTABLE%\.deps-installed" del "%PORTABLE%\.deps-installed"
  93. call :safe_rmdir "%ROOT%\static" "static"
  94. if errorlevel 1 exit /b 1
  95. )
  96. REM ---- Check prerequisites ----
  97. where curl >nul 2>&1
  98. if errorlevel 1 (
  99. echo.
  100. echo [ERROR] curl.exe is not available.
  101. echo Windows 10 version 1803 or later is required.
  102. echo.
  103. pause
  104. exit /b 1
  105. )
  106. where tar >nul 2>&1
  107. if errorlevel 1 (
  108. echo.
  109. echo [ERROR] tar.exe is not available.
  110. echo Windows 10 version 1803 or later is required.
  111. echo.
  112. pause
  113. exit /b 1
  114. )
  115. REM ---- Verify project structure ----
  116. if not exist "%ROOT%\backend\app\main.py" (
  117. echo.
  118. echo [ERROR] backend\app\main.py not found.
  119. echo This script must be in the Bambuddy project root.
  120. echo.
  121. pause
  122. exit /b 1
  123. )
  124. echo.
  125. echo ____ _ _ _
  126. echo ^| __ ) __ _ _ __ ___ ^| ^|__ _ _ __^| ^| __^| ^|_ _
  127. echo ^| _ \ / _` ^| '_ ` _ \^| '_ \^| ^| ^| ^|/ _` ^|/ _` ^| ^| ^| ^|
  128. echo ^| ^|_) ^| (_^| ^| ^| ^| ^| ^| ^| ^|_) ^| ^|_^| ^| (_^| ^| (_^| ^| ^|_^| ^|
  129. echo ^|____/ \__,_^|_^| ^|_^| ^|_^|_.__/ \__,_^|\__,_^|\__,_^|\__, ^|
  130. echo ^|___/
  131. echo.
  132. REM ============================================
  133. REM Step 1: Setup Portable Python
  134. REM ============================================
  135. if exist "%PYTHON_DIR%\python.exe" (
  136. echo [OK] Python %PYTHON_VER% found.
  137. goto :python_ready
  138. )
  139. echo [1/6] Downloading Python %PYTHON_VER% (portable)...
  140. if not exist "%PORTABLE%" mkdir "%PORTABLE%"
  141. if not exist "%PYTHON_DIR%" mkdir "%PYTHON_DIR%"
  142. curl -L --fail --show-error --progress-bar -o "%PORTABLE%\python.zip" ^
  143. "https://www.python.org/ftp/python/%PYTHON_VER%/python-%PYTHON_VER%-embed-%PYTHON_ARCH%.zip"
  144. if errorlevel 1 (
  145. echo [ERROR] Failed to download Python.
  146. pause
  147. exit /b 1
  148. )
  149. call :verify_sha256 "%PORTABLE%\python.zip" "%PYTHON_ZIP_HASH_EXPECTED%" "Python"
  150. if errorlevel 1 (
  151. echo [ERROR] Failed to download Python archive.
  152. pause
  153. exit /b 1
  154. )
  155. REM Download official SHA256 checksum for the Python archive
  156. curl -L --progress-bar -o "%PORTABLE%\python.zip.sha256" ^
  157. "https://www.python.org/ftp/python/%PYTHON_VER%/python-%PYTHON_VER%-embed-amd64.zip.sha256"
  158. if errorlevel 1 (
  159. echo [ERROR] Failed to download Python checksum file.
  160. del "%PORTABLE%\python.zip" >nul 2>&1
  161. pause
  162. exit /b 1
  163. )
  164. REM Compute SHA256 hash of the downloaded archive
  165. set "PYTHON_ZIP_HASH="
  166. for /f "tokens=1 usebackq" %%H in (`
  167. certutil -hashfile "%PORTABLE%\python.zip" SHA256 ^| findstr /R /I "^[0-9A-F][0-9A-F]"
  168. `) do (
  169. set "PYTHON_ZIP_HASH=%%H"
  170. goto :python_hash_done
  171. )
  172. :python_hash_done
  173. if not defined PYTHON_ZIP_HASH (
  174. echo [ERROR] Failed to compute SHA256 hash for Python archive.
  175. del "%PORTABLE%\python.zip" >nul 2>&1
  176. del "%PORTABLE%\python.zip.sha256" >nul 2>&1
  177. pause
  178. exit /b 1
  179. )
  180. REM Read expected SHA256 hash from the checksum file
  181. set "PYTHON_ZIP_HASH_EXPECTED="
  182. for /f "tokens=1" %%H in ('type "%PORTABLE%\python.zip.sha256"') do (
  183. set "PYTHON_ZIP_HASH_EXPECTED=%%H"
  184. goto :python_expected_hash_done
  185. )
  186. :python_expected_hash_done
  187. if not defined PYTHON_ZIP_HASH_EXPECTED (
  188. echo [ERROR] Failed to read expected SHA256 hash for Python archive.
  189. del "%PORTABLE%\python.zip" >nul 2>&1
  190. del "%PORTABLE%\python.zip.sha256" >nul 2>&1
  191. pause
  192. exit /b 1
  193. )
  194. REM Compare actual and expected hashes (case-insensitive)
  195. if /I not "%PYTHON_ZIP_HASH%"=="%PYTHON_ZIP_HASH_EXPECTED%" (
  196. echo [ERROR] SHA256 checksum verification for Python archive failed.
  197. echo [INFO] Expected: %PYTHON_ZIP_HASH_EXPECTED%
  198. echo [INFO] Actual: %PYTHON_ZIP_HASH%
  199. del "%PORTABLE%\python.zip" >nul 2>&1
  200. del "%PORTABLE%\python.zip.sha256" >nul 2>&1
  201. pause
  202. exit /b 1
  203. )
  204. del "%PORTABLE%\python.zip.sha256" >nul 2>&1
  205. echo Extracting Python...
  206. tar -xf "%PORTABLE%\python.zip" -C "%PYTHON_DIR%"
  207. if errorlevel 1 (
  208. echo [ERROR] Failed to extract Python archive.
  209. del "%PORTABLE%\python.zip" >nul 2>&1
  210. pause
  211. exit /b 1
  212. )
  213. del "%PORTABLE%\python.zip"
  214. if not exist "%PYTHON_DIR%\python.exe" (
  215. echo [ERROR] Python executable not found after extraction.
  216. pause
  217. exit /b 1
  218. )
  219. REM Enable site-packages by rewriting the ._pth file
  220. REM Derive python tag (e.g., 3.13.x -> 313) from %PYTHON_VER%
  221. for /f "tokens=1,2 delims=." %%A in ("%PYTHON_VER%") do (
  222. set "PY_MAJOR=%%A"
  223. set "PY_MINOR=%%B"
  224. )
  225. set "PYTHON_TAG=%PY_MAJOR%%PY_MINOR%"
  226. (
  227. echo python!PYTHON_TAG!.zip
  228. echo .
  229. echo import site
  230. ) > "%PYTHON_DIR%\python!PYTHON_TAG!._pth"
  231. REM ============================================
  232. REM Step 2: Install pip
  233. REM ============================================
  234. echo.
  235. echo [2/6] Installing pip...
  236. curl -L --fail -sS -o "%PORTABLE%\get-pip.py" "https://bootstrap.pypa.io/get-pip.py"
  237. if errorlevel 1 (
  238. echo [ERROR] Failed to download get-pip.py.
  239. pause
  240. exit /b 1
  241. )
  242. call :verify_sha256 "%PORTABLE%\get-pip.py" "%GET_PIP_SHA256%" "get-pip.py"
  243. if errorlevel 1 (
  244. del "%PORTABLE%\get-pip.py" >nul 2>&1
  245. pause
  246. exit /b 1
  247. )
  248. "%PYTHON_DIR%\python.exe" "%PORTABLE%\get-pip.py" --no-warn-script-location -q
  249. if errorlevel 1 (
  250. echo [ERROR] Failed to install pip.
  251. pause
  252. exit /b 1
  253. )
  254. del "%PORTABLE%\get-pip.py"
  255. echo [OK] Python %PYTHON_VER% ready.
  256. :python_ready
  257. REM ============================================
  258. REM Step 2.5: Create Virtual Environment (best effort)
  259. REM ============================================
  260. set "VENV_DIR=%PORTABLE%\venv"
  261. set "PYTHON_EXE=%PYTHON_DIR%\python.exe"
  262. if not exist "%VENV_DIR%\Scripts\python.exe" (
  263. echo.
  264. echo Creating virtual environment [optional]...
  265. "%PYTHON_DIR%\python.exe" -m venv "%VENV_DIR%"
  266. if errorlevel 1 (
  267. echo [WARN] Failed to create virtual environment. Continuing without venv.
  268. )
  269. )
  270. if exist "%VENV_DIR%\Scripts\python.exe" (
  271. set "PYTHON_EXE=%VENV_DIR%\Scripts\python.exe"
  272. )
  273. REM ============================================
  274. REM Step 3: Install Python Dependencies
  275. REM ============================================
  276. if exist "%PORTABLE%\.deps-installed" (
  277. echo [OK] Python packages found.
  278. goto :deps_ready
  279. )
  280. echo.
  281. echo [3/6] Installing Python packages (this may take a few minutes)...
  282. if exist "%ROOT%\requirements.lock" (
  283. "%PYTHON_EXE%" -m pip install -r "%ROOT%\requirements.lock" --require-hashes --no-warn-script-location -q
  284. ) else (
  285. echo [WARN] requirements.lock not found. Falling back to requirements.txt - no hash enforcement.
  286. "%PYTHON_EXE%" -m pip install -r "%ROOT%\requirements.txt" --no-warn-script-location -q
  287. )
  288. if errorlevel 1 (
  289. echo [ERROR] Failed to install Python packages.
  290. pause
  291. exit /b 1
  292. )
  293. REM Create marker file
  294. echo %date% %time% > "%PORTABLE%\.deps-installed"
  295. echo [OK] Packages installed.
  296. :deps_ready
  297. REM ============================================
  298. REM Step 4-6: Build Frontend (if needed)
  299. REM ============================================
  300. if exist "%ROOT%\static\index.html" (
  301. echo [OK] Frontend found.
  302. goto :frontend_ready
  303. )
  304. REM ---- Download Node.js if needed ----
  305. if exist "%NODE_DIR%\node.exe" goto :node_ready
  306. echo.
  307. echo [4/6] Downloading Node.js %NODE_VER% (portable)...
  308. curl -L --fail --show-error --progress-bar -o "%PORTABLE%\node.zip" ^
  309. "https://nodejs.org/dist/v%NODE_VER%/node-v%NODE_VER%-win-%NODE_ARCH%.zip"
  310. if errorlevel 1 (
  311. echo [ERROR] Failed to download Node.js.
  312. pause
  313. exit /b 1
  314. )
  315. call :verify_sha256 "%PORTABLE%\node.zip" "%NODE_ZIP_HASH_EXPECTED%" "Node.js"
  316. if errorlevel 1 (
  317. del "%PORTABLE%\node.zip" >nul 2>&1
  318. pause
  319. exit /b 1
  320. )
  321. echo Extracting Node.js...
  322. tar -xf "%PORTABLE%\node.zip" -C "%PORTABLE%"
  323. if errorlevel 1 (
  324. echo [ERROR] Failed to extract Node.js archive.
  325. del "%PORTABLE%\node.zip" >nul 2>&1
  326. pause
  327. exit /b 1
  328. )
  329. if exist "%PORTABLE%\node-v%NODE_VER%-win-%NODE_ARCH%" (
  330. ren "%PORTABLE%\node-v%NODE_VER%-win-%NODE_ARCH%" node
  331. )
  332. del "%PORTABLE%\node.zip"
  333. echo [OK] Node.js %NODE_VER% ready.
  334. :node_ready
  335. REM ---- Build frontend ----
  336. echo.
  337. echo [5/6] Building frontend (this may take a while)...
  338. set "PATH=%NODE_DIR%;%PATH%"
  339. pushd "%ROOT%\frontend"
  340. if exist "%ROOT%\frontend\package-lock.json" (
  341. call "%NODE_DIR%\npm.cmd" ci
  342. ) else (
  343. call "%NODE_DIR%\npm.cmd" install
  344. )
  345. if errorlevel 1 (
  346. echo [ERROR] npm install failed.
  347. popd
  348. pause
  349. exit /b 1
  350. )
  351. call "%NODE_DIR%\npm.cmd" run build
  352. if errorlevel 1 (
  353. echo [ERROR] Frontend build failed.
  354. popd
  355. pause
  356. exit /b 1
  357. )
  358. popd
  359. if not exist "%ROOT%\frontend\static\index.html" (
  360. echo [ERROR] Frontend build did not produce static\index.html.
  361. echo Expected: "%ROOT%\frontend\static\index.html"
  362. pause
  363. exit /b 1
  364. )
  365. if not exist "%ROOT%\static\index.html" (
  366. echo [ERROR] Frontend build did not produce static\index.html.
  367. echo Expected: "%ROOT%\static\index.html"
  368. pause
  369. exit /b 1
  370. )
  371. echo [OK] Frontend built.
  372. :frontend_ready
  373. REM ============================================
  374. REM Step 6: Setup Portable FFmpeg (if needed)
  375. REM ============================================
  376. where ffmpeg >nul 2>&1
  377. if not errorlevel 1 (
  378. echo [OK] FFmpeg found in system PATH.
  379. goto :ffmpeg_ready
  380. )
  381. if exist "%FFMPEG_DIR%\bin\ffmpeg.exe" (
  382. echo [OK] FFmpeg found.
  383. goto :ffmpeg_ready
  384. )
  385. echo.
  386. echo [6/6] FFmpeg not found.
  387. echo [INFO] Install FFmpeg from the official site and add it to PATH:
  388. echo https://ffmpeg.org/download.html
  389. echo [INFO] Timelapse features will be unavailable until FFmpeg is installed.
  390. :ffmpeg_ready
  391. REM ============================================
  392. REM Launch Bambuddy
  393. REM ============================================
  394. echo.
  395. echo ================================================
  396. echo Bambuddy is starting on port %PORT%
  397. echo Open: http://localhost:%PORT%
  398. echo.
  399. echo Press Ctrl+C to stop
  400. echo ================================================
  401. echo.
  402. REM Set PYTHONPATH so "backend.app.main" module is found
  403. set "PYTHONPATH=%ROOT%"
  404. REM Add portable FFmpeg to PATH if available
  405. if exist "%FFMPEG_DIR%\bin\ffmpeg.exe" set "PATH=%FFMPEG_DIR%\bin;%PATH%"
  406. REM Open browser after server is ready (poll localhost)
  407. start /b cmd /c "for /l %%i in (1,1,30) do (curl -s -f -o nul http://localhost:%PORT% && (start http://localhost:%PORT% & exit /b 0) & timeout /t 1 /nobreak >nul)"
  408. REM Launch the application
  409. "%PYTHON_EXE%" -m uvicorn backend.app.main:app --host 0.0.0.0 --port %PORT% --loop asyncio
  410. echo.
  411. echo Bambuddy has stopped.
  412. pause
  413. endlocal
  414. goto :eof
  415. REM ============================================
  416. REM Helpers
  417. REM ============================================
  418. :safe_rmdir
  419. set "TARGET=%~1"
  420. set "LABEL=%~2"
  421. if "%TARGET%"=="" (
  422. echo [ERROR] %LABEL% path is empty. Aborting.
  423. exit /b 1
  424. )
  425. if /I "%TARGET%"=="\" (
  426. echo [ERROR] %LABEL% path resolved to root. Aborting.
  427. exit /b 1
  428. )
  429. if not exist "%TARGET%" exit /b 0
  430. echo Deleting "%TARGET%"
  431. rmdir /s /q "%TARGET%"
  432. if errorlevel 1 (
  433. echo [ERROR] Failed to delete "%TARGET%".
  434. exit /b 1
  435. )
  436. exit /b 0
  437. :verify_sha256
  438. set "FILE=%~1"
  439. set "EXPECTED=%~2"
  440. set "LABEL=%~3"
  441. if "%EXPECTED%"=="" (
  442. echo [ERROR] %LABEL% checksum not found.
  443. exit /b 1
  444. )
  445. set "ACTUAL="
  446. for /f "tokens=1" %%H in ('certutil -hashfile "%FILE%" SHA256 ^| findstr /R /I "^[0-9A-F][0-9A-F]"') do (
  447. set "ACTUAL=%%H"
  448. goto :hash_done
  449. )
  450. :hash_done
  451. if not defined ACTUAL (
  452. echo [ERROR] Failed to compute SHA256 for %LABEL%.
  453. exit /b 1
  454. )
  455. if /I not "%ACTUAL%"=="%EXPECTED%" (
  456. echo [ERROR] SHA256 verification failed for %LABEL%.
  457. echo [INFO] Expected: %EXPECTED%
  458. echo [INFO] Actual: %ACTUAL%
  459. exit /b 1
  460. )
  461. exit /b 0