How to Install Google Antigravity on Fedora 44

Install Google Antigravity on Fedora 44 without mixing the current desktop release with the legacy RPM's conflicting command and launcher. The graphical and terminal tools use different sources, so using one install method per command or launcher keeps upgrades and removal predictable.

Last updatedAuthorJoshua JamesRead time16 minGuide typeFedora

Google Antigravity is not available from Fedora’s default repositories. Fedora 44 users can install the current Antigravity 2.0 desktop app, the separate Antigravity IDE, or the terminal-based agy CLI. The graphical downloads use guarded root-owned helpers, while the CLI uses Google’s per-user installer. An unsigned RPM repository still provides the older Antigravity IDE 1.23.2, but that package conflicts with the current desktop command and launcher.

Choose a Google Antigravity Install Path on Fedora

Google’s download page presents Antigravity 2.0, Antigravity CLI, Antigravity IDE, and Antigravity SDK as separate products. The installation choices are the desktop app, IDE, and CLI. The Python SDK needs a separate Python environment and is not installed by these commands.

These instructions target a traditional DNF-based Fedora Workstation installation. Fedora Silverblue, Kinoite, and other Atomic desktops use an image-based host and are not covered; do not paste the host-level DNF and /opt helper commands into an Atomic system.

Install pathBest fitSource and architectureUpdate methodWhat it manages
Option 1: Antigravity 2.0 desktop appStandalone projects, artifacts, scheduled work, and visual agent orchestration.Official HTTPS tarball for x86_64 or ARM64.Close the app, then run sudo update-antigravity.Owns antigravity and antigravity.desktop; do not combine it with the legacy RPM.
Option 2: Antigravity IDEEditor-first work with agents, artifacts, completion, and code-aware commands.Official HTTPS IDE tarball for x86_64 or ARM64.Close the IDE, then run sudo update-antigravity-ide.Uses separate antigravity-ide command, launcher, icon, and install prefix.
Option 3: Antigravity CLITerminal work in local projects, SSH sessions, and scripts.Official HTTPS per-user installer, which selects the Linux binary.Rerun the verified upstream installer from your normal user account.Owns the marked ~/.local/bin/agy installation and, when needed, a marked Bash PATH block.
Legacy: Antigravity IDE RPMOlder package-managed IDE when version 1.x is specifically required.Unsigned Google RPM repository; currently x86_64 only.Run sudo dnf upgrade --refresh --from-repo=antigravity-rpm antigravity if Google publishes a newer RPM.Owns /usr/bin/antigravity and antigravity.desktop; conflicts with option 1.

The Antigravity 2.0 desktop helper exposes antigravity, while the current IDE helper exposes antigravity-ide. The legacy RPM also exposes antigravity and antigravity.desktop, so remove the legacy RPM before using the current desktop helper on the same Fedora system.

Install Google Antigravity on Fedora

The current desktop and IDE downloads are HTTPS-hosted tarballs without a separately published checksum. The helpers validate every archive path and link before extraction, reject special files and unmanaged path collisions, stage replacements on the /opt filesystem, and retain one ownership-marked previous version for guarded transaction recovery. Each helper records both the semantic version and Google’s numeric build, so a republished same-version archive is not mistaken for the installed build.

Google currently lists glibc 2.28 and GLIBCXX 3.4.25 as the Linux library floors. Fedora 44 exceeds those library versions, but the helpers’ URL and archive checks do not replace a cryptographic release signature; the official HTTPS origin remains the trust boundary.

Option 1: Install Antigravity 2.0 Desktop App on Fedora

Prepare Fedora for the Desktop Helper

Refresh Fedora first, then install the small set of tools used by the desktop helper:

sudo dnf upgrade --refresh && sudo dnf install curl desktop-file-utils python3 util-linux

These commands use sudo for tasks that need root privileges. If your account is not in the sudoers file yet, follow the guide on how to add a user to sudoers on Fedora.

Save the Desktop Update Helper

The desktop helper reads Google’s download page, selects the x86_64 or ARM64 tarball for the local machine, installs the app under /opt/antigravity, creates /usr/local/bin/antigravity, extracts Google’s bundled icon, and adds the desktop launcher Fedora needs for correct app-menu and dock matching.

The setup block replaces only a helper carrying the LinuxCapable ownership marker or the exact fingerprint of the earlier download-page helper. The helper refuses unmanaged install roots, launchers, links, and icons instead of overwriting unrelated files.

Paste the entire block beginning with (. It stages and syntax-checks the helper, acquires the same lock used by updates, and atomically replaces only a recognized LinuxCapable helper.

(
set -euo pipefail

helper_path="/usr/local/bin/update-antigravity"
helper_lock="/run/lock/linuxcapable-antigravity-desktop.lock"
helper_source=""

# shellcheck disable=SC2329
cleanup_helper_source() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$helper_source" ]; then rm -f -- "$helper_source"; fi
	exit "$status"
}

trap cleanup_helper_source EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

helper_source=$(mktemp "${TMPDIR:-/tmp}/update-antigravity.XXXXXX")
cat >"$helper_source" <<'HELPER'
#!/usr/bin/env bash
# LinuxCapable owner: linuxcapable-google-antigravity-desktop-helper-v1
set -Eeuo pipefail

if [ "$(id -u)" -ne 0 ]; then
	echo "Run with sudo: sudo update-antigravity" >&2
	exit 1
fi

download_page="https://antigravity.google/download"
install_root="/opt/antigravity"
previous_root="${install_root}.previous"
command_link="/usr/local/bin/antigravity"
desktop_file="/usr/share/applications/antigravity.desktop"
icon_file="/usr/share/icons/hicolor/512x512/apps/antigravity.png"
old_icon_file="/usr/share/icons/hicolor/scalable/apps/antigravity.svg"
owner_id="linuxcapable-google-antigravity-desktop-v1"

case "$(uname -m)" in
x86_64 | amd64)
	platform="linux-x64"
	expected_top_dir="Antigravity-x64"
	;;
aarch64 | arm64)
	platform="linux-arm"
	expected_top_dir="Antigravity-arm64"
	;;
*)
	echo "Unsupported architecture: $(uname -m)" >&2
	exit 1
	;;
esac

expected_target="$install_root/$expected_top_dir/antigravity"

for required_command in curl desktop-file-validate flock python3; do
	if ! command -v "$required_command" >/dev/null 2>&1; then
		echo "$required_command is required to install Antigravity." >&2
		exit 1
	fi
done

if rpm -q antigravity >/dev/null 2>&1; then
	echo "Remove the legacy antigravity RPM before installing the current desktop app." >&2
	exit 1
fi

exec 9>/run/lock/linuxcapable-antigravity-desktop.lock
if ! flock -n 9; then
	echo "Another Antigravity desktop helper is already running." >&2
	exit 1
fi

work_dir=""
stage_dir=""
saved_previous=""
transaction_started="no"
had_current="no"
had_previous="no"
committed="no"
transaction_id=""
transaction_file=".linuxcapable-transaction"
release_file=".linuxcapable-release"
adopted_install_root="no"
adopted_previous_root="no"

path_exists() {
	[ -e "$1" ] || [ -L "$1" ]
}

is_managed_root() {
	local path=$1
	[ -d "$path" ] && [ ! -L "$path" ] &&
		[ -f "$path/.linuxcapable-owner" ] && [ ! -L "$path/.linuxcapable-owner" ] &&
		[ "$(cat "$path/.linuxcapable-owner")" = "$owner_id" ] &&
		[ -f "$path/.linuxcapable-version" ] && [ ! -L "$path/.linuxcapable-version" ] &&
		{ ! path_exists "$path/$release_file" ||
			{ [ -f "$path/$release_file" ] && [ ! -L "$path/$release_file" ]; }; }
}

is_legacy_root() {
	local path=$1
	[ -d "$path" ] && [ ! -L "$path" ] &&
		[ -f "$path/.linuxcapable-version" ] && [ ! -L "$path/.linuxcapable-version" ] &&
		! path_exists "$path/.linuxcapable-owner" &&
		! path_exists "$path/$release_file" &&
		[ -f "$path/$expected_top_dir/antigravity" ] &&
		[ ! -L "$path/$expected_top_dir/antigravity" ] &&
		[ -x "$path/$expected_top_dir/antigravity" ]
}

is_transaction_root() {
	local path=$1
	is_managed_root "$path" &&
		[ -f "$path/$transaction_file" ] && [ ! -L "$path/$transaction_file" ] &&
		[ "$(cat "$path/$transaction_file")" = "$transaction_id" ]
}

remove_adopted_owner() {
	local path=$1
	local launcher="$path/$expected_top_dir/antigravity"
	if ! path_exists "$path/.linuxcapable-owner"; then
		return 0
	fi
	if [ -d "$path" ] && [ ! -L "$path" ] &&
		[ -f "$path/.linuxcapable-owner" ] && [ ! -L "$path/.linuxcapable-owner" ] &&
		[ -f "$path/.linuxcapable-version" ] && [ ! -L "$path/.linuxcapable-version" ] &&
		! path_exists "$path/$release_file" &&
		[ -f "$launcher" ] && [ ! -L "$launcher" ] && [ -x "$launcher" ]; then
		rm -f -- "$path/.linuxcapable-owner"
	else
		echo "Could not safely roll back the temporary owner marker under $path." >&2
		return 1
	fi
}

is_expected_desktop() {
	local path=$1
	[ -f "$path" ] && [ ! -L "$path" ] &&
		desktop-file-validate "$path" >/dev/null 2>&1 &&
		grep -Fqx 'Name=Antigravity' "$path" &&
		grep -Fqx 'Comment=Google Antigravity 2.0 agent platform' "$path" &&
		grep -Fqx "Exec=$command_link %U" "$path" &&
		grep -Fqx 'Icon=antigravity' "$path" &&
		grep -Fqx 'Terminal=false' "$path" &&
		grep -Fqx 'Type=Application' "$path" &&
		grep -Fqx 'Categories=Development;IDE;' "$path" &&
		grep -Fqx 'StartupNotify=true' "$path" &&
		grep -Fqx 'StartupWMClass=Antigravity' "$path" &&
		grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$path"
}

is_png_file() {
	local path=$1
	[ -f "$path" ] && [ ! -L "$path" ] || return 1
	python3 - "$path" <<'PY'
import struct
import sys
import zlib
from pathlib import Path

path = Path(sys.argv[1])
if path.is_symlink() or not path.is_file():
    raise SystemExit(1)
data = path.read_bytes()
if len(data) < 45 or not data.startswith(b"\x89PNG\r\n\x1a\n"):
    raise SystemExit(1)

offset = 8
chunk_index = 0
seen_ihdr = False
seen_idat = False
seen_iend = False
while offset < len(data):
    if offset + 12 > len(data):
        raise SystemExit(1)
    length = struct.unpack(">I", data[offset : offset + 4])[0]
    chunk_type = data[offset + 4 : offset + 8]
    chunk_end = offset + 12 + length
    if chunk_end > len(data):
        raise SystemExit(1)
    payload = data[offset + 8 : offset + 8 + length]
    expected_crc = struct.unpack(">I", data[offset + 8 + length : chunk_end])[0]
    actual_crc = zlib.crc32(payload, zlib.crc32(chunk_type)) & 0xFFFFFFFF
    if actual_crc != expected_crc:
        raise SystemExit(1)

    if chunk_index == 0:
        if chunk_type != b"IHDR" or length != 13:
            raise SystemExit(1)
        width, height = struct.unpack(">II", payload[:8])
        if width == 0 or height == 0:
            raise SystemExit(1)
        seen_ihdr = True
    elif chunk_type == b"IHDR":
        raise SystemExit(1)

    if chunk_type == b"IDAT":
        seen_idat = True
    elif chunk_type == b"IEND":
        if length != 0 or not seen_ihdr or not seen_idat or chunk_end != len(data):
            raise SystemExit(1)
        seen_iend = True
        offset = chunk_end
        break

    offset = chunk_end
    chunk_index += 1

raise SystemExit(0 if seen_ihdr and seen_idat and seen_iend and offset == len(data) else 1)
PY
}

remove_tracked_temp() {
	local path=$1
	case "$path" in
	/var/tmp/antigravity.* | /opt/.antigravity-stage.*)
		if [ -d "$path" ] && [ ! -L "$path" ]; then
			rm -rf -- "$path"
		fi
		;;
	esac
}

backup_item() {
	local source=$1
	local name=$2
	if [ -e "$source" ] || [ -L "$source" ]; then
		cp -a -- "$source" "$work_dir/backups/$name"
	else
		: >"$work_dir/backups/$name.missing"
	fi
}

restore_item() {
	local destination=$1
	local name=$2
	if ! rm -f -- "$destination"; then return 1; fi
	if [ -e "$work_dir/backups/$name" ] || [ -L "$work_dir/backups/$name" ]; then
		if ! cp -a -- "$work_dir/backups/$name" "$destination"; then return 1; fi
	fi
	return 0
}

finalize_commit() {
	local ok=yes

	if path_exists "$install_root/$transaction_file"; then
		if is_transaction_root "$install_root"; then
			rm -f -- "$install_root/$transaction_file" || ok=no
		else
			echo "Commit stopped: the Antigravity transaction marker changed unexpectedly." >&2
			ok=no
		fi
	elif ! is_managed_root "$install_root"; then
		echo "Commit stopped: $install_root is not the expected managed root." >&2
		ok=no
	fi

	if [ -n "$saved_previous" ] && path_exists "$saved_previous"; then
		if ! is_managed_root "$saved_previous"; then
			echo "Commit preserved an unrecognized rollback at $saved_previous for manual review." >&2
			ok=no
		elif [ "$had_current" = yes ]; then
			rm -rf -- "$saved_previous" || ok=no
		elif [ "$had_previous" = yes ] && ! path_exists "$previous_root"; then
			mv -- "$saved_previous" "$previous_root" || ok=no
		else
			echo "Commit could not place the prior rollback automatically: $saved_previous" >&2
			ok=no
		fi
	fi

	[ "$ok" = yes ]
}

rollback_transaction() {
	local ok=yes

	if is_transaction_root "$install_root"; then
		rm -rf -- "$install_root" || ok=no
	elif path_exists "$install_root"; then
		if [ "$had_current" != yes ] || ! is_managed_root "$install_root"; then
			echo "Rollback stopped at an unexpected live root: $install_root" >&2
			ok=no
		fi
	fi

	if [ "$had_current" = yes ] && ! path_exists "$install_root"; then
		if is_managed_root "$previous_root"; then
			mv -- "$previous_root" "$install_root" || ok=no
		else
			echo "Rollback could not restore the previous live root automatically." >&2
			ok=no
		fi
	fi

	if [ -n "$saved_previous" ] && path_exists "$saved_previous"; then
		if ! path_exists "$previous_root" && is_managed_root "$saved_previous"; then
			mv -- "$saved_previous" "$previous_root" || ok=no
		else
			echo "Prior rollback preserved for manual recovery at $saved_previous" >&2
			ok=no
		fi
	fi

	[ "$ok" = yes ]
}

cleanup() {
	local status=$?
	local rollback_ok=yes
	set +e
	trap - EXIT HUP INT TERM

	if [ "$transaction_started" = yes ]; then
		if [ "$committed" = yes ]; then
			if ! finalize_commit; then rollback_ok=no; fi
		else
			if ! rollback_transaction; then rollback_ok=no; fi

			if [ -n "$work_dir" ] && [ -d "$work_dir/backups" ]; then
				if ! restore_item "$command_link" command-link; then rollback_ok=no; fi
				if ! restore_item "$desktop_file" desktop-file; then rollback_ok=no; fi
				if ! restore_item "$icon_file" icon-file; then rollback_ok=no; fi
				if ! restore_item "$old_icon_file" old-icon-file; then rollback_ok=no; fi
			fi

			if command -v restorecon >/dev/null 2>&1; then
				if [ -e "$install_root" ] && ! restorecon -R "$install_root"; then rollback_ok=no; fi
				if { [ -e "$command_link" ] || [ -L "$command_link" ]; } && ! restorecon "$command_link"; then rollback_ok=no; fi
				if [ -e "$desktop_file" ] && ! restorecon "$desktop_file"; then rollback_ok=no; fi
				if [ -e "$icon_file" ] && ! restorecon "$icon_file"; then rollback_ok=no; fi
				if [ -e "$old_icon_file" ] && ! restorecon "$old_icon_file"; then rollback_ok=no; fi
			fi

			if command -v update-desktop-database >/dev/null 2>&1 &&
				! update-desktop-database /usr/share/applications >/dev/null; then
				rollback_ok=no
			fi
			if command -v gtk-update-icon-cache >/dev/null 2>&1 &&
				! gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null; then
				rollback_ok=no
			fi
		fi

		if [ "$rollback_ok" != yes ]; then status=1; fi
	fi
	if [ "$transaction_started" = no ] && [ -n "$saved_previous" ] && path_exists "$saved_previous"; then
		if [ -d "$saved_previous" ] && [ ! -L "$saved_previous" ] &&
			[ -z "$(find "$saved_previous" -mindepth 1 -print -quit)" ]; then
			if ! rmdir -- "$saved_previous"; then status=1; fi
		else
			echo "Preserving unexpected rollback-reservation state at $saved_previous" >&2
			status=1
		fi
	fi

	if [ "$committed" != yes ]; then
		if [ "$adopted_install_root" = yes ] && ! remove_adopted_owner "$install_root"; then rollback_ok=no; fi
		if [ "$adopted_previous_root" = yes ] && ! remove_adopted_owner "$previous_root"; then rollback_ok=no; fi
		if [ "$rollback_ok" != yes ]; then status=1; fi
	fi

	if [ -n "$stage_dir" ]; then
		if ! remove_tracked_temp "$stage_dir"; then status=1; fi
	fi
	if [ -n "$work_dir" ]; then
		if ! remove_tracked_temp "$work_dir"; then status=1; fi
	fi
	exit "$status"
}

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

