Last active
August 4, 2026 09:53
-
-
Save abdelouahabb/9bd173e19461078c15daba6d78a4412c to your computer and use it in GitHub Desktop.
fixed Antigravity CLI installation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Antigravity Linux One-Command Wrapper & Installer | |
| # Installs/updates Google Antigravity 2.0, Antigravity IDE, and CLI on Debian/Ubuntu. | |
| set -euo pipefail | |
| # 1. Local Workspace Setup | |
| WORKSPACE="$HOME/ag_tmp" | |
| mkdir -p "$WORKSPACE" | |
| trap 'rm -rf "$WORKSPACE"' EXIT | |
| # Self-referential URL setting for updater helper | |
| INSTALLER_URL="${ANTIGRAVITY_LINUX_INSTALLER_URL:-https://opensnap.github.io/antigravity/install.sh}" | |
| export ANTIGRAVITY_LINUX_INSTALLER_URL="$INSTALLER_URL" | |
| ORIGINAL_ARGS=("$@") | |
| PROJECT_NAME="antigravity-linux" | |
| DOWNLOAD_PAGE="https://antigravity.google/download" | |
| CLI_INSTALLER="https://antigravity.google/cli/install.sh" | |
| INSTALL_DESKTOP=1 | |
| INSTALL_IDE=0 | |
| INSTALL_CLI=0 | |
| INSTALL_NAUTILUS=1 | |
| INSTALL_DEPS=1 | |
| DO_UNINSTALL=0 | |
| DO_STATUS=0 | |
| DO_PRINT_DOWNLOADS=0 | |
| FORCE=0 | |
| YES=0 | |
| log() { printf '🤖 %s\n' "$*"; } | |
| warn() { printf '⚠️ WARN: %s\n' "$*" >&2; } | |
| err() { printf '❌ ERROR: %s\n' "$*" >&2; exit 1; } | |
| need() { command -v "$1" >/dev/null 2>&1 || err "Required command not found: $1"; } | |
| # Redirected spinner output to stderr (>&2) to prevent command substitution pollution | |
| spin() { | |
| local pid=$1 | |
| local msg="$2" | |
| local frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏") | |
| while kill -0 "$pid" 2>/dev/null; do | |
| for frame in "${frames[@]}"; do | |
| kill -0 "$pid" 2>/dev/null || break | |
| printf "\r\033[K🤖 %s %s" "$frame" "$msg" >&2 | |
| sleep 0.1 | |
| done | |
| done | |
| wait "$pid" | |
| if [ $? -eq 0 ]; then | |
| printf "\r\033[K🤖 ✅ %s\n" "$msg" >&2 | |
| else | |
| printf "\r\033[K❌ ERROR: %s failed.\n" "$msg" >&2 | |
| exit 1 | |
| fi | |
| } | |
| usage() { | |
| cat <<'USAGE' | |
| Antigravity Linux Installer | |
| Usage: | |
| install.sh [install|update] [options] | |
| install.sh --status | |
| install.sh --print-downloads | |
| install.sh --uninstall | |
| Options: | |
| --desktop Install/update Antigravity 2.0 desktop app only | |
| --ide Install/update Antigravity IDE only | |
| --all Install/update Antigravity 2.0 desktop app + Antigravity IDE | |
| --cli Also run Google's official Antigravity CLI installer | |
| --no-nautilus Skip GNOME Files/Nautilus context-menu helper | |
| --no-apt Do not install apt dependencies automatically | |
| --force Reinstall even when the recorded version matches | |
| --status Show installed helper-managed apps and versions | |
| --print-downloads Print the resolved official Google tarball URLs | |
| --uninstall Remove helper-managed Antigravity desktop/IDE files | |
| -y, --yes Non-interactive mode | |
| -h, --help Show this help | |
| USAGE | |
| } | |
| # Parse Default or Provided Arguments | |
| if [ $# -eq 0 ]; then | |
| INSTALL_DESKTOP=1 | |
| INSTALL_IDE=1 | |
| fi | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| install|update) ;; | |
| --desktop) INSTALL_DESKTOP=1; INSTALL_IDE=0 ;; | |
| --ide) INSTALL_DESKTOP=0; INSTALL_IDE=1 ;; | |
| --all) INSTALL_DESKTOP=1; INSTALL_IDE=1 ;; | |
| --cli) INSTALL_CLI=1 ;; | |
| --no-nautilus) INSTALL_NAUTILUS=0 ;; | |
| --no-apt) INSTALL_DEPS=0 ;; | |
| --force) FORCE=1 ;; | |
| --status) DO_STATUS=1 ;; | |
| --print-downloads) DO_PRINT_DOWNLOADS=1 ;; | |
| --uninstall) DO_UNINSTALL=1 ;; | |
| -y|--yes) YES=1 ;; | |
| -h|--help) usage; exit 0 ;; | |
| *) err "Unknown option: $1" ;; | |
| esac | |
| shift | |
| done | |
| if [ "$(uname -s)" != "Linux" ]; then | |
| err "This installer is for Linux only." | |
| fi | |
| case "$(uname -m)" in | |
| x86_64|amd64) AG_PLATFORM="linux-x64"; DESKTOP_TOP="Antigravity-x64" ;; | |
| aarch64|arm64) AG_PLATFORM="linux-arm"; DESKTOP_TOP="Antigravity-arm64" ;; | |
| *) err "Unsupported CPU architecture: $(uname -m)." ;; | |
| esac | |
| require_root_or_reexec() { | |
| if [ "$(id -u)" -eq 0 ]; then | |
| return 0 | |
| fi | |
| if command -v sudo >/dev/null 2>&1 && [ -n "${BASH_SOURCE[0]:-}" ] && [ -r "${BASH_SOURCE[0]}" ]; then | |
| exec sudo -E env "ANTIGRAVITY_LINUX_INSTALLER_URL=$INSTALLER_URL" bash "${BASH_SOURCE[0]}" "${ORIGINAL_ARGS[@]}" | |
| fi | |
| err "System-wide install needs root privileges." | |
| } | |
| install_deps_debian() { | |
| [ "$INSTALL_DEPS" -eq 1 ] || return 0 | |
| if command -v apt-get >/dev/null 2>&1; then | |
| export DEBIAN_FRONTEND=noninteractive | |
| ( | |
| apt-get update -qq | |
| local packages=(ca-certificates curl tar python3 desktop-file-utils xdg-utils) | |
| if [ "$INSTALL_NAUTILUS" -eq 1 ] && [ "$INSTALL_IDE" -eq 1 ]; then | |
| packages+=(python3-nautilus) | |
| fi | |
| apt-get install -y -qq "${packages[@]}" | |
| ) >/dev/null 2>&1 & | |
| spin $! "Dependencies installed." | |
| else | |
| for c in curl tar python3; do need "$c"; done | |
| fi | |
| } | |
| resolve_main_bundle() { | |
| local tmpdir="$1" | |
| local html="$tmpdir/download.html" | |
| local js="$tmpdir/download.js" | |
| (curl -fsSL --compressed --retry 3 -o "$html" "$DOWNLOAD_PAGE") >/dev/null 2>&1 & | |
| spin $! "Fetching Google release endpoints..." | |
| local main_js_url | |
| main_js_url=$(python3 - "$html" "$DOWNLOAD_PAGE" <<'PY' | |
| import re, sys | |
| from pathlib import Path | |
| from urllib.parse import urljoin | |
| html = Path(sys.argv[1]).read_text(errors='replace') | |
| page = sys.argv[2] | |
| matches = re.findall(r'(?:src|href)=["\']([^"\']+\.js)["\']', html) | |
| if not matches: | |
| sys.exit(0) | |
| print(urljoin(page, matches[-1])) | |
| PY | |
| ) | |
| if [ -n "$main_js_url" ]; then | |
| curl -fsSL --compressed --retry 3 -o "$js" "$main_js_url" 2>/dev/null || echo "" > "$js" | |
| else | |
| echo "" > "$js" | |
| fi | |
| printf '%s\n' "$js" | |
| } | |
| resolve_download_from_bundle() { | |
| local js="$1" | |
| local product="$2" | |
| python3 - "$js" "$AG_PLATFORM" "$product" <<'PY' | |
| import html, re, sys | |
| from pathlib import Path | |
| from urllib.parse import unquote | |
| bundle_path = Path(sys.argv[1]) | |
| html_path = bundle_path.parent / "download.html" | |
| content = "" | |
| if html_path.exists(): | |
| content += html_path.read_text(errors='ignore') | |
| if bundle_path.exists(): | |
| content += bundle_path.read_text(errors='ignore') | |
| content = html.unescape(content).replace('\\/', '/') | |
| platform = sys.argv[2] | |
| product = sys.argv[3] | |
| def version_from_url(url): | |
| decoded = unquote(url) | |
| m = re.search(r'/(\d+\.\d+\.\d+-[0-9]+)/', decoded) | |
| if not m: | |
| m = re.search(r'(\d+\.\d+\.\d+)', decoded) | |
| return m.group(1).split('-', 1)[0] if m else 'unknown' | |
| if product == 'desktop': | |
| pattern = r'https?://[^\'"\s<>)]*' + re.escape(platform) + r'/[^\'"\s<>)]*Antigravity\.tar\.gz' | |
| else: | |
| pattern = r'https?://[^\'"\s<>)]*' + re.escape(platform) + r'/[^\'"\s<>)]*Antigravity(?:%20|\+)IDE\.tar\.gz' | |
| matches = re.findall(pattern, content, re.IGNORECASE) | |
| if product == 'desktop': | |
| matches = [u for u in matches if 'ide' not in u.lower()] | |
| if matches: | |
| url = matches[-1] | |
| print(version_from_url(url), url) | |
| sys.exit(0) | |
| sys.stderr.write(f"⚠️ WARN: Could not dynamically resolve latest {product} link. Using fallback mirror.\n") | |
| if product == 'desktop': | |
| fallback_url = f"https://storage.googleapis.com/antigravity-public/antigravity-hub/2.4.3-5358163105546240/{platform}/Antigravity.tar.gz" | |
| print("2.4.3", fallback_url) | |
| else: | |
| fallback_url = f"https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/2.1.1-6123990880747520/{platform}/Antigravity%20IDE.tar.gz" | |
| print("2.1.1", fallback_url) | |
| PY | |
| } | |
| resolve_cli_version() { | |
| local js="$1" | |
| python3 - "$js" "$AG_PLATFORM" <<'PY' | |
| import html, re, sys | |
| from pathlib import Path | |
| from urllib.parse import unquote | |
| bundle_path = Path(sys.argv[1]) | |
| html_path = bundle_path.parent / "download.html" | |
| content = "" | |
| if html_path.exists(): | |
| content += html_path.read_text(errors='ignore') | |
| if bundle_path.exists(): | |
| content += bundle_path.read_text(errors='ignore') | |
| content = html.unescape(content).replace('\\/', '/') | |
| platform = sys.argv[2] | |
| def extract(pattern, text): | |
| m = re.search(pattern, text, re.IGNORECASE) | |
| return m.group(1) if m else None | |
| # 1. Search for a direct agy tarball link in JS/HTML | |
| m = re.search(r'https?://[^\'"\s<>)]*' + re.escape(platform) + r'/[^\'"\s<>)]*agy[^\'"\s<>)]*\.tar\.gz', content, re.IGNORECASE) | |
| if m: | |
| url = unquote(m.group(0)) | |
| v = extract(r'/(\d+\.\d+\.\d+-[0-9]+)/', url) or extract(r'(\d+\.\d+\.\d+)', url) | |
| if v: | |
| print(v.split('-', 1)[0]) | |
| sys.exit(0) | |
| # 2. Search for generic JSON keys | |
| v = extract(r'cli[_-]?version[\'"]?\s*:\s*[\'"]?(\d+\.\d+\.\d+)', content) | |
| if v: | |
| print(v) | |
| sys.exit(0) | |
| # 3. Last resort: fetch the install.sh and parse VERSION= | |
| import urllib.request | |
| try: | |
| req = urllib.request.Request("https://antigravity.google/cli/install.sh", headers={'User-Agent': 'Mozilla/5.0'}) | |
| with urllib.request.urlopen(req, timeout=5) as resp: | |
| script_text = resp.read().decode('utf-8') | |
| v = extract(r'VERSION=[\'"]?(\d+\.\d+\.\d+)[\'"]?', script_text) | |
| if v: | |
| print(v) | |
| sys.exit(0) | |
| except Exception: | |
| pass | |
| print("unknown") | |
| PY | |
| } | |
| resolve_desktop_download() { resolve_download_from_bundle "$1" desktop; } | |
| resolve_ide_download() { resolve_download_from_bundle "$1" ide; } | |
| asar_extract_icon_png() { | |
| local asar="$1" | |
| local out="$2" | |
| python3 - "$asar" "$out" <<'PY' | |
| import json, struct, sys | |
| from pathlib import Path | |
| asar = Path(sys.argv[1]) | |
| out = Path(sys.argv[2]) | |
| with asar.open('rb') as f: | |
| f.read(4) | |
| header_size = struct.unpack('<I', f.read(4))[0] | |
| f.read(4) | |
| json_size = struct.unpack('<I', f.read(4))[0] | |
| header = json.loads(f.read(json_size).decode()) | |
| icon = header.get('files', {}).get('icon.png') | |
| if not icon: | |
| raise SystemExit('icon.png not found') | |
| with asar.open('rb') as f: | |
| f.seek(8 + header_size + int(icon['offset'])) | |
| out.write_bytes(f.read(int(icon['size']))) | |
| PY | |
| } | |
| safe_replace_dir() { | |
| local newdir="$1" | |
| local target="$2" | |
| rm -rf "${target}.previous" | |
| if [ -d "$target" ]; then | |
| mv "$target" "${target}.previous" | |
| fi | |
| mv "$newdir" "$target" | |
| } | |
| fix_chrome_sandbox() { | |
| local sandbox="$1" | |
| if [ -f "$sandbox" ]; then | |
| chown root:root "$sandbox" | |
| chmod 4755 "$sandbox" | |
| fi | |
| } | |
| refresh_desktop_caches() { | |
| if command -v update-desktop-database >/dev/null 2>&1; then | |
| update-desktop-database /usr/share/applications >/dev/null 2>&1 || true | |
| fi | |
| if command -v gtk-update-icon-cache >/dev/null 2>&1; then | |
| gtk-update-icon-cache -q /usr/share/icons/hicolor >/dev/null 2>&1 || true | |
| fi | |
| } | |
| installed_version() { | |
| cat "$1" 2>/dev/null || true | |
| } | |
| install_desktop_app() { | |
| local tmpdir="$1" | |
| local js="$2" | |
| local version url | |
| read -r version url < <(resolve_desktop_download "$js") | |
| local root="/opt/antigravity" | |
| local target="$root/$DESKTOP_TOP/antigravity" | |
| local version_file="$root/.antigravity-linux-version" | |
| if [ "$FORCE" -eq 0 ] && [ -x "$target" ] && [ "$(installed_version "$version_file")" = "$version" ]; then | |
| log "Antigravity 2.0 $version is already installed." | |
| return | |
| fi | |
| local archive="$tmpdir/Antigravity.tar.gz" | |
| (curl -fsSL --retry 3 -o "$archive" "$url") >/dev/null 2>&1 & | |
| spin $! "Downloading Antigravity 2.0 ($version)..." | |
| tar -tzf "$archive" > "$tmpdir/desktop-list.txt" | |
| local top_dir | |
| top_dir=$(sed -n '1{s#/.*##;p;q}' "$tmpdir/desktop-list.txt") | |
| tar -xzf "$archive" -C "$tmpdir" | |
| local icon_staged="$tmpdir/antigravity.png" | |
| if [ -f "$tmpdir/$top_dir/resources/app.asar" ]; then | |
| asar_extract_icon_png "$tmpdir/$top_dir/resources/app.asar" "$icon_staged" || warn "Could not extract desktop icon; continuing." | |
| fi | |
| rm -rf "${root}.new" | |
| mkdir -p "${root}.new" | |
| cp -a "$tmpdir/$top_dir" "${root}.new/" | |
| printf '%s\n' "$version" > "${root}.new/.antigravity-linux-version" | |
| printf '%s\n' "$url" > "${root}.new/.antigravity-linux-source-url" | |
| fix_chrome_sandbox "${root}.new/$top_dir/chrome-sandbox" | |
| safe_replace_dir "${root}.new" "$root" | |
| ln -sfn "$root/$top_dir/antigravity" /usr/local/bin/antigravity | |
| mkdir -p /usr/share/icons/hicolor/512x512/apps /usr/share/applications | |
| if [ -f "$icon_staged" ]; then | |
| install -m 0644 "$icon_staged" /usr/share/icons/hicolor/512x512/apps/antigravity.png | |
| fi | |
| cat > /usr/share/applications/antigravity.desktop <<DESKTOP | |
| [Desktop Entry] | |
| Name=Antigravity | |
| Comment=Google Antigravity 2.0 agent platform | |
| Exec=/usr/local/bin/antigravity %U | |
| Icon=antigravity | |
| Terminal=false | |
| Type=Application | |
| Categories=Development;IDE; | |
| StartupNotify=true | |
| StartupWMClass=Antigravity | |
| DESKTOP | |
| refresh_desktop_caches | |
| log "Configured Antigravity 2.0 $version at $root/$top_dir" | |
| } | |
| install_ide_app() { | |
| local tmpdir="$1" | |
| local js="$2" | |
| local version url | |
| read -r version url < <(resolve_ide_download "$js") | |
| local root="/opt/antigravity-ide" | |
| local install_dir="Antigravity-IDE" | |
| local version_file="$root/.antigravity-linux-version" | |
| if [ "$FORCE" -eq 0 ] && [ -x "$root/$install_dir/antigravity-ide" ] && [ "$(installed_version "$version_file")" = "$version" ]; then | |
| log "Antigravity IDE $version is already installed." | |
| return | |
| fi | |
| local archive="$tmpdir/Antigravity-IDE.tar.gz" | |
| (curl -fsSL --retry 3 -o "$archive" "$url") >/dev/null 2>&1 & | |
| spin $! "Downloading Antigravity IDE ($version)..." | |
| tar -tzf "$archive" > "$tmpdir/ide-list.txt" | |
| local top_dir | |
| top_dir=$(sed -n '1{s#/.*##;p;q}' "$tmpdir/ide-list.txt") | |
| tar -xzf "$archive" -C "$tmpdir" | |
| rm -rf "${root}.new" | |
| mkdir -p "${root}.new/$install_dir" | |
| cp -a "$tmpdir/$top_dir/." "${root}.new/$install_dir/" | |
| printf '%s\n' "$version" > "${root}.new/.antigravity-linux-version" | |
| printf '%s\n' "$url" > "${root}.new/.antigravity-linux-source-url" | |
| fix_chrome_sandbox "${root}.new/$install_dir/chrome-sandbox" | |
| safe_replace_dir "${root}.new" "$root" | |
| ln -sfn "$root/$install_dir/antigravity-ide" /usr/local/bin/antigravity-ide | |
| mkdir -p /usr/share/icons/hicolor/512x512/apps /usr/share/applications | |
| local icon_source="$root/$install_dir/resources/app/resources/linux/code.png" | |
| if [ -f "$icon_source" ]; then | |
| install -m 0644 "$icon_source" /usr/share/icons/hicolor/512x512/apps/antigravity-ide.png | |
| fi | |
| cat > /usr/share/applications/antigravity-ide.desktop <<DESKTOP | |
| [Desktop Entry] | |
| Name=Antigravity IDE | |
| Comment=Google Antigravity IDE | |
| Exec=/usr/local/bin/antigravity-ide %F | |
| Icon=antigravity-ide | |
| Terminal=false | |
| Type=Application | |
| Categories=Development;IDE; | |
| MimeType=inode/directory;text/plain;application/x-code-workspace;application/x-antigravity-workspace;x-scheme-handler/antigravity-ide; | |
| StartupNotify=true | |
| StartupWMClass=antigravity-ide | |
| DESKTOP | |
| refresh_desktop_caches | |
| log "Configured Antigravity IDE $version at $root/$install_dir" | |
| } | |
| install_cli_app() { | |
| local js="$1" | |
| local target_user="${SUDO_USER:-$USER}" | |
| local home_dir | |
| home_dir=$(eval echo "~$target_user") | |
| local cli_bin="$home_dir/.local/bin/agy" | |
| local local_version="none" | |
| if [ -x "$cli_bin" ]; then | |
| local_version=$("$cli_bin" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 || echo "none") | |
| fi | |
| local remote_version | |
| remote_version=$(resolve_cli_version "$js") | |
| if [ "$FORCE" -eq 0 ] && [ "$local_version" != "none" ] && [ "$local_version" = "$remote_version" ]; then | |
| log "Antigravity CLI (agy) $local_version is already installed." | |
| return | |
| fi | |
| if [ "$local_version" != "none" ]; then | |
| if [ "$remote_version" != "unknown" ]; then | |
| log "Updating Antigravity CLI from $local_version to $remote_version..." | |
| else | |
| log "Force updating Antigravity CLI (local: $local_version)..." | |
| fi | |
| # Delete binary to bypass Google's block message | |
| rm -f "$cli_bin" | |
| else | |
| if [ "$remote_version" != "unknown" ]; then | |
| log "Downloading Antigravity CLI ($remote_version)..." | |
| else | |
| log "Downloading Antigravity CLI..." | |
| fi | |
| fi | |
| local cmd="curl -fsSL '$CLI_INSTALLER' | bash" | |
| if [ "$target_user" != "root" ] && [ "$(id -u)" -eq 0 ]; then | |
| (sudo -u "$target_user" -H bash -lc "$cmd") >/dev/null 2>&1 & | |
| else | |
| (eval "$cmd") >/dev/null 2>&1 & | |
| fi | |
| spin $! "Installing Antigravity CLI..." | |
| if [ -x "$cli_bin" ]; then | |
| local installed_ver | |
| installed_ver=$("$cli_bin" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 || echo "unknown") | |
| log "Configured Antigravity CLI $installed_ver at $cli_bin" | |
| fi | |
| } | |
| uninstall_all() { | |
| require_root_or_reexec | |
| rm -rf /opt/antigravity /opt/antigravity-ide | |
| rm -f /usr/local/bin/antigravity /usr/local/bin/antigravity-ide /usr/local/bin/antigravity-linux | |
| rm -f /usr/share/applications/antigravity.desktop /usr/share/applications/antigravity-ide.desktop | |
| refresh_desktop_caches | |
| log "Removed Antigravity components successfully." | |
| } | |
| main() { | |
| if [ "$DO_STATUS" -eq 1 ]; then exit 0; fi | |
| if [ "$DO_UNINSTALL" -eq 1 ]; then uninstall_all; exit 0; fi | |
| require_root_or_reexec | |
| install_deps_debian | |
| local js | |
| js=$(resolve_main_bundle "$WORKSPACE") | |
| [ "$INSTALL_DESKTOP" -eq 1 ] && install_desktop_app "$WORKSPACE" "$js" | |
| [ "$INSTALL_IDE" -eq 1 ] && install_ide_app "$WORKSPACE" "$js" | |
| [ "$INSTALL_CLI" -eq 1 ] && install_cli_app "$js" | |
| log "✅ Installation complete. You're ready to go!" | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment