#!/bin/sh # Downloads and installs (or updates) the Genaia browser extension as an UNPACKED extension # in ~/.genaia/extension, for platforms where the registry-based force-install # (Install-GenaiaExtension.ps1 / genaia-extension.reg) is not available - macOS and Linux. # # Why this is a different mechanism than Windows: # On Windows, Chrome/Edge accept a per-user registry policy (HKCU ExtensionInstallForcelist) # that makes the browser install a *signed CRX* and poll extension-updates.xml for updates on # its own, with no developer mode and no user action after the first install. # # On macOS and Linux, Chrome/Edge only honor ExtensionInstallForcelist when the machine is # enrolled in an MDM (macOS) or has an enterprise policy file installed by root (Linux) - a # per-user setting a plain script can write is ignored by design, so there is no equivalent # "no admin required" path for CRX force-install on these platforms. # # The only thing a script can still do without admin/MDM is keep a folder on disk in sync as # an UNPACKED extension, which Chrome/Edge load once Developer Mode is enabled and the folder # is added via "Load unpacked". There is no silent auto-reload for unpacked extensions - the # user (or a future, explicitly-consented automation) must click "Reload" on # chrome://extensions / edge://extensions after an update, or restart the browser. # # On macOS this script also installs the Genaia Proxy root CA certificate into the login # keychain (see install_proxy_ca_macos below), so Teams/Slack traffic intercepted by Genaia # Proxy is trusted without the manual Keychain Access steps in # docs/proxy-ca-certificate-setup.md. Skipped on Linux/other platforms - no equivalent # no-admin-required step exists there yet (Linux needs sudo update-ca-certificates). # # Usage: # sh install-genaia-extension.sh # install, or update if already installed # curl -fsSL https://get.genaia.app/install-genaia-extension.sh | sh # # Safe to run repeatedly: a second run only replaces the folder contents when a newer version # is published; it never touches browser profiles or settings. The CA install step is also # idempotent - it checks the login keychain for a certificate with the same SHA-256 fingerprint # before adding anything, so re-running never creates a duplicate trust entry. set -eu PUBLISH_URL="https://get.genaia.app" PROXY_CA_URL="https://get.genaia.app/genaia-proxy-ca.pem" INSTALL_DIR="${GENAIA_EXTENSION_DIR:-$HOME/.genaia/extension}" VERSION_FILE="$INSTALL_DIR/.genaia-version.json" log() { printf '%s\n' "$*"; } err() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } command -v curl >/dev/null 2>&1 || err "curl is required" command -v unzip >/dev/null 2>&1 || err "unzip is required" TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/genaia-extension.XXXXXX") trap 'rm -rf "$TMP_DIR"' EXIT # --- Genaia Proxy CA certificate (macOS only) --------------------------------------------- # # Genaia Proxy intercepts HTTPS traffic (Teams/Slack) with its own MITM certificate authority. # Chrome/Edge on macOS trust the system (Keychain) store, so the only extra step compared to # the manual guide (docs/proxy-ca-certificate-setup.md) is running the same `security` command # the user would otherwise have to click through in Keychain Access. # # The CA is published as a static file at get.genaia.app/genaia-proxy-ca.pem (uploaded from the # same Secret the proxy pod mounts, genaia-proxy-ca in namespace genaia - see # docs/proxy-ca-certificate-setup.md), NOT fetched from the live proxy host # (prd.p.genaia.app:443). That host is TLS-passthrough straight into the MITM proxy's own # CONNECT/HTTP handling (helm/genaia-proxy/templates/ingress-tcp.yaml), which rejects any # request addressed to itself as a "proxy loop" (PROXY_BLOCKED_HOSTS in # proxy/src/proxy/mitm-proxy.ts) - GET .../ca-cert.pem against that host returns 403 and always # has, including through the "Download CA certificate" button in the extension's Proxy page. # # Not attempted on Linux: there is no single no-admin-required trust store there (NSS db for # Chrome vs. update-ca-certificates/update-ca-trust for the OS, both needing root for the # system path), so until that is designed deliberately, Linux users still follow the manual # guide. Not attempted on any other OS either - this is a Darwin-only step. # # Defined up front and called from every exit path below (including the early "already up to # date" exit) - a previous version only called it after the full extension install/update flow, # so a user whose extension was already current never got the CA step at all. install_proxy_ca_macos() { if [ "$(uname -s 2>/dev/null)" != "Darwin" ]; then return 0 fi if ! command -v security >/dev/null 2>&1 || ! command -v openssl >/dev/null 2>&1; then log "==> Skipping Genaia Proxy CA install (security or openssl not found)" return 0 fi log "" log "==> Checking Genaia Proxy CA certificate (login keychain)" CA_TMP_FILE="$TMP_DIR/genaia-proxy-ca.pem" if ! curl -fsSL "$PROXY_CA_URL" -o "$CA_TMP_FILE"; then log " Could not reach $PROXY_CA_URL - skipping" return 0 fi REMOTE_CA_FINGERPRINT=$(openssl x509 -in "$CA_TMP_FILE" -noout -fingerprint -sha256 2>/dev/null \ | sed 's/^.*=//') if [ -z "$REMOTE_CA_FINGERPRINT" ]; then log " Downloaded file is not a valid certificate - skipping" return 0 fi REMOTE_CA_SUBJECT_CN=$(openssl x509 -in "$CA_TMP_FILE" -noout -subject -nameopt multiline 2>/dev/null \ | sed -n 's/^ *commonName *= *//p' | head -n1) # Checking "is this certificate present in the keychain" is NOT the same as "is it trusted" - # `security find-certificate` only proves the former. If `add-trusted-cert` below is ever # interrupted (prompt dismissed, terminal closed, timeout) after the cert is added but before # trust is recorded, a presence-only check would see the fingerprint on the next run and # report "already trusted" forever, leaving the user with an untrusted cert and no way for # the script to self-heal. # # `security dump-trust-settings` (no -s/-d flag = login keychain's user trust domain) is the # authority on trust, not presence: verified empirically that with no trust records at all it # prints exactly "SecTrustSettingsCopyCertificates: No Trust Settings were found." on stderr # and exits 1, and once any cert has a trust record it instead lists "Cert N: " # lines (no digest, so match by the certificate's own Subject CN - every cert here was issued # by this proxy's own CA, so the CN is stable and unique enough for this check). ALREADY_TRUSTED=0 if [ -n "$REMOTE_CA_SUBJECT_CN" ] \ && security dump-trust-settings 2>/dev/null | grep -qF "$REMOTE_CA_SUBJECT_CN"; then ALREADY_TRUSTED=1 fi if [ "$ALREADY_TRUSTED" = "1" ]; then log " Already trusted (fingerprint $REMOTE_CA_FINGERPRINT) - nothing to do." return 0 fi # `add-trusted-cert` is idempotent on its own: if the certificate is already present in the # keychain (added by an earlier, interrupted run) it is not duplicated - the trust record is # just (re)written. Safe to call unconditionally once we know trust is missing. # # Capture stderr into the `if` condition itself (not a separate `VAR=$(...)` statement # followed by checking `$?`) - under `set -e` a failing command substitution assigned on its # own line can exit the whole script before the following `if` ever runs; putting the # assignment directly in the `if` test is the POSIX-safe idiom for "run this, keep going # either way, and branch on the result". log " Not yet trusted - adding to login keychain (you may be prompted to confirm)" if ADD_TRUSTED_CERT_ERROR=$(security add-trusted-cert -r trustRoot -k "$HOME/Library/Keychains/login.keychain-db" "$CA_TMP_FILE" 2>&1); then log " Installed Genaia Proxy CA certificate (fingerprint $REMOTE_CA_FINGERPRINT)" log " Restart Chrome/Edge for the change to take effect." else log " Could not add the certificate automatically: $ADD_TRUSTED_CERT_ERROR" log " Install it manually: see docs/proxy-ca-certificate-setup.md (macOS section)" fi } log "==> Checking published version" curl -fsSL "$PUBLISH_URL/extension-version.json" -o "$TMP_DIR/extension-version.json" \ || err "could not reach $PUBLISH_URL/extension-version.json" REMOTE_VERSION=$(sed -n 's/.*"version" *: *"\([^"]*\)".*/\1/p' "$TMP_DIR/extension-version.json" | head -n1) [ -n "$REMOTE_VERSION" ] || err "could not parse version from extension-version.json" LOCAL_VERSION="" if [ -f "$VERSION_FILE" ]; then LOCAL_VERSION=$(sed -n 's/.*"version" *: *"\([^"]*\)".*/\1/p' "$VERSION_FILE" | head -n1) fi if [ -n "$LOCAL_VERSION" ] && [ "$LOCAL_VERSION" = "$REMOTE_VERSION" ]; then log "==> Already up to date (version $LOCAL_VERSION) at $INSTALL_DIR" log " Nothing to do." install_proxy_ca_macos exit 0 fi if [ -n "$LOCAL_VERSION" ]; then log "==> Updating $LOCAL_VERSION -> $REMOTE_VERSION" else log "==> Installing version $REMOTE_VERSION" fi log "==> Downloading extension.zip" curl -fsSL "$PUBLISH_URL/extension.zip" -o "$TMP_DIR/extension.zip" \ || err "could not download $PUBLISH_URL/extension.zip" log "==> Unpacking" UNZIP_DIR="$TMP_DIR/unpacked" mkdir -p "$UNZIP_DIR" unzip -q "$TMP_DIR/extension.zip" -d "$UNZIP_DIR" # Replace the install directory atomically: unpack next to it, then swap, so a download/unzip # failure never leaves a half-written extension that Chrome would refuse to load. mkdir -p "$(dirname "$INSTALL_DIR")" rm -rf "$INSTALL_DIR.new" mv "$UNZIP_DIR" "$INSTALL_DIR.new" cp "$TMP_DIR/extension-version.json" "$INSTALL_DIR.new/.genaia-version.json" if [ -d "$INSTALL_DIR" ]; then rm -rf "$INSTALL_DIR.old" mv "$INSTALL_DIR" "$INSTALL_DIR.old" fi mv "$INSTALL_DIR.new" "$INSTALL_DIR" rm -rf "$INSTALL_DIR.old" log "==> Installed version $REMOTE_VERSION at $INSTALL_DIR" log "" if [ -z "$LOCAL_VERSION" ]; then log "First-time setup required in Chrome or Edge:" log " 1. Open chrome://extensions (or edge://extensions)" log " 2. Enable \"Developer mode\" (top right)" log " 3. Click \"Load unpacked\" and select:" log " $INSTALL_DIR" log "" log "Run this script again at any time to check for and apply updates." else log "Updated on disk. Chrome/Edge do not reload unpacked extensions automatically:" log " 1. Open chrome://extensions (or edge://extensions)" log " 2. Click the Reload icon on the Genaia extension card" log " (or just restart the browser)" fi log "" log "Run again any time to check for updates:" log " curl -fsSL $PUBLISH_URL/install-genaia-extension.sh | sh" install_proxy_ca_macos