desktop_owned=no
if [ -L "$desktop_file" ] || { [ -e "$desktop_file" ] && [ ! -f "$desktop_file" ]; }; then
	echo "$desktop_file is not a regular file. Move it before rerunning this helper." >&2
	exit 1
elif [ -f "$desktop_file" ]; then
	if grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$desktop_file"; then
		desktop_owned=yes
	elif grep -Fqx 'Exec=/usr/local/bin/antigravity %U' "$desktop_file" &&
		grep -Fqx 'Icon=antigravity' "$desktop_file" &&
		grep -Fqx 'StartupWMClass=Antigravity' "$desktop_file"; then
		desktop_owned=yes
	else
		echo "$desktop_file does not carry the expected LinuxCapable owner marker. Move it before rerunning this helper." >&2
		exit 1
	fi
fi

for managed_icon in "$icon_file" "$old_icon_file"; do
	if [ -L "$managed_icon" ] || { [ -e "$managed_icon" ] && [ ! -f "$managed_icon" ]; }; then
		echo "$managed_icon is not a regular file. Move it before rerunning this helper." >&2
		exit 1
	elif [ -f "$managed_icon" ] && [ "$desktop_owned" != yes ]; then
		echo "$managed_icon exists without a LinuxCapable-managed desktop entry. Move it before rerunning this helper." >&2
		exit 1
	fi
done

if [ -L "$command_link" ]; then
	if [ "$(readlink "$command_link")" != "$expected_target" ]; then
		echo "$command_link points somewhere else. Move it before rerunning this helper." >&2
		exit 1
	fi
elif [ -e "$command_link" ]; then
	echo "$command_link exists and is not a symlink. Move it before rerunning this helper." >&2
	exit 1
fi

if [ -e "$install_root" ] || [ -L "$install_root" ]; then
	if is_managed_root "$install_root"; then
		:
	elif is_legacy_root "$install_root" && [ -L "$command_link" ] && [ "$desktop_owned" = yes ] && [ -f "$icon_file" ]; then
		adopted_install_root=yes
		printf '%s\n' "$owner_id" >"$install_root/.linuxcapable-owner"
		chmod 0644 "$install_root/.linuxcapable-owner"
	else
		echo "$install_root does not carry the expected LinuxCapable owner marker. Move it before rerunning this helper." >&2
		exit 1
	fi
elif [ -L "$command_link" ]; then
	echo "$command_link exists but $install_root is missing. Move the stale link before rerunning this helper." >&2
	exit 1
fi

if [ -e "$previous_root" ] || [ -L "$previous_root" ]; then
	if is_managed_root "$previous_root"; then
		:
	elif is_legacy_root "$previous_root" &&
		is_managed_root "$install_root" &&
		[ -L "$command_link" ] && [ "$desktop_owned" = yes ] && [ -f "$icon_file" ]; then
		adopted_previous_root=yes
		printf '%s\n' "$owner_id" >"$previous_root/.linuxcapable-owner"
		chmod 0644 "$previous_root/.linuxcapable-owner"
	else
		echo "$previous_root is not an ownership-marked rollback. Move it before rerunning this helper." >&2
		exit 1
	fi
fi

for managed_root in "$install_root" "$previous_root"; do
	if [ -e "$managed_root/$transaction_file" ] || [ -L "$managed_root/$transaction_file" ]; then
		echo "A prior Antigravity transaction marker remains under $managed_root. Preserve the current and previous roots for manual recovery before rerunning this helper." >&2
		exit 1
	fi
done

shopt -s nullglob
recovery_paths=(/opt/.antigravity-stage.* /opt/.antigravity-previous-save.*)
shopt -u nullglob
for recovery_path in "${recovery_paths[@]}"; do
	echo "Prior Antigravity transaction state requires manual recovery: $recovery_path" >&2
	exit 1
done
unset recovery_paths recovery_path

work_dir=$(mktemp -d /var/tmp/antigravity.XXXXXX)
mkdir -m 0700 "$work_dir/backups"
download_html="$work_dir/download.html"
archive="$work_dir/Antigravity.tar.gz"
icon_staged="$work_dir/antigravity.png"
desktop_staged="$work_dir/antigravity.desktop"

curl -fsSL --proto '=https' --proto-redir '=https' --compressed --retry 3 -o "$download_html" "$download_page"
download_fields=$(
	python3 - "$download_html" "$download_page" "$platform" <<'PY'
import re
import sys
from html.parser import HTMLParser
from pathlib import Path
from urllib.parse import urljoin

class LinkParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.hrefs = []

    def handle_starttag(self, tag, attrs):
        if tag != "a":
            return
        href = dict(attrs).get("href")
        if href:
            self.hrefs.append(href)

html = Path(sys.argv[1]).read_text(errors="replace")
page_url = sys.argv[2]
platform = sys.argv[3]
parser = LinkParser()
parser.feed(html)
pattern = re.compile(
    r"https://storage\.googleapis\.com/antigravity-public/antigravity-hub/"
    r"(([0-9]+\.[0-9]+\.[0-9]+)-[0-9]+)/"
    + re.escape(platform)
    + r"/Antigravity\.tar\.gz"
)
matches = []
for href in parser.hrefs:
    url = urljoin(page_url, href)
    match = pattern.fullmatch(url)
    if match and url not in {item[2] for item in matches}:
        matches.append((match.group(2), match.group(1), url))

if len(matches) != 1:
    raise SystemExit(f"Could not find exactly one download for {platform}")

print(*matches[0], sep="\t")
PY
)
IFS=$'\t' read -r version release_id download_url <<<"$download_fields"

if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
	[[ ! "$release_id" =~ ^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$ ]] ||
	[ "${release_id%-*}" != "$version" ] ||
	[ "$download_url" != "https://storage.googleapis.com/antigravity-public/antigravity-hub/$release_id/$platform/Antigravity.tar.gz" ]; then
	echo "Could not parse the Antigravity download page." >&2
	exit 1
fi

if is_managed_root "$install_root" &&
	[ "$(cat "$install_root/.linuxcapable-version" 2>/dev/null || true)" = "$version" ] &&
	[ -f "$install_root/$release_file" ] && [ ! -L "$install_root/$release_file" ] &&
	[ "$(cat "$install_root/$release_file")" = "$release_id" ] &&
	[ -f "$expected_target" ] && [ ! -L "$expected_target" ] && [ -x "$expected_target" ] &&
	[ -L "$command_link" ] && [ "$(readlink "$command_link")" = "$expected_target" ] &&
	is_expected_desktop "$desktop_file" &&
	is_png_file "$icon_file"; then
	printf 'Antigravity %s is already installed at %s\n' "$version" "$install_root/$expected_top_dir"
	exit 0
fi

printf 'Downloading Antigravity %s for %s...\n' "$version" "$platform"
curl -fsSL --proto '=https' --proto-redir '=https' --retry 3 -o "$archive" "$download_url"
stage_dir=$(mktemp -d /opt/.antigravity-stage.XXXXXX)

python3 - "$archive" "$stage_dir" "$expected_top_dir" <<'PY'
import os
import sys
import tarfile
from pathlib import Path, PurePosixPath

archive_path = Path(sys.argv[1])
destination = Path(sys.argv[2])
expected_top = sys.argv[3]


def normalize(parts):
    output = []
    for part in parts:
        if part in ("", "."):
            continue
        if part == "..":
            if not output:
                return None
            output.pop()
        else:
            output.append(part)
    return output


launcher_seen = False
with tarfile.open(archive_path, "r:gz") as bundle:
    members = bundle.getmembers()
    if not members:
        raise SystemExit("The Antigravity archive is empty")

    for member in members:
        raw_path = PurePosixPath(member.name)
        if raw_path.is_absolute() or ".." in raw_path.parts:
            raise SystemExit(f"Unsafe archive path: {member.name}")
        member_parts = normalize(raw_path.parts)
        if not member_parts or member_parts[0] != expected_top:
            raise SystemExit(f"Unexpected archive path: {member.name}")
        if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):
            raise SystemExit(f"Unsupported archive member: {member.name}")

        if member.issym() or member.islnk():
            link_path = PurePosixPath(member.linkname)
            if link_path.is_absolute():
                raise SystemExit(f"Unsafe archive link: {member.name} -> {member.linkname}")
            link_base = member_parts[:-1] if member.issym() else []
            target_parts = normalize([*link_base, *link_path.parts])
            if not target_parts or target_parts[0] != expected_top:
                raise SystemExit(f"Out-of-tree archive link: {member.name} -> {member.linkname}")

        if member_parts == [expected_top, "antigravity"]:
            if not member.isfile():
                raise SystemExit("The Antigravity launcher is not a regular archive file")
            launcher_seen = True

    if not launcher_seen:
        raise SystemExit("The Antigravity launcher was not found in the archive")
    bundle.extractall(destination, members=members, filter="data")

root = (destination / expected_top).resolve(strict=True)
launcher = destination / expected_top / "antigravity"
if launcher.is_symlink() or not launcher.is_file() or not os.access(launcher, os.X_OK):
    raise SystemExit("The extracted Antigravity launcher is not a regular executable")
launcher.resolve(strict=True).relative_to(root)
PY

python3 - "$stage_dir/$expected_top_dir/resources/app.asar" "$icon_staged" <<'PY'
import json
import struct
import sys
from pathlib import Path

asar = Path(sys.argv[1])
output = Path(sys.argv[2])
if asar.is_symlink() or not asar.is_file():
    raise SystemExit("The Antigravity app.asar file is not a regular file")

file_size = asar.stat().st_size
with asar.open("rb") as source:
    source.read(4)
    header_size = struct.unpack("<I", source.read(4))[0]
    source.read(4)
    json_size = struct.unpack("<I", source.read(4))[0]
    if header_size < 8 or json_size > header_size or 8 + header_size > file_size:
        raise SystemExit("The Antigravity app.asar header is invalid")
    header = json.loads(source.read(json_size).decode())

try:
    icon = header["files"]["icon.png"]
    icon_offset = int(icon["offset"])
    icon_size = int(icon["size"])
except (KeyError, TypeError, ValueError) as exc:
    raise SystemExit("icon.png was not found in the Antigravity bundle") from exc

data_offset = 8 + header_size + icon_offset
if icon_offset < 0 or icon_size <= 0 or data_offset + icon_size > file_size:
    raise SystemExit("The Antigravity icon entry is invalid")

with asar.open("rb") as source:
    source.seek(data_offset)
    data = source.read(icon_size)
if not data.startswith(b"\x89PNG\r\n\x1a\n"):
    raise SystemExit("The extracted Antigravity icon is not a PNG file")
output.write_bytes(data)
PY
is_png_file "$icon_staged"

printf '%s\n' "$owner_id" >"$stage_dir/.linuxcapable-owner"
printf '%s\n' "$version" >"$stage_dir/.linuxcapable-version"
printf '%s\n' "$release_id" >"$stage_dir/$release_file"
transaction_id="${stage_dir##*.}.$$"
printf '%s\n' "$transaction_id" >"$stage_dir/$transaction_file"
chmod 0644 "$stage_dir/.linuxcapable-owner" "$stage_dir/.linuxcapable-version" "$stage_dir/$release_file" "$stage_dir/$transaction_file"
chmod 0755 "$stage_dir" "$stage_dir/$expected_top_dir"

cat >"$desktop_staged" <<DESKTOP
[Desktop Entry]
Name=Antigravity
Comment=Google Antigravity 2.0 agent platform
Exec=$command_link %U
Icon=antigravity
Terminal=false
Type=Application
Categories=Development;IDE;
StartupNotify=true
StartupWMClass=Antigravity
X-LinuxCapable-Owner=$owner_id
DESKTOP
desktop-file-validate "$desktop_staged"

backup_item "$command_link" command-link
backup_item "$desktop_file" desktop-file
backup_item "$icon_file" icon-file
backup_item "$old_icon_file" old-icon-file

if path_exists "$install_root"; then
	if ! is_managed_root "$install_root"; then
		echo "$install_root lost its ownership marker before promotion." >&2
		exit 1
	fi
	had_current=yes
fi

if path_exists "$previous_root"; then
	if ! is_managed_root "$previous_root"; then
		echo "$previous_root lost its ownership marker before promotion." >&2
		exit 1
	fi
	had_previous=yes
	saved_previous=$(mktemp -d /opt/.antigravity-previous-save.XXXXXX)
	rmdir -- "$saved_previous"
fi

transaction_started=yes
if [ "$had_previous" = yes ]; then
	mv -- "$previous_root" "$saved_previous"
fi
if [ "$had_current" = yes ]; then
	mv -- "$install_root" "$previous_root"
fi

mv -- "$stage_dir" "$install_root"
stage_dir=""
ln -sfn -- "$expected_target" "$command_link"
install -D -m 0644 "$desktop_staged" "$desktop_file"
install -D -m 0644 "$icon_staged" "$icon_file"
rm -f -- "$old_icon_file"

if command -v restorecon >/dev/null 2>&1; then
	restorecon -R "$install_root"
	restorecon "$command_link" "$desktop_file" "$icon_file"
fi

if command -v update-desktop-database >/dev/null 2>&1; then
	update-desktop-database /usr/share/applications >/dev/null
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
	gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null
fi

is_managed_root "$install_root"
[ -f "$expected_target" ] && [ ! -L "$expected_target" ] && [ -x "$expected_target" ]
[ -L "$command_link" ] && [ "$(readlink -f "$command_link")" = "$expected_target" ]
desktop-file-validate "$desktop_file"
grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$desktop_file"
is_png_file "$icon_file"

if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != root ]; then
	runuser -u "$SUDO_USER" -- test -x "$expected_target"
fi

committed=yes
finalize_commit
printf 'Installed Antigravity %s at %s\n' "$version" "$install_root/$expected_top_dir"
HELPER

bash -n "$helper_source"
helper_digest=$(
	python3 - "$helper_source" <<'PY'
import hashlib
import sys
from pathlib import Path

print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest())
PY
)
sudo bash -s -- "$helper_source" "$helper_path" "$helper_lock" "$helper_digest" <<'ROOT_INSTALL'
set -euo pipefail

source_file=$1
helper_path=$2
lock_file=$3
expected_digest=$4
staged_helper=""

cleanup_helper_install() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$staged_helper" ]; then rm -f -- "$staged_helper"; fi
	exit "$status"
}

trap cleanup_helper_install EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

exec 9>"$lock_file"
if ! flock -n 9; then
	echo "The Antigravity desktop helper or removal command is already running." >&2
	exit 1
fi

if [ -L "$helper_path" ] || { [ -e "$helper_path" ] && [ ! -f "$helper_path" ]; }; then
	echo "$helper_path is not a regular file. Move it before continuing." >&2
	exit 1
elif [ -f "$helper_path" ]; then
	if grep -Fqx '# LinuxCapable owner: linuxcapable-google-antigravity-desktop-helper-v1' "$helper_path"; then
		:
	elif grep -Fqx '#!/usr/bin/env bash' "$helper_path" &&
		grep -Fqx 'download_page="https://antigravity.google/download"' "$helper_path" &&
		grep -Fqx 'install_root="/opt/antigravity"' "$helper_path" &&
		grep -Fqx 'command_link="/usr/local/bin/antigravity"' "$helper_path"; then
		echo "Replacing the earlier LinuxCapable download-page helper."
	else
		echo "$helper_path does not carry a recognized LinuxCapable helper marker. Move it before continuing." >&2
		exit 1
	fi
fi

staged_helper=$(mktemp /usr/local/bin/.update-antigravity.XXXXXX)
python3 - "$source_file" "$staged_helper" "$expected_digest" <<'PY'
import hashlib
import os
import stat
import sys

source, destination, expected = sys.argv[1:]
source_fd = os.open(source, os.O_RDONLY | os.O_NOFOLLOW)
try:
    source_stat = os.fstat(source_fd)
    if not stat.S_ISREG(source_stat.st_mode):
        raise SystemExit("The staged helper source is not a regular file")
    chunks = []
    while True:
        chunk = os.read(source_fd, 65536)
        if not chunk:
            break
        chunks.append(chunk)
