diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c36a90a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# Shell scripts must always use LF line endings +# CRLF breaks shebang lines in Linux containers (e.g. Docker builds) +*.sh text eol=lf + +# JS/TS and other text files use native line endings +*.js text +*.ts text +*.json text +*.yml text +*.yaml text +*.md text +*.toml text diff --git a/Dockerfile b/Dockerfile index 99067dd..adb6a76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,7 +68,7 @@ COPY plugins/ ./plugins/ COPY scripts/ ./scripts/ # Install default plugin dependencies (apt packages + post-install hooks) -RUN scripts/install-plugin-deps.sh +RUN sh scripts/install-plugin-deps.sh ENV NODE_ENV=production ENV CAMOFOX_PORT=9377 diff --git a/Dockerfile.ci b/Dockerfile.ci index 5d25155..4ab8fd2 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -74,7 +74,9 @@ COPY plugins/ ./plugins/ COPY scripts/ ./scripts/ # Install default plugin dependencies -RUN scripts/install-plugin-deps.sh +# Note: sh is used explicitly (./scripts/) because COPY from Windows +# does not preserve +x permission bits in Docker build contexts. +RUN sh scripts/install-plugin-deps.sh ENV NODE_ENV=production ENV CAMOFOX_PORT=9377 diff --git a/README.md b/README.md index dd96991..3bb6405 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,39 @@ make up ARCH=x86_64 make up VERSION=135.0.1 RELEASE=beta.24 ``` +#### Windows + +On Windows, `make` is not available. Use the included `build.ps1` PowerShell script instead: + +```powershell +# Build and start +.\build.ps1 up + +# Stop and remove the container +.\build.ps1 down + +# Build image only +.\build.ps1 build + +# Force a clean rebuild +.\build.ps1 reset + +# Download binaries only (without building) +.\build.ps1 fetch + +# Override architecture +.\build.ps1 up -Arch x86_64 +.\build.ps1 up -Arch aarch64 +``` + +> **Note:** PowerShell 7+ (`pwsh`) is recommended but `powershell.exe` (Windows PowerShell 5.1) also works. The script requires Docker Desktop for Windows with the WSL2 backend. +> +> **Line endings:** This project includes a `.gitattributes` file that forces Unix (`LF`) line endings for `.sh` files. If you've already cloned the repo and get `sh: not found` or `set: Illegal option -` errors during `docker build`, run: +> ```powershell +> Get-ChildItem -Recurse *.sh | ForEach-Object { (Get-Content $_) -join "`n" + "`n" | Set-Content $_ -NoNewline } +> ``` +> This converts shell scripts to LF line endings. Future clones will handle this automatically thanks to `.gitattributes`. + > **WARNING: Do not run `docker build` directly.** The Dockerfile uses bind mounts to pull pre-downloaded binaries from `dist/`. Always use `make up` (or `make fetch` then `make build`) -- it downloads the binaries first. ### Fly.io diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..2e5281a --- /dev/null +++ b/build.ps1 @@ -0,0 +1,171 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Build and manage camofox-browser on Windows (PowerShell alternative to Makefile). + +.DESCRIPTION + Provides the same targets as the Makefile for Windows users without make: + build - Download Camoufox + yt-dlp, then build the Docker image. + up - Build (if needed) and run the container. + down - Stop and remove the container. + reset - Full rebuild from scratch. + clean - Remove downloaded binaries. + fetch - Download Camoufox + yt-dlp binaries only. + +.PARAMETER Target + The action to perform: build, up, down, reset, clean, fetch (default: build). + +.PARAMETER Arch + Target architecture: x86_64 or aarch64 (default: x86_64). + +.PARAMETER CamoufoxVersion + Camoufox version (default: 135.0.1). + +.PARAMETER CamoufoxRelease + Camoufox release channel (default: beta.24). + +.PARAMETER ContainerName + Docker container name (default: camofox-browser). + +.PARAMETER HostPort + Host port to map (default: 9377). + +.EXAMPLE + .\build.ps1 up # Build + run + .\build.ps1 down # Stop container + .\build.ps1 fetch # Download binaries only +#> + +param( + [ValidateSet('build', 'up', 'down', 'reset', 'clean', 'fetch')] + [string]$Target = 'build', + + [ValidateSet('x86_64', 'aarch64')] + [string]$Arch = 'x86_64', + + [string]$CamoufoxVersion = '135.0.1', + [string]$CamoufoxRelease = 'beta.24', + [string]$ContainerName = 'camofox-browser', + [int]$HostPort = 9377 +) + +$ErrorActionPreference = 'Stop' +$ProjectRoot = Split-Path -Parent $PSCommandPath +$DistDir = Join-Path $ProjectRoot 'dist' +$CamoufoxZip = Join-Path $DistDir "camoufox-$Arch.zip" +$YtDlpBin = Join-Path $DistDir "yt-dlp-$Arch" +$ImageTag = "camofox-browser:$CamoufoxVersion-$Arch" +$ContainerPort = 9377 + +# Map architecture to upstream release filenames +if ($Arch -eq 'aarch64') { + $CamoufoxArch = 'arm64' + $YtDlpSuffix = '_aarch64' +} else { + $CamoufoxArch = 'x86_64' + $YtDlpSuffix = '' +} + +$CamoufoxUrl = "https://github.com/daijro/camoufox/releases/download/v$CamoufoxVersion-$CamoufoxRelease/camoufox-$CamoufoxVersion-$CamoufoxRelease-lin.$CamoufoxArch.zip" +$YtDlpUrl = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux$YtDlpSuffix" + +function Write-Step { + param([string]$Message) + Write-Host ">>> $Message" -ForegroundColor Cyan +} + +function Invoke-Fetch { + Write-Step "Creating dist directory..." + New-Item -ItemType Directory -Path $DistDir -Force | Out-Null + + if (-not (Test-Path $CamoufoxZip)) { + Write-Step "Downloading Camoufox browser ($CamoufoxArch)..." + Write-Host " URL: $CamoufoxUrl" + curl.exe -L -o $CamoufoxZip $CamoufoxUrl + Write-Host " Downloaded: $(Get-Item $CamoufoxZip | Select-Object -ExpandProperty Length) bytes" + } else { + Write-Host " [SKIP] Camoufox already downloaded" + } + + if (-not (Test-Path $YtDlpBin)) { + Write-Step "Downloading yt-dlp ($Arch)..." + Write-Host " URL: $YtDlpUrl" + curl.exe -L -o $YtDlpBin $YtDlpUrl + Write-Host " Downloaded: $(Get-Item $YtDlpBin | Select-Object -ExpandProperty Length) bytes" + } else { + Write-Host " [SKIP] yt-dlp already downloaded" + } +} + +function Invoke-Build { + Invoke-Fetch + + Write-Step "Building Docker image: $ImageTag" + docker build ` + --build-arg "ARCH=$Arch" ` + --build-arg "CAMOUFOX_VERSION=$CamoufoxVersion" ` + --build-arg "CAMOUFOX_RELEASE=$CamoufoxRelease" ` + -t $ImageTag ` + -f (Join-Path $ProjectRoot 'Dockerfile') ` + $ProjectRoot +} + +function Invoke-Up { + # Check if image exists + $imageExists = docker images -q $ImageTag 2>$null + if (-not $imageExists) { + Write-Step "Image not found — building first..." + Invoke-Build + } + + # Stop & remove existing container + docker stop $ContainerName 2>$null | Out-Null + docker rm $ContainerName 2>$null | Out-Null + + Write-Step "Starting container: $ContainerName on port $HostPort" + docker run -d ` + --restart unless-stopped ` + --name $ContainerName ` + -p "${HostPort}:${ContainerPort}" ` + $ImageTag + + Write-Host "Container started. Server should be available at http://localhost:$HostPort" -ForegroundColor Green + Write-Host "Check logs: docker logs $ContainerName" -ForegroundColor Gray +} + +function Invoke-Down { + Write-Step "Stopping container: $ContainerName" + docker stop $ContainerName 2>$null + docker rm $ContainerName 2>$null + Write-Host "Container stopped and removed." -ForegroundColor Green +} + +function Invoke-Reset { + Invoke-Down + + Write-Step "Removing Docker image: $ImageTag" + docker rmi $ImageTag 2>$null + + Invoke-Build + Invoke-Up +} + +function Invoke-Clean { + Write-Step "Removing dist directory..." + if (Test-Path $DistDir) { + Remove-Item -Recurse -Force $DistDir + Write-Host "Removed: $DistDir" -ForegroundColor Green + } else { + Write-Host "Nothing to clean." -ForegroundColor Yellow + } +} + +# --- Main dispatch --- +switch ($Target) { + 'build' { Invoke-Build } + 'up' { Invoke-Up } + 'down' { Invoke-Down } + 'reset' { Invoke-Reset } + 'clean' { Invoke-Clean } + 'fetch' { Invoke-Fetch } +}