Read the installers
The one-line installers run a script from this site. Read it first. Below are the exact files served at/install.sh and /install.ps1, read from the repository when this site was built. (Opening those two URLs directly may download the file instead of showing it, because GitHub Pages serves them with a generic content type.)
Run it:
curl -fsSL https://gdt.roxguel.dev/install.sh | shOr download, read and then run:
curl -fsSL https://gdt.roxguel.dev/install.sh -o install.shless install.shsh install.shSHA-256 of this file: 3eaa78b7f830ffa199c61defed127656441bab4315cce141e9c94e545bf9983c. Compare with curl -fsSL https://gdt.roxguel.dev/install.sh | sha256sum.
#!/bin/sh# Installs gdt (Godot Development Tools) on Linux and macOS.## curl -fsSL https://gdt.roxguel.dev/install.sh | sh# curl -fsSL https://gdt.roxguel.dev/install.sh | sh -s -- --version 0.1.0 --dir "$HOME/bin"## It downloads the release archive for this machine from GitHub, checks it# against the SHA256SUMS file of the same release, and puts the `gdt` executable# in one folder. It does not change PATH, shell profiles or anything else.## Options (or the environment variables in brackets):# --version <v> install this version, for example 0.1.0 [GDT_VERSION]# default: the latest release# --dir <path> install into this folder [GDT_INSTALL_DIR]# default: $HOME/.local/bin# --verify-attestation also check the build provenance with `gh attestation verify`# (needs the GitHub CLI, signed in)# -h, --help show this text## GDT_BASE_URL replaces https://github.com/roxguel/godot-dev-tools (for mirrors and tests).# It also lifts the HTTPS-only rule that curl otherwise follows, and a plain http://# address is warned about; with a mirror, trust --verify-attestation, not the transport.
set -eu
REPO_URL="${GDT_BASE_URL:-https://github.com/roxguel/godot-dev-tools}"REPO_SLUG="roxguel/godot-dev-tools"
say() { printf '%s\n' "$*"; }fail() { printf 'gdt installer: %s\n' "$*" >&2; exit 1; }have() { command -v "$1" >/dev/null 2>&1; }
usage() { cat <<'EOF'Installs gdt (Godot Development Tools).
Options (or the environment variables in brackets): --version <v> install this version, for example 0.1.0 [GDT_VERSION] default: the latest release --dir <path> install into this folder [GDT_INSTALL_DIR] default: $HOME/.local/bin --verify-attestation also check the build provenance with `gh attestation verify` (needs the GitHub CLI, signed in) -h, --help show this text
It checks the download against the SHA256SUMS of the release and changesnothing but the one executable. It does not touch PATH.EOF}
# --- network ------------------------------------------------------------------
# When the default GitHub URL is used, nothing but HTTPS is accepted.proto_curl() { if [ -z "${GDT_BASE_URL:-}" ]; then printf '%s' "--proto =https --tlsv1.2" fi}
download() { # url file if have curl; then # shellcheck disable=SC2046 curl -fsSL $(proto_curl) -o "$2" "$1" || fail "could not download $1" elif have wget; then # wget cannot be told to refuse http:// redirects; the GitHub addresses used # by default are https and redirect only to https. Prefer curl where there is one. wget -q -O "$2" "$1" || fail "could not download $1" else fail "curl or wget is needed" fi}
latest_tag() { url="$REPO_URL/releases/latest" if have curl; then # shellcheck disable=SC2046 final=$(curl -fsSL $(proto_curl) -o /dev/null -w '%{url_effective}' "$url") \ || fail "could not find the latest release ($url); is there a published release yet?" elif have wget; then final=$(wget --spider -S --max-redirect=0 "$url" 2>&1 | sed -n 's/^ *[Ll]ocation: *//p' | head -n 1 | tr -d '\r') \ || true [ -n "$final" ] || fail "could not find the latest release ($url); is there a published release yet?" else fail "curl or wget is needed" fi tag=${final##*/} case "$tag" in v[0-9]*) printf '%s' "$tag" ;; *) fail "could not read the latest version from $final" ;; esac}
# --- this machine -------------------------------------------------------------
detect_target() { os=$(uname -s) arch=$(uname -m) case "$os" in Linux) case "$arch" in x86_64 | amd64) printf 'x86_64-unknown-linux-musl' ;; aarch64 | arm64) printf 'aarch64-unknown-linux-musl' ;; *) fail "no build for Linux on $arch" ;; esac ;; Darwin) # A shell running under Rosetta reports x86_64 on an Apple silicon Mac. if [ "$arch" = "x86_64" ] && [ "$(sysctl -n hw.optional.arm64 2>/dev/null || true)" = "1" ]; then arch=arm64 fi case "$arch" in arm64 | aarch64) printf 'aarch64-apple-darwin' ;; x86_64) printf 'x86_64-apple-darwin' ;; *) fail "no build for macOS on $arch" ;; esac ;; *) fail "no build for $os (Windows has install.ps1)" ;; esac}
sha256_of() { if have sha256sum; then sha256sum "$1" | cut -d ' ' -f 1 elif have shasum; then shasum -a 256 "$1" | cut -d ' ' -f 1 else fail "sha256sum or shasum is needed to check the download" fi}
# --- main ---------------------------------------------------------------------
main() { version="${GDT_VERSION:-}" dir="${GDT_INSTALL_DIR:-}" verify_attestation=0 while [ $# -gt 0 ]; do case "$1" in --version) [ $# -ge 2 ] || fail "--version needs a value"; version=$2; shift 2 ;; --dir) [ $# -ge 2 ] || fail "--dir needs a value"; dir=$2; shift 2 ;; --verify-attestation) verify_attestation=1; shift ;; -h | --help) usage; exit 0 ;; *) fail "unknown option: $1 (see --help)" ;; esac done
case "$REPO_URL" in https://*) ;; *) printf 'gdt installer: warning: %s is not an https:// address, so the download is not protected in transit. Only --verify-attestation says where the file was built.\n' "$REPO_URL" >&2 ;; esac
if [ -z "$dir" ]; then [ -n "${HOME:-}" ] || fail "HOME is not set; say where to install with --dir" dir="$HOME/.local/bin" fi
if [ -z "$version" ]; then tag=$(latest_tag) else tag="v${version#v}" fi # The tag ends up in URLs and file names, so it is held to a strict shape. case "$tag" in v[0-9]*.[0-9]*.[0-9]*) ;; *) fail "not a version: $tag" ;; esac case "$tag" in *[!0-9A-Za-z.+-]*) fail "not a version: $tag" ;; esac
target=$(detect_target) name="gdt-$tag-$target" archive="$name.tar.gz"
tmp=$(mktemp -d 2>/dev/null || mktemp -d -t gdt-install) || fail "could not create a temporary folder" trap 'rm -rf "$tmp"' EXIT INT TERM
say "Installing gdt $tag for $target" download "$REPO_URL/releases/download/$tag/$archive" "$tmp/$archive" download "$REPO_URL/releases/download/$tag/SHA256SUMS" "$tmp/SHA256SUMS"
expected=$(awk -v f="$archive" '{ n = $2; sub(/^\*/, "", n); if (n == f) { print $1; exit } }' "$tmp/SHA256SUMS") [ -n "$expected" ] || fail "$archive is not listed in SHA256SUMS; nothing was installed" actual=$(sha256_of "$tmp/$archive") if [ "$actual" != "$expected" ]; then fail "checksum mismatch for $archive (expected $expected, got $actual); nothing was installed" fi say "Checksum ok"
if [ "$verify_attestation" = 1 ]; then have gh || fail "--verify-attestation needs the GitHub CLI (gh)" # Not any attestation of the repository: the one that this repository's release # workflow made, on a GitHub-hosted runner, for this very tag. gh attestation verify "$tmp/$archive" --repo "$REPO_SLUG" \ --signer-workflow "$REPO_SLUG/.github/workflows/release.yml" \ --source-ref "refs/tags/$tag" \ --deny-self-hosted-runners >/dev/null \ || fail "the build provenance of $archive could not be verified (it needs a recent GitHub CLI, signed in); nothing was installed" say "Build provenance ok" else say "(The checksum shows the download is intact. --verify-attestation also shows where it was built.)" fi
tar -xzf "$tmp/$archive" -C "$tmp" "$name/gdt" || fail "could not unpack $archive" [ -f "$tmp/$name/gdt" ] || fail "$archive has no gdt executable"
mkdir -p "$dir" || fail "could not create $dir" # Copy beside the final name, then rename: a gdt that is running keeps working. staged="$dir/.gdt.new.$$" cp "$tmp/$name/gdt" "$staged" || fail "could not write to $dir" chmod 755 "$staged" mv -f "$staged" "$dir/gdt" || { rm -f "$staged"; fail "could not replace $dir/gdt"; }
say "Installed $("$dir/gdt" --version 2>/dev/null || printf 'gdt') in $dir" case ":${PATH:-}:" in *":$dir:"*) ;; *) say "" say "$dir is not on your PATH. Add it, for example:" say " export PATH=\"$dir:\$PATH\"" say "(put that line in your shell profile to keep it). The installer changes nothing else." ;; esac}
main "$@"Run it:
irm https://gdt.roxguel.dev/install.ps1 | iexOr download, read and then run:
irm https://gdt.roxguel.dev/install.ps1 -OutFile install.ps1notepad install.ps1& ([scriptblock]::Create((Get-Content .\install.ps1 -Raw)))SHA-256 of this file: 9a8d2c71ba11e0b38bbb29203e42f49078724f0e385a2fed5753b037e261176c. After saving it, compare withGet-FileHash install.ps1 -Algorithm SHA256.
# Installs gdt (Godot Development Tools) on Windows.## irm https://gdt.roxguel.dev/install.ps1 | iex## Options through environment variables (set them before the line above):# $env:GDT_VERSION install this version, for example 0.1.0 (default: the latest release)# $env:GDT_INSTALL_DIR install into this folder (default: %LOCALAPPDATA%\Programs\gdt)# $env:GDT_VERIFY_ATTESTATION = '1' also check the build provenance with `gh attestation verify`## Or, to pass them as parameters:# & ([scriptblock]::Create((irm https://gdt.roxguel.dev/install.ps1))) -Version 0.1.0 -InstallDir C:\tools\gdt## It downloads the release archive for this machine from GitHub, checks it against the# SHA256SUMS file of the same release, and puts gdt.exe in one folder. It does not change# PATH or anything else. $env:GDT_BASE_URL replaces https://github.com/roxguel/godot-dev-tools# (for mirrors and tests); with a mirror, trust the build provenance check, not the transport.
function Install-Gdt { [CmdletBinding()] param( [string]$Version = $env:GDT_VERSION, [string]$InstallDir = $env:GDT_INSTALL_DIR, [switch]$VerifyAttestation = ($env:GDT_VERIFY_ATTESTATION -eq '1') )
$ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # the progress bar makes Windows PowerShell 5.1 downloads very slow try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { }
$custom = [bool]$env:GDT_BASE_URL $repoUrl = if ($custom) { $env:GDT_BASE_URL.TrimEnd('/') } else { 'https://github.com/roxguel/godot-dev-tools' } $slug = 'roxguel/godot-dev-tools'
function Fail([string]$message) { throw "gdt installer: $message" }
# GDT_BASE_URL (mirrors and tests) lifts the transport rules of the default address. if ($custom -and $repoUrl -notlike 'https://*') { Write-Warning "gdt installer: $repoUrl is not an https:// address, so the download is not protected in transit. Only -VerifyAttestation says where the file was built." }
function Get-LatestTag { # Windows PowerShell 5.1 does not load this assembly by itself. Add-Type -AssemblyName System.Net.Http $url = "$repoUrl/releases/latest" $handler = [System.Net.Http.HttpClientHandler]::new() $handler.AllowAutoRedirect = $false $client = [System.Net.Http.HttpClient]::new($handler) try { $response = $client.GetAsync($url).GetAwaiter().GetResult() $location = $response.Headers.Location if (-not $location) { Fail "could not find the latest release ($url); is there a published release yet?" } return ($location.ToString() -split '/')[-1] } finally { $client.Dispose() } }
if (-not $Version) { $tag = Get-LatestTag } else { $tag = 'v' + $Version.TrimStart('v') } # The tag ends up in URLs and file names, so it is held to a strict shape. if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+([-+.][0-9A-Za-z.+-]+)?$') { Fail "not a version: $tag" }
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() $target = switch ($arch) { 'X64' { 'x86_64-pc-windows-msvc' } 'Arm64' { 'aarch64-pc-windows-msvc' } default { Fail "no build for Windows on $arch" } } $name = "gdt-$tag-$target" $archive = "$name.zip"
if (-not $InstallDir) { if (-not $env:LOCALAPPDATA) { Fail 'LOCALAPPDATA is not set; say where to install with -InstallDir' } $InstallDir = Join-Path $env:LOCALAPPDATA 'Programs\gdt' }
$tmp = Join-Path ([IO.Path]::GetTempPath()) ("gdt-install-" + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $tmp | Out-Null try { Write-Host "Installing gdt $tag for $target" $base = "$repoUrl/releases/download/$tag" try { Invoke-WebRequest -UseBasicParsing -Uri "$base/$archive" -OutFile (Join-Path $tmp $archive) Invoke-WebRequest -UseBasicParsing -Uri "$base/SHA256SUMS" -OutFile (Join-Path $tmp 'SHA256SUMS') } catch { Fail "could not download from $base ($($_.Exception.Message))" }
$expected = $null foreach ($line in Get-Content (Join-Path $tmp 'SHA256SUMS')) { if ($line -match '^([0-9a-fA-F]{64})\s+\*?(.+)$' -and $Matches[2].Trim() -eq $archive) { $expected = $Matches[1]; break } } if (-not $expected) { Fail "$archive is not listed in SHA256SUMS; nothing was installed" } $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath (Join-Path $tmp $archive)).Hash if ($actual -ne $expected.ToUpperInvariant()) { Fail "checksum mismatch for $archive (expected $expected, got $actual); nothing was installed" } Write-Host 'Checksum ok'
if ($VerifyAttestation) { if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { Fail '-VerifyAttestation needs the GitHub CLI (gh)' } # Not any attestation of the repository: the one that this repository's release # workflow made, on a GitHub-hosted runner, for this very tag. Windows PowerShell 5.1 # turns what a program writes to stderr into an error while 'Stop' is set, so the # call runs under 'Continue' and the exit code decides. $previous = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { $null = & gh attestation verify (Join-Path $tmp $archive) --repo $slug ` --signer-workflow "$slug/.github/workflows/release.yml" ` --source-ref "refs/tags/$tag" ` --deny-self-hosted-runners 2>&1 $code = $LASTEXITCODE } finally { $ErrorActionPreference = $previous } if ($code -ne 0) { Fail "the build provenance of $archive could not be verified (it needs a recent GitHub CLI, signed in); nothing was installed" } Write-Host 'Build provenance ok' } else { Write-Host '(The checksum shows the download is intact. -VerifyAttestation also shows where it was built.)' }
# Only the one expected file is taken out of the archive, to a path chosen here, so # nothing in the archive decides where anything is written. Entry names use '/' or, # in archives made by Windows PowerShell 5.1, '\'. Add-Type -AssemblyName System.IO.Compression.FileSystem $exe = Join-Path $tmp 'gdt.exe' $zip = [IO.Compression.ZipFile]::OpenRead((Join-Path $tmp $archive)) try { $entry = $zip.Entries | Where-Object { $_.FullName.Replace('\', '/') -eq "$name/gdt.exe" } | Select-Object -First 1 if (-not $entry) { Fail "$archive has no gdt.exe" } [IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $exe, $true) } finally { $zip.Dispose() }
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null $final = Join-Path $InstallDir 'gdt.exe' # Copy beside the final name, then rename, so a failed copy never leaves half a gdt.exe. $staged = Join-Path $InstallDir (".gdt.new." + [Guid]::NewGuid().ToString('N')) try { Copy-Item -LiteralPath $exe -Destination $staged -Force Move-Item -LiteralPath $staged -Destination $final -Force } catch { Remove-Item -LiteralPath $staged -Force -ErrorAction SilentlyContinue Fail "could not write $final; if gdt is running, close it and try again ($($_.Exception.Message))" }
# Asking for the version is only a courtesy; whatever it does must not undo a finished install # (Windows PowerShell 5.1 raises an error for a program's stderr while 'Stop' is set). $installed = try { & $final --version 2>$null } catch { $null } if (-not $installed) { $installed = 'gdt' } Write-Host "Installed $installed in $InstallDir"
$onPath = $false foreach ($entry in ($env:Path -split ';')) { if ($entry -and ($entry.TrimEnd('\') -ieq $InstallDir.TrimEnd('\'))) { $onPath = $true } } if (-not $onPath) { Write-Host '' Write-Host "$InstallDir is not on your Path. To add it, open `"Edit environment variables for your account`"" Write-Host '(Start menu) and add it to the user variable Path, then open a new terminal.' Write-Host 'The installer changes nothing else.' } } finally { Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue }}
Install-Gdt @argsWhat they do
- Pick the archive for your system and architecture, and the release you asked for (default: the latest).
- Download it and the
SHA256SUMSfile of the same release from GitHub over HTTPS, and stop if the checksum does not match. - With
--verify-attestation(GDT_VERIFY_ATTESTATION=1on Windows), also rungh attestation verifyon the archive. - Copy
gdtinto one folder. Nothing else is changed: notPATH, not your profile.
Verify without the installer
You can skip the installers and check a release by hand, with the checksum file andgh attestation verify: Verify a download.
Independent community project. Not affiliated with, endorsed by or sponsored by the Godot Foundation or the Godot Engine project. “Godot” is a trademark of the Godot Foundation; this site does not use the Godot logo.