finally:
    os.close(source_fd)

data = b"".join(chunks)
if hashlib.sha256(data).hexdigest() != expected:
    raise SystemExit("The staged helper source changed before the root copy")

destination_fd = os.open(destination, os.O_WRONLY | os.O_TRUNC | os.O_NOFOLLOW)
try:
    view = memoryview(data)
    while view:
        written = os.write(destination_fd, view)
        view = view[written:]
finally:
    os.close(destination_fd)
PY
grep -Fqx '# LinuxCapable owner: linuxcapable-google-antigravity-desktop-helper-v1' "$staged_helper"
chown root:root "$staged_helper"
chmod 0755 "$staged_helper"
bash -n "$staged_helper"
mv -fT -- "$staged_helper" "$helper_path"
staged_helper=""
ROOT_INSTALL
)

Install or Update the Desktop App

Run the helper to install or refresh the Antigravity 2.0 desktop app. A successful run prints the installed version and path:

command -v update-antigravity
sudo update-antigravity
/usr/local/bin/update-antigravity
Downloading Antigravity 2.3.1 for linux-x64...
Installed Antigravity 2.3.1 at /opt/antigravity/Antigravity-x64

Run the helper once more to confirm its no-op path:

sudo update-antigravity
Antigravity 2.3.1 is already installed at /opt/antigravity/Antigravity-x64

Verify the Desktop Integration

Verify the launcher path and desktop entry. The resolved directory name can differ by architecture, but it should end with the antigravity executable.

cat /opt/antigravity/.linuxcapable-version
cat /opt/antigravity/.linuxcapable-release
cat /opt/antigravity/.linuxcapable-owner
readlink -f /usr/local/bin/antigravity
test -f "$(readlink -f /usr/local/bin/antigravity)" && test -x "$(readlink -f /usr/local/bin/antigravity)" && echo "Antigravity launcher is installed"
grep -E '^(Name|Exec|Icon|Categories|StartupWMClass)=' /usr/share/applications/antigravity.desktop
sed -n 's/^Directories=//p' /usr/share/icons/hicolor/index.theme | tr ',' '\n' | grep -Fx '512x512/apps'
test -f /usr/share/icons/hicolor/512x512/apps/antigravity.png && echo "Antigravity icon is installed"
2.3.1
2.3.1-5358163105546240
linuxcapable-google-antigravity-desktop-v1
/opt/antigravity/Antigravity-x64/antigravity
Antigravity launcher is installed
Name=Antigravity
Exec=/usr/local/bin/antigravity %U
Icon=antigravity
Categories=Development;IDE;
StartupWMClass=Antigravity
512x512/apps
Antigravity icon is installed

The helper also restores Fedora’s default SELinux labels after copying the tarball payload. The important context types are usr_t for the application directory, desktop file, and icon, and bin_t for the command link:

ls -Zd /opt/antigravity /usr/local/bin/antigravity /usr/share/applications/antigravity.desktop /usr/share/icons/hicolor/512x512/apps/antigravity.png
unconfined_u:object_r:usr_t:s0 /opt/antigravity
unconfined_u:object_r:bin_t:s0 /usr/local/bin/antigravity
unconfined_u:object_r:usr_t:s0 /usr/share/applications/antigravity.desktop
system_u:object_r:usr_t:s0 /usr/share/icons/hicolor/512x512/apps/antigravity.png

Option 2: Install Antigravity IDE on Fedora

Install the IDE Prerequisites

The current Antigravity IDE is a separate 2.x editor build from Google’s download page. It uses a Linux tarball instead of the legacy RPM repo, so install it with its own helper, command name, desktop file, and icon.

Install the helper prerequisites if they are not already present:

sudo dnf install curl desktop-file-utils python3 util-linux

Save the IDE Update Helper

The IDE helper reads Google’s download page, selects the Linux x64 or ARM64 IDE tarball, installs it under /opt/antigravity-ide/Antigravity-IDE, creates /usr/local/bin/antigravity-ide, installs the upstream icon, and writes a separate Fedora launcher. The normalized directory avoids a space in the managed executable path.

Paste the entire block beginning with (. It stages and syntax-checks the IDE helper, acquires the same lock used by updates, and atomically replaces only a recognized LinuxCapable helper.

(
set -euo pipefail

helper_path="/usr/local/bin/update-antigravity-ide"
helper_lock="/run/lock/linuxcapable-antigravity-ide.lock"
helper_source=""

# shellcheck disable=SC2329
cleanup_helper_source() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$helper_source" ]; then rm -f -- "$helper_source"; fi
	exit "$status"
}

trap cleanup_helper_source EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

helper_source=$(mktemp "${TMPDIR:-/tmp}/update-antigravity-ide.XXXXXX")
cat >"$helper_source" <<'HELPER'
#!/usr/bin/env bash
# LinuxCapable owner: linuxcapable-google-antigravity-ide-helper-v1
set -Eeuo pipefail

if [ "$(id -u)" -ne 0 ]; then
	echo "Run with sudo: sudo update-antigravity-ide" >&2
	exit 1
fi

download_page="https://antigravity.google/download"
install_root="/opt/antigravity-ide"
previous_root="${install_root}.previous"
command_link="/usr/local/bin/antigravity-ide"
desktop_file="/usr/share/applications/antigravity-ide.desktop"
icon_file="/usr/share/icons/hicolor/512x512/apps/antigravity-ide.png"
owner_id="linuxcapable-google-antigravity-ide-v1"
archive_top_dir="Antigravity IDE"
installed_top_dir="Antigravity-IDE"

case "$(uname -m)" in
x86_64 | amd64)
	platform="linux-x64"
	;;
aarch64 | arm64)
	platform="linux-arm"
	;;
*)
	echo "Unsupported architecture: $(uname -m)" >&2
	exit 1
	;;
esac

expected_target="$install_root/$installed_top_dir/antigravity-ide"
legacy_expected_target="$install_root/$archive_top_dir/antigravity-ide"

for required_command in curl desktop-file-validate flock python3; do
	if ! command -v "$required_command" >/dev/null 2>&1; then
		echo "$required_command is required to install Antigravity IDE." >&2
		exit 1
	fi
done

exec 9>/run/lock/linuxcapable-antigravity-ide.lock
if ! flock -n 9; then
	echo "Another Antigravity IDE helper is already running." >&2
	exit 1
fi

work_dir=""
stage_dir=""
saved_previous=""
transaction_started="no"
had_current="no"
had_previous="no"
committed="no"
transaction_id=""
transaction_file=".linuxcapable-transaction"
release_file=".linuxcapable-release"
adopted_install_root="no"
adopted_previous_root="no"

path_exists() {
	[ -e "$1" ] || [ -L "$1" ]
}

is_managed_root() {
	local path=$1
	[ -d "$path" ] && [ ! -L "$path" ] &&
		[ -f "$path/.linuxcapable-owner" ] && [ ! -L "$path/.linuxcapable-owner" ] &&
		[ "$(cat "$path/.linuxcapable-owner")" = "$owner_id" ] &&
		[ -f "$path/.linuxcapable-version" ] && [ ! -L "$path/.linuxcapable-version" ] &&
		{ ! path_exists "$path/$release_file" ||
			{ [ -f "$path/$release_file" ] && [ ! -L "$path/$release_file" ]; }; }
}

is_legacy_root() {
	local path=$1
	[ -d "$path" ] && [ ! -L "$path" ] &&
		[ -f "$path/.linuxcapable-version" ] && [ ! -L "$path/.linuxcapable-version" ] &&
		! path_exists "$path/.linuxcapable-owner" &&
		! path_exists "$path/$release_file" &&
		[ -f "$path/$archive_top_dir/antigravity-ide" ] &&
		[ ! -L "$path/$archive_top_dir/antigravity-ide" ] &&
		[ -x "$path/$archive_top_dir/antigravity-ide" ]
}

is_transaction_root() {
	local path=$1
	is_managed_root "$path" &&
		[ -f "$path/$transaction_file" ] && [ ! -L "$path/$transaction_file" ] &&
		[ "$(cat "$path/$transaction_file")" = "$transaction_id" ]
}

remove_adopted_owner() {
	local path=$1
	local launcher="$path/$archive_top_dir/antigravity-ide"
	if ! path_exists "$path/.linuxcapable-owner"; then
		return 0
	fi
	if [ -d "$path" ] && [ ! -L "$path" ] &&
		[ -f "$path/.linuxcapable-owner" ] && [ ! -L "$path/.linuxcapable-owner" ] &&
		[ -f "$path/.linuxcapable-version" ] && [ ! -L "$path/.linuxcapable-version" ] &&
		! path_exists "$path/$release_file" &&
		[ -f "$launcher" ] && [ ! -L "$launcher" ] && [ -x "$launcher" ]; then
		rm -f -- "$path/.linuxcapable-owner"
	else
		echo "Could not safely roll back the temporary owner marker under $path." >&2
		return 1
	fi
}

is_expected_desktop() {
	local path=$1
	[ -f "$path" ] && [ ! -L "$path" ] &&
		desktop-file-validate "$path" >/dev/null 2>&1 &&
		grep -Fqx 'Name=Antigravity IDE' "$path" &&
		grep -Fqx 'Comment=Google Antigravity IDE' "$path" &&
		grep -Fqx "Exec=$command_link %U" "$path" &&
		grep -Fqx 'Icon=antigravity-ide' "$path" &&
		grep -Fqx 'Terminal=false' "$path" &&
		grep -Fqx 'Type=Application' "$path" &&
		grep -Fqx 'Categories=Development;IDE;' "$path" &&
		grep -Fqx 'MimeType=x-scheme-handler/antigravity-ide;application/x-antigravity-workspace;' "$path" &&
		grep -Fqx 'StartupNotify=true' "$path" &&
		grep -Fqx 'StartupWMClass=antigravity-ide' "$path" &&
		grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$path"
}

is_png_file() {
	local path=$1
	[ -f "$path" ] && [ ! -L "$path" ] || return 1
	python3 - "$path" <<'PY'
import struct
import sys
import zlib
from pathlib import Path

path = Path(sys.argv[1])
if path.is_symlink() or not path.is_file():
    raise SystemExit(1)
data = path.read_bytes()
if len(data) < 45 or not data.startswith(b"\x89PNG\r\n\x1a\n"):
    raise SystemExit(1)

offset = 8
chunk_index = 0
seen_ihdr = False
seen_idat = False
seen_iend = False
while offset < len(data):
    if offset + 12 > len(data):
        raise SystemExit(1)
    length = struct.unpack(">I", data[offset : offset + 4])[0]
    chunk_type = data[offset + 4 : offset + 8]
    chunk_end = offset + 12 + length
    if chunk_end > len(data):
        raise SystemExit(1)
    payload = data[offset + 8 : offset + 8 + length]
    expected_crc = struct.unpack(">I", data[offset + 8 + length : chunk_end])[0]
    actual_crc = zlib.crc32(payload, zlib.crc32(chunk_type)) & 0xFFFFFFFF
    if actual_crc != expected_crc:
        raise SystemExit(1)

    if chunk_index == 0:
        if chunk_type != b"IHDR" or length != 13:
            raise SystemExit(1)
        width, height = struct.unpack(">II", payload[:8])
        if width == 0 or height == 0:
            raise SystemExit(1)
        seen_ihdr = True
    elif chunk_type == b"IHDR":
        raise SystemExit(1)

    if chunk_type == b"IDAT":
        seen_idat = True
    elif chunk_type == b"IEND":
        if length != 0 or not seen_ihdr or not seen_idat or chunk_end != len(data):
            raise SystemExit(1)
        seen_iend = True
        offset = chunk_end
        break

    offset = chunk_end
    chunk_index += 1

raise SystemExit(0 if seen_ihdr and seen_idat and seen_iend and offset == len(data) else 1)
PY
}

remove_tracked_temp() {
	local path=$1
	case "$path" in
	/var/tmp/antigravity-ide.* | /opt/.antigravity-ide-stage.*)
		if [ -d "$path" ] && [ ! -L "$path" ]; then
			rm -rf -- "$path"
		fi
		;;
	esac
}

backup_item() {
	local source=$1
	local name=$2
	if [ -e "$source" ] || [ -L "$source" ]; then
		cp -a -- "$source" "$work_dir/backups/$name"
	else
		: >"$work_dir/backups/$name.missing"
	fi
}

restore_item() {
	local destination=$1
	local name=$2
	if ! rm -f -- "$destination"; then return 1; fi
	if [ -e "$work_dir/backups/$name" ] || [ -L "$work_dir/backups/$name" ]; then
		if ! cp -a -- "$work_dir/backups/$name" "$destination"; then return 1; fi
	fi
	return 0
}

finalize_commit() {
	local ok=yes

	if path_exists "$install_root/$transaction_file"; then
		if is_transaction_root "$install_root"; then
			rm -f -- "$install_root/$transaction_file" || ok=no
		else
			echo "Commit stopped: the Antigravity IDE transaction marker changed unexpectedly." >&2
			ok=no
		fi
	elif ! is_managed_root "$install_root"; then
		echo "Commit stopped: $install_root is not the expected managed root." >&2
		ok=no
	fi

	if [ -n "$saved_previous" ] && path_exists "$saved_previous"; then
		if ! is_managed_root "$saved_previous"; then
			echo "Commit preserved an unrecognized rollback at $saved_previous for manual review." >&2
			ok=no
		elif [ "$had_current" = yes ]; then
			rm -rf -- "$saved_previous" || ok=no
		elif [ "$had_previous" = yes ] && ! path_exists "$previous_root"; then
			mv -- "$saved_previous" "$previous_root" || ok=no
		else
			echo "Commit could not place the prior rollback automatically: $saved_previous" >&2
			ok=no
		fi
	fi

	[ "$ok" = yes ]
}

rollback_transaction() {
	local ok=yes

	if is_transaction_root "$install_root"; then
		rm -rf -- "$install_root" || ok=no
	elif path_exists "$install_root"; then
		if [ "$had_current" != yes ] || ! is_managed_root "$install_root"; then
			echo "Rollback stopped at an unexpected live root: $install_root" >&2
			ok=no
		fi
	fi

	if [ "$had_current" = yes ] && ! path_exists "$install_root"; then
		if is_managed_root "$previous_root"; then
			mv -- "$previous_root" "$install_root" || ok=no
		else
			echo "Rollback could not restore the previous live root automatically." >&2
			ok=no
		fi
	fi

	if [ -n "$saved_previous" ] && path_exists "$saved_previous"; then
		if ! path_exists "$previous_root" && is_managed_root "$saved_previous"; then
			mv -- "$saved_previous" "$previous_root" || ok=no
		else
			echo "Prior rollback preserved for manual recovery at $saved_previous" >&2
			ok=no
		fi
	fi

	[ "$ok" = yes ]
}

cleanup() {
	local status=$?
	local rollback_ok=yes
	set +e
	trap - EXIT HUP INT TERM

	if [ "$transaction_started" = yes ]; then
		if [ "$committed" = yes ]; then
			if ! finalize_commit; then rollback_ok=no; fi
		else
			if ! rollback_transaction; then rollback_ok=no; fi

			if [ -n "$work_dir" ] && [ -d "$work_dir/backups" ]; then
				if ! restore_item "$command_link" command-link; then rollback_ok=no; fi
				if ! restore_item "$desktop_file" desktop-file; then rollback_ok=no; fi
				if ! restore_item "$icon_file" icon-file; then rollback_ok=no; fi
			fi

			if command -v restorecon >/dev/null 2>&1; then
				if [ -e "$install_root" ] && ! restorecon -R "$install_root"; then rollback_ok=no; fi
				if { [ -e "$command_link" ] || [ -L "$command_link" ]; } && ! restorecon "$command_link"; then rollback_ok=no; fi
				if [ -e "$desktop_file" ] && ! restorecon "$desktop_file"; then rollback_ok=no; fi
				if [ -e "$icon_file" ] && ! restorecon "$icon_file"; then rollback_ok=no; fi
			fi

			if command -v update-desktop-database >/dev/null 2>&1 &&
				! update-desktop-database /usr/share/applications >/dev/null; then
				rollback_ok=no
			fi
			if command -v gtk-update-icon-cache >/dev/null 2>&1 &&
				! gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null; then
				rollback_ok=no
			fi
		fi

		if [ "$rollback_ok" != yes ]; then status=1; fi
	fi
	if [ "$transaction_started" = no ] && [ -n "$saved_previous" ] && path_exists "$saved_previous"; then
		if [ -d "$saved_previous" ] && [ ! -L "$saved_previous" ] &&
			[ -z "$(find "$saved_previous" -mindepth 1 -print -quit)" ]; then
			if ! rmdir -- "$saved_previous"; then status=1; fi
		else
			echo "Preserving unexpected rollback-reservation state at $saved_previous" >&2
			status=1
		fi
	fi

	if [ "$committed" != yes ]; then
		if [ "$adopted_install_root" = yes ] && ! remove_adopted_owner "$install_root"; then rollback_ok=no; fi
		if [ "$adopted_previous_root" = yes ] && ! remove_adopted_owner "$previous_root"; then rollback_ok=no; fi
		if [ "$rollback_ok" != yes ]; then status=1; fi
	fi

	if [ -n "$stage_dir" ]; then
		if ! remove_tracked_temp "$stage_dir"; then status=1; fi
	fi
	if [ -n "$work_dir" ]; then
		if ! remove_tracked_temp "$work_dir"; then status=1; fi
	fi
	exit "$status"
}

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

