Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $script_dir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path } $repo_root = (Resolve-Path (Join-Path $script_dir "..")).Path $hf_token_file = if ($env:HF_TOKEN_FILE) { $env:HF_TOKEN_FILE } else { Join-Path $HOME ".cache/huggingface/token" } $script:RestoreHfLoginRequired = $false $script:RestoreHfLoginToken = "" $script:RestoreHfLoginUsername = "" function Trim-Value { param([AllowNull()][string]$Value) if ($null -eq $Value) { return "" } return $Value.Trim() } function Write-Info { param([string]$Message) Write-Host $Message } $script:BootstrapStepIndex = 0 function Get-LogTimestamp { return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } function Write-StepStart { param([string]$Message) $script:BootstrapStepIndex += 1 Write-Info ("[{0}] [bootstrap step {1}] START {2}" -f (Get-LogTimestamp), $script:BootstrapStepIndex, $Message) } function Write-StepDone { param([string]$Message) Write-Info ("[{0}] [bootstrap step {1}] DONE {2}" -f (Get-LogTimestamp), $script:BootstrapStepIndex, $Message) } function Invoke-Step { param( [string]$Message, [ScriptBlock]$Action ) Write-StepStart -Message $Message & $Action Write-StepDone -Message $Message } function Fail { param([string]$Message) throw $Message } function Prompt-Line { param( [string]$Prompt, [string]$DefaultValue = "" ) if ([string]::IsNullOrEmpty($DefaultValue)) { $raw = Read-Host -Prompt $Prompt } else { $raw = Read-Host -Prompt "$Prompt [$DefaultValue]" } $value = Trim-Value $raw if ([string]::IsNullOrEmpty($value)) { $value = $DefaultValue } return $value } function Prompt-Required { param( [string]$Prompt, [string]$DefaultValue = "" ) while ($true) { $value = Trim-Value (Prompt-Line -Prompt $Prompt -DefaultValue $DefaultValue) if (-not [string]::IsNullOrEmpty($value)) { return $value } Write-Host "This value is required." -ForegroundColor Red } } function Convert-SecureStringToPlainText { param([System.Security.SecureString]$SecureValue) if ($null -eq $SecureValue) { return "" } $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureValue) try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } } function Prompt-SecretOptional { param([string]$Prompt) $secure_value = Read-Host -Prompt $Prompt -AsSecureString $plain_text = Convert-SecureStringToPlainText -SecureValue $secure_value return Trim-Value $plain_text } function Prompt-SecretRequired { param([string]$Prompt) while ($true) { $value = Trim-Value (Prompt-SecretOptional -Prompt $Prompt) if (-not [string]::IsNullOrEmpty($value)) { return $value } Write-Host "This value is required." -ForegroundColor Red } } function Prompt-YesNo { param( [string]$Prompt, [ValidateSet("y", "n")][string]$DefaultValue = "n" ) $hint = if ($DefaultValue -eq "y") { "[Y/n]" } else { "[y/N]" } while ($true) { $answer = Trim-Value (Read-Host -Prompt "$Prompt $hint") $answer = $answer.ToLowerInvariant() if ([string]::IsNullOrEmpty($answer)) { $answer = $DefaultValue } switch ($answer) { { $_ -in @("y", "yes") } { return "yes" } { $_ -in @("n", "no") } { return "no" } default { Write-Host "Please enter y or n." -ForegroundColor Red } } } } function Login-WithHfToken { param([string]$Token) & hf auth login --token $Token *> $null if ($LASTEXITCODE -ne 0) { Fail "hf auth login with token failed." } } function Restore-PreviousHfLoginIfNeeded { if (-not $script:RestoreHfLoginRequired) { return } if ([string]::IsNullOrEmpty($script:RestoreHfLoginToken)) { Write-Error "Skip restoring previous HF login because backup token is empty." $script:RestoreHfLoginRequired = $false return } $display_user = if ([string]::IsNullOrEmpty($script:RestoreHfLoginUsername)) { "unknown" } else { $script:RestoreHfLoginUsername } Write-Info "Restoring previous HF login for user: $display_user" & hf auth login --token $script:RestoreHfLoginToken *> $null if ($LASTEXITCODE -eq 0) { Write-Info "Previous HF login restored." } else { Write-Error "Failed to restore previous HF login. Please run: hf auth login" } $script:RestoreHfLoginRequired = $false } function New-RandomHex { param([int]$Length) if ($Length -le 0 -or ($Length % 2) -ne 0) { Fail "Length must be a positive even number." } $bytes = New-Object byte[] ($Length / 2) [System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes) return ($bytes | ForEach-Object { $_.ToString("x2") }) -join "" } function Ensure-Git { if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Fail "git is required. Please install git first." } } function Ensure-HfCli { if (Get-Command hf -ErrorAction SilentlyContinue) { return } Write-Info "hf CLI not found. Installing via powershell -ExecutionPolicy ByPass -c `"irm https://hf.co/cli/install.ps1 | iex`" ..." & powershell -ExecutionPolicy ByPass -c "irm https://hf.co/cli/install.ps1 | iex" if ($LASTEXITCODE -ne 0) { Fail "hf CLI install failed. Please install manually and rerun." } $local_bin = Join-Path $HOME ".local/bin" if ((Test-Path $local_bin) -and -not (($env:PATH -split [IO.Path]::PathSeparator) -contains $local_bin)) { $env:PATH = "$local_bin$([IO.Path]::PathSeparator)$env:PATH" } if (-not (Get-Command hf -ErrorAction SilentlyContinue)) { Fail "hf CLI install failed. Please install manually and rerun." } } function Ensure-Python3 { if (-not (Get-Command python3 -ErrorAction SilentlyContinue)) { Fail "python3 is required. Please install python3 first." } } function Ensure-HuggingFaceHubPy { & python3 -c "import huggingface_hub" 2>$null if ($LASTEXITCODE -ne 0) { Fail "python3 package 'huggingface_hub' is required. Install with: python3 -m pip install --user 'huggingface_hub[cli]'" } } function Resolve-LatestOpenclawVersion { $python_code = @' import json import urllib.request url = "https://registry.npmjs.org/openclaw/latest" try: with urllib.request.urlopen(url, timeout=8) as response: payload = response.read().decode("utf-8", errors="replace") data = json.loads(payload) version = str(data.get("version", "")).strip() if version: print(version) except Exception: pass '@ $version_output = & python3 -c $python_code 2>$null $version = Trim-Value (($version_output | Out-String)) if ([string]::IsNullOrEmpty($version)) { return "latest" } return $version } function Read-CurrentHfToken { if (-not [string]::IsNullOrEmpty($env:HUGGINGFACE_HUB_TOKEN)) { return Trim-Value $env:HUGGINGFACE_HUB_TOKEN } if (-not [string]::IsNullOrEmpty($env:HF_TOKEN)) { return Trim-Value $env:HF_TOKEN } if (Test-Path $hf_token_file) { return Trim-Value (Get-Content -Path $hf_token_file -TotalCount 1) } return "" } function Test-HfLoggedIn { & hf auth whoami *> $null return ($LASTEXITCODE -eq 0) } function Get-HfUsername { $whoami_output = (& hf auth whoami 2>&1 | Out-String) $user_match = [regex]::Match($whoami_output, "(?im)^\s*user:\s*([^\s]+)") if ($user_match.Success) { return Trim-Value $user_match.Groups[1].Value } $login_match = [regex]::Match($whoami_output, "(?im)logged in as\s+([^\s]+)") if ($login_match.Success) { return Trim-Value $login_match.Groups[1].Value } $token_from_cache = Read-CurrentHfToken if (-not [string]::IsNullOrEmpty($token_from_cache)) { $env:HF_API_TOKEN = $token_from_cache $python_code = @' from huggingface_hub import HfApi import os token = (os.environ.get("HF_API_TOKEN") or "").strip() or None api = HfApi(token=token) data = api.whoami(token=token) name = data.get("name", "") if isinstance(data, dict) else "" print((name or "").strip()) '@ $name_output = & python3 -c $python_code 2>$null $name = Trim-Value (($name_output | Out-String)) if (-not [string]::IsNullOrEmpty($name)) { return $name } } return "" } function Set-SpaceVariable { param( [string]$SpaceRepoId, [string]$Key, [string]$Value, [string]$ApiToken ) $env:SPACE_REPO_ID = $SpaceRepoId $env:SPACE_VARIABLE_KEY = $Key $env:SPACE_VARIABLE_VALUE = $Value $env:HF_API_TOKEN = $ApiToken $python_code = @' from huggingface_hub import HfApi import os token = (os.environ.get("HF_API_TOKEN") or "").strip() or None api = HfApi(token=token) try: api.add_space_variable( repo_id=os.environ["SPACE_REPO_ID"], key=os.environ["SPACE_VARIABLE_KEY"], value=os.environ["SPACE_VARIABLE_VALUE"], ) except Exception as exc: key = os.environ.get("SPACE_VARIABLE_KEY", "") repo_id = os.environ.get("SPACE_REPO_ID", "") raise SystemExit(f"failed to set space variable {key} on {repo_id}: {exc}") '@ & python3 -c $python_code if ($LASTEXITCODE -ne 0) { Fail "failed to set space variable $Key" } } function Set-SpaceSecret { param( [string]$SpaceRepoId, [string]$Key, [string]$Value, [string]$ApiToken ) $env:SPACE_REPO_ID = $SpaceRepoId $env:SPACE_SECRET_KEY = $Key $env:SPACE_SECRET_VALUE = $Value $env:HF_API_TOKEN = $ApiToken $python_code = @' from huggingface_hub import HfApi import os token = (os.environ.get("HF_API_TOKEN") or "").strip() or None api = HfApi(token=token) try: api.add_space_secret( repo_id=os.environ["SPACE_REPO_ID"], key=os.environ["SPACE_SECRET_KEY"], value=os.environ["SPACE_SECRET_VALUE"], ) except Exception as exc: key = os.environ.get("SPACE_SECRET_KEY", "") repo_id = os.environ.get("SPACE_REPO_ID", "") raise SystemExit(f"failed to set space secret {key} on {repo_id}: {exc}") '@ & python3 -c $python_code if ($LASTEXITCODE -ne 0) { Fail "failed to set space secret $Key" } } function Set-SpaceVariableLogged { param( [string]$SpaceRepoId, [string]$Key, [string]$Value, [string]$ApiToken ) Invoke-Step -Message "Set Space variable $Key" -Action { Set-SpaceVariable -SpaceRepoId $SpaceRepoId -Key $Key -Value $Value -ApiToken $ApiToken } } function Set-SpaceSecretLogged { param( [string]$SpaceRepoId, [string]$Key, [string]$Value, [string]$ApiToken ) Invoke-Step -Message "Set Space secret $Key" -Action { Set-SpaceSecret -SpaceRepoId $SpaceRepoId -Key $Key -Value $Value -ApiToken $ApiToken } } function Restart-Space { param( [string]$SpaceRepoId, [string]$ApiToken ) $env:SPACE_RESTART_REPO_ID = $SpaceRepoId $env:HF_API_TOKEN = $ApiToken $python_code = @' from huggingface_hub import HfApi import os token = (os.environ.get("HF_API_TOKEN") or "").strip() or None api = HfApi(token=token) try: api.restart_space(repo_id=os.environ["SPACE_RESTART_REPO_ID"]) except Exception as exc: repo_id = os.environ.get("SPACE_RESTART_REPO_ID", "") raise SystemExit(f"failed to restart space {repo_id}: {exc}") '@ & python3 -c $python_code if ($LASTEXITCODE -ne 0) { Fail "failed to restart space $SpaceRepoId" } } function Main { Invoke-Step -Message "Change directory to repository root" -Action { Set-Location $repo_root } Write-Info "OpenClaw Hugging Face bootstrap (interactive, Windows PowerShell)" Invoke-Step -Message "Check dependency git" -Action { Ensure-Git } Invoke-Step -Message "Check dependency hf CLI" -Action { Ensure-HfCli } Invoke-Step -Message "Check dependency python3" -Action { Ensure-Python3 } Invoke-Step -Message "Check dependency python package huggingface_hub" -Action { Ensure-HuggingFaceHubPy } Invoke-Step -Message "Print dependency versions" -Action { & git --version & hf version & python3 --version } $hf_token_for_backup = "" Invoke-Step -Message "Resolve HF login and HF_TOKEN" -Action { if (-not (Test-HfLoggedIn)) { Write-Info "HF CLI is not logged in." $hf_token_for_backup = Prompt-SecretRequired -Prompt "HF_TOKEN (required for hf auth login)" Login-WithHfToken -Token $hf_token_for_backup } else { $current_hf_username = Trim-Value (Get-HfUsername) if ([string]::IsNullOrEmpty($current_hf_username)) { $use_current_hf_user = Prompt-YesNo -Prompt "HF CLI is already logged in. Use current user?" -DefaultValue "y" } else { $use_current_hf_user = Prompt-YesNo -Prompt "HF CLI is already logged in as '$current_hf_username'. Use this user?" -DefaultValue "y" } if ($use_current_hf_user -eq "yes") { $hf_token_for_backup = Trim-Value (Read-CurrentHfToken) if ([string]::IsNullOrEmpty($hf_token_for_backup)) { $hf_token_for_backup = Prompt-SecretRequired -Prompt "Cannot read current token. Enter HF_TOKEN" Login-WithHfToken -Token $hf_token_for_backup } } else { $script:RestoreHfLoginToken = Trim-Value (Read-CurrentHfToken) if ([string]::IsNullOrEmpty($script:RestoreHfLoginToken)) { Fail "Cannot backup current HF token. Ensure current token is readable before switching users." } $script:RestoreHfLoginRequired = $true $script:RestoreHfLoginUsername = $current_hf_username $switch_hf_token = Prompt-SecretRequired -Prompt "HF_TOKEN for switching HF user" Login-WithHfToken -Token $switch_hf_token $hf_token_for_backup = $switch_hf_token } } if ([string]::IsNullOrEmpty($hf_token_for_backup)) { Fail "HF_TOKEN is required to configure Space secret HF_TOKEN." } } $hf_username = "" Invoke-Step -Message "Resolve HF username" -Action { $hf_username = Trim-Value (Get-HfUsername) if ([string]::IsNullOrEmpty($hf_username)) { $hf_username = Prompt-Required -Prompt "HF username (cannot parse from hf auth whoami)" } Write-Info "HF user: $hf_username" } $space_name = "" $dataset_name = "" Invoke-Step -Message "Collect Space and Dataset names" -Action { $space_name = Prompt-Required -Prompt "Space name (without username)" -DefaultValue "openclaw-hf" $dataset_name = Prompt-Required -Prompt "Dataset name (without username)" -DefaultValue "$space_name-backup" } $openclaw_version = "" Invoke-Step -Message "Resolve and confirm OPENCLAW_VERSION" -Action { $default_openclaw_version = Trim-Value $env:OPENCLAW_VERSION if ([string]::IsNullOrEmpty($default_openclaw_version)) { $default_openclaw_version = Resolve-LatestOpenclawVersion } $openclaw_version = Prompt-Required -Prompt "OPENCLAW_VERSION (press Enter to use detected latest)" -DefaultValue $default_openclaw_version } $space_repo_id = "$hf_username/$space_name" $dataset_repo_id = "$hf_username/$dataset_name" $gateway_token = "" $generated_gateway_token = $false Invoke-Step -Message "Collect OPENCLAW_GATEWAY_TOKEN" -Action { $gateway_token = Prompt-SecretOptional -Prompt "OPENCLAW_GATEWAY_TOKEN (optional, leave empty to auto-generate 32 chars)" if ([string]::IsNullOrEmpty($gateway_token)) { $gateway_token = New-RandomHex -Length 32 $generated_gateway_token = $true Write-Info "OPENCLAW_GATEWAY_TOKEN generated automatically." } } $gateway_password = "" $generated_gateway_password = $false Invoke-Step -Message "Collect OPENCLAW_GATEWAY_PASSWORD" -Action { $gateway_password = Prompt-SecretOptional -Prompt "OPENCLAW_GATEWAY_PASSWORD (optional, leave empty to auto-generate 16 chars)" if ([string]::IsNullOrEmpty($gateway_password)) { $gateway_password = New-RandomHex -Length 16 $generated_gateway_password = $true Write-Info "OPENCLAW_GATEWAY_PASSWORD generated automatically." } } $configure_llm = "no" $llm_base_url = "" $llm_model = "" $llm_api_key = "" $enable_sshx = "no" $sshx_auto_start_value = "false" Invoke-Step -Message "Collect custom LLM or sshx bootstrap options" -Action { $configure_llm = Prompt-YesNo -Prompt "Configure custom LLM now?" -DefaultValue "n" if ($configure_llm -eq "yes") { $llm_base_url = Prompt-Required -Prompt "OPENCLAW_LLM_BASE_URL" $llm_model = Prompt-Required -Prompt "OPENCLAW_LLM_MODEL" $llm_api_key = Prompt-SecretOptional -Prompt "OPENCLAW_LLM_API_KEY" if ([string]::IsNullOrEmpty($llm_api_key)) { Fail "OPENCLAW_LLM_API_KEY is required when enabling custom LLM config." } } else { $enable_sshx = Prompt-YesNo -Prompt "Set OPENCLAW_SSHX_AUTO_START=true for later sshx setup?" -DefaultValue "y" } } if ($enable_sshx -eq "yes") { $sshx_auto_start_value = "true" } Write-Info "" Write-Info "Planned deployment configuration:" Write-Info "Space repo: $space_repo_id" Write-Info "Dataset repo: $dataset_repo_id" Write-Info "OPENCLAW_VERSION: $openclaw_version" Write-Info "OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH=false" Write-Info "OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH=false" Write-Info "OPENCLAW_SSHX_AUTO_START=$sshx_auto_start_value" if ($configure_llm -eq "yes") { Write-Info "Custom LLM config: enabled" } else { Write-Info "Custom LLM config: disabled" } $proceed_with_deploy = Prompt-YesNo -Prompt "Proceed with these settings?" -DefaultValue "y" if ($proceed_with_deploy -ne "yes") { Write-Info "Cancelled by user before creating/updating Space or Dataset." return } Invoke-Step -Message "Create Space repo $space_repo_id" -Action { & hf repo create $space_repo_id --repo-type space --space-sdk docker --private --exist-ok if ($LASTEXITCODE -ne 0) { Fail "failed to create Space repo" } } Invoke-Step -Message "Create Dataset repo $dataset_repo_id" -Action { & hf repo create $dataset_repo_id --repo-type dataset --private --exist-ok if ($LASTEXITCODE -ne 0) { Fail "failed to create Dataset repo" } } Invoke-Step -Message "Upload repository to Space $space_repo_id" -Action { & hf upload $space_repo_id . --repo-type space --exclude ".git/**" --exclude ".git" --commit-message "feat: deploy Gemma 4 to hf space" if ($LASTEXITCODE -ne 0) { Fail "failed to upload repository to Space" } } $api_token = "" Invoke-Step -Message "Resolve API token for Space configuration" -Action { $api_token = Read-CurrentHfToken if ([string]::IsNullOrEmpty($api_token)) { $api_token = $hf_token_for_backup } if ([string]::IsNullOrEmpty($api_token)) { Fail "unable to resolve API token for Space configuration." } } Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_BACKUP_DATASET_REPO" -Value $dataset_repo_id -ApiToken $api_token Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_VERSION" -Value $openclaw_version -ApiToken $api_token Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH" -Value "false" -ApiToken $api_token Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH" -Value "false" -ApiToken $api_token Set-SpaceSecretLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_GATEWAY_TOKEN" -Value $gateway_token -ApiToken $api_token Set-SpaceSecretLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_GATEWAY_PASSWORD" -Value $gateway_password -ApiToken $api_token Set-SpaceSecretLogged -SpaceRepoId $space_repo_id -Key "HF_TOKEN" -Value $hf_token_for_backup -ApiToken $api_token if ($configure_llm -eq "yes") { Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_LLM_BASE_URL" -Value $llm_base_url -ApiToken $api_token Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_LLM_MODEL" -Value $llm_model -ApiToken $api_token Set-SpaceSecretLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_LLM_API_KEY" -Value $llm_api_key -ApiToken $api_token } Set-SpaceVariableLogged -SpaceRepoId $space_repo_id -Key "OPENCLAW_SSHX_AUTO_START" -Value $sshx_auto_start_value -ApiToken $api_token Invoke-Step -Message "Print deployment summary" -Action { $space_page_url = "https://huggingface.co/spaces/$space_repo_id" $space_host = "$($space_repo_id -replace '/', '-').hf.space" $app_url = "https://$space_host" $health_url = "$app_url/healthz" Write-Info "" Write-Info "Deployment complete." Write-Info "Space repo: $space_repo_id" Write-Info "Hugging Face Space: $space_page_url" Write-Info "Dataset repo: $dataset_repo_id" Write-Info "OPENCLAW_VERSION: $openclaw_version" Write-Info "Space URL: $app_url" Write-Info "Health URL: $health_url" if ($generated_gateway_token) { Write-Info "Generated OPENCLAW_GATEWAY_TOKEN=$gateway_token" } if ($generated_gateway_password) { Write-Info "Generated OPENCLAW_GATEWAY_PASSWORD=$gateway_password" } } } try { Main } catch { Write-Error $_.Exception.Message exit 1 } finally { Restore-PreviousHfLoginIfNeeded }