@echo off setlocal EnableDelayedExpansion REM Build api/player/admin images and export as tar (Windows CMD). REM Usage: REM docs\docker\build-and-export-images.bat REM docs\docker\build-and-export-images.bat --service admin REM docs\docker\build-and-export-images.bat --tag v1.2.3 REM docs\docker\build-and-export-images.bat --use-cache REM docs\docker\build-and-export-images.bat --export-only cd /d "%~dp0..\.." set "COMPOSE_FILE=docker-compose.prod.yml" set "ENV_FILE=.env.docker" set "OUTPUT=" set "NO_CACHE=1" set "EXPORT_ONLY=0" set "SERVICE=all" if defined IMAGE_TAG (set "TAG=%IMAGE_TAG%") else (set "TAG=") :parse_args if "%~1"=="" goto args_done if /i "%~1"=="--tag" goto parse_tag if /i "%~1"=="--service" goto parse_service if /i "%~1"=="--use-cache" set "NO_CACHE=0" & shift & goto parse_args if /i "%~1"=="--export-only" set "EXPORT_ONLY=1" & shift & goto parse_args if /i "%~1"=="--output" goto parse_output if /i "%~1"=="-h" goto show_help if /i "%~1"=="--help" goto show_help if /i "%~1"=="/?" goto show_help echo Unknown argument: %~1 goto show_help :parse_tag if "%~2"=="" ( echo ERROR: missing value for --tag exit /b 1 ) set "TAG=%~2" shift shift goto parse_args :parse_service if "%~2"=="" ( echo ERROR: missing value for --service exit /b 1 ) set "SERVICE=%~2" shift shift goto parse_args :parse_output if "%~2"=="" ( echo ERROR: missing value for --output exit /b 1 ) set "OUTPUT=%~2" shift shift goto parse_args :show_help echo. echo Usage: docs\docker\build-and-export-images.bat [options] echo. echo --service SVC api ^| player ^| admin ^| all (default: all) echo --tag TAG Image tag (default: latest) echo --use-cache Use Docker build cache echo --export-only Skip build, export existing images only echo --output PATH Output tar path echo -h, --help Show help echo. echo Examples: echo docs\docker\build-and-export-images.bat --service admin echo docs\docker\build-and-export-admin.bat echo docs\docker\build-and-export-admin.bat --export-only echo. exit /b 0 :args_done if not exist "%COMPOSE_FILE%" ( echo ERROR: missing %COMPOSE_FILE% in %CD% exit /b 1 ) if not exist "%ENV_FILE%" ( if exist ".env.docker.example" ( echo WARN: using .env.docker.example instead of .env.docker set "ENV_FILE=.env.docker.example" ) else ( echo ERROR: missing .env.docker and .env.docker.example exit /b 1 ) ) call :validate_service if errorlevel 1 exit /b 1 if not defined TAG call :default_tag if not defined TAG ( echo ERROR: unable to determine image tag exit /b 1 ) call :validate_tag if errorlevel 1 exit /b 1 if not defined OUTPUT call :default_output set "OUTPUT_PATH=%OUTPUT%" echo %OUTPUT_PATH%| findstr /r "^[A-Za-z]:\\" >nul if errorlevel 1 set "OUTPUT_PATH=%CD%\%OUTPUT%" where docker >nul 2>&1 if errorlevel 1 ( echo ERROR: docker not found. Start Docker Desktop first. exit /b 1 ) if "%EXPORT_ONLY%"=="0" goto do_build goto do_export :do_build echo ==^> Building %SERVICE% (tag: %TAG%) set "IMAGE_TAG=%TAG%" if "%NO_CACHE%"=="1" goto build_no_cache if /i "%SERVICE%"=="all" ( docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build api player admin ) else ( docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build %SERVICE% ) goto build_done :build_no_cache if /i "%SERVICE%"=="all" ( docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build --no-cache api player admin ) else ( docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build --no-cache %SERVICE% ) :build_done if errorlevel 1 ( echo ERROR: docker build failed exit /b 1 ) :do_export echo ==^> Exporting %SERVICE% to %OUTPUT_PATH% if /i "%SERVICE%"=="all" ( docker save thebet365-api:%TAG% thebet365-player:%TAG% thebet365-admin:%TAG% -o "%OUTPUT_PATH%" ) else ( docker save thebet365-%SERVICE%:%TAG% -o "%OUTPUT_PATH%" ) if errorlevel 1 ( echo ERROR: docker save failed exit /b 1 ) if /i "%OUTPUT_PATH:~-4%"==".tar" ( set "MANIFEST_PATH=%OUTPUT_PATH:~0,-4%.manifest.txt" ) else ( set "MANIFEST_PATH=%OUTPUT_PATH%.manifest.txt" ) call :collect_git_info call :collect_built_at call :collect_tar_sha256 call :collect_image_ids for %%F in ("%OUTPUT_PATH%") do set "TAR_NAME=%%~nxF" for %%F in ("%OUTPUT_PATH%") do set /a SIZE_MB=%%~zF/1048576 > "%MANIFEST_PATH%" ( echo tag=%TAG% echo service=%SERVICE% echo built_at=!BUILT_AT! echo git_commit=!GIT_COMMIT! echo git_dirty=!GIT_DIRTY! echo tar=!TAR_NAME! echo tar_sha256=!TAR_SHA256! if /i not "!SERVICE!"=="all" ( echo image=thebet365-!SERVICE!:%TAG% echo image_id=!SVC_IMAGE_ID! ) else ( echo api_image=thebet365-api:%TAG% echo api_image_id=!API_IMAGE_ID! echo player_image=thebet365-player:%TAG% echo player_image_id=!PLAYER_IMAGE_ID! echo admin_image=thebet365-admin:%TAG% echo admin_image_id=!ADMIN_IMAGE_ID! ) ) echo Done: %OUTPUT_PATH% ^(!SIZE_MB! MB^) echo Manifest: %MANIFEST_PATH% echo. echo Server load and recreate: if /i "%SERVICE%"=="all" ( echo docker load -i !TAR_NAME! echo docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --force-recreate api player admin ) else ( echo docker load -i !TAR_NAME! echo docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --force-recreate %SERVICE% ) echo. endlocal exit /b 0 :default_tag set "TAG=latest" exit /b 0 :default_output if /i "%SERVICE%"=="all" ( set "OUTPUT=thebet365-images-%TAG%.tar" ) else ( set "OUTPUT=thebet365-%SERVICE%-%TAG%.tar" ) exit /b 0 :validate_service if /i "%SERVICE%"=="all" exit /b 0 if /i "%SERVICE%"=="api" exit /b 0 if /i "%SERVICE%"=="player" exit /b 0 if /i "%SERVICE%"=="admin" exit /b 0 echo ERROR: invalid --service: %SERVICE% (use api, player, admin, or all) exit /b 1 :validate_tag echo %TAG%| findstr /r "^[A-Za-z0-9_][A-Za-z0-9_.-]*$" >nul if errorlevel 1 ( echo ERROR: invalid image tag: %TAG% exit /b 1 ) exit /b 0 :collect_git_info set "GIT_COMMIT=unknown" set "GIT_DIRTY=unknown" for /f "delims=" %%C in ('git rev-parse HEAD 2^>nul') do set "GIT_COMMIT=%%C" git diff --quiet 2>nul set "DIFF_EXIT=!ERRORLEVEL!" git diff --cached --quiet 2>nul set "CACHED_EXIT=!ERRORLEVEL!" if "!DIFF_EXIT!"=="0" if "!CACHED_EXIT!"=="0" set "GIT_DIRTY=false" if not "!GIT_COMMIT!"=="unknown" if not "!GIT_DIRTY!"=="false" set "GIT_DIRTY=true" exit /b 0 :collect_built_at set "BUILT_AT=unknown" for /f "delims=" %%T in ('powershell -NoProfile -Command "(Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')"') do set "BUILT_AT=%%T" exit /b 0 :collect_tar_sha256 set "TAR_SHA256=unavailable" for /f "usebackq delims=" %%H in (`certutil -hashfile "%OUTPUT_PATH%" SHA256 ^| findstr /r "^[0-9a-fA-F][0-9a-fA-F]"`) do set "TAR_SHA256=%%H" if defined TAR_SHA256 set "TAR_SHA256=!TAR_SHA256: =!" exit /b 0 :collect_image_ids set "API_IMAGE_ID=" set "PLAYER_IMAGE_ID=" set "ADMIN_IMAGE_ID=" set "SVC_IMAGE_ID=" for /f "delims=" %%I in ('docker image inspect thebet365-api:%TAG% --format "{{.Id}}" 2^>nul') do set "API_IMAGE_ID=%%I" for /f "delims=" %%I in ('docker image inspect thebet365-player:%TAG% --format "{{.Id}}" 2^>nul') do set "PLAYER_IMAGE_ID=%%I" for /f "delims=" %%I in ('docker image inspect thebet365-admin:%TAG% --format "{{.Id}}" 2^>nul') do set "ADMIN_IMAGE_ID=%%I" if /i not "%SERVICE%"=="all" ( for /f "delims=" %%I in ('docker image inspect thebet365-%SERVICE%:%TAG% --format "{{.Id}}" 2^>nul') do set "SVC_IMAGE_ID=%%I" ) exit /b 0