desktop_owned=no
if [ -L "$desktop_file" ] || { [ -e "$desktop_file" ] && [ ! -f "$desktop_file" ]; }; then
	echo "$desktop_file is not a regular file. Move it before rerunning this helper." >&2
	exit 1
elif [ -f "$desktop_file" ]; then
	if grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$desktop_file"; then
		desktop_owned=yes
	elif grep -Fqx 'Exec=/usr/local/bin/antigravity-ide %U' "$desktop_file" &&
		grep -Fqx 'Icon=antigravity-ide' "$desktop_file" &&
		grep -Fqx 'StartupWMClass=antigravity-ide' "$desktop_file"; then
		desktop_owned=yes
	else
		echo "$desktop_file does not carry the expected LinuxCapable owner marker. Move it before rerunning this helper." >&2
		exit 1
	fi
fi

for managed_icon in "$icon_file"; do
	if [ -L "$managed_icon" ] || { [ -e "$managed_icon" ] && [ ! -f "$managed_icon" ]; }; then
		echo "$managed_icon is not a regular file. Move it before rerunning this helper." >&2
		exit 1
	elif [ -f "$managed_icon" ] && [ "$desktop_owned" != yes ]; then
		echo "$managed_icon exists without a LinuxCapable-managed desktop entry. Move it before rerunning this helper." >&2
		exit 1
	fi
done

if [ -L "$command_link" ]; then
	command_target=$(readlink "$command_link")
	if [ "$command_target" != "$expected_target" ] && [ "$command_target" != "$legacy_expected_target" ]; then
		echo "$command_link points somewhere else. Move it before rerunning this helper." >&2
		exit 1
	fi
elif [ -e "$command_link" ]; then
	echo "$command_link exists and is not a symlink. Move it before rerunning this helper." >&2
	exit 1
fi

if [ -e "$install_root" ] || [ -L "$install_root" ]; then
	if is_managed_root "$install_root"; then
		:
	elif is_legacy_root "$install_root" && [ -L "$command_link" ] && [ "$(readlink "$command_link")" = "$legacy_expected_target" ] && [ "$desktop_owned" = yes ] && [ -f "$icon_file" ]; then
		adopted_install_root=yes
		printf '%s\n' "$owner_id" >"$install_root/.linuxcapable-owner"
		chmod 0644 "$install_root/.linuxcapable-owner"
	else
		echo "$install_root does not carry the expected LinuxCapable owner marker. Move it before rerunning this helper." >&2
		exit 1
	fi
elif [ -L "$command_link" ]; then
	echo "$command_link exists but $install_root is missing. Move the stale link before rerunning this helper." >&2
	exit 1
fi

if [ -e "$previous_root" ] || [ -L "$previous_root" ]; then
	if is_managed_root "$previous_root"; then
		:
	elif is_legacy_root "$previous_root" &&
		is_managed_root "$install_root" &&
		[ -L "$command_link" ] && [ "$desktop_owned" = yes ] && [ -f "$icon_file" ]; then
		adopted_previous_root=yes
		printf '%s\n' "$owner_id" >"$previous_root/.linuxcapable-owner"
		chmod 0644 "$previous_root/.linuxcapable-owner"
	else
		echo "$previous_root is not an ownership-marked rollback. Move it before rerunning this helper." >&2
		exit 1
	fi
fi

for managed_root in "$install_root" "$previous_root"; do
	if [ -e "$managed_root/$transaction_file" ] || [ -L "$managed_root/$transaction_file" ]; then
		echo "A prior Antigravity IDE transaction marker remains under $managed_root. Preserve the current and previous roots for manual recovery before rerunning this helper." >&2
		exit 1
	fi
done

shopt -s nullglob
recovery_paths=(/opt/.antigravity-ide-stage.* /opt/.antigravity-ide-previous-save.*)
shopt -u nullglob
for recovery_path in "${recovery_paths[@]}"; do
	echo "Prior Antigravity IDE transaction state requires manual recovery: $recovery_path" >&2
	exit 1
done
unset recovery_paths recovery_path

work_dir=$(mktemp -d /var/tmp/antigravity-ide.XXXXXX)
mkdir -m 0700 "$work_dir/backups"
download_html="$work_dir/download.html"
archive="$work_dir/Antigravity-IDE.tar.gz"
icon_staged="$work_dir/antigravity-ide.png"
desktop_staged="$work_dir/antigravity-ide.desktop"

curl -fsSL --proto '=https' --proto-redir '=https' --compressed --retry 3 -o "$download_html" "$download_page"
download_fields=$(
	python3 - "$download_html" "$download_page" "$platform" <<'PY'
import re
import sys
from html.parser import HTMLParser
from pathlib import Path
from urllib.parse import urljoin

class LinkParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.hrefs = []

    def handle_starttag(self, tag, attrs):
        if tag != "a":
            return
        href = dict(attrs).get("href")
        if href:
            self.hrefs.append(href)

html = Path(sys.argv[1]).read_text(errors="replace")
page_url = sys.argv[2]
platform = sys.argv[3]
parser = LinkParser()
parser.feed(html)
pattern = re.compile(
    r"https://edgedl\.me\.gvt1\.com/edgedl/release2/j0qc3/antigravity/stable/"
    r"(([0-9]+\.[0-9]+\.[0-9]+)-[0-9]+)/"
    + re.escape(platform)
    + r"/Antigravity%20IDE\.tar\.gz"
)
matches = []
for href in parser.hrefs:
    url = urljoin(page_url, href)
    match = pattern.fullmatch(url)
    if match and url not in {item[2] for item in matches}:
        matches.append((match.group(2), match.group(1), url))

if len(matches) != 1:
    raise SystemExit(f"Could not find exactly one IDE download for {platform}")

print(*matches[0], sep="\t")
PY
)
IFS=$'\t' read -r version release_id download_url <<<"$download_fields"

if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
	[[ ! "$release_id" =~ ^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$ ]] ||
	[ "${release_id%-*}" != "$version" ] ||
	[ "$download_url" != "https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/$release_id/$platform/Antigravity%20IDE.tar.gz" ]; then
	echo "Could not parse the Antigravity IDE download page." >&2
	exit 1
fi

if is_managed_root "$install_root" &&
	[ "$(cat "$install_root/.linuxcapable-version" 2>/dev/null || true)" = "$version" ] &&
	[ -f "$install_root/$release_file" ] && [ ! -L "$install_root/$release_file" ] &&
	[ "$(cat "$install_root/$release_file")" = "$release_id" ] &&
	[ -f "$expected_target" ] && [ ! -L "$expected_target" ] && [ -x "$expected_target" ] &&
	[ -L "$command_link" ] && [ "$(readlink "$command_link")" = "$expected_target" ] &&
	is_expected_desktop "$desktop_file" &&
	is_png_file "$icon_file"; then
	printf 'Antigravity IDE %s is already installed at %s\n' "$version" "$install_root/$installed_top_dir"
	exit 0
fi

printf 'Downloading Antigravity IDE %s for %s...\n' "$version" "$platform"
curl -fsSL --proto '=https' --proto-redir '=https' --retry 3 -o "$archive" "$download_url"
stage_dir=$(mktemp -d /opt/.antigravity-ide-stage.XXXXXX)

python3 - "$archive" "$stage_dir" "$archive_top_dir" <<'PY'
import os
import sys
import tarfile
from pathlib import Path, PurePosixPath

archive_path = Path(sys.argv[1])
destination = Path(sys.argv[2])
expected_top = sys.argv[3]


def normalize(parts):
    output = []
    for part in parts:
        if part in ("", "."):
            continue
        if part == "..":
            if not output:
                return None
            output.pop()
        else:
            output.append(part)
    return output


launcher_seen = False
with tarfile.open(archive_path, "r:gz") as bundle:
    members = bundle.getmembers()
    if not members:
        raise SystemExit("The Antigravity archive is empty")

    for member in members:
        raw_path = PurePosixPath(member.name)
        if raw_path.is_absolute() or ".." in raw_path.parts:
            raise SystemExit(f"Unsafe archive path: {member.name}")
        member_parts = normalize(raw_path.parts)
        if not member_parts or member_parts[0] != expected_top:
            raise SystemExit(f"Unexpected archive path: {member.name}")
        if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):
            raise SystemExit(f"Unsupported archive member: {member.name}")

        if member.issym() or member.islnk():
            link_path = PurePosixPath(member.linkname)
            if link_path.is_absolute():
                raise SystemExit(f"Unsafe archive link: {member.name} -> {member.linkname}")
            link_base = member_parts[:-1] if member.issym() else []
            target_parts = normalize([*link_base, *link_path.parts])
            if not target_parts or target_parts[0] != expected_top:
                raise SystemExit(f"Out-of-tree archive link: {member.name} -> {member.linkname}")

        if member_parts == [expected_top, "antigravity-ide"]:
            if not member.isfile():
                raise SystemExit("The Antigravity IDE launcher is not a regular archive file")
            launcher_seen = True

    if not launcher_seen:
        raise SystemExit("The Antigravity IDE launcher was not found in the archive")
    bundle.extractall(destination, members=members, filter="data")

root = (destination / expected_top).resolve(strict=True)
launcher = destination / expected_top / "antigravity-ide"
if launcher.is_symlink() or not launcher.is_file() or not os.access(launcher, os.X_OK):
    raise SystemExit("The extracted Antigravity IDE launcher is not a regular executable")
launcher.resolve(strict=True).relative_to(root)
PY

mv -- "$stage_dir/$archive_top_dir" "$stage_dir/$installed_top_dir"
python3 - "$stage_dir/$installed_top_dir" "$icon_staged" <<'PY'
import sys
from pathlib import Path

root = Path(sys.argv[1]).resolve(strict=True)
icon = root / "resources/app/resources/linux/code.png"
output = Path(sys.argv[2])
if icon.is_symlink() or not icon.is_file():
    raise SystemExit("The Antigravity IDE icon is not a regular file")
icon.resolve(strict=True).relative_to(root)
data = icon.read_bytes()
if not data.startswith(b"\x89PNG\r\n\x1a\n"):
    raise SystemExit("The Antigravity IDE icon is not a PNG file")
output.write_bytes(data)
PY
is_png_file "$icon_staged"

printf '%s\n' "$owner_id" >"$stage_dir/.linuxcapable-owner"
printf '%s\n' "$version" >"$stage_dir/.linuxcapable-version"
printf '%s\n' "$release_id" >"$stage_dir/$release_file"
transaction_id="${stage_dir##*.}.$$"
printf '%s\n' "$transaction_id" >"$stage_dir/$transaction_file"
chmod 0644 "$stage_dir/.linuxcapable-owner" "$stage_dir/.linuxcapable-version" "$stage_dir/$release_file" "$stage_dir/$transaction_file"
chmod 0755 "$stage_dir" "$stage_dir/$installed_top_dir"

cat >"$desktop_staged" <<DESKTOP
[Desktop Entry]
Name=Antigravity IDE
Comment=Google Antigravity IDE
Exec=$command_link %U
Icon=antigravity-ide
Terminal=false
Type=Application
Categories=Development;IDE;
MimeType=x-scheme-handler/antigravity-ide;application/x-antigravity-workspace;
StartupNotify=true
StartupWMClass=antigravity-ide
X-LinuxCapable-Owner=$owner_id
DESKTOP
desktop-file-validate "$desktop_staged"

backup_item "$command_link" command-link
backup_item "$desktop_file" desktop-file
backup_item "$icon_file" icon-file

if path_exists "$install_root"; then
	if ! is_managed_root "$install_root"; then
		echo "$install_root lost its ownership marker before promotion." >&2
		exit 1
	fi
	had_current=yes
fi

if path_exists "$previous_root"; then
	if ! is_managed_root "$previous_root"; then
		echo "$previous_root lost its ownership marker before promotion." >&2
		exit 1
	fi
	had_previous=yes
	saved_previous=$(mktemp -d /opt/.antigravity-ide-previous-save.XXXXXX)
	rmdir -- "$saved_previous"
fi

transaction_started=yes
if [ "$had_previous" = yes ]; then
	mv -- "$previous_root" "$saved_previous"
fi
if [ "$had_current" = yes ]; then
	mv -- "$install_root" "$previous_root"
fi

mv -- "$stage_dir" "$install_root"
stage_dir=""
ln -sfn -- "$expected_target" "$command_link"
install -D -m 0644 "$desktop_staged" "$desktop_file"
install -D -m 0644 "$icon_staged" "$icon_file"

if command -v restorecon >/dev/null 2>&1; then
	restorecon -R "$install_root"
	restorecon "$command_link" "$desktop_file" "$icon_file"
fi

if command -v update-desktop-database >/dev/null 2>&1; then
	update-desktop-database /usr/share/applications >/dev/null
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
	gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null
fi

is_managed_root "$install_root"
[ -f "$expected_target" ] && [ ! -L "$expected_target" ] && [ -x "$expected_target" ]
[ -L "$command_link" ] && [ "$(readlink -f "$command_link")" = "$expected_target" ]
desktop-file-validate "$desktop_file"
grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$desktop_file"
is_png_file "$icon_file"

if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != root ]; then
	runuser -u "$SUDO_USER" -- test -x "$expected_target"
fi

committed=yes
finalize_commit
printf 'Installed Antigravity IDE %s at %s\n' "$version" "$install_root/$installed_top_dir"
HELPER

bash -n "$helper_source"
helper_digest=$(
	python3 - "$helper_source" <<'PY'
import hashlib
import sys
from pathlib import Path

print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest())
PY
)
sudo bash -s -- "$helper_source" "$helper_path" "$helper_lock" "$helper_digest" <<'ROOT_INSTALL'
set -euo pipefail

source_file=$1
helper_path=$2
lock_file=$3
expected_digest=$4
staged_helper=""

cleanup_helper_install() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$staged_helper" ]; then rm -f -- "$staged_helper"; fi
	exit "$status"
}

trap cleanup_helper_install EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

exec 9>"$lock_file"
if ! flock -n 9; then
	echo "The Antigravity IDE helper or removal command is already running." >&2
	exit 1
fi

if [ -L "$helper_path" ] || { [ -e "$helper_path" ] && [ ! -f "$helper_path" ]; }; then
	echo "$helper_path is not a regular file. Move it before continuing." >&2
	exit 1
elif [ -f "$helper_path" ]; then
	if grep -Fqx '# LinuxCapable owner: linuxcapable-google-antigravity-ide-helper-v1' "$helper_path"; then
		:
	elif grep -Fqx '#!/usr/bin/env bash' "$helper_path" &&
		grep -Fqx 'download_page="https://antigravity.google/download"' "$helper_path" &&
		grep -Fqx 'install_root="/opt/antigravity-ide"' "$helper_path" &&
		grep -Fqx 'command_link="/usr/local/bin/antigravity-ide"' "$helper_path"; then
		echo "Replacing the earlier LinuxCapable Antigravity IDE download-page helper."
	else
		echo "$helper_path does not carry a recognized LinuxCapable helper marker. Move it before continuing." >&2
		exit 1
	fi
fi

staged_helper=$(mktemp /usr/local/bin/.update-antigravity-ide.XXXXXX)
python3 - "$source_file" "$staged_helper" "$expected_digest" <<'PY'
import hashlib
import os
import stat
import sys

source, destination, expected = sys.argv[1:]
source_fd = os.open(source, os.O_RDONLY | os.O_NOFOLLOW)
try:
    source_stat = os.fstat(source_fd)
    if not stat.S_ISREG(source_stat.st_mode):
        raise SystemExit("The staged helper source is not a regular file")
    chunks = []
    while True:
        chunk = os.read(source_fd, 65536)
        if not chunk:
            break
        chunks.append(chunk)
finally:
    os.close(source_fd)

data = b"".join(chunks)
if hashlib.sha256(data).hexdigest() != expected:
    raise SystemExit("The staged helper source changed before the root copy")

destination_fd = os.open(destination, os.O_WRONLY | os.O_TRUNC | os.O_NOFOLLOW)
try:
    view = memoryview(data)
    while view:
        written = os.write(destination_fd, view)
        view = view[written:]
finally:
    os.close(destination_fd)
PY
grep -Fqx '# LinuxCapable owner: linuxcapable-google-antigravity-ide-helper-v1' "$staged_helper"
chown root:root "$staged_helper"
chmod 0755 "$staged_helper"
bash -n "$staged_helper"
mv -fT -- "$staged_helper" "$helper_path"
staged_helper=""
ROOT_INSTALL
)

Install or Update the IDE

Run the helper to install or refresh the current Antigravity IDE. A successful run prints the version and install path:

command -v update-antigravity-ide
sudo update-antigravity-ide
/usr/local/bin/update-antigravity-ide
Downloading Antigravity IDE 2.1.1 for linux-x64...
Installed Antigravity IDE 2.1.1 at /opt/antigravity-ide/Antigravity-IDE

Run the IDE helper once more to confirm its no-op path:

sudo update-antigravity-ide
Antigravity IDE 2.1.1 is already installed at /opt/antigravity-ide/Antigravity-IDE

Verify the IDE Integration

Verify the IDE command, desktop entry, and icon:

cat /opt/antigravity-ide/.linuxcapable-version
cat /opt/antigravity-ide/.linuxcapable-release
cat /opt/antigravity-ide/.linuxcapable-owner
readlink -f /usr/local/bin/antigravity-ide
test -f "$(readlink -f /usr/local/bin/antigravity-ide)" && test -x "$(readlink -f /usr/local/bin/antigravity-ide)" && echo "Antigravity IDE launcher is installed"
grep -E '^(Name|Exec|Icon|Categories|StartupWMClass)=' /usr/share/applications/antigravity-ide.desktop
sed -n 's/^Directories=//p' /usr/share/icons/hicolor/index.theme | tr ',' '\n' | grep -Fx '512x512/apps'
test -f /usr/share/icons/hicolor/512x512/apps/antigravity-ide.png && echo "Antigravity IDE icon is installed"
2.1.1
2.1.1-6123990880747520
linuxcapable-google-antigravity-ide-v1
/opt/antigravity-ide/Antigravity-IDE/antigravity-ide
Antigravity IDE launcher is installed
Name=Antigravity IDE
Exec=/usr/local/bin/antigravity-ide %U
Icon=antigravity-ide
Categories=Development;IDE;
StartupWMClass=antigravity-ide
512x512/apps
Antigravity IDE icon is installed

Option 3: Install Antigravity CLI on Fedora

Install the CLI Prerequisite

The Antigravity CLI is a separate terminal interface named agy. Install curl first, then run the remaining commands from your normal Fedora account:

sudo dnf install curl

Run the Verified CLI Installer

Google’s official CLI installer writes the binary under ~/.local/bin instead of installing a system RPM. Google does not publish a separate checksum or signature beside the script, so the block downloads one HTTPS copy, checks its Bash syntax, runs that saved file, validates the installed command, and records ownership only after those checks pass.

