Created
October 26, 2025 17:51
-
-
Save CypherpunkSamurai/df1bd8626db1393cbebacfeedb0a2115 to your computer and use it in GitHub Desktop.
Tunnel Cloudflare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Cloudflared Tunnel Setup Script for Windows | |
| # Portable script with no external dependencies | |
| # All files are stored in ./cloudflared directory using environment variable tricks | |
| param( | |
| [string]$LocalPort = "8080", | |
| [string]$LocalHost = "127.0.0.1" | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| # Color output functions | |
| function Write-ColorOutput($ForegroundColor, $Message) { | |
| $fc = $host.UI.RawUI.ForegroundColor | |
| $host.UI.RawUI.ForegroundColor = $ForegroundColor | |
| Write-Output $Message | |
| $host.UI.RawUI.ForegroundColor = $fc | |
| } | |
| function Write-Info($Message) { Write-ColorOutput Cyan "INFO: $Message" } | |
| function Write-Success($Message) { Write-ColorOutput Green "SUCCESS: $Message" } | |
| function Write-Error($Message) { Write-ColorOutput Red "ERROR: $Message" } | |
| function Write-Warning($Message) { Write-ColorOutput Yellow "WARNING: $Message" } | |
| # Get the REAL original USERPROFILE from system (not from environment which might be modified) | |
| $OriginalUserProfile = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::UserProfile) | |
| # Configuration - everything in ./cloudflared | |
| $ScriptDir = $PSScriptRoot | |
| $CloudflaredDir = Join-Path $ScriptDir "cloudflared" | |
| $CloudflaredSubDir = Join-Path $CloudflaredDir ".cloudflared" | |
| $CloudflaredExe = Join-Path $CloudflaredDir "cloudflared.exe" | |
| $ConfigFile = Join-Path $CloudflaredSubDir "config.yml" | |
| $CertFile = Join-Path $CloudflaredDir ".cloudflared\cert.pem" | |
| # Store original USERPROFILE before we change it | |
| $OriginalUserProfile = $env:USERPROFILE | |
| Write-Info "Cloudflared Tunnel Setup Script" | |
| Write-Info "================================" | |
| Write-Info "Working directory: $CloudflaredDir" | |
| Write-Output "" | |
| # Function to run cloudflared with custom environment variables | |
| function Invoke-Cloudflared { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string[]]$Arguments | |
| ) | |
| # Store current USERPROFILE temporarily | |
| $tempUserProfile = $env:USERPROFILE | |
| # Set custom environment variables for this execution | |
| $env:USERPROFILE = $CloudflaredDir | |
| $env:HOME = $CloudflaredDir | |
| $env:TUNNEL_ORIGIN_CERT = $CertFile | |
| $env:NO_AUTOUPDATE = "true" | |
| # Execute cloudflared with the provided arguments | |
| & $CloudflaredExe @Arguments | |
| # Restore USERPROFILE after execution | |
| $env:USERPROFILE = $tempUserProfile | |
| } | |
| # Create directories | |
| if (-not (Test-Path $CloudflaredDir)) { | |
| New-Item -ItemType Directory -Path $CloudflaredDir -Force | Out-Null | |
| Write-Success "Created cloudflared directory" | |
| } | |
| if (-not (Test-Path $CloudflaredSubDir)) { | |
| New-Item -ItemType Directory -Path $CloudflaredSubDir -Force | Out-Null | |
| Write-Success "Created .cloudflared subdirectory" | |
| } | |
| Write-Info "Portable directory configuration:" | |
| Write-Output " Working Dir: $CloudflaredDir" | |
| Write-Output " Cert Path: $CertFile" | |
| Write-Output "" | |
| # Check if cloudflared exists, if not download it | |
| if (-not (Test-Path $CloudflaredExe)) { | |
| Write-Info "Cloudflared not found. Fetching latest release from GitHub..." | |
| try { | |
| # Fetch latest release info from GitHub API | |
| $apiUrl = "https://api.github.com/repos/cloudflare/cloudflared/releases/latest" | |
| Write-Info "Querying GitHub API: $apiUrl" | |
| $release = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers @{ | |
| "User-Agent" = "PowerShell-CloudflaredSetup" | |
| } | |
| $version = $release.tag_name | |
| Write-Success "Latest version: $version" | |
| # Find Windows AMD64 asset | |
| $asset = $release.assets | Where-Object { | |
| $_.name -like "*windows-amd64.exe" -or $_.name -eq "cloudflared-windows-amd64.exe" | |
| } | Select-Object -First 1 | |
| if (-not $asset) { | |
| Write-Error "Could not find Windows AMD64 binary in release" | |
| exit 1 | |
| } | |
| $downloadUrl = $asset.browser_download_url | |
| Write-Info "Downloading from: $downloadUrl" | |
| Write-Info "File size: $([math]::Round($asset.size / 1MB, 2)) MB" | |
| # Download the binary | |
| $tempFile = Join-Path $CloudflaredDir "cloudflared-temp.exe" | |
| Invoke-WebRequest -Uri $downloadUrl -OutFile $tempFile -UseBasicParsing | |
| # Rename to cloudflared.exe | |
| Move-Item -Path $tempFile -Destination $CloudflaredExe -Force | |
| Write-Success "Downloaded cloudflared $version successfully" | |
| # Verify the binary works | |
| $versionOutput = Invoke-Cloudflared --version 2>&1 | |
| Write-Success "Cloudflared version: $versionOutput" | |
| } | |
| catch { | |
| Write-Error "Failed to download cloudflared: $_" | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-Success "Cloudflared already exists at: $CloudflaredExe" | |
| try { | |
| $versionOutput = Invoke-Cloudflared --version 2>&1 | |
| Write-Info "Current version: $versionOutput" | |
| } | |
| catch { | |
| Write-Warning "Could not verify cloudflared version" | |
| } | |
| } | |
| Write-Output "" | |
| Write-Info "Select Tunnel Mode:" | |
| Write-Output "1. Quick Tunnel (No account required, random trycloudflare.com URL)" | |
| Write-Output "2. DNS Tunnel (Named tunnel with custom domain - requires Cloudflare account)" | |
| Write-Output "" | |
| do { | |
| $mode = Read-Host "Enter your choice (1 or 2)" | |
| } while ($mode -ne "1" -and $mode -ne "2") | |
| Write-Output "" | |
| if ($mode -eq "1") { | |
| # Quick Tunnel Mode | |
| Write-Info "Starting Quick Tunnel Mode..." | |
| Write-Info "This will create a temporary tunnel with a random trycloudflare.com URL" | |
| Write-Output "" | |
| # Ask for local service details | |
| $customPort = Read-Host "Enter local port to tunnel (default: $LocalPort)" | |
| if ($customPort) { $LocalPort = $customPort } | |
| $customHost = Read-Host "Enter local host (default: $LocalHost, use 127.0.0.1 for local services)" | |
| if ($customHost) { $LocalHost = $customHost } | |
| $serviceUrl = "http://${LocalHost}:${LocalPort}" | |
| Write-Output "" | |
| Write-Success "Configuration:" | |
| Write-Output " Local Service: $serviceUrl" | |
| Write-Output " Tunnel Type: Quick Tunnel (trycloudflare.com)" | |
| Write-Output "" | |
| Write-Info "Starting tunnel... Press Ctrl+C to stop" | |
| Write-Output "" | |
| # Start quick tunnel (no cert needed for quick tunnels) | |
| Invoke-Cloudflared "tunnel", "--url", $serviceUrl, "--no-autoupdate" | |
| } | |
| elseif ($mode -eq "2") { | |
| # DNS Tunnel Mode (Named Tunnel) | |
| Write-Info "Starting DNS Tunnel Mode (Named Tunnel)..." | |
| Write-Output "" | |
| Write-Warning "This mode requires:" | |
| Write-Output " 1. A Cloudflare account" | |
| Write-Output " 2. A domain managed by Cloudflare" | |
| Write-Output " 3. Authentication (via login or token)" | |
| Write-Output "" | |
| # Check if already authenticated | |
| $credentialsFound = $false | |
| # Check for cert.pem in our custom directory | |
| if (Test-Path $CertFile) { | |
| Write-Success "Found existing cert.pem at: $CertFile" | |
| $credentialsFound = $true | |
| } | |
| # Check for existing tunnel credentials | |
| $credFiles = Get-ChildItem -Path $CloudflaredSubDir -Filter "*.json" -ErrorAction SilentlyContinue | |
| if ($credFiles) { | |
| Write-Success "Found existing tunnel credentials" | |
| $credentialsFound = $true | |
| } | |
| if (-not $credentialsFound) { | |
| Write-Info "No credentials found. You need to authenticate first." | |
| Write-Output "" | |
| Write-Output "Choose authentication method:" | |
| Write-Output "1. Login via browser (opens browser for Cloudflare login)" | |
| Write-Output "2. Use tunnel token (paste token from Cloudflare dashboard)" | |
| Write-Output "" | |
| $authChoice = Read-Host "Enter choice (1 or 2)" | |
| if ($authChoice -eq "1") { | |
| Write-Info "Opening browser for authentication..." | |
| Write-Success "With USERPROFILE redirected, cert.pem will be saved to: $CertFile" | |
| Write-Output "" | |
| # Login - with USERPROFILE set, it will save to our directory | |
| Invoke-Cloudflared "tunnel", "login" | |
| Write-Output "" | |
| # Verify cert.pem was created | |
| if (Test-Path $CertFile) { | |
| Write-Success "Authentication successful!" | |
| Write-Success "Certificate saved to: $CertFile" | |
| } | |
| else { | |
| Write-Error "Authentication failed. cert.pem not found at: $CertFile" | |
| Write-Info "Please check if login was successful" | |
| exit 1 | |
| } | |
| } | |
| elseif ($authChoice -eq "2") { | |
| Write-Info "Using tunnel token..." | |
| $token = Read-Host "Paste your tunnel token" | |
| if (-not $token) { | |
| Write-Error "No token provided" | |
| exit 1 | |
| } | |
| Write-Output "" | |
| Write-Success "Token received. Starting tunnel with token..." | |
| Write-Output "" | |
| # Set token and run tunnel | |
| $env:TUNNEL_TOKEN = $token | |
| # Run tunnel with token | |
| Invoke-Cloudflared "tunnel", "--no-autoupdate", "run", "--token", $token | |
| exit 0 | |
| } | |
| else { | |
| Write-Error "Invalid choice" | |
| exit 1 | |
| } | |
| } | |
| Write-Output "" | |
| # Create or select tunnel | |
| Write-Info "Checking for existing tunnels..." | |
| try { | |
| $tunnelList = Invoke-Cloudflared "tunnel", "list" 2>&1 | |
| Write-Output $tunnelList | |
| } | |
| catch { | |
| Write-Warning "Could not list tunnels: $_" | |
| } | |
| Write-Output "" | |
| Write-Output "Do you want to:" | |
| Write-Output "1. Create a new tunnel" | |
| Write-Output "2. Use an existing tunnel" | |
| Write-Output "" | |
| $tunnelChoice = Read-Host "Enter choice (1 or 2)" | |
| if ($tunnelChoice -eq "1") { | |
| # Create new tunnel | |
| $tunnelName = Read-Host "Enter tunnel name (e.g., my-tunnel)" | |
| if (-not $tunnelName) { | |
| Write-Error "Tunnel name cannot be empty" | |
| exit 1 | |
| } | |
| Write-Info "Creating tunnel: $tunnelName" | |
| $createOutput = Invoke-Cloudflared "tunnel", "create", $tunnelName 2>&1 | |
| Write-Output $createOutput | |
| # Extract tunnel ID from output | |
| $tunnelId = $createOutput | Select-String -Pattern "([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})" | | |
| ForEach-Object { $_.Matches.Groups[1].Value } | Select-Object -First 1 | |
| if (-not $tunnelId) { | |
| Write-Error "Failed to create tunnel or extract tunnel ID" | |
| exit 1 | |
| } | |
| Write-Success "Tunnel created with ID: $tunnelId" | |
| # Credentials file should be in our custom directory now | |
| $credFile = Join-Path $CloudflaredSubDir "$tunnelId.json" | |
| if (Test-Path $credFile) { | |
| Write-Success "Credentials saved to: $credFile" | |
| } | |
| else { | |
| Write-Warning "Credentials file not found at: $credFile" | |
| } | |
| } | |
| else { | |
| # Use existing tunnel | |
| $tunnelName = Read-Host "Enter existing tunnel name or ID" | |
| if (-not $tunnelName) { | |
| Write-Error "Tunnel name/ID cannot be empty" | |
| exit 1 | |
| } | |
| # Try to get tunnel info | |
| try { | |
| $tunnelInfo = Invoke-Cloudflared "tunnel", "info", $tunnelName 2>&1 | |
| Write-Output $tunnelInfo | |
| $tunnelId = $tunnelInfo | Select-String -Pattern "([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})" | | |
| ForEach-Object { $_.Matches.Groups[1].Value } | Select-Object -First 1 | |
| if (-not $tunnelId) { | |
| $tunnelId = $tunnelName | |
| } | |
| } | |
| catch { | |
| Write-Warning "Could not get tunnel info: $_" | |
| $tunnelId = $tunnelName | |
| } | |
| # Check if credentials file exists in our directory | |
| $credFile = Join-Path $CloudflaredSubDir "$tunnelId.json" | |
| if (-not (Test-Path $credFile)) { | |
| Write-Warning "Credentials file not found at: $credFile" | |
| # Check original cloudflared location | |
| $originalCredFile = Join-Path $OriginalUserProfile ".cloudflared\$tunnelId.json" | |
| Write-Info "Real user profile: $OriginalUserProfile" | |
| Write-Info "Looking for credentials at: $originalCredFile" | |
| if (Test-Path $originalCredFile) { | |
| Write-Success "Found credentials in original location!" | |
| Write-Info "Copying credentials to portable directory..." | |
| Copy-Item $originalCredFile $credFile -Force | |
| Write-Success "Copied credentials to: $credFile" | |
| } | |
| else { | |
| Write-Error "Credentials file not found in either location" | |
| Write-Output "" | |
| Write-Warning "The tunnel exists but credentials are missing. Options:" | |
| Write-Output "1. Manually copy the credentials file if you have it elsewhere" | |
| Write-Output "2. Delete this tunnel and create a new one" | |
| Write-Output "3. Exit and fix manually" | |
| Write-Output "" | |
| $choice = Read-Host "Enter choice (1, 2, or 3)" | |
| if ($choice -eq "1") { | |
| Write-Info "Please copy $tunnelId.json to: $CloudflaredSubDir" | |
| Read-Host "Press Enter after copying the file" | |
| if (Test-Path $credFile) { | |
| Write-Success "Credentials file found!" | |
| } | |
| else { | |
| Write-Error "Credentials file still not found. Exiting." | |
| exit 1 | |
| } | |
| } | |
| elseif ($choice -eq "2") { | |
| Write-Warning "Deleting tunnel: $tunnelName (ID: $tunnelId)" | |
| try { | |
| $deleteOutput = Invoke-Cloudflared "tunnel", "delete", $tunnelId 2>&1 | |
| Write-Output $deleteOutput | |
| Write-Success "Tunnel deleted" | |
| } | |
| catch { | |
| Write-Warning "Failed to delete tunnel: $_" | |
| } | |
| Write-Output "" | |
| Write-Info "Creating new tunnel with same name: $tunnelName" | |
| $createOutput = Invoke-Cloudflared "tunnel", "create", $tunnelName 2>&1 | |
| Write-Output $createOutput | |
| # Extract new tunnel ID | |
| $tunnelId = $createOutput | Select-String -Pattern "([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})" | | |
| ForEach-Object { $_.Matches.Groups[1].Value } | Select-Object -First 1 | |
| if (-not $tunnelId) { | |
| Write-Error "Failed to create tunnel or extract tunnel ID" | |
| exit 1 | |
| } | |
| Write-Success "New tunnel created with ID: $tunnelId" | |
| # Update credential file path | |
| $credFile = Join-Path $CloudflaredSubDir "$tunnelId.json" | |
| if (Test-Path $credFile) { | |
| Write-Success "Credentials saved to: $credFile" | |
| } | |
| else { | |
| Write-Error "Credentials file not found after creation!" | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-Info "Exiting. Please fix credentials manually." | |
| exit 1 | |
| } | |
| } | |
| } | |
| else { | |
| Write-Success "Credentials file found: $credFile" | |
| } | |
| } | |
| Write-Output "" | |
| # Configure routing | |
| $hostname = Read-Host "Enter hostname/subdomain (e.g., tunnel.example.com)" | |
| $customPort = Read-Host "Enter local port to tunnel (default: $LocalPort)" | |
| if ($customPort) { $LocalPort = $customPort } | |
| $customHost = Read-Host "Enter local host (default: $LocalHost, use 127.0.0.1 for local services)" | |
| if ($customHost) { $LocalHost = $customHost } | |
| $serviceUrl = "http://${LocalHost}:${LocalPort}" | |
| # Create config file with absolute path to credentials file | |
| $credFileRelative = "$tunnelId.json" | |
| $configContent = @" | |
| # Tunnel ID | |
| tunnel: $tunnelId | |
| # Path to credentials file (absolute path) | |
| credentials-file: $credFile | |
| # Ingress rules - order matters, first match wins | |
| ingress: | |
| # Route hostname to local service | |
| - hostname: $hostname | |
| service: $serviceUrl | |
| # Catch-all rule (required) - returns 404 for unmatched requests | |
| - service: http_status:404 | |
| # Optional: Logging configuration | |
| # loglevel: info | |
| # logfile: $CloudflaredDir\cloudflared.log | |
| # Optional: Origin request configuration | |
| # originRequest: | |
| # connectTimeout: 30s | |
| # noTLSVerify: false | |
| # http2Origin: false | |
| # httpHostHeader: $hostname | |
| # Optional: Configure metrics server | |
| # metrics: localhost:60123 | |
| # Optional: Enable WARP routing (for private network access) | |
| # warp-routing: | |
| # enabled: true | |
| # Optional: Protocol configuration (auto, http2, or quic) | |
| # protocol: auto | |
| # Optional: Connection retry settings | |
| # retries: 5 | |
| # Optional: Custom tags for tunnel identification | |
| # tag: | |
| # - KEY1=VALUE1 | |
| # - KEY2=VALUE2 | |
| "@ | |
| Set-Content -Path $ConfigFile -Value $configContent -Encoding UTF8 | |
| Write-Success "Created config file: $ConfigFile" | |
| Write-Output "" | |
| Write-Info "Creating DNS route..." | |
| try { | |
| $dnsOutput = Invoke-Cloudflared "tunnel", "route", "dns", $tunnelId, $hostname 2>&1 | |
| Write-Output $dnsOutput | |
| Write-Success "DNS route created" | |
| } | |
| catch { | |
| Write-Warning "DNS route creation failed (may already exist): $_" | |
| } | |
| Write-Output "" | |
| Write-Success "Configuration complete!" | |
| Write-Output "" | |
| Write-Output "Configuration summary:" | |
| Write-Output " Tunnel ID: $tunnelId" | |
| Write-Output " Hostname: $hostname" | |
| Write-Output " Local Service: $serviceUrl" | |
| Write-Output " Config File: $ConfigFile" | |
| Write-Output " Certificate: $CertFile" | |
| Write-Output " Credentials: $CloudflaredSubDir\$([System.IO.Path]::GetFileName($credFile))" | |
| Write-Output "" | |
| Write-Warning "Make sure your local service is running at $serviceUrl before continuing" | |
| Write-Output "" | |
| $startTunnel = Read-Host "Start the tunnel now? (y/n)" | |
| if ($startTunnel -ne "y" -and $startTunnel -ne "Y") { | |
| Write-Info "Tunnel not started. To start it later, run:" | |
| Write-Output " .\cloudflared-setup.ps1" | |
| Write-Output "Or manually run:" | |
| Write-Output " cd $CloudflaredDir" | |
| Write-Output " .\cloudflared.exe tunnel --config `"$ConfigFile`" run $tunnelId" | |
| exit 0 | |
| } | |
| Write-Output "" | |
| Write-Info "Starting tunnel... Press Ctrl+C to stop" | |
| Write-Output "" | |
| # Run the tunnel with config file | |
| Invoke-Cloudflared "tunnel", "--config", $ConfigFile, "run" | |
| } | |
| Write-Output "" | |
| Write-Info "Tunnel stopped." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment