start_bambuddy.bat 15 KB

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