(
set -u

agy_path="$HOME/.local/bin/agy"
owner_dir="$HOME/.local/share/linuxcapable"
owner_file="$owner_dir/antigravity-cli.owner"
owner_id="linuxcapable-google-antigravity-cli-v1"

if [ -e "$agy_path" ] || [ -L "$agy_path" ]; then
  echo "$agy_path already exists; move it before running Google's installer." >&2
  exit 1
elif [ -L "$owner_file" ] || { [ -e "$owner_file" ] && [ ! -f "$owner_file" ]; }; then
  echo "Unexpected owner-record path: $owner_file" >&2
  exit 1
elif [ -f "$owner_file" ] && {
  ! grep -Fqx "owner=$owner_id" "$owner_file" ||
  ! grep -Fqx "path=$agy_path" "$owner_file";
}; then
  echo "Unrecognized owner record: $owner_file" >&2
  exit 1
else
  (
  set -euo pipefail
  installer=""
  owner_stage=""
  install_committed=no

  # shellcheck disable=SC2329
  cleanup_cli_install() {
    local status=$?
    trap - EXIT HUP INT TERM
    if [ -n "$installer" ]; then rm -f -- "$installer"; fi
    if [ -n "$owner_stage" ]; then rm -f -- "$owner_stage"; fi
    if [ "$install_committed" != yes ]; then
      if [ -L "$agy_path" ] || { [ -e "$agy_path" ] && [ -f "$agy_path" ]; }; then
        rm -f -- "$agy_path"
      elif [ -e "$agy_path" ]; then
        echo "The failed installer left an unexpected path for manual review: $agy_path" >&2
      fi
    fi
    exit "$status"
  }

  trap cleanup_cli_install EXIT
  trap 'exit 129' HUP
  trap 'exit 130' INT
  trap 'exit 143' TERM

  installer=$(mktemp "${TMPDIR:-/tmp}/antigravity-cli-installer.XXXXXX")
  curl -fsSL --proto '=https' --proto-redir '=https' --retry 3 -o "$installer" https://antigravity.google/cli/install.sh
  bash -n "$installer"
  bash "$installer" --skip-path --skip-aliases
  [ -f "$agy_path" ] && [ ! -L "$agy_path" ] && [ -x "$agy_path" ]
  agy_version=$("$agy_path" --version 2>/dev/null || true)
  agy_help=$("$agy_path" --help 2>&1 || true)
  [[ "$agy_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
  grep -Fq 'Usage of agy:' <<<"$agy_help"
  grep -Fq 'Available subcommands:' <<<"$agy_help"

  if [ -L "$owner_dir" ] || { [ -e "$owner_dir" ] && [ ! -d "$owner_dir" ]; }; then
    echo "Unexpected owner-record directory: $owner_dir" >&2
    exit 1
  fi
  mkdir -p -- "$owner_dir"
  owner_stage=$(mktemp "$owner_dir/.antigravity-cli.owner.XXXXXX")
  printf 'owner=%s\npath=%s\n' "$owner_id" "$agy_path" >"$owner_stage"
  chmod 0600 "$owner_stage"
  mv -fT -- "$owner_stage" "$owner_file"
  owner_stage=""
  install_committed=yes
  )
fi
)

Verify the CLI Command

The installer’s --skip-path and --skip-aliases flags prevent untracked profile edits and alias removal. The next block leaves an existing exact Bash PATH line intact or appends its own marked entry, then starts a fresh Bash with a deliberately minimal inherited PATH. This proves a new terminal can select the installed command instead of passing only because the current shell was configured manually:

(
set -euo pipefail
bashrc="$HOME/.bashrc"
path_marker='# LinuxCapable Antigravity CLI PATH'
path_line='export PATH="$HOME/.local/bin:$PATH"'
if [ -L "$bashrc" ] || { [ -e "$bashrc" ] && [ ! -f "$bashrc" ]; }; then
  echo "Unexpected Bash profile path: $bashrc" >&2
  exit 1
fi
if ! grep -Fqx "$path_line" "$bashrc" 2>/dev/null; then
  if grep -Fqx "$path_marker" "$bashrc" 2>/dev/null; then
    echo "The LinuxCapable PATH marker already exists without its expected line; review $bashrc manually." >&2
    exit 1
  fi
  printf '\n%s\n%s\n' "$path_marker" "$path_line" >>"$bashrc"
fi
fresh_output=""
if ! fresh_output=$(
  PATH=/usr/bin:/bin bash --noprofile --rcfile "$bashrc" -ic '
    resolved_agy=$(command -v agy || true)
    if [ "$resolved_agy" != "$HOME/.local/bin/agy" ]; then
      echo "A fresh Bash did not select $HOME/.local/bin/agy." >&2
      exit 1
    fi
    printf "%s\n" "$resolved_agy"
    agy --version
  '
); then
  echo "Fresh-shell CLI verification failed; review the Bash profile and any agy alias or function." >&2
  exit 1
fi
printf '%s\n' "$fresh_output"
)
/home/username/.local/bin/agy
1.1.5

Check the available CLI flags and subcommands after installation. The help includes --dangerously-skip-permissions; do not use that permission-bypass flag for ordinary projects because it auto-approves tool actions.

agy --help 2>&1 | sed -n '1,34p'
Usage of agy:
  --add-dir                       Add a directory to the workspace (repeatable) (default [])
  --agent                         Agent for the current CLI session
  -c                              Short alias for --continue
  --continue                      Continue the most recent conversation
  --conversation                  Resume a previous conversation by ID
  --dangerously-skip-permissions  Auto-approve all tool permission requests without prompting
  -i                              Short alias for --prompt-interactive
  --log-file                      Override CLI log file path
  --mode                          Set the agent execution mode for this session (accept-edits, plan)
  --model                         Model for the current CLI session
  --new-project                   Create a new project for this session
  -p                              Short alias for --print
  --print                         Run a single prompt non-interactively and print the response
  --print-timeout                 Timeout for print mode wait (default 5m0s)
  --project                       Project ID for the current CLI session
  --prompt                        Alias for --print
  --prompt-interactive            Run an initial prompt interactively and continue the session
  --sandbox                       Run in a sandbox with terminal restrictions enabled

Available subcommands:
  agent           List available agents
  agents          List available agents
  changelog       Show changelog and release notes
  help            Show help for subcommands
  install         Configure environment paths and shell settings
  models          List available models
  plugin          Manage plugins (install, uninstall, list, enable, disable)
  plugins         Alias for plugin
  update          Update CLI

Legacy Method: Install Antigravity IDE RPM on Fedora

The legacy repository exposes Antigravity IDE 1.23.2 and sets gpgcheck=0, so Fedora cannot authenticate the package with OpenPGP. Use this method only if you deliberately need the older package-managed IDE and accept that reduced trust boundary.

Create the RPM repository file only if you specifically need the older Antigravity IDE package:

sudo bash <<'EOF'
set -euo pipefail

repo_file="/etc/yum.repos.d/antigravity.repo"
stage_file=""

if [ "$(uname -m)" != x86_64 ]; then
	echo "The current legacy repository exposes only an x86_64 package." >&2
	exit 1
fi
for current_path in /opt/antigravity /usr/local/bin/update-antigravity /usr/local/bin/antigravity; do
	if [ -e "$current_path" ] || [ -L "$current_path" ]; then
		echo "Remove the current desktop-method path before enabling the conflicting legacy RPM: $current_path" >&2
		exit 1
	fi
done
for rpm_path in /usr/bin/antigravity /usr/share/applications/antigravity.desktop; do
	if [ -e "$rpm_path" ] || [ -L "$rpm_path" ]; then
		rpm_owner=$(rpm -qf --qf '%{NAME}\n' "$rpm_path" 2>/dev/null || true)
		if [ "$rpm_owner" != antigravity ]; then
			echo "Refusing to overwrite a path not owned by the antigravity RPM: $rpm_path" >&2
			exit 1
		fi
	fi
done

cleanup_repo_stage() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$stage_file" ]; then rm -f -- "$stage_file"; fi
	exit "$status"
}
trap cleanup_repo_stage EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

if [ -L "$repo_file" ] || { [ -e "$repo_file" ] && [ ! -f "$repo_file" ]; }; then
	echo "Refusing unexpected repository path: $repo_file" >&2
	exit 1
fi

stage_file=$(mktemp /etc/yum.repos.d/.antigravity.repo.XXXXXX)
cat >"$stage_file" <<'REPO'
[antigravity-rpm]
name=Antigravity RPM Repository
baseurl=https://us-central1-yum.pkg.dev/projects/antigravity-auto-updater-dev/antigravity-rpm
enabled=1
gpgcheck=0
REPO
chmod 0644 "$stage_file"

if [ -f "$repo_file" ]; then
	if cmp -s -- "$stage_file" "$repo_file"; then
		echo "The LinuxCapable-managed Antigravity RPM repository file is already present."
		exit 0
	fi
	echo "Refusing to replace a modified or unrelated repository file: $repo_file" >&2
	exit 1
fi

mv -fT -- "$stage_file" "$repo_file"
stage_file=""
if command -v restorecon >/dev/null 2>&1; then restorecon "$repo_file"; fi
EOF

Refresh that repository and check the newest package visible for your architecture before installing:

(
set -euo pipefail
sudo dnf makecache --refresh --repo antigravity-rpm
dnf repoquery --repo antigravity-rpm --arch="$(uname -m)" --latest-limit=1 antigravity
)
antigravity-0:1.23.2-1776330658.el8.x86_64

If you still want the legacy IDE package after confirming the version, install it with DNF:

sudo dnf install --from-repo=antigravity-rpm antigravity
Warning: skipped OpenPGP checks for 1 package from repository: antigravity-rpm
Complete!

Verify the installed version, architecture, package-owned command, and desktop entry before launch:

rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' antigravity
rpm -qf /usr/bin/antigravity /usr/share/applications/antigravity.desktop
command -v antigravity
grep -E '^(Name|Exec|Icon|Categories|StartupWMClass)=' /usr/share/applications/antigravity.desktop

The legacy RPM owns /usr/bin/antigravity and /usr/share/applications/antigravity.desktop. After those checks pass, launch the older IDE from Activities as Antigravity, or run /usr/bin/antigravity when you intentionally want the RPM-managed 1.x IDE.

Launch Google Antigravity on Fedora

The graphical Antigravity surfaces and the terminal CLI launch separately. Use the Fedora app menu for the desktop app or IDE, and use agy from a terminal when you want the CLI workflow.

The Antigravity 2.0 desktop app and Antigravity IDE need a logged-in graphical session before their interfaces are useful. Fedora Server or a minimal install can hold the files, but the GUI surfaces are meant for a desktop session.

Open Antigravity 2.0 Desktop App from Activities

  1. Open Activities.
  2. Search for Antigravity.
  3. Select the Antigravity launcher to open the app.

A desktop terminal can start the same launcher directly:

antigravity

On first launch, choose a Google account or Google Cloud project if the desktop app asks you to sign in. After sign-in, the main workspace opens with the model picker and conversation input described in Google’s Antigravity 2.0 overview. For a low-risk first check, select a small project folder and ask Antigravity to summarize its files without making changes.

Open Antigravity IDE on Fedora

The IDE helper creates a separate Antigravity IDE launcher for editor-first work.

  1. Open Activities.
  2. Search for Antigravity IDE.
  3. Select the Antigravity IDE launcher to open the editor.

A desktop terminal can start the same IDE launcher directly:

antigravity-ide

The IDE is the right surface when you want normal file editing, tab completion, code commands, and the agent manager in one editor window. Use File > Open Folder to open a small project, select a text file, and ask the agent panel to explain it without editing. Google’s Antigravity IDE product page describes the wider editor and artifact-review workflow. Use the Antigravity 2.0 desktop app instead when you want the standalone project and scheduled-task surface.

Start Antigravity CLI from a Terminal

Run agy from a project directory when you want the terminal interface. The CLI uses the current directory as the main workspace unless you add more paths with --add-dir.

agy

Authenticate Antigravity CLI on Fedora

When no saved session is available, Antigravity CLI uses a browser-backed sign-in flow. On a local Fedora desktop, agy can open the browser sign-in page. In an SSH session, it prints an authorization URL for your local browser and asks you to paste the returned code into the terminal.

To clear saved CLI credentials later, open agy and run /logout from the prompt.

Run First Antigravity CLI Commands

The CLI can run interactively or answer a single non-interactive prompt. This disposable example creates its own small project, runs one read-only prompt, and removes the demo directory when the command finishes:

(
set -euo pipefail
demo_dir=$(mktemp -d "${TMPDIR:-/tmp}/antigravity-cli-demo.XXXXXX")
trap 'rm -rf -- "$demo_dir"' EXIT
printf '%s\n' '# Antigravity CLI demo' 'A disposable read-only workspace.' >"$demo_dir/README.md"
cd "$demo_dir"
agy --print "Summarize this demo project without changing files."
)

Use --prompt-interactive when you want to seed the first message and continue the conversation in the terminal UI:

agy --prompt-interactive "Review this repository and ask before changing files."

To test --add-dir without relying on placeholder paths, create two disposable folders and ask the CLI to inspect both:

(
set -euo pipefail
project_dir=$(mktemp -d "${TMPDIR:-/tmp}/antigravity-project.XXXXXX")
shared_dir=$(mktemp -d "${TMPDIR:-/tmp}/antigravity-shared.XXXXXX")
trap 'rm -rf -- "$project_dir" "$shared_dir"' EXIT
printf '%s\n' 'project file' >"$project_dir/project.txt"
printf '%s\n' 'shared file' >"$shared_dir/shared.txt"
cd "$project_dir"
agy --add-dir "$shared_dir" --prompt-interactive "Describe both files and ask before changing anything."
)

Use the sandbox flag for a session where local command execution should start from stricter containment:

agy --sandbox --print "Run a read-only project health check and report findings."

Use Antigravity CLI Features

Several CLI controls are typed directly into the agy prompt. These are useful after the first launch; Google’s CLI feature documentation covers plugins, sandboxing, slash commands, and subagents in more depth.

CLI controlUse
?Open inline help and available slash commands.
@Trigger path suggestions when referencing files.
!Start a terminal command prompt from inside the CLI.
/config or /settingsOpen the settings panel for behavior, safety, and interface options.
/permissionsSet how much review the agent needs before actions.
/agentsOpen the subagents panel to monitor parallel agent work.
/tasksMonitor, inspect, or stop background tasks.
/skillsBrowse available local and global skills.
/mcpConfigure Model Context Protocol servers.
/resumeResume or switch conversations.
/rewindRoll back conversation history to an earlier point.
/logoutSign out and clear saved CLI credentials.

If this Fedora workstation still needs Git, install Git on Fedora first, then configure Git username and email before you start cloning repositories inside Antigravity.

For Python projects, Antigravity uses the interpreter and virtual environments available in the workspace. If that toolchain is not ready yet, install Python on Fedora before opening the project folder.

Update Google Antigravity on Fedora

Update Antigravity 2.0 Desktop App

The desktop app is installed as a root-owned /opt tree. Close Antigravity, verify the saved helper, run it, and check the locally recorded release before relaunching. Use this helper rather than an in-app update prompt for the root-owned layout:

(
set -euo pipefail
command -v update-antigravity
sudo update-antigravity
cat /opt/antigravity/.linuxcapable-version
cat /opt/antigravity/.linuxcapable-release
sudo update-antigravity
)

When the desktop app is already current, the combined output resembles:

/usr/local/bin/update-antigravity
Antigravity 2.3.1 is already installed at /opt/antigravity/Antigravity-x64
2.3.1
2.3.1-5358163105546240
Antigravity 2.3.1 is already installed at /opt/antigravity/Antigravity-x64

The helper validates the current online download, stages the replacement, and retains the preceding ownership-marked payload at /opt/antigravity.previous. If the installation transaction fails, it restores that payload automatically. The second helper run is a no-op check and should report that the same version is already installed.

Update Antigravity IDE

Close Antigravity IDE before updating its root-owned /opt layout. Verify the helper, run it, inspect the recorded version, and run it a second time to prove the same-version no-op path:

(
set -euo pipefail
command -v update-antigravity-ide
sudo update-antigravity-ide
cat /opt/antigravity-ide/.linuxcapable-version
cat /opt/antigravity-ide/.linuxcapable-release
sudo update-antigravity-ide
)

When the IDE is already current, the combined output resembles:

/usr/local/bin/update-antigravity-ide
Antigravity IDE 2.1.1 is already installed at /opt/antigravity-ide/Antigravity-IDE
2.1.1
2.1.1-6123990880747520
Antigravity IDE 2.1.1 is already installed at /opt/antigravity-ide/Antigravity-IDE

The helper resolves the current online IDE release, retains the preceding ownership-marked payload at /opt/antigravity-ide.previous, and restores it automatically if the installation transaction fails. The second run should report that the same IDE version is already installed.

Update Antigravity CLI

Google documents the same installer as the supported install-or-upgrade path. Run this block from your normal Fedora account; it prechecks and backs up the marked ~/.local/bin/agy binary, prevents untracked profile or alias changes, validates the replacement, and restores the prior binary if the update fails:

(
set -euo pipefail
agy_path="$HOME/.local/bin/agy"
owner_file="$HOME/.local/share/linuxcapable/antigravity-cli.owner"
owner_id="linuxcapable-google-antigravity-cli-v1"
installer=""
binary_backup=""
update_committed=no

# shellcheck disable=SC2329
cleanup_cli_update() {
  local status=$?
  trap - EXIT HUP INT TERM
  if [ "$update_committed" != yes ] && [ -n "$binary_backup" ]; then
    if [ -L "$agy_path" ] || { [ -e "$agy_path" ] && [ ! -f "$agy_path" ]; }; then
      echo "The failed update left an unexpected path at $agy_path." >&2
      echo "The prior binary is preserved for manual recovery at $binary_backup." >&2
      binary_backup=""
      status=1
    elif mv -fT -- "$binary_backup" "$agy_path"; then
      binary_backup=""
      echo "Restored the preceding agy binary after the update failed." >&2
    else
      echo "Automatic rollback failed; the prior binary remains at $binary_backup." >&2
      binary_backup=""
      status=1
    fi
  fi
  if [ -n "$installer" ]; then rm -f -- "$installer" || status=1; fi
  if [ -n "$binary_backup" ]; then rm -f -- "$binary_backup" || status=1; fi
  exit "$status"
}

trap cleanup_cli_update EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

if [ ! -f "$owner_file" ] || [ -L "$owner_file" ] ||
  ! grep -Fqx "owner=$owner_id" "$owner_file" ||
  ! grep -Fqx "path=$agy_path" "$owner_file"; then
  echo "The LinuxCapable CLI owner record is missing or unrecognized; leave the existing command in place." >&2
  exit 1
fi
if [ ! -f "$agy_path" ] || [ -L "$agy_path" ] || [ ! -x "$agy_path" ]; then
  echo "The managed agy path is missing or has an unexpected type: $agy_path" >&2
  exit 1
fi

old_version=$("$agy_path" --version 2>/dev/null || true)
old_help=$("$agy_path" --help 2>&1 || true)
if [[ ! "$old_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
  ! grep -Fq 'Usage of agy:' <<<"$old_help" ||
  ! grep -Fq 'Available subcommands:' <<<"$old_help"; then
  echo "The existing agy command did not pass its identity checks; no update was attempted." >&2
  exit 1
fi

binary_backup=$(mktemp "$HOME/.local/bin/.agy.before-update.XXXXXX")
if ! cp -p -- "$agy_path" "$binary_backup"; then
  rm -f -- "$binary_backup"
  binary_backup=""
  echo "Could not back up the existing agy binary; no update was attempted." >&2
  exit 1
fi
installer=$(mktemp "${TMPDIR:-/tmp}/antigravity-cli-installer.XXXXXX")
curl -fsSL --proto '=https' --proto-redir '=https' --retry 3 -o "$installer" https://antigravity.google/cli/install.sh
bash -n "$installer"
bash "$installer" --skip-path --skip-aliases

if [ ! -f "$agy_path" ] || [ -L "$agy_path" ] || [ ! -x "$agy_path" ]; then
  echo "The updated agy path has an unexpected type; starting rollback." >&2
  exit 1
fi
new_version=$("$agy_path" --version 2>/dev/null || true)
agy_help=$("$agy_path" --help 2>&1 || true)
if [[ ! "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
  ! grep -Fq 'Usage of agy:' <<<"$agy_help" ||
  ! grep -Fq 'Available subcommands:' <<<"$agy_help"; then
  echo "The updated agy command did not pass its identity checks." >&2
  exit 1
fi
update_committed=yes
rm -f -- "$binary_backup"
binary_backup=""
printf 'Antigravity CLI: %s -> %s\n' "$old_version" "$new_version"
)

The final line reports the version before and after the installer runs. Equal versions mean the installed CLI was already current; a higher second version confirms an upgrade.

Antigravity CLI: 1.1.5 -> 1.1.5

Update the Legacy Antigravity IDE RPM

DNF can update the legacy RPM if Google publishes another package to the RPM repo:

sudo dnf upgrade --refresh --from-repo=antigravity-rpm antigravity

Confirm which RPM version and architecture remain installed:

rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' antigravity

Troubleshoot Google Antigravity on Fedora

Antigravity RPM Still Shows Version 1.23.2

A 1.23.2 result can be correct for the legacy RPM even when a current tarball method is also present. Identify the installed package and the command selected by your PATH:

rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' antigravity
rpm -qf /usr/bin/antigravity /usr/share/applications/antigravity.desktop
command -v antigravity
readlink -f "$(command -v antigravity)"

If RPM reports 1.23.2 and the command resolves to /usr/bin/antigravity, you are using the legacy IDE as expected. If the command resolves through /usr/local/bin/antigravity, the current desktop helper is taking PATH precedence and the two conflicting methods should not remain installed together. Remove the method you do not want, then rerun the four checks. Use sudo update-antigravity for the current desktop app, sudo update-antigravity-ide for the current IDE, or rerun the repository query later if Google starts publishing 2.x RPMs.

A saved helper reports an older version is already installed

An unexpected old-version message or a repeatable parser failure usually means a saved helper still reads Google’s retired updater endpoint or hashed JavaScript bundle. Check both installed helpers:

for helper in /usr/local/bin/update-antigravity /usr/local/bin/update-antigravity-ide; do
  if sudo test -L "$helper" || { sudo test -e "$helper" && ! sudo test -f "$helper"; }; then
    printf 'Unexpected helper path: %s\n' "$helper"
  elif sudo test -f "$helper" && sudo grep -Fq 'antigravity-auto-updater-974169037036' "$helper"; then
    printf 'Legacy endpoint helper requires the backup step: %s\n' "$helper"
  elif sudo test -f "$helper" && sudo grep -Eq 'download_js=|main-\[\^"\]' "$helper"; then
    printf 'Hashed-JavaScript resolver can be replaced by the setup block: %s\n' "$helper"
  elif sudo test -f "$helper"; then
    printf 'No obsolete resolver fingerprint found: %s\n' "$helper"
  else
    printf 'Helper is not installed: %s\n' "$helper"
  fi
done

If the desktop helper reports the legacy endpoint, preserve that exact older file at a non-command backup path before recreating it:

(
set -euo pipefail
old_helper="/usr/local/bin/update-antigravity"
old_backup="/usr/local/bin/update-antigravity.auto-updater-backup"

if sudo test -L "$old_helper" || { sudo test -e "$old_helper" && ! sudo test -f "$old_helper"; }; then
  echo "Refusing unexpected helper path: $old_helper" >&2
  exit 1
elif sudo test -e "$old_backup" || sudo test -L "$old_backup"; then
  echo "Move the existing backup before continuing: $old_backup" >&2
  exit 1
elif sudo test -f "$old_helper" &&
  sudo grep -Fqx '#!/usr/bin/env bash' "$old_helper" &&
  sudo grep -Fq 'antigravity-auto-updater-974169037036' "$old_helper" &&
  sudo grep -Fqx 'install_root="/opt/antigravity"' "$old_helper"; then
  sudo mv -- "$old_helper" "$old_backup"
  echo "Saved the legacy endpoint helper at $old_backup"
else
  echo "The desktop helper does not match the legacy LinuxCapable fingerprint; leave it in place for manual review." >&2
  exit 1
fi
)

A hashed-JavaScript helper already carries the recognized LinuxCapable marker, so its setup block can replace it directly. Recreate each affected helper from the matching install section, then run sudo update-antigravity or sudo update-antigravity-ide twice: the first run installs the current release, and the second must report that the same version is already installed. Keep the preserved endpoint backup until both desktop runs pass; the desktop removal block deletes that recognized backup later.

Desktop or IDE Helper Fails with a Download or 404 Error

A download 404 usually means Google’s download page changed or an old helper built an invalid URL. Check whether the page still exposes the allowlisted desktop and IDE tarball anchors:

(
set -euo pipefail
download_html=$(curl -fsSL --proto '=https' --proto-redir '=https' --compressed https://antigravity.google/download)
if download_urls=$(
  printf '%s' "$download_html" |
    grep -Eo 'https://storage\.googleapis\.com/antigravity-public/antigravity-hub/[0-9]+\.[0-9]+\.[0-9]+-[0-9]+/(linux-x64|linux-arm)/Antigravity\.tar\.gz|https://edgedl\.me\.gvt1\.com/edgedl/release2/j0qc3/antigravity/stable/[0-9]+\.[0-9]+\.[0-9]+-[0-9]+/(linux-x64|linux-arm)/Antigravity%20IDE\.tar\.gz' |
    sort -u
); then
  printf '%s\n' "$download_urls"
else
  grep_status=$?
  if [ "$grep_status" -eq 1 ]; then
    echo "No allowlisted Antigravity desktop or IDE URLs were found."
  else
    exit "$grep_status"
  fi
fi
)

If the command prints the desktop and IDE URLs, recreate the affected helper and rerun its update command. If it reports that no allowlisted URLs were found, check Google’s download page, release notes, and support resources before retrying.

Troubleshoot a Black or Frozen Antigravity Window

Inspect the User Journal

Inspect the user journal before applying a rendering workaround. This separates GPU or display errors from authentication, profile, permission, or other startup failures. The journalctl command guide explains broader filtering and boot selection when the short check is not enough.

(
set -euo pipefail
if ! journal_output=$(journalctl --user --no-pager -n 300); then
  echo "Could not read the user journal." >&2
  exit 1
fi
if ! grep -i antigravity <<<"$journal_output"; then
  echo "No Antigravity messages found"
fi
)

Test One Affected Product

If the log specifically names GPU, VA-API, or rendering failures, run only the command for the affected product from a desktop terminal. Test the desktop app with:

antigravity --disable-gpu

Test the IDE separately with:

antigravity-ide --disable-gpu

On Wayland, test an Xwayland fallback only if the matching GPU-disabled command still fails. For the desktop app, run:

ELECTRON_OZONE_PLATFORM_HINT=x11 antigravity --disable-gpu

For the IDE, run:

ELECTRON_OZONE_PLATFORM_HINT=x11 antigravity-ide --disable-gpu

Create a Separate X11 Fallback Launcher

Keep the normal launchers unchanged until a workaround succeeds in the affected desktop session. If the desktop app command works, create a separate fallback launcher:

sudo bash <<'EOF'
set -euo pipefail

source_file="/usr/share/applications/antigravity.desktop"
fallback_file="/usr/share/applications/antigravity-x11.desktop"
owner_line="X-LinuxCapable-Owner=linuxcapable-google-antigravity-desktop-v1"
stage_file=""

cleanup_stage() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$stage_file" ]; then rm -f -- "$stage_file"; fi
	exit "$status"
}
trap cleanup_stage EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

exec 9>/run/lock/linuxcapable-antigravity-desktop.lock
flock -n 9 || { echo "The Antigravity desktop helper or removal command is already running." >&2; exit 1; }

if [ ! -f "$source_file" ] || [ -L "$source_file" ] || ! grep -Fqx "$owner_line" "$source_file"; then
	echo "The LinuxCapable-managed desktop launcher is missing or unrecognized." >&2
	exit 1
fi
if [ -L "$fallback_file" ] || { [ -e "$fallback_file" ] && [ ! -f "$fallback_file" ]; }; then
	echo "Refusing unexpected fallback path: $fallback_file" >&2
	exit 1
elif [ -f "$fallback_file" ] && ! grep -Fqx "$owner_line" "$fallback_file"; then
	echo "Refusing to replace an unmarked fallback launcher: $fallback_file" >&2
	exit 1
fi

stage_file=$(mktemp --suffix=.desktop /usr/share/applications/.antigravity-x11.XXXXXX)
install -o root -g root -m 0644 -- "$source_file" "$stage_file"
sed -i \
  -e 's/^Name=.*/Name=Antigravity (X11 fallback)/' \
  -e 's#^Exec=.*#Exec=env ELECTRON_OZONE_PLATFORM_HINT=x11 /usr/local/bin/antigravity --disable-gpu %U#' \
  "$stage_file"
desktop-file-validate "$stage_file"
mv -fT -- "$stage_file" "$fallback_file"
stage_file=""
if command -v restorecon >/dev/null 2>&1; then restorecon "$fallback_file"; fi
update-desktop-database /usr/share/applications
EOF

If the IDE command works with the same fallback, create a separate IDE fallback launcher:

sudo bash <<'EOF'
set -euo pipefail

source_file="/usr/share/applications/antigravity-ide.desktop"
fallback_file="/usr/share/applications/antigravity-ide-x11.desktop"
owner_line="X-LinuxCapable-Owner=linuxcapable-google-antigravity-ide-v1"
stage_file=""

cleanup_stage() {
	local status=$?
	trap - EXIT HUP INT TERM
	if [ -n "$stage_file" ]; then rm -f -- "$stage_file"; fi
	exit "$status"
}
trap cleanup_stage EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

exec 9>/run/lock/linuxcapable-antigravity-ide.lock
flock -n 9 || { echo "The Antigravity IDE helper or removal command is already running." >&2; exit 1; }

if [ ! -f "$source_file" ] || [ -L "$source_file" ] || ! grep -Fqx "$owner_line" "$source_file"; then
	echo "The LinuxCapable-managed IDE launcher is missing or unrecognized." >&2
	exit 1
fi
if [ -L "$fallback_file" ] || { [ -e "$fallback_file" ] && [ ! -f "$fallback_file" ]; }; then
	echo "Refusing unexpected fallback path: $fallback_file" >&2
	exit 1
elif [ -f "$fallback_file" ] && ! grep -Fqx "$owner_line" "$fallback_file"; then
	echo "Refusing to replace an unmarked fallback launcher: $fallback_file" >&2
	exit 1
fi

stage_file=$(mktemp --suffix=.desktop /usr/share/applications/.antigravity-ide-x11.XXXXXX)
install -o root -g root -m 0644 -- "$source_file" "$stage_file"
sed -i \
  -e 's/^Name=.*/Name=Antigravity IDE (X11 fallback)/' \
  -e 's#^Exec=.*#Exec=env ELECTRON_OZONE_PLATFORM_HINT=x11 /usr/local/bin/antigravity-ide --disable-gpu %U#' \
  "$stage_file"
desktop-file-validate "$stage_file"
mv -fT -- "$stage_file" "$fallback_file"
stage_file=""
if command -v restorecon >/dev/null 2>&1; then restorecon "$fallback_file"; fi
update-desktop-database /usr/share/applications
EOF

Remove the X11 Fallback Launchers

Remove the fallback launchers later if a Google update or Fedora graphics update fixes the black window:

sudo bash <<'EOF'
set -euo pipefail

desktop_fallback="/usr/share/applications/antigravity-x11.desktop"
ide_fallback="/usr/share/applications/antigravity-ide-x11.desktop"

exec 9>/run/lock/linuxcapable-antigravity-desktop.lock
flock -n 9 || { echo "The Antigravity desktop helper or removal command is already running." >&2; exit 1; }
exec 8>/run/lock/linuxcapable-antigravity-ide.lock
flock -n 8 || { echo "The Antigravity IDE helper or removal command is already running." >&2; exit 1; }

check_fallback() {
	local path=$1
	local owner_line=$2
	if [ -L "$path" ] || { [ -e "$path" ] && [ ! -f "$path" ]; }; then
		echo "Refusing unexpected fallback path: $path" >&2
		return 1
	fi
	if [ -f "$path" ] && ! grep -Fqx "$owner_line" "$path"; then
		echo "Refusing to remove an unmarked fallback launcher: $path" >&2
		return 1
	fi
}

check_fallback "$desktop_fallback" "X-LinuxCapable-Owner=linuxcapable-google-antigravity-desktop-v1"
check_fallback "$ide_fallback" "X-LinuxCapable-Owner=linuxcapable-google-antigravity-ide-v1"
rm -f -- "$desktop_fallback" "$ide_fallback"
update-desktop-database /usr/share/applications
EOF

Fix a SIGILL Language-Server Crash

On x86_64 systems, a language server that exits with SIGILL or status 132 points to an unavailable CPU instruction rather than a graphics failure. Check the CPU features exposed to Fedora before changing SELinux, sandbox, or GPU settings:

for flag in aes sse4_1 sse4_2; do
  if grep -qw "$flag" /proc/cpuinfo; then
    printf '%-7s available\n' "$flag"
  else
    printf '%-7s MISSING\n' "$flag"
  fi
done

If aes is missing in a virtual machine, expose AES-NI through a compatible virtual CPU model when the host supports it. A physical CPU without AES-NI needs compatible hardware; reinstalling Fedora cannot add the instruction. If all three flags are present, update the affected Antigravity product and collect the exact product version, CPU model, and journal error before trying another fix. Do not apply the generic Go setting GODEBUG=cpu.aes=off unless Google confirms it for the affected binary.

Check SELinux Labels for Desktop and IDE Paths

A normal helper install should not need a custom SELinux policy after Fedora labels the copied files correctly. If you edited a helper, copied files manually, or moved Antigravity to a different prefix, restore and inspect only the paths that exist:

(
set -euo pipefail
antigravity_paths=(
  /opt/antigravity
  /opt/antigravity-ide
  /usr/local/bin/antigravity
  /usr/local/bin/antigravity-ide
  /usr/share/applications/antigravity.desktop
  /usr/share/applications/antigravity-ide.desktop
  /usr/share/icons/hicolor/512x512/apps/antigravity.png
  /usr/share/icons/hicolor/512x512/apps/antigravity-ide.png
)
existing_paths=()
for path in "${antigravity_paths[@]}"; do
  if [ -e "$path" ] || [ -L "$path" ]; then
    if [ -d "$path" ] && [ ! -L "$path" ]; then
      sudo restorecon -R -- "$path"
    else
      sudo restorecon -- "$path"
    fi
    existing_paths+=("$path")
  fi
done

if [ "${#existing_paths[@]}" -eq 0 ]; then
  echo "No Antigravity desktop or IDE paths are installed"
else
  ls -Zd -- "${existing_paths[@]}"
fi
)

With both graphical methods installed, the label check should show each application path with the expected context types. A single-method installation shows only its own paths. Fedora should show normal user and binary contexts, not temporary extraction contexts from /tmp:

unconfined_u:object_r:usr_t:s0 /opt/antigravity
unconfined_u:object_r:usr_t:s0 /opt/antigravity-ide
unconfined_u:object_r:bin_t:s0 /usr/local/bin/antigravity
unconfined_u:object_r:bin_t:s0 /usr/local/bin/antigravity-ide
unconfined_u:object_r:usr_t:s0 /usr/share/applications/antigravity.desktop
unconfined_u:object_r:usr_t:s0 /usr/share/applications/antigravity-ide.desktop
system_u:object_r:usr_t:s0 /usr/share/icons/hicolor/512x512/apps/antigravity.png
system_u:object_r:usr_t:s0 /usr/share/icons/hicolor/512x512/apps/antigravity-ide.png

Refresh a Missing Antigravity Icon

If Activities shows a generic icon, confirm that Fedora’s hicolor theme advertises the installed directory and that the matching PNG exists, then refresh both desktop caches:

sed -n 's/^Directories=//p' /usr/share/icons/hicolor/index.theme | tr ',' '\n' | grep -Fx '512x512/apps'
ls -l /usr/share/icons/hicolor/512x512/apps/antigravity*.png
sudo update-desktop-database /usr/share/applications
sudo gtk-update-icon-cache -q /usr/share/icons/hicolor

Fix agy Command Not Found

The CLI installer writes agy to ~/.local/bin. Open a new terminal first. If Bash still cannot find it, add one guarded PATH entry to ~/.bashrc and retest from a fresh Bash whose inherited PATH does not already contain that directory:

(
set -euo pipefail
bashrc="$HOME/.bashrc"
path_marker='# LinuxCapable Antigravity CLI PATH'
path_line='export PATH="$HOME/.local/bin:$PATH"'
if [ -L "$bashrc" ] || { [ -e "$bashrc" ] && [ ! -f "$bashrc" ]; }; then
  echo "Unexpected Bash profile path: $bashrc" >&2
  exit 1
fi
if ! grep -Fqx "$path_line" "$bashrc" 2>/dev/null; then
  if grep -Fqx "$path_marker" "$bashrc" 2>/dev/null; then
    echo "The LinuxCapable PATH marker already exists without its expected line; review $bashrc manually." >&2
    exit 1
  fi
  printf '\n%s\n%s\n' "$path_marker" "$path_line" >>"$bashrc"
fi
fresh_output=""
if ! fresh_output=$(
  PATH=/usr/bin:/bin bash --noprofile --rcfile "$bashrc" -ic '
    resolved_agy=$(command -v agy || true)
    if [ "$resolved_agy" != "$HOME/.local/bin/agy" ]; then
      echo "A fresh Bash did not select $HOME/.local/bin/agy." >&2
      exit 1
    fi
    printf "%s\n" "$resolved_agy"
    agy --version
  '
); then
  echo "Fresh-shell CLI verification failed; review the Bash profile and any agy alias or function." >&2
  exit 1
fi
printf '%s\n' "$fresh_output"
)

Fix Sign-In Opening in a Text Editor

If an Antigravity sign-in URL opens as raw HTML in a text editor, inspect the user-level HTTP and HTML handlers instead of changing Antigravity authentication:

(
set -euo pipefail
for mime_type in x-scheme-handler/http x-scheme-handler/https text/html application/xhtml+xml application/xml text/xml; do
  printf '\n%s\n' "$mime_type"
  xdg-mime query default "$mime_type"
  gio mime "$mime_type" | sed -n '1,3p'
done
)

Each result should identify the desktop file for your intended browser. If an editor is registered instead, open Settings > Apps > Default Apps, choose the browser for Web, rerun the checks, and restart the affected Antigravity surface.

Fix Antigravity CLI Sign-In on SSH

If agy cannot open a browser from an SSH session, use the authorization URL it prints in the terminal. Open that URL in a local browser, sign in, then paste the returned code back into the SSH terminal when the CLI asks for it.

Handle Legacy RPM Signature Verification Errors

Do not change a local gpgcheck=1 policy to suppress this error. The upstream legacy RPM is unsigned; use a current tarball or CLI method when package signature verification is required. There is no signing key to import for the current 1.23.2 package.

Remove Google Antigravity from Fedora

Remove Antigravity 2.0 Desktop App

Close Antigravity first. This removal block deletes only paths carrying the helper’s ownership marker or an exact managed target. It stops without deleting anything if an unmanaged collision is present:

if (
set -e
sudo bash <<'EOF'
set -euo pipefail
(

install_root="/opt/antigravity"
previous_root="${install_root}.previous"
command_link="/usr/local/bin/antigravity"
helper_file="/usr/local/bin/update-antigravity"
legacy_helper_backup="/usr/local/bin/update-antigravity.auto-updater-backup"
desktop_file="/usr/share/applications/antigravity.desktop"
fallback_file="/usr/share/applications/antigravity-x11.desktop"
icon_file="/usr/share/icons/hicolor/512x512/apps/antigravity.png"
old_icon_file="/usr/share/icons/hicolor/scalable/apps/antigravity.svg"
owner_id="linuxcapable-google-antigravity-desktop-v1"

exec 9>/run/lock/linuxcapable-antigravity-desktop.lock
if ! flock -n 9; then
  echo "The Antigravity desktop helper or another removal command is already running." >&2
  exit 1
fi

path_exists() { [ -e "$1" ] || [ -L "$1" ]; }
managed_root() {
  [ -d "$1" ] && [ ! -L "$1" ] &&
    [ -f "$1/.linuxcapable-owner" ] && [ ! -L "$1/.linuxcapable-owner" ] &&
    [ "$(cat "$1/.linuxcapable-owner")" = "$owner_id" ] &&
    [ -f "$1/.linuxcapable-version" ] && [ ! -L "$1/.linuxcapable-version" ] &&
    { ! path_exists "$1/.linuxcapable-release" ||
      { [ -f "$1/.linuxcapable-release" ] && [ ! -L "$1/.linuxcapable-release" ]; }; }
}
managed_desktop() {
  [ -f "$1" ] && [ ! -L "$1" ] &&
    grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$1"
}

managed_evidence=no
for root in "$install_root" "$previous_root"; do
  if path_exists "$root"; then
    if path_exists "$root/.linuxcapable-transaction"; then
      echo "Refusing to remove a root with an unfinished transaction marker: $root" >&2
      exit 1
    fi
    if ! managed_root "$root"; then
      echo "Refusing to remove unmarked path: $root" >&2
      exit 1
    fi
    managed_evidence=yes
  fi
done

if path_exists "$command_link"; then
  if [ ! -L "$command_link" ]; then
    echo "Refusing to remove non-symlink: $command_link" >&2
    exit 1
  fi
  case "$(readlink "$command_link")" in
  "$install_root/Antigravity-x64/antigravity" | "$install_root/Antigravity-arm64/antigravity") ;;
  *) echo "Refusing to remove unmanaged link: $command_link" >&2; exit 1 ;;
  esac
fi

for launcher in "$desktop_file" "$fallback_file"; do
  if path_exists "$launcher"; then
    if ! managed_desktop "$launcher"; then
      echo "Refusing to remove unmarked desktop file: $launcher" >&2
      exit 1
    fi
    managed_evidence=yes
  fi
done

if path_exists "$helper_file" && { [ ! -f "$helper_file" ] || [ -L "$helper_file" ] || ! grep -Fqx '# LinuxCapable owner: linuxcapable-google-antigravity-desktop-helper-v1' "$helper_file"; }; then
  echo "Refusing to remove unmarked helper: $helper_file" >&2
  exit 1
fi

if path_exists "$legacy_helper_backup"; then
  if [ ! -f "$legacy_helper_backup" ] || [ -L "$legacy_helper_backup" ] ||
    ! grep -Fqx '#!/usr/bin/env bash' "$legacy_helper_backup" ||
    ! grep -Fq 'antigravity-auto-updater-974169037036' "$legacy_helper_backup" ||
    ! grep -Fqx 'install_root="/opt/antigravity"' "$legacy_helper_backup"; then
    echo "Refusing to remove unrecognized helper backup: $legacy_helper_backup" >&2
    exit 1
  fi
fi

for icon in "$icon_file" "$old_icon_file"; do
  if path_exists "$icon" && { [ ! -f "$icon" ] || [ -L "$icon" ] || [ "$managed_evidence" != yes ]; }; then
    echo "Refusing to remove unproven icon: $icon" >&2
    exit 1
  fi
done

rm -f -- "$command_link" "$helper_file" "$legacy_helper_backup" "$desktop_file" "$fallback_file" "$icon_file" "$old_icon_file"
for root in "$install_root" "$previous_root"; do
  if managed_root "$root"; then rm -rf -- "$root"; fi
done

if command -v update-desktop-database >/dev/null 2>&1; then
  update-desktop-database /usr/share/applications >/dev/null
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
  gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null
fi

for path in "$install_root" "$previous_root" "$command_link" "$helper_file" "$legacy_helper_backup" "$desktop_file" "$fallback_file" "$icon_file" "$old_icon_file"; do
  if path_exists "$path"; then echo "Removal check failed: $path" >&2; exit 1; fi
done
echo "Antigravity 2.0 LinuxCapable-managed files are removed"
)
EOF
hash -r
); then
  hash -r
else
  hash -r
  false
fi

Remove Antigravity IDE

Close Antigravity IDE first. Use the matching ownership checks for the current IDE, its marked rollback, launcher, helper, and icon:

if (
set -e
sudo bash <<'EOF'
set -euo pipefail
(

install_root="/opt/antigravity-ide"
previous_root="${install_root}.previous"
command_link="/usr/local/bin/antigravity-ide"
helper_file="/usr/local/bin/update-antigravity-ide"
desktop_file="/usr/share/applications/antigravity-ide.desktop"
fallback_file="/usr/share/applications/antigravity-ide-x11.desktop"
icon_file="/usr/share/icons/hicolor/512x512/apps/antigravity-ide.png"
owner_id="linuxcapable-google-antigravity-ide-v1"

exec 9>/run/lock/linuxcapable-antigravity-ide.lock
if ! flock -n 9; then
  echo "The Antigravity IDE helper or another removal command is already running." >&2
  exit 1
fi

path_exists() { [ -e "$1" ] || [ -L "$1" ]; }
managed_root() {
  [ -d "$1" ] && [ ! -L "$1" ] &&
    [ -f "$1/.linuxcapable-owner" ] && [ ! -L "$1/.linuxcapable-owner" ] &&
    [ "$(cat "$1/.linuxcapable-owner")" = "$owner_id" ] &&
    [ -f "$1/.linuxcapable-version" ] && [ ! -L "$1/.linuxcapable-version" ] &&
    { ! path_exists "$1/.linuxcapable-release" ||
      { [ -f "$1/.linuxcapable-release" ] && [ ! -L "$1/.linuxcapable-release" ]; }; }
}
managed_desktop() {
  [ -f "$1" ] && [ ! -L "$1" ] &&
    grep -Fqx "X-LinuxCapable-Owner=$owner_id" "$1"
}

managed_evidence=no
for root in "$install_root" "$previous_root"; do
  if path_exists "$root"; then
    if path_exists "$root/.linuxcapable-transaction"; then
      echo "Refusing to remove a root with an unfinished transaction marker: $root" >&2
      exit 1
    fi
    if ! managed_root "$root"; then
      echo "Refusing to remove unmarked path: $root" >&2
      exit 1
    fi
    managed_evidence=yes
  fi
done

if path_exists "$command_link"; then
  if [ ! -L "$command_link" ] || [ "$(readlink "$command_link")" != "$install_root/Antigravity-IDE/antigravity-ide" ]; then
    echo "Refusing to remove unmanaged command path: $command_link" >&2
    exit 1
  fi
fi

for launcher in "$desktop_file" "$fallback_file"; do
  if path_exists "$launcher"; then
    if ! managed_desktop "$launcher"; then
      echo "Refusing to remove unmarked desktop file: $launcher" >&2
      exit 1
    fi
    managed_evidence=yes
  fi
done

if path_exists "$helper_file" && { [ ! -f "$helper_file" ] || [ -L "$helper_file" ] || ! grep -Fqx '# LinuxCapable owner: linuxcapable-google-antigravity-ide-helper-v1' "$helper_file"; }; then
  echo "Refusing to remove unmarked helper: $helper_file" >&2
  exit 1
fi

if path_exists "$icon_file" && { [ ! -f "$icon_file" ] || [ -L "$icon_file" ] || [ "$managed_evidence" != yes ]; }; then
  echo "Refusing to remove unproven icon: $icon_file" >&2
  exit 1
fi

rm -f -- "$command_link" "$helper_file" "$desktop_file" "$fallback_file" "$icon_file"
for root in "$install_root" "$previous_root"; do
  if managed_root "$root"; then rm -rf -- "$root"; fi
done

if command -v update-desktop-database >/dev/null 2>&1; then
  update-desktop-database /usr/share/applications >/dev/null
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
  gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null
fi

for path in "$install_root" "$previous_root" "$command_link" "$helper_file" "$desktop_file" "$fallback_file" "$icon_file"; do
  if path_exists "$path"; then echo "Removal check failed: $path" >&2; exit 1; fi
done
echo "Antigravity IDE LinuxCapable-managed files are removed"
)
EOF
hash -r
); then
  hash -r
else
  hash -r
  false
fi

Remove Antigravity CLI

Open agy, run /logout, and exit the CLI before removing its binary. This clears the saved authentication session through the application instead of guessing at credential files.

agy

At the agy prompt, enter /logout, wait for confirmation, and exit the CLI.

Then remove agy only when its owner record, expected path, executable shape, and Antigravity CLI help signature all match. The compatibility check also removes the earlier LinuxCapable CLI update helper only when its recognized installer-and-update fingerprint matches:

if (
set -u

agy_path="$HOME/.local/bin/agy"
owner_dir="$HOME/.local/share/linuxcapable"
owner_file="$owner_dir/antigravity-cli.owner"
owner_id="linuxcapable-google-antigravity-cli-v1"
legacy_helper="$HOME/.local/bin/update-antigravity-cli"
owner_valid=no
owner_dir_valid=yes
removal_ok=yes

path_exists() { [ -e "$1" ] || [ -L "$1" ]; }

if [ -L "$owner_dir" ] || { [ -e "$owner_dir" ] && [ ! -d "$owner_dir" ]; }; then
  echo "Refusing unexpected owner directory: $owner_dir" >&2
  owner_dir_valid=no
  removal_ok=no
fi

if [ "$owner_dir_valid" = yes ]; then
  if [ -L "$owner_file" ] || { [ -e "$owner_file" ] && [ ! -f "$owner_file" ]; }; then
    echo "Refusing unexpected owner-record path: $owner_file" >&2
    removal_ok=no
  elif [ -f "$owner_file" ]; then
    if grep -Fqx "owner=$owner_id" "$owner_file" &&
      grep -Fqx "path=$agy_path" "$owner_file"; then
      owner_valid=yes
    else
      echo "Refusing unrecognized owner record: $owner_file" >&2
      removal_ok=no
    fi
  fi
fi

if [ -e "$agy_path" ] || [ -L "$agy_path" ]; then
  if [ "$owner_valid" != yes ]; then
    echo "Refusing to remove $agy_path without its valid LinuxCapable owner record." >&2
    removal_ok=no
  elif [ ! -f "$agy_path" ] || [ -L "$agy_path" ] || [ ! -x "$agy_path" ]; then
    echo "Refusing to remove unexpected CLI path: $agy_path" >&2
    removal_ok=no
  elif ! agy_help=$("$agy_path" --help 2>&1); then
    echo "Refusing to remove a CLI whose help check fails: $agy_path" >&2
    removal_ok=no
  elif ! grep -Fq 'Usage of agy:' <<<"$agy_help" ||
    ! grep -Fq 'Available subcommands:' <<<"$agy_help"; then
    echo "Refusing to remove a CLI with an unexpected help signature: $agy_path" >&2
    removal_ok=no
  else
    if ! rm -f -- "$agy_path" "$owner_file"; then
      echo "Managed CLI deletion failed." >&2
      removal_ok=no
    elif path_exists "$agy_path" || path_exists "$owner_file"; then
      echo "Managed CLI removal is incomplete." >&2
      removal_ok=no
    else
      rmdir -- "$owner_dir" 2>/dev/null || true
    fi
  fi
elif [ "$owner_valid" = yes ]; then
  if ! rm -f -- "$owner_file" || path_exists "$owner_file"; then
    echo "The stale owner record could not be removed: $owner_file" >&2
    removal_ok=no
  else
    rmdir -- "$owner_dir" 2>/dev/null || true
  fi
fi

if [ "$removal_ok" = yes ]; then
  if [ -L "$legacy_helper" ] || { [ -e "$legacy_helper" ] && [ ! -f "$legacy_helper" ]; }; then
    echo "Leaving unrelated path in place: $legacy_helper" >&2
  elif [ -f "$legacy_helper" ]; then
    # Match the literal command text in the previously published helper.
    # shellcheck disable=SC2016
    if grep -Fqx '#!/usr/bin/env bash' "$legacy_helper" &&
      grep -Fq 'https://antigravity.google/cli/install.sh' "$legacy_helper" &&
      grep -Fq '"$HOME/.local/bin/agy" update' "$legacy_helper"; then
      if ! rm -f -- "$legacy_helper" || path_exists "$legacy_helper"; then
        echo "The recognized compatibility helper could not be removed: $legacy_helper" >&2
        removal_ok=no
      fi
    else
      echo "Leaving unrecognized helper in place: $legacy_helper" >&2
    fi
  fi
fi

hash -r
resolved_agy=$(command -v agy 2>/dev/null || true)
if [ "$resolved_agy" = "$agy_path" ]; then
  echo "Managed agy still resolves from $agy_path" >&2
  removal_ok=no
elif [ -n "$resolved_agy" ]; then
  echo "Another agy command remains at $resolved_agy"
else
  echo "agy command is removed"
fi
if [ "$removal_ok" != yes ]; then
  exit 1
fi
); then
  hash -r
else
  hash -r
  false
fi

If the CLI verification or PATH troubleshooting block added its marked Bash entry, remove only that exact two-line block. This leaves an unmarked ~/.local/bin PATH entry intact when another tool or your Fedora profile owns it:

(
set -euo pipefail
bashrc="$HOME/.bashrc"
path_marker='# LinuxCapable Antigravity CLI PATH'
path_line='export PATH="$HOME/.local/bin:$PATH"'
if [ -L "$bashrc" ] || { [ -e "$bashrc" ] && [ ! -f "$bashrc" ]; }; then
  echo "Unexpected Bash profile path: $bashrc" >&2
  exit 1
elif [ -f "$bashrc" ] && grep -Fqx "$path_marker" "$bashrc"; then
  profile_stage=$(mktemp "$HOME/.bashrc.antigravity.XXXXXX")
  trap 'if [ -n "$profile_stage" ]; then rm -f -- "$profile_stage"; fi' EXIT
  awk -v marker="$path_marker" -v pathline="$path_line" '
    $0 == marker {
      if ((getline nextline) > 0 && nextline == pathline) next
      print
      if (nextline != "") print nextline
      next
    }
    { print }
  ' "$bashrc" >"$profile_stage"
  chmod --reference="$bashrc" "$profile_stage"
  mv -fT -- "$profile_stage" "$bashrc"
  profile_stage=""
fi
)

An older Antigravity CLI installation may predate the owner record. In that case, the guarded block leaves agy in place. Verify its help signature, move it to a non-command backup path, start a new terminal, and confirm your scripts no longer need that copy before deleting the backup:

if (
set -euo pipefail

agy_path="$HOME/.local/bin/agy"
owner_file="$HOME/.local/share/linuxcapable/antigravity-cli.owner"
backup_path="$HOME/.local/bin/agy.pre-owner-backup"
agy_help=""

if [ -e "$owner_file" ] || [ -L "$owner_file" ]; then
  echo "The owner record is present; use the guarded owner-record removal command instead." >&2
  exit 1
elif [ ! -f "$agy_path" ] || [ -L "$agy_path" ] || [ ! -x "$agy_path" ]; then
  echo "No regular executable is available for legacy review: $agy_path" >&2
  exit 1
elif [ -e "$backup_path" ] || [ -L "$backup_path" ]; then
  echo "Move the existing backup before continuing: $backup_path" >&2
  exit 1
elif ! agy_help=$("$agy_path" --help 2>&1) ||
  ! grep -Fq 'Usage of agy:' <<<"$agy_help" ||
  ! grep -Fq 'Available subcommands:' <<<"$agy_help"; then
  echo "The CLI help signature is not recognized; leave $agy_path in place." >&2
  exit 1
else
  mv -- "$agy_path" "$backup_path"
  echo "Moved the older CLI to $backup_path for review"
fi
); then
  hash -r
else
  hash -r
  false
fi

After confirming the older command is no longer needed, delete only that reviewed backup and verify that the path is gone:

(
set -euo pipefail
backup_path="$HOME/.local/bin/agy.pre-owner-backup"
backup_help=""
backup_version=""
if [ -L "$backup_path" ] || { [ -e "$backup_path" ] && [ ! -f "$backup_path" ]; }; then
  echo "Refusing unexpected backup path: $backup_path" >&2
  exit 1
elif [ -f "$backup_path" ]; then
  backup_version=$("$backup_path" --version 2>/dev/null || true)
  backup_help=$("$backup_path" --help 2>&1 || true)
  if [ ! -x "$backup_path" ] ||
    [[ ! "$backup_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
    ! grep -Fq 'Usage of agy:' <<<"$backup_help" ||
    ! grep -Fq 'Available subcommands:' <<<"$backup_help"; then
    echo "The reviewed backup no longer has the expected Antigravity CLI signature: $backup_path" >&2
    exit 1
  fi
fi
rm -f -- "$backup_path"
test ! -e "$backup_path" && echo "Reviewed agy backup is removed"
)

Remove the Legacy Antigravity IDE RPM

Uninstall the RPM package, then remove the repository file only when its exact section, URL, and trust setting still match this method:

(
set -euo pipefail

if ! sudo dnf remove --no-autoremove antigravity; then
  echo "Package removal failed; leaving the Antigravity repository file in place." >&2
  exit 1
fi

repo_file="/etc/yum.repos.d/antigravity.repo"
repo_owned=yes
if sudo test -L "$repo_file" || { sudo test -e "$repo_file" && ! sudo test -f "$repo_file"; }; then
  echo "Refusing to remove unexpected repository path: $repo_file" >&2
  repo_owned=no
elif sudo test -f "$repo_file"; then
  if ! repo_line_count=$(sudo awk 'END { print NR }' "$repo_file"); then
    echo "Could not inspect the repository file; leaving it in place: $repo_file" >&2
    exit 1
  fi
  if [ "$repo_line_count" -ne 5 ] ||
    ! sudo grep -Fqx '[antigravity-rpm]' "$repo_file" ||
    ! sudo grep -Fqx 'name=Antigravity RPM Repository' "$repo_file" ||
    ! sudo grep -Fqx 'baseurl=https://us-central1-yum.pkg.dev/projects/antigravity-auto-updater-dev/antigravity-rpm' "$repo_file" ||
    ! sudo grep -Fqx 'enabled=1' "$repo_file" ||
    ! sudo grep -Fqx 'gpgcheck=0' "$repo_file"; then
    echo "Leaving modified repository file in place for manual review: $repo_file" >&2
    repo_owned=no
  fi
fi

if [ "$repo_owned" != yes ]; then
  exit 1
fi
sudo rm -f -- "$repo_file"
if sudo test -e "$repo_file" || sudo test -L "$repo_file"; then
  echo "Repository file removal is incomplete: $repo_file" >&2
  exit 1
fi
sudo dnf clean metadata
sudo dnf check
)

Confirm the RPM package and repo file are gone:

(
set -euo pipefail
if rpm -q antigravity >/dev/null 2>&1; then
  echo "The antigravity RPM is still installed." >&2
  exit 1
else
  echo "antigravity RPM is removed"
fi
if [ -e /etc/yum.repos.d/antigravity.repo ] || [ -L /etc/yum.repos.d/antigravity.repo ]; then
  echo "The antigravity repo path still exists." >&2
  exit 1
else
  echo "antigravity repo file is removed"
fi
if ! repo_list=$(dnf repo list --all); then
  echo "DNF repository listing failed." >&2
  exit 1
fi
if grep -Fq antigravity-rpm <<<"$repo_list"; then
  echo "antigravity-rpm still appears in DNF." >&2
  exit 1
else
  echo "antigravity-rpm is absent from DNF"
fi
sudo dnf check
)

Delete Antigravity User Data

User-data cleanup permanently deletes Antigravity desktop settings, IDE settings, CLI state, cached files, and updater data from your home directory. Keep a backup first if you may need the profile later.

Review matching Antigravity paths before deleting anything:

find "$HOME" -mindepth 1 -maxdepth 1 -name '.antigravity*' -print 2>/dev/null
find "$HOME/.config" "$HOME/.cache" -mindepth 1 -maxdepth 1 -iname '*antigravity*' -print 2>/dev/null
find "$HOME/.gemini" -mindepth 1 -maxdepth 1 -iname 'antigravity*' -print 2>/dev/null

Remove the known desktop, IDE, and CLI profile paths when you are ready to discard them. The guarded block refuses root and any target that resolves outside your real home directory. The ~/.gemini/antigravity-backup directory is intentionally preserved so a prior migration backup is not erased by the cleanup command:

(
set -euo pipefail
if [ "$(id -u)" -eq 0 ]; then
  echo "Run this user-data cleanup from your normal Fedora account, not with sudo." >&2
  exit 1
fi
if [ -z "${HOME:-}" ] || [ "$HOME" = / ] || [ ! -d "$HOME" ]; then
  echo "HOME is not a safe existing user directory." >&2
  exit 1
fi
real_home=$(realpath -e -- "$HOME")
if [ -z "$real_home" ] || [ "$real_home" = / ]; then
  echo "The resolved home directory is unsafe: $real_home" >&2
  exit 1
fi
cleanup_paths=(
  "$HOME/.config/Antigravity"
  "$HOME/.config/Antigravity IDE"
  "$HOME/.antigravity"
  "$HOME/.antigravity-ide"
  "$HOME/.cache/antigravity"
  "$HOME/.cache/antigravity-updater"
  "$HOME/.gemini/antigravity"
  "$HOME/.gemini/antigravity-ide"
  "$HOME/.gemini/antigravity-cli"
)
for path in "${cleanup_paths[@]}"; do
  resolved_path=$(realpath -m -- "$path")
  if [[ "$resolved_path" != "$real_home/"* ]]; then
    echo "Refusing user-data path outside the resolved home directory: $path -> $resolved_path" >&2
    exit 1
  fi
done
rm -rf -- "${cleanup_paths[@]}"
)

Check every removed path afterward. The final message confirms that no active profile path remains; a separate line reports the preserved migration backup when it exists:

(
set -u
cleanup_failed=no
for path in \
  "$HOME/.config/Antigravity" \
  "$HOME/.config/Antigravity IDE" \
  "$HOME/.antigravity" \
  "$HOME/.antigravity-ide" \
  "$HOME/.cache/antigravity" \
  "$HOME/.cache/antigravity-updater" \
  "$HOME/.gemini/antigravity" \
  "$HOME/.gemini/antigravity-ide" \
  "$HOME/.gemini/antigravity-cli"; do
  if [ -e "$path" ] || [ -L "$path" ]; then
    echo "User-data path remains: $path" >&2
    cleanup_failed=yes
  fi
done
if [ "$cleanup_failed" = yes ]; then
  exit 1
fi
echo "Active Antigravity profile paths are removed"
if [ -e "$HOME/.gemini/antigravity-backup" ]; then
  echo "Preserved backup: $HOME/.gemini/antigravity-backup"
fi
)

Conclusion

Antigravity now has a Fedora launcher or terminal command, with updates and removal tied to the selected source. Start with a small project, verify the intended launcher or agy command, and avoid the unsigned legacy repository unless an older 1.x IDE is specifically required.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee

10 thoughts on “How to Install Google Antigravity on Fedora 44”

  1. already update the script but cant update to 2.3.0 🙁

    Replacing the earlier LinuxCapable download-page helper.
    cleanup_helper_source:1: read-only variable: status

    ~
    ➜ sudo update-antigravity
    Could not find the Antigravity download bundle

    Reply
    • Hi Tony. Both errors came from the earlier helper. Your current shell rejected the setup wrapper’s status variable, and Google’s download page no longer exposes the hashed JavaScript bundle that version searched for.

      The Fedora guide now uses Google’s direct official download links. Start a Bash subshell before pasting the revised setup block:

      bash

      Then paste the complete revised Save the Desktop Update Helper block and run:

      sudo update-antigravity

      This replaces the recognized older helper and selects the current desktop release automatically; you do not need to specify 2.3.0.

      Reply
    • Hi Alex. That 2.0.1 result indicates that /usr/local/bin/update-antigravity was created from the older article version and is still using Google’s previous updater endpoint.

      In the Fedora guide, follow the section titled A saved helper reports an older version is already installed. It shows how to identify and preserve the older helper safely. Then rerun the complete desktop helper setup block and run:

      sudo update-antigravity

      The current helper obtains the latest desktop release from Google’s download page, so you do not need to specify 2.0.6 manually.

      Reply
    • That output comes from an older saved copy of /usr/local/bin/update-antigravity that still uses Google’s previous updater endpoint. It can stop at 2.0.1 and incorrectly report that version as current.

      The Fedora guide now includes a section titled A saved helper reports an older version is already installed. Follow that section to identify and preserve the older helper, then rerun the complete desktop helper setup block before running:

      sudo update-antigravity

      The current helper resolves the latest desktop release from Google’s download page. Keep the saved backup until the new helper completes both a successful install and a same-version no-op check.

      Reply
    • Added. The Fedora guide now includes a separate helper for the current Antigravity IDE tarball. It installs the IDE under /opt/antigravity-ide, creates its launcher and icon, and provides this command for later updates:

      sudo update-antigravity-ide

      Reply
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: