@@ -420,6 +420,35 @@ function Get-TimestampFile
420420 return $timestampFile
421421}
422422
423+ function Get-ContentHash {
424+ param (
425+ [string ]$DockerfilePath ,
426+ [string ]$ContextDirectory
427+ )
428+
429+ $hashInput = Get-Content $DockerfilePath - Raw - ErrorAction SilentlyContinue
430+ if (-not $hashInput ) { $hashInput = " " }
431+
432+ # Add context files (excluding generated .g/ directory)
433+ $contextFiles = Get-ChildItem $ContextDirectory - Recurse - File - ErrorAction SilentlyContinue |
434+ Where-Object { $_.FullName -notmatch ' [/\\]\.g[/\\]' } |
435+ Sort-Object FullName
436+
437+ foreach ($file in $contextFiles ) {
438+ $content = Get-Content $file.FullName - Raw - ErrorAction SilentlyContinue
439+ if ($content ) {
440+ $hashInput += " `n --- $ ( $file.Name ) ---`n "
441+ $hashInput += $content
442+ }
443+ }
444+
445+ $hashBytes = [System.Security.Cryptography.SHA256 ]::Create().ComputeHash(
446+ [System.Text.Encoding ]::UTF8.GetBytes($hashInput )
447+ )
448+ # Use 8 bytes (16 hex chars) for uniqueness
449+ return [System.BitConverter ]::ToString($hashBytes , 0 , 8 ).Replace(" -" , " " ).ToLower()
450+ }
451+
423452# Dictionary to track volume mounts with "writable wins" logic
424453$script :VolumeMountDict = @ {}
425454
@@ -464,6 +493,22 @@ if (-not $Dockerfile)
464493 {
465494 $Dockerfile = " Dockerfile"
466495 }
496+
497+ # On Windows, detect if we're running on Server 2022 (build < 26100) and use matching Dockerfile
498+ # Windows Server 2025 is build 26100+, 2022 is build 20348-26099
499+ if ($IsWindows -and -not $Claude )
500+ {
501+ $osBuild = [System.Environment ]::OSVersion.Version.Build
502+ if ($osBuild -lt 26100 )
503+ {
504+ $win2022Dockerfile = " Dockerfile.win2022"
505+ if (Test-Path (Join-Path $PSScriptRoot $win2022Dockerfile ))
506+ {
507+ Write-Host " Detected Windows Server 2022 (build $osBuild ), using $win2022Dockerfile " - ForegroundColor Cyan
508+ $Dockerfile = $win2022Dockerfile
509+ }
510+ }
511+ }
467512}
468513
469514# Get the full path of the Dockerfile
@@ -476,19 +521,21 @@ else
476521 $dockerfileFullPath = Join-Path $PSScriptRoot $Dockerfile
477522}
478523
479- # Generate a hash of the Dockerfile full path (4 bytes, 8 hex chars)
480- $hashBytes = ( New-Object - TypeName System.Security.Cryptography.SHA256Managed).ComputeHash([ System.Text.Encoding ]::UTF8.GetBytes( $dockerfileFullPath ))
481- $dockerfileHash = [ System.BitConverter ]::ToString( $hashBytes , 0 , 4 ).Replace( " - " , " " ).ToLower()
524+ # Generate content-based hash for image tag
525+ $contentHash = Get-ContentHash - DockerfilePath $dockerfileFullPath - ContextDirectory $dockerContextDirectory
526+ $ghcrRegistry = $ env: GHCR_REGISTRY
482527
483- # Generate ImageTag using the hash
484- if ( [string ]::IsNullOrEmpty($ImageName ))
485- {
486- $ImageTag = " dockerfile-$dockerfileHash "
487- Write-Host " Generated image tag from Dockerfile path hash: $ImageTag " - ForegroundColor Cyan
528+ if ($ghcrRegistry ) {
529+ # GHCR mode: use registry URL with content hash
530+ $ImageTag = " ${ghcrRegistry} :${contentHash} "
531+ Write-Host " GHCR image tag: $ImageTag " - ForegroundColor Cyan
488532}
489- else
490- {
491- $ImageTag = " $ImageName `:$dockerfileHash "
533+ elseif ([string ]::IsNullOrEmpty($ImageName )) {
534+ $ImageTag = " dockerfile-$contentHash "
535+ Write-Host " Generated image tag from content hash: $ImageTag " - ForegroundColor Cyan
536+ }
537+ else {
538+ $ImageTag = " $ImageName `:$contentHash "
492539 Write-Host " Image will be tagged as: $ImageTag " - ForegroundColor Cyan
493540}
494541
@@ -1148,6 +1195,47 @@ if (-not $existingContainerId)
11481195 }
11491196}
11501197
1198+ # GHCR authentication and pull logic
1199+ $builtNewImage = $false
1200+ $dockerConfigArg = @ ()
1201+
1202+ if ($ghcrRegistry -and -not $NoBuildImage -and -not $existingContainerId ) {
1203+ # Extract registry host from full URL (e.g., ghcr.io from ghcr.io/owner/repo)
1204+ $registryHost = ($ghcrRegistry -split ' /' )[0 ]
1205+
1206+ # Create a temporary Docker config directory to avoid credential helper issues
1207+ # (e.g., docker-credential-desktop not found when using Docker Engine without Desktop)
1208+ $tempDockerConfig = Join-Path $env: TEMP " docker-config-$ ( New-Guid ) "
1209+ New-Item - ItemType Directory - Path $tempDockerConfig - Force | Out-Null
1210+ @ { auths = @ {} } | ConvertTo-Json | Set-Content (Join-Path $tempDockerConfig " config.json" )
1211+ $dockerConfigArg = @ (" --config" , $tempDockerConfig )
1212+
1213+ # Authenticate to GHCR
1214+ $ghcrToken = $env: GHCR_TOKEN
1215+ if ($ghcrToken ) {
1216+ Write-Host " Authenticating to GHCR..." - ForegroundColor Gray
1217+ # GHCR accepts any username when using a PAT, commonly 'github' is used
1218+ $ghcrToken | docker @dockerConfigArg login $registryHost -- username github -- password- stdin 2> $null
1219+ if ($LASTEXITCODE -ne 0 ) {
1220+ Write-Host " Warning: GHCR authentication failed. Pull/push may fail." - ForegroundColor Yellow
1221+ }
1222+ }
1223+ else {
1224+ Write-Host " Warning: GHCR_TOKEN not set. GHCR pull/push may fail." - ForegroundColor Yellow
1225+ }
1226+
1227+ # Try to pull the image
1228+ Write-Host " Checking GHCR for existing image: $ImageTag " - ForegroundColor Cyan
1229+ docker @dockerConfigArg pull $ImageTag 2> $null
1230+ if ($LASTEXITCODE -eq 0 ) {
1231+ Write-Host " Using cached image from GHCR." - ForegroundColor Green
1232+ $NoBuildImage = $true
1233+ }
1234+ else {
1235+ Write-Host " Image not found in GHCR, will build locally." - ForegroundColor Yellow
1236+ }
1237+ }
1238+
11511239# Building the image.
11521240if (-not $NoBuildImage -and -not $existingContainerId )
11531241{
@@ -1221,6 +1309,20 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \
12211309 Write-Host " Docker build failed with exit code $LASTEXITCODE " - ForegroundColor Red
12221310 exit $LASTEXITCODE
12231311 }
1312+
1313+ $builtNewImage = $true
1314+
1315+ # Auto-push to GHCR after successful build
1316+ if ($ghcrRegistry ) {
1317+ Write-Host " Pushing image to GHCR: $ImageTag " - ForegroundColor Cyan
1318+ docker @dockerConfigArg push $ImageTag
1319+ if ($LASTEXITCODE -ne 0 ) {
1320+ Write-Host " Warning: Failed to push image to GHCR" - ForegroundColor Yellow
1321+ }
1322+ else {
1323+ Write-Host " Successfully pushed to GHCR" - ForegroundColor Green
1324+ }
1325+ }
12241326}
12251327else
12261328{
0 commit comments