diff --git a/.env.docker.example b/.env.docker.example index 58413ad..977d457 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -25,6 +25,9 @@ BIND_ADDR=127.0.0.1 PLAYER_PORT=8082 ADMIN_PORT=8081 +# 管理端构建时注入:生成邀请注册链接的玩家端公网地址(勿带末尾 /) +VITE_PLAYER_URL=https://www.thebet365.net + # 备份保留天数;留空表示不自动清理 BACKUP_RETENTION_DAYS= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index db4a410..7f71f6a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -100,6 +100,8 @@ services: build: context: . dockerfile: docker/admin/Dockerfile + args: + VITE_PLAYER_URL: ${VITE_PLAYER_URL:-} container_name: thebet365-admin depends_on: api: diff --git a/docker/admin/Dockerfile b/docker/admin/Dockerfile index 460b9de..5f8107a 100644 --- a/docker/admin/Dockerfile +++ b/docker/admin/Dockerfile @@ -13,6 +13,8 @@ COPY packages/shared/package.json packages/shared/ RUN pnpm install --frozen-lockfile FROM base AS builder +ARG VITE_PLAYER_URL +ENV VITE_PLAYER_URL=${VITE_PLAYER_URL} WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile index 99453d3..3017fb6 100644 --- a/docker/api/Dockerfile +++ b/docker/api/Dockerfile @@ -39,7 +39,8 @@ COPY --from=builder /app/apps/api/node_modules ./apps/api/node_modules COPY uploads/banners ./uploads-default/banners COPY docker/api/entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +# Windows checkouts may use CRLF; strip before chmod or Linux exec fails with "no such file". +RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh WORKDIR /app/apps/api EXPOSE 3000 diff --git a/docs/docker/build-and-export-images.bat b/docs/docker/build-and-export-images.bat new file mode 100644 index 0000000..f23eed4 --- /dev/null +++ b/docs/docker/build-and-export-images.bat @@ -0,0 +1,214 @@ +@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 --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" +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"=="--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_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 --tag TAG Image tag (default: git short commit) +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. +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 + ) +) + +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 set "OUTPUT=thebet365-images-%TAG%.tar" + +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 api, player, admin (tag: %TAG%) +set "IMAGE_TAG=%TAG%" +if "%NO_CACHE%"=="1" goto build_no_cache +docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build api player admin +goto build_done + +:build_no_cache +docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build --no-cache api player admin + +:build_done +if errorlevel 1 ( + echo ERROR: docker build failed + exit /b 1 +) + +:do_export +echo ==^> Exporting images to %OUTPUT_PATH% +docker save thebet365-api:%TAG% thebet365-player:%TAG% thebet365-admin:%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 built_at=!BUILT_AT! + echo git_commit=!GIT_COMMIT! + echo git_dirty=!GIT_DIRTY! + echo tar=!TAR_NAME! + echo tar_sha256=!TAR_SHA256! + 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 first deploy: +echo ./scripts/deploy-first.sh --images !TAR_NAME! --tag %TAG% +echo. +echo Server update: +echo ./scripts/deploy-update.sh --images !TAR_NAME! --tag %TAG% +echo. + +endlocal +exit /b 0 + +:default_tag +for /f "delims=" %%T in ('git rev-parse --short HEAD 2^>nul') do set "TAG=%%T" +if defined TAG exit /b 0 +for /f "delims=" %%T in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd-HHmmss"') do set "TAG=%%T" +exit /b 0 + +: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=" +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" +exit /b 0 diff --git a/docs/docker/镜像构建与导出.md b/docs/docker/镜像构建与导出.md index de576fd..77ec8ba 100644 --- a/docs/docker/镜像构建与导出.md +++ b/docs/docker/镜像构建与导出.md @@ -12,6 +12,7 @@ | 文件 | 适用环境 | |------|----------| +| `docs/docker/build-and-export-images.bat` | Windows(CMD,可双击) | | `docs/docker/build-and-export-images.ps1` | Windows(PowerShell) | | `docs/docker/build-and-export-images.sh` | Linux / macOS / Git Bash | @@ -40,7 +41,13 @@ ### Windows -在项目根目录打开 PowerShell: +**推荐(CMD / 双击):** 在项目根目录打开命令提示符,或直接双击 `docs\docker\build-and-export-images.bat`: + +```bat +docs\docker\build-and-export-images.bat +``` + +PowerShell 也可用: ```powershell .\docs\docker\build-and-export-images.ps1 @@ -57,16 +64,20 @@ chmod +x docs/docker/build-and-export-images.sh ### 可选参数 -| PowerShell | Bash | 说明 | -|------------|------|------| -| `-Tag v1.2.3` | `--tag v1.2.3` | 指定镜像 tag;未指定时默认当前 Git 短提交号 | +| BAT / PowerShell | Bash | 说明 | +|----------------|------|------| +| `--tag v1.2.3` / `-Tag v1.2.3` | `--tag v1.2.3` | 指定镜像 tag;未指定时默认当前 Git 短提交号 | | (默认) | (默认) | `--no-cache` 全量构建,适合发版 | -| `-UseCache` | `--use-cache` | 使用 Docker 缓存,构建更快 | -| `-ExportOnly` | `--export-only` | 跳过构建,仅导出已有指定 tag 镜像 | -| `-Output my.tar` | `--output my.tar` | 自定义导出文件名 | +| `--use-cache` / `-UseCache` | `--use-cache` | 使用 Docker 缓存,构建更快 | +| `--export-only` / `-ExportOnly` | `--export-only` | 跳过构建,仅导出已有指定 tag 镜像 | +| `--output my.tar` / `-Output my.tar` | `--output my.tar` | 自定义导出文件名 | 示例:仅重新导出已有镜像 +```bat +docs\docker\build-and-export-images.bat --export-only +``` + ```powershell .\docs\docker\build-and-export-images.ps1 -ExportOnly ``` @@ -197,6 +208,7 @@ thebet365/ ├── Docker部署指南.md └── docker/ ├── 镜像构建与导出.md # 本文档 + ├── build-and-export-images.bat ├── build-and-export-images.ps1 └── build-and-export-images.sh ```