|
|
@echo off
|
|
|
setlocal EnableExtensions EnableDelayedExpansion
|
|
|
|
|
|
rem Build and export only the linux/amd64 inspect-nvr application image.
|
|
|
rem The BAT file intentionally contains ASCII only so cmd.exe cannot corrupt
|
|
|
rem UTF-8 comments or messages into executable command fragments.
|
|
|
rem
|
|
|
rem Usage:
|
|
|
rem export-inspect-nvr-amd64.bat
|
|
|
rem export-inspect-nvr-amd64.bat --no-cache
|
|
|
rem export-inspect-nvr-amd64.bat D:\backup\inspect-nvr-app-amd64.tar --no-cache
|
|
|
rem export-inspect-nvr-amd64.bat --preflight
|
|
|
rem export-inspect-nvr-amd64.bat --help
|
|
|
|
|
|
cd /d "%~dp0"
|
|
|
if not exist "%~dp0deploy-package" mkdir "%~dp0deploy-package"
|
|
|
|
|
|
set "IMAGE=inspect-nvr:local"
|
|
|
set "FFMPEG_IMAGE="
|
|
|
set "FFMPEG_DIGEST=sha256:3d1966c91f65f7e7a4ea09203852c8563ef9e48c3ca2cd5e79c8bc80bde02569"
|
|
|
set "OUTPUT=%~dp0deploy-package\inspect-nvr-app-amd64-latest.tar"
|
|
|
set "NO_CACHE=0"
|
|
|
set "PREFLIGHT=0"
|
|
|
set "OLD_CONTAINER_ID="
|
|
|
set "OLD_IMAGE_ID="
|
|
|
set "NEW_IMAGE_ID="
|
|
|
|
|
|
if /I "%~1"=="--help" goto :usage
|
|
|
if /I "%~1"=="--preflight" set "PREFLIGHT=1"
|
|
|
if /I "%~1"=="--no-cache" set "NO_CACHE=1"
|
|
|
if not "%~1"=="" if /I not "%~1"=="--preflight" if /I not "%~1"=="--no-cache" set "OUTPUT=%~1"
|
|
|
if /I "%~2"=="--no-cache" set "NO_CACHE=1"
|
|
|
|
|
|
echo [1/10] Checking Docker...
|
|
|
where docker >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] Docker was not found. Start Docker Desktop first.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
docker info >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] The Docker engine is not running.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
docker compose version >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] The docker compose plugin is not available.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
docker buildx version >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] Docker Buildx is not available.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
if not exist "%~dp0docker-compose.yml" (
|
|
|
echo [ERROR] docker-compose.yml was not found in %~dp0
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
rem Compose only needs placeholder ZLM URLs while resolving the build model.
|
|
|
rem Real remote ZLM URLs are still mandatory before the local container starts.
|
|
|
findstr /R /B /C:"ZLM_RTMP_BASE_URL=" "%~dp0.env" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
set "ZLM_RTMP_BASE_URL=rtmp://build-only.invalid:1935"
|
|
|
echo [INFO] ZLM_RTMP_BASE_URL is missing. Using a build-only placeholder.
|
|
|
)
|
|
|
findstr /R /B /C:"ZLM_HTTP_BASE_URL=" "%~dp0.env" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
set "ZLM_HTTP_BASE_URL=http://build-only.invalid:18081"
|
|
|
echo [INFO] ZLM_HTTP_BASE_URL is missing. Using a build-only placeholder.
|
|
|
)
|
|
|
|
|
|
echo [2/10] Checking the FFmpeg base image...
|
|
|
rem Try verified mirrors first. The digest check prevents a mirror from
|
|
|
rem returning content that differs from the official 6.1 manifest.
|
|
|
call :try_ffmpeg_image "docker.1ms.run/mwader/static-ffmpeg:6.1"
|
|
|
call :try_ffmpeg_image "dockerproxy.net/mwader/static-ffmpeg:6.1"
|
|
|
call :try_ffmpeg_image "hub.rat.dev/mwader/static-ffmpeg:6.1"
|
|
|
call :try_ffmpeg_image "registry-1.docker.io/mwader/static-ffmpeg:6.1"
|
|
|
if not defined FFMPEG_IMAGE (
|
|
|
echo [ERROR] No verified FFmpeg registry is reachable.
|
|
|
echo [ERROR] Check Docker Desktop proxy, DNS, or registry connectivity.
|
|
|
goto :fail
|
|
|
)
|
|
|
echo Selected: !FFMPEG_IMAGE!
|
|
|
echo Verified digest: %FFMPEG_DIGEST%
|
|
|
|
|
|
if "!PREFLIGHT!"=="1" (
|
|
|
echo.
|
|
|
echo Preflight passed. No image was removed or built.
|
|
|
endlocal
|
|
|
exit /b 0
|
|
|
)
|
|
|
|
|
|
echo [3/10] Removing the old %IMAGE% image...
|
|
|
rem Read the image from the existing Compose container first. This also finds
|
|
|
rem an untagged image left behind when an earlier build stopped with an error.
|
|
|
for /f "delims=" %%C in ('docker compose ps --all -q inspect-nvr 2^>nul') do set "OLD_CONTAINER_ID=%%C"
|
|
|
if defined OLD_CONTAINER_ID (
|
|
|
for /f "delims=" %%I in ('docker inspect --format "{{.Image}}" "!OLD_CONTAINER_ID!" 2^>nul') do set "OLD_IMAGE_ID=%%I"
|
|
|
)
|
|
|
docker image inspect "%IMAGE%" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
if defined OLD_IMAGE_ID (
|
|
|
echo The running container uses an already untagged old image.
|
|
|
) else (
|
|
|
echo No old image with this name exists.
|
|
|
)
|
|
|
) else (
|
|
|
rem Remember the exact old image so its untagged data can be deleted after
|
|
|
rem Compose replaces the running application container.
|
|
|
if not defined OLD_IMAGE_ID (
|
|
|
for /f "delims=" %%I in ('docker image inspect --format "{{.Id}}" "%IMAGE%" 2^>nul') do set "OLD_IMAGE_ID=%%I"
|
|
|
)
|
|
|
if not defined OLD_IMAGE_ID (
|
|
|
echo [ERROR] Failed to read the old %IMAGE% image ID.
|
|
|
goto :fail
|
|
|
)
|
|
|
rem Force only removes the image tag when a running container still uses
|
|
|
rem the old image ID, so the current container can keep running during build.
|
|
|
docker image rm --force "%IMAGE%"
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] Failed to remove the old %IMAGE% image.
|
|
|
goto :fail
|
|
|
)
|
|
|
)
|
|
|
|
|
|
echo [4/10] Building %IMAGE%...
|
|
|
if "!NO_CACHE!"=="1" (
|
|
|
echo Building with --no-cache. The first build can take a while.
|
|
|
docker compose build --no-cache inspect-nvr
|
|
|
) else (
|
|
|
echo Using the build cache. Source or pom.xml changes rebuild the JAR.
|
|
|
docker compose build inspect-nvr
|
|
|
)
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] Docker image build failed. Review the Maven or Docker log above.
|
|
|
echo [INFO] An existing running container was not stopped by this script.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
echo [5/10] Checking the image platform...
|
|
|
docker image inspect "%IMAGE%" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] The build finished without creating %IMAGE%.
|
|
|
goto :fail
|
|
|
)
|
|
|
for /f "delims=" %%I in ('docker image inspect --format "{{.Id}}" "%IMAGE%" 2^>nul') do set "NEW_IMAGE_ID=%%I"
|
|
|
if not defined NEW_IMAGE_ID (
|
|
|
echo [ERROR] Failed to read the new %IMAGE% image ID.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
set "PLATFORM_FILE=%TEMP%\inspect-nvr-platform-%RANDOM%.txt"
|
|
|
docker image inspect --format "{{.Os}}/{{.Architecture}}" "%IMAGE%" > "%PLATFORM_FILE%"
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] Failed to read the image platform.
|
|
|
del /f /q "%PLATFORM_FILE%" >nul 2>&1
|
|
|
goto :fail
|
|
|
)
|
|
|
set "PLATFORM="
|
|
|
set /p PLATFORM=<"%PLATFORM_FILE%"
|
|
|
del /f /q "%PLATFORM_FILE%" >nul 2>&1
|
|
|
if /I not "!PLATFORM!"=="linux/amd64" (
|
|
|
echo [ERROR] Image platform is !PLATFORM!, expected linux/amd64.
|
|
|
goto :fail
|
|
|
)
|
|
|
echo Platform confirmed: !PLATFORM!
|
|
|
|
|
|
if exist "%OUTPUT%" (
|
|
|
echo [INFO] Output file already exists: %OUTPUT%
|
|
|
choice /C YN /N /M "Overwrite it? [Y/N]"
|
|
|
if errorlevel 2 (
|
|
|
echo Cancelled.
|
|
|
goto :fail
|
|
|
)
|
|
|
del /f /q "%OUTPUT%" >nul 2>&1
|
|
|
)
|
|
|
if exist "%OUTPUT%.sha256" del /f /q "%OUTPUT%.sha256" >nul 2>&1
|
|
|
|
|
|
echo [6/10] Exporting the application image...
|
|
|
docker save -o "%OUTPUT%" "%IMAGE%"
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] docker save failed. The output file may be in use.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
echo [7/10] Checking the tar contents...
|
|
|
where tar >nul 2>&1
|
|
|
if not errorlevel 1 (
|
|
|
tar -xOf "%OUTPUT%" manifest.json | findstr /I /C:"zlmediakit" >nul
|
|
|
if not errorlevel 1 (
|
|
|
echo [ERROR] ZLMediaKit was found in the application tar.
|
|
|
goto :fail
|
|
|
)
|
|
|
echo ZLMediaKit is not present in the tar.
|
|
|
) else (
|
|
|
echo [INFO] tar was not found. docker save only exported %IMAGE%.
|
|
|
)
|
|
|
|
|
|
echo [8/10] Generating the SHA256 file...
|
|
|
where powershell >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] PowerShell was not found.
|
|
|
goto :fail
|
|
|
)
|
|
|
set "HASH_FILE=%OUTPUT%.sha256"
|
|
|
rem Use .NET directly because Windows PowerShell launched through cmd.exe can
|
|
|
rem inherit PowerShell 7 module paths and then fail to autoload Get-FileHash.
|
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference='Stop'; try { $stream=[IO.File]::OpenRead($env:OUTPUT); try { $sha=[Security.Cryptography.SHA256]::Create(); try { $hash=([BitConverter]::ToString($sha.ComputeHash($stream))).Replace('-','').ToLowerInvariant() } finally { if ($null -ne $sha) { $sha.Dispose() } } } finally { if ($null -ne $stream) { $stream.Dispose() } }; if ($hash.Length -ne 64) { throw 'Unexpected SHA256 length.' }; $name=[IO.Path]::GetFileName($env:OUTPUT); [IO.File]::WriteAllText($env:HASH_FILE, ($hash + ' ' + $name + [char]10), [Text.Encoding]::ASCII) } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 1 }"
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] SHA256 generation failed.
|
|
|
goto :fail
|
|
|
)
|
|
|
set "HASH="
|
|
|
set /p HASH=<"%HASH_FILE%"
|
|
|
|
|
|
echo [9/10] Recreating the local application container...
|
|
|
rem Build placeholders must never be used when the application starts.
|
|
|
findstr /R /B /C:"ZLM_RTMP_BASE_URL=" "%~dp0.env" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] The image was built and exported, but the container was not recreated.
|
|
|
echo [ERROR] Set a real ZLM_RTMP_BASE_URL in .env and run Compose again.
|
|
|
goto :fail
|
|
|
)
|
|
|
findstr /R /B /C:"ZLM_HTTP_BASE_URL=" "%~dp0.env" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] The image was built and exported, but the container was not recreated.
|
|
|
echo [ERROR] Set a real ZLM_HTTP_BASE_URL in .env and run Compose again.
|
|
|
goto :fail
|
|
|
)
|
|
|
docker compose up -d --force-recreate --no-build inspect-nvr
|
|
|
if errorlevel 1 (
|
|
|
echo [ERROR] The image was exported, but the local container recreate failed.
|
|
|
goto :fail
|
|
|
)
|
|
|
|
|
|
echo [10/10] Releasing the previous application image...
|
|
|
if not defined OLD_IMAGE_ID (
|
|
|
echo No previous image data needs cleanup.
|
|
|
) else if /I "!OLD_IMAGE_ID!"=="!NEW_IMAGE_ID!" (
|
|
|
rem An unchanged cached build can reproduce the same image ID. That ID now
|
|
|
rem belongs to the new tag and container, so there is no duplicate to delete.
|
|
|
echo The rebuilt image reused the same image ID. No duplicate remains.
|
|
|
) else (
|
|
|
rem The old Compose container is gone, so deleting its exact image ID frees
|
|
|
rem only obsolete inspect-nvr layers and leaves other project images intact.
|
|
|
docker image rm "!OLD_IMAGE_ID!"
|
|
|
if errorlevel 1 (
|
|
|
echo [WARN] The previous image is still used by another container.
|
|
|
echo [WARN] Docker kept it to avoid breaking that container.
|
|
|
) else (
|
|
|
echo Previous image data released: !OLD_IMAGE_ID!
|
|
|
)
|
|
|
)
|
|
|
|
|
|
echo.
|
|
|
echo Export completed:
|
|
|
echo Image: %IMAGE%
|
|
|
echo File: %OUTPUT%
|
|
|
echo Hash: %HASH_FILE%
|
|
|
echo SHA256: !HASH!
|
|
|
echo Local container: recreated with the new image
|
|
|
echo.
|
|
|
echo Ubuntu import command:
|
|
|
echo sudo docker load -i inspect-nvr-app-amd64-latest.tar
|
|
|
echo.
|
|
|
echo For a clean rebuild:
|
|
|
echo export-inspect-nvr-amd64.bat --no-cache
|
|
|
echo.
|
|
|
pause
|
|
|
endlocal
|
|
|
exit /b 0
|
|
|
|
|
|
:usage
|
|
|
echo Usage:
|
|
|
echo export-inspect-nvr-amd64.bat
|
|
|
echo export-inspect-nvr-amd64.bat --no-cache
|
|
|
echo export-inspect-nvr-amd64.bat OUTPUT_FILE [--no-cache]
|
|
|
echo export-inspect-nvr-amd64.bat --preflight
|
|
|
endlocal
|
|
|
exit /b 0
|
|
|
|
|
|
:try_ffmpeg_image
|
|
|
if defined FFMPEG_IMAGE exit /b 0
|
|
|
set "FFMPEG_CANDIDATE=%~1"
|
|
|
set "FFMPEG_CHECK_FILE=%TEMP%\inspect-nvr-ffmpeg-%RANDOM%.txt"
|
|
|
echo Trying !FFMPEG_CANDIDATE!...
|
|
|
docker buildx imagetools inspect "!FFMPEG_CANDIDATE!" >"!FFMPEG_CHECK_FILE!" 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo Unavailable.
|
|
|
del /f /q "!FFMPEG_CHECK_FILE!" >nul 2>&1
|
|
|
exit /b 0
|
|
|
)
|
|
|
findstr /L /C:"!FFMPEG_DIGEST!" "!FFMPEG_CHECK_FILE!" >nul 2>&1
|
|
|
if errorlevel 1 (
|
|
|
echo Rejected because the manifest digest does not match.
|
|
|
del /f /q "!FFMPEG_CHECK_FILE!" >nul 2>&1
|
|
|
exit /b 0
|
|
|
)
|
|
|
del /f /q "!FFMPEG_CHECK_FILE!" >nul 2>&1
|
|
|
set "FFMPEG_IMAGE=!FFMPEG_CANDIDATE!"
|
|
|
echo Available and digest verified.
|
|
|
exit /b 0
|
|
|
|
|
|
:fail
|
|
|
echo.
|
|
|
echo Script stopped. Press any key to close this window.
|
|
|
pause >nul
|
|
|
endlocal
|
|
|
exit /b 1
|