Skip to content

Instantly share code, notes, and snippets.

@hsandt
Last active July 12, 2026 20:06
Show Gist options
  • Select an option

  • Save hsandt/113d3c6698a54e378d44b5024140c2a0 to your computer and use it in GitHub Desktop.

Select an option

Save hsandt/113d3c6698a54e378d44b5024140c2a0 to your computer and use it in GitHub Desktop.
Install Godot 3 stable or 4 beta, rc or stable and create desktop entry for Windows or Linux
[Desktop Entry]
Name=Godot Engine $VERSION $CHANNEL
GenericName=Libre game engine
Comment=Multi-platform 2D and 3D game engine with a feature-rich editor
Exec=godot$VERSION_$CHANNEL %f
Icon=$HOME/.local/share/applications/godot.png
Terminal=false
Type=Application
MimeType=application/x-godot-project;
Categories=Development;IDE;
Name[en_US]=Godot $VERSION $CHANNEL.desktop
#!/usr/bin/env bash
# Gist: https://gist.github.com/hsandt/113d3c6698a54e378d44b5024140c2a0
# CONFIG: customize to match your needs
default_base_install_folder="${HOME}/Applications/Game Engines/Godot"
usage() {
echo "Usage: install_godot_and_create_desktop_entry.sh <version> <channel>
Install a version of Godot for Linux or Windows 64-bit in Godot path
(default '${HOME}/Applications/Game Engines/Godot/').
You may customize Godot path by adding a file 'godot_path.txt' in the bin directory
(Linux: '${HOME}/.local/bin', Windows: '$HOME/bin')
containing the path to your install folder of choice.
On Linux, also create a .desktop launcher for it.
On Windows, also installs the '_console.exe' binary variant, which opens a terminal besides the application.
Requirements:
- perl installed
- Linux: \"Godot template.desktop\" placed in the same directory as this script
- Linux: Godot icon placed at ~/.local/share/applications/godot.png
- jq installed (https://jqlang.org/) if using --latest option
(for Windows, see https://stackoverflow.com/questions/53967693/how-to-run-jq-from-gitbash-in-windows)
- Windows: '$HOME/bin' must be in your Git Bash PATH
ARGUMENTS
version Version of godot to download. Do not include channel like 'beta' here.
Ex: '4.0.1'.
channel Channel of godot to download, including number, excluding mono (see option -m instead).
Ex: 'beta14', 'rc1', 'stable'
-m, --mono Download mono build
-l, --latest Show latest version available on Godot repository and quit
-h, --help Show this help and quit
"
}
show_latest() {
# Use CLI to get latest release on GitHub: https://stackoverflow.com/questions/34899736/how-can-i-use-git-cli-to-get-the-latest-release
# Parsing JSON with Unix tools: https://stackoverflow.com/questions/1955505/parsing-json-with-unix-tools
# Requires jq (https://jqlang.org/)
if hash jq 2> /dev/null; then
latest_version_with_quotes=`curl -s https://api.github.com/repos/godotengine/godot/releases/latest | jq .name`
# Remove surrounding quotes: https://stackoverflow.com/questions/9733338/shell-script-remove-first-and-last-quote-from-a-variable
latest_version="${latest_version_with_quotes%\"}"
latest_version="${latest_version#\"}"
echo "$latest_version"
else
echo "ERROR: no jq, cannot read JSON data"
success=false
fi
}
# Default arguments
mono=false
# Read arguments
positional_args=()
while [[ $# -gt 0 ]]; do
case $1 in
-h | --help )
usage
exit 0
;;
-l | --latest )
show_latest
exit 0
;;
-m | --mono )
mono=true
shift # past argument
;;
-* ) # unknown option
echo "Unknown option: '$1'"
usage
exit 1
;;
* ) # store positional argument for later
positional_args+=("$1")
shift # past argument
;;
esac
done
if ! [[ ${#positional_args[@]} -eq 2 ]]; then
echo "Wrong number of positional arguments: found $#, expected 2."
echo "Passed positional arguments: $@"
usage
exit 1
fi
version="${positional_args[0]}"
channel="${positional_args[1]}"
parent_dir_path="$(dirname "$0")"
# https://stackoverflow.com/questions/394230/how-to-detect-the-os-from-a-bash-script
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
if [[ "$version" == 3* ]]; then
# Godot 3 was naming "X11" the Linux build
platform_suffix="x11_64"
binary_suffix="x11.64"
if [[ "$mono" == true ]]; then
zip_suffix="x11_64"
else
zip_suffix="x11.64"
fi
else
platform_suffix="linux_x86_64"
binary_suffix="linux.x86_64"
if [[ "$mono" == true ]]; then
zip_suffix="linux_x86_64"
else
zip_suffix="linux.x86_64"
fi
fi
elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
# Windows, either:
# - using Cygwin (POSIX compatibility layer and Linux environment emulation for Windows)
# - using MSYS (Lightweight shell and GNU utilities compiled for Windows), itself part of MinGW, itself used by Git Bash
platform_suffix="win64"
binary_suffix="win64.exe"
if [[ "$mono" == true ]]; then
zip_suffix="win64"
else
zip_suffix="win64.exe"
fi
else
echo "Unsupported platform '$OSTYPE'. STOP."
exit 1
fi
if [[ "$mono" == true ]]; then
full_channel="${channel}_mono"
else
full_channel="${channel}"
fi
# Non-mono build example: "Godot_v4.2.2-stable_linux_x86_64", "Godot_v4.2.2-stable_win64.exe"
# Mono build example: "Godot_v4.2.2-stable_mono_linux_x86_64", "Godot_v4.2.2-stable_mono_win64"
godot_zip_basename="Godot_v${version}-${full_channel}_${zip_suffix}"
godot_zip_name="${godot_zip_basename}.zip"
godot_install_dir_name="Godot_v${version}-${full_channel}_${platform_suffix}"
godot_bin_name="Godot_v${version}-${full_channel}_${binary_suffix}"
# Remove existing archive if any (in case it was previously incorrectly downloaded)
rm -f "$godot_zip_name"
if [[ "$channel" == "stable"* ]]; then
# Stable releases are on GitHub
# Note that the directory never contains "mono", so we use channel instead of full_channel
# but we use full_channel for the actual zip
download_url="https://github.com/godotengine/godot/releases/download/${version}-${channel}/${godot_zip_name}"
elif [[ "$channel" == "dev"* ]]; then
# Dev releases are on GitHub with a slightly different directory name, godot-builds
download_url="https://github.com/godotengine/godot-builds/releases/download/${version}-${channel}/${godot_zip_name}"
else
# Release candidates are on Tux Family
# Note that the directory never contains "mono", so we use channel instead of full_channel
download_url="https://downloads.tuxfamily.org/godotengine/${version}/${channel}/${godot_zip_name}"
fi
# Download archive for passed version in current folder
# We prefer curl (-L follow redirect, important on GitHub releases, -O output file with same name)
# to wget so Windows users don't need to install wget on Windows
curl -LO "$download_url"
if [[ "$mono" == true ]]; then
# Mono zip already contains a wrapping directory (named like zip basename),
# which contains binary + GodotSharp directory, so just unzip in the wild
unzip "$godot_zip_name"
else
# Non-mono zip contains wild files: the binary and, on Windows, the '_console.exe' binary variant
# Since there are multiple files on Windows, it's easier to keep extract files under a directory
# to move them together later (and on Linux it will just prepare a sub-folder containing 1 file
# for the final installation)
# so unzip to folder named like zip basename
unzip "$godot_zip_name" -d "$godot_install_dir_name"
fi
# Remove archive to clean up current folder
rm "$godot_zip_name"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
bin_dir_path="${HOME}/.local/bin"
elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
bin_dir_path="${HOME}/bin"
else
echo "Unsupported platform '$OSTYPE'. STOP."
exit 1
fi
# Create bin directory if needed
mkdir -p "$bin_dir_path"
# Set base install folder to use:
# - read godot_path.txt if present
# - use default path else
if [[ -f "${bin_dir_path}/godot_path.txt" ]]; then
base_install_folder=`cat "${bin_dir_path}/godot_path.txt"`
echo "Found '${bin_dir_path}/godot_path.txt', installing Godot in '$base_install_folder'..."
else
base_install_folder="$default_base_install_folder"
echo "No '${bin_dir_path}/godot_path.txt', installing Godot in default location '$base_install_folder'..."
fi
binary_install_folder="$base_install_folder/$godot_install_dir_name"
# Delete any previous installation to be clean, then move the unzipped archive folder to target path
rm -rf "$binary_install_folder"
mkdir -p "$base_install_folder"
# The unzipped archive folder already has the target folder name so just move it to the *parent* folder
mv -f "$godot_install_dir_name" "$base_install_folder"
echo "Installed '${godot_install_dir_name}' folder in '${base_install_folder}/'"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Create symbolic link for terminal and future Desktop entry (overwrites any existing link)
install_path="${binary_install_folder}/${godot_bin_name}"
symlink_path="${bin_dir_path}/godot${version}_${full_channel}"
ln -sf "$install_path" "$symlink_path"
echo "Created symbolic link at '$symlink_path'"
# Create Desktop entry from template (overwrites any existing file)
desktop_entry_path="${HOME}/.local/share/applications/Godot ${version} ${full_channel}.desktop"
cp "${parent_dir_path}/Godot template.desktop" "$desktop_entry_path"
# https://stackoverflow.com/questions/6994947/how-to-replace-a-string-in-an-existing-file-in-perl
perl -pi -e "s/\\\$VERSION/${version}/g" "$desktop_entry_path"
perl -pi -e "s/\\\$CHANNEL/${full_channel}/g" "$desktop_entry_path"
# https://stackoverflow.com/questions/26080096/installed-desktop-file-to-have-users-home-directory-path-inserted
# mind the comma separator since we deal with paths containing slashes
# note that "~" doesn't work with Icon=, we need full path expansion
perl -pi -e "s,\\\$HOME,${HOME},g" "$desktop_entry_path"
echo "Created desktop entry at '$desktop_entry_path'"
elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
# Create symbolic link for terminal (overwrites any existing link)
install_path="${binary_install_folder}/${godot_bin_name}"
# On Windows we just use "$HOME/bin" to simplify, but make sure it is in your Git Bash PATH
symlink_path="${bin_dir_path}/godot${version}_${full_channel}.exe"
ln -sf "$install_path" "$symlink_path"
if [[ $? -ne 0 ]]; then
echo "$conversion_info" | tee -a "$conversion_log_filename"
echo "Creating symlink of '$install_path' at '$symlink_path' failed, STOP."
exit 1
fi
echo "Created symbolic link at '$symlink_path'"
else
echo "Unsupported platform '$OSTYPE'. STOP."
exit 1
fi
#!/usr/bin/env bash
# The opposite operation of install_godot_and_create_desktop_entry.sh
# Gist: https://gist.github.com/hsandt/113d3c6698a54e378d44b5024140c2a0
# CONFIG: customize to match your needs
base_install_folder="${HOME}/Applications/Game Engines/Godot"
usage() {
echo "Usage: uninstall_godot_and_create_desktop_entry.sh version channel
Uninstall a version of Godot for Linux installed in ${HOME}/Applications/Game Engines/Godot/ and
remove the .desktop launcher for it.
Requirements:
- perl installed
- the Godot version to remove must have been installed with install_godot_and_create_desktop_entry.sh
(or using the same path conventions)
ARGUMENTS
version Version of godot to uninstall. Do not include channel like 'beta' here.
Ex: '4.0.1'.
channel Channel of godot to uninstall, including number, excluding mono (see option -m instead).
Ex: 'beta14', 'rc1', 'stable'
-m Uninstall mono build
"
}
# Default arguments
mono=false
# Read arguments
positional_args=()
while [[ $# -gt 0 ]]; do
case $1 in
-h | --help )
usage
exit 0
;;
-m | --mono )
mono=true
shift # past argument
;;
-* ) # unknown option
echo "Unknown option: '$1'"
usage
exit 1
;;
* ) # store positional argument for later
positional_args+=("$1")
shift # past argument
;;
esac
done
if ! [[ ${#positional_args[@]} -eq 2 ]]; then
echo "Wrong number of positional arguments: found $#, expected 2."
echo "Passed positional arguments: $@"
usage
exit 1
fi
version="${positional_args[0]}"
channel="${positional_args[1]}"
if [[ "$version" == 3* ]]; then
# Godot 3 was naming "X11" the Linux build
binary_linux_suffix="x11.64"
if [[ "$mono" == true ]]; then
zip_linux_suffix="x11_64"
else
zip_linux_suffix="x11.64"
fi
else
binary_linux_suffix="linux.x86_64"
if [[ "$mono" == true ]]; then
zip_linux_suffix="linux_x86_64"
else
zip_linux_suffix="linux.x86_64"
fi
fi
if [[ "$mono" == true ]]; then
full_channel="${channel}_mono"
else
full_channel="${channel}"
fi
# Non-mono build example: "Godot_v4.2.2-stable_linux_x86_64"
# Mono build example: "Godot_v4.2.2-stable_mono_linux_x86_64"
godot_zip_basename="Godot_v${version}-${full_channel}_${zip_linux_suffix}"
godot_bin_name="Godot_v${version}-${full_channel}_${binary_linux_suffix}"
if [[ "$mono" == true ]]; then
# Move the whole directory (named like zip basename), which contains binary + GodotSharp directory, under the install folder
binary_install_folder="$base_install_folder/$godot_zip_basename"
else
# Non-mono build: we should have extracted a single file named "$godot_bin_name"
# Move it to the target folder (overwrites any existing file)
binary_install_folder="$base_install_folder"
fi
install_path="${binary_install_folder}/${godot_bin_name}"
if [[ -f "$install_path" ]]; then
rm "$install_path"
echo "Removed Godot binary at $install_path"
else
echo "No file at '$install_path', so nothing to remove"
fi
symlink_path="${HOME}/.local/bin/godot${version}_${full_channel}"
if [[ -L "$symlink_path" ]]; then
rm "$symlink_path"
echo "Removed symlink at $symlink_path"
else
echo "No symlink at '$symlink_path', so nothing to remove"
fi
desktop_entry_path="${HOME}/.local/share/applications/Godot ${version} ${full_channel}.desktop"
if [[ -f "$desktop_entry_path" ]]; then
rm "$desktop_entry_path"
echo "Removed desktop entry at ${desktop_entry_path}"
else
echo "No file at '$desktop_entry_path', so nothing to remove"
fi
@hsandt

hsandt commented Jan 10, 2024

Copy link
Copy Markdown
Author

I also found another script that does a similar thing but is self-contained: https://gist.github.com/hieuns/ac4f117492771a690fa06cfa47b24714

It auto-downloads the icon svg from Wikimedia and embeds the .desktop template content in the same script.

However, it takes the Godot URL argument, making it more flexible but unable to retrieve the correct URL from version+channel automatically.

It may be worth merging both methods (download icon and deduce URL, possibly embed the template too) for a simple script the user can run without extra setup.

@hsandt

hsandt commented Aug 28, 2024

Copy link
Copy Markdown
Author

I added an uninstall script to revert the operations of the install script. Convenient to remove older versions of Godot, esp. dev and rc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment