#!/usr/bin/env bash # EmAGI: connect this Linux computer to your agent (screen, keyboard and mouse, files, and a # Bluetooth headset role for phone calls). One line in a Terminal: # curl -fsSL https://pcuse.emagi.co/pc.sh | bash # Stop it forever: # systemctl --user disable --now emagi-pc 2>/dev/null; rm -rf ~/.local/share/emagi-pc # rm -f ~/.config/autostart/emagi-pc.desktop ~/.config/systemd/user/emagi-pc.service # # Safety: the agent reaches this computer only with a 32-byte random key generated HERE that never # leaves it in readable form. The line you send the agent carries that key ENCRYPTED to the agent's # public key, so the key shown in chat is useless to anyone else. Every request to the bridge must # present the key; the tunnel address alone does nothing. You can stop it any time with the two # lines above. set -euo pipefail BASE="${EMAGI_PC_BASE:-https://pcuse.emagi.co}" D="$HOME/.local/share/emagi-pc"; mkdir -p "$D"; chmod 700 "$D" say(){ printf '\033[36m%s\033[0m\n' "$*"; } command -v apt-get >/dev/null || { echo "This needs an Ubuntu/Debian-based Linux (Linux Mint works). Tell your agent which system you have."; exit 1; } SUDO=sudo; [ "$(id -u)" = 0 ] && SUDO="" say "1/5 installing required packages (your password may be asked)..." export DEBIAN_FRONTEND=noninteractive $SUDO apt-get -o DPkg::Lock::Timeout=600 update -qq # screen and input: xdotool/scrot/wmctrl/imagemagick; accessibility tree: python3-gi, python3-pyatspi, # gir1.2-atspi-2.0; phone headset: pipewire + bluez (+ ofono on older pipewire). $SUDO apt-get -o DPkg::Lock::Timeout=600 install -y -qq \ xdotool scrot wmctrl imagemagick xclip \ python3 python3-dbus python3-gi python3-pyatspi gir1.2-atspi-2.0 \ pipewire pipewire-pulse wireplumber libspa-0.2-bluetooth bluez ofono \ curl openssl >/dev/null printf '%s ALL=(ALL) NOPASSWD: /usr/bin/apt-get, /usr/bin/systemctl, /usr/bin/bluetoothctl, /usr/sbin/rfkill, /usr/bin/journalctl\n' "$USER" \ | $SUDO tee /etc/sudoers.d/emagi-pc >/dev/null; $SUDO chmod 440 /etc/sudoers.d/emagi-pc # the accessibility tree is readable only when the desktop a11y bridge is on gsettings set org.gnome.desktop.interface toolkit-accessibility true 2>/dev/null || true say "2/5 enabling the headset role for phone calls..." PWV=$(pipewire --version 2>/dev/null | grep -o 'libpipewire [0-9.]*' | head -1 | awk '{print $2}'); PWV=${PWV:-0} if [ "$(printf '%s\n' 1.3.82 "$PWV" | sort -V | head -1)" = 1.3.82 ]; then BACKEND=native; else BACKEND=ofono; fi CFG="$HOME/.config/wireplumber/wireplumber.conf.d"; mkdir -p "$CFG" cat > "$CFG/51-emagi-hf.conf" </dev/null 2>&1 || true $SUDO systemctl enable --now bluetooth >/dev/null 2>&1 || true systemctl --user restart wireplumber pipewire pipewire-pulse 2>/dev/null || true for c in "power on" "system-alias EmAGI-$(hostname -s)" "pairable on" "discoverable on"; do bluetoothctl --timeout 3 $c >/dev/null 2>&1 || true done say "3/5 installing the EmAGI bridge..." curl -fsSL "$BASE/pc_bridge.py" -o "$D/pc_bridge.py.new" && mv "$D/pc_bridge.py.new" "$D/pc_bridge.py" curl -fsSL "$BASE/pc_pub.pem" -o "$D/emagi_pub.pem" CF="$D/cloudflared" if [ ! -x "$CF" ]; then A=$(uname -m); case "$A" in x86_64) A=amd64;; aarch64) A=arm64;; esac curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-$A" -o "$CF.new" && mv "$CF.new" "$CF"; chmod +x "$CF" fi [ -s "$D/key" ] || { head -c32 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$D/key"; chmod 600 "$D/key"; } [ -s "$D/id" ] || head -c8 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$D/id" say "4/5 starting the service (it comes back on its own after a reboot)..." START_CMD="/usr/bin/python3 $D/pc_bridge.py" if systemctl --user show-environment >/dev/null 2>&1; then mkdir -p "$HOME/.config/systemd/user" cat > "$HOME/.config/systemd/user/emagi-pc.service" </dev/null systemctl --user restart emagi-pc loginctl enable-linger "$USER" 2>/dev/null || $SUDO loginctl enable-linger "$USER" 2>/dev/null || true else # no user systemd: autostart on desktop login + start now mkdir -p "$HOME/.config/autostart" cat > "$HOME/.config/autostart/emagi-pc.desktop" </dev/null || true setsid nohup $START_CMD >"$D/bridge.log" 2>&1 & fi rm -f "$D/url"; URL="" for i in $(seq 1 90); do sleep 2; [ -s "$D/url" ] && { URL=$(cat "$D/url"); break; }; done [ -n "$URL" ] || { echo "The connection did not come up in 3 minutes. Check your internet and run the line again; log: journalctl --user -u emagi-pc or $D/bridge.log"; exit 1; } ENC=$(printf '%s %s' "$(cat "$D/id")" "$(cat "$D/key")" \ | openssl pkeyutl -encrypt -pubin -inkey "$D/emagi_pub.pem" \ -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha1 | base64 -w0) say "5/5 done. Nothing else to set up, the agent works on its own." echo echo " • On your phone (if you want calls): Bluetooth -> find \"EmAGI-$(hostname -s)\" -> connect, tap Yes on the phone." echo " • Send your agent this whole line (the key inside is encrypted, useless in chat):" echo echo "EMAGI-PC $URL $ENC" echo