|
#!/usr/bin/env bash |
|
# |
|
# goose-mobile-tunnel.sh |
|
# |
|
# Stopgap to restore the goose mobile app <-> desktop connection. |
|
# |
|
# The mobile tunnel was removed from goose after v1.39.0 (PR #9932 / #10224). |
|
# This script builds the last release that still had the working tunnel |
|
# (v1.39.0) in an isolated git worktree, runs its `goosed` server on its own |
|
# port, and opens the Cloudflare tunnel the mobile app expects. |
|
# |
|
# It reads the SAME ~/.config/goose config + SQLite as your normal goose, and |
|
# makes NO code changes, so it is safe to run in parallel with your current |
|
# goose desktop/CLI. |
|
# |
|
# See: https://github.com/aaif-goose/goose/issues/10523 |
|
# |
|
# Usage: |
|
# ./goose-mobile-tunnel.sh # build (if needed), run, open tunnel |
|
# ./goose-mobile-tunnel.sh --rebuild # force a fresh rebuild |
|
# ./goose-mobile-tunnel.sh --stop # stop any running goosed started here |
|
# |
|
# Requirements: git, a Rust toolchain (or hermit, which the repo ships with), |
|
# curl, and openssl. macOS and Linux. |
|
# |
|
set -euo pipefail |
|
|
|
# ----- config (override via env if you like) -------------------------------- |
|
GOOSE_REPO="${GOOSE_REPO:-https://github.com/aaif-goose/goose.git}" |
|
TUNNEL_TAG="${TUNNEL_TAG:-v1.39.0}" # last release with the working tunnel |
|
WORKDIR="${WORKDIR:-$HOME/.goose-mobile-tunnel}" |
|
SRC_DIR="$WORKDIR/src" |
|
PORT="${GOOSE_PORT:-3010}" # own port; won't clash with desktop |
|
PIDFILE="$WORKDIR/goosed.pid" |
|
LOGFILE="$WORKDIR/goosed.log" |
|
SECRETFILE="$WORKDIR/server-secret" |
|
|
|
# ----- helpers -------------------------------------------------------------- |
|
say() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } |
|
warn() { printf '\033[1;33m!!\033[0m %s\n' "$*"; } |
|
die() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } |
|
|
|
stop_goosed() { |
|
if [[ -f "$PIDFILE" ]]; then |
|
local pid; pid="$(cat "$PIDFILE" 2>/dev/null || true)" |
|
if [[ -n "${pid:-}" ]] && kill -0 "$pid" 2>/dev/null; then |
|
say "Stopping goosed (pid $pid) and children..." |
|
pkill -P "$pid" 2>/dev/null || true |
|
kill "$pid" 2>/dev/null || true |
|
fi |
|
rm -f "$PIDFILE" |
|
fi |
|
# belt and suspenders: kill any goosed we built here |
|
pkill -f "$WORKDIR/.*goosed agent" 2>/dev/null || true |
|
} |
|
|
|
# ----- arg handling --------------------------------------------------------- |
|
REBUILD=false |
|
case "${1:-}" in |
|
--stop) stop_goosed; say "Stopped."; exit 0 ;; |
|
--rebuild) REBUILD=true ;; |
|
"" ) ;; |
|
* ) die "Unknown option: $1 (use --rebuild or --stop)" ;; |
|
esac |
|
|
|
command -v git >/dev/null || die "git is required" |
|
command -v curl >/dev/null || die "curl is required" |
|
command -v openssl>/dev/null || die "openssl is required" |
|
|
|
mkdir -p "$WORKDIR" |
|
|
|
# ----- 1. get the source at the last-good tag ------------------------------- |
|
if [[ ! -d "$SRC_DIR/.git" ]]; then |
|
say "Cloning goose @ $TUNNEL_TAG (shallow) into $SRC_DIR ..." |
|
git clone --depth 1 --branch "$TUNNEL_TAG" "$GOOSE_REPO" "$SRC_DIR" |
|
else |
|
say "Source already present at $SRC_DIR" |
|
fi |
|
|
|
cd "$SRC_DIR" |
|
|
|
# Prefer the repo's hermit toolchain if present, else fall back to system cargo. |
|
if [[ -f bin/activate-hermit ]]; then |
|
say "Activating hermit toolchain..." |
|
# shellcheck disable=SC1091 |
|
source bin/activate-hermit |
|
fi |
|
command -v cargo >/dev/null || die "cargo (Rust) not found. Install Rust: https://rustup.rs" |
|
|
|
# ----- 2. build goosed ------------------------------------------------------ |
|
BIN="$SRC_DIR/target/release/goosed" |
|
if [[ "$REBUILD" == true || ! -x "$BIN" ]]; then |
|
say "Building goosed (release). First build can take several minutes..." |
|
cargo build --release -p goose-server --bin goosed |
|
else |
|
say "Reusing existing build: $BIN" |
|
fi |
|
[[ -x "$BIN" ]] || die "Build did not produce $BIN" |
|
|
|
# ----- 3. run goosed on its own port ---------------------------------------- |
|
stop_goosed # ensure no stale instance from a previous run |
|
|
|
if [[ ! -f "$SECRETFILE" ]]; then |
|
openssl rand -hex 32 > "$SECRETFILE" |
|
fi |
|
SERVER_SECRET="$(cat "$SECRETFILE")" |
|
|
|
say "Starting goosed on https://127.0.0.1:$PORT (parallel-safe, own port)..." |
|
GOOSE_PORT="$PORT" GOOSE_SERVER__SECRET_KEY="$SERVER_SECRET" \ |
|
nohup "$BIN" agent >"$LOGFILE" 2>&1 & |
|
echo $! > "$PIDFILE" |
|
|
|
# wait for it to listen |
|
for _ in $(seq 1 30); do |
|
if grep -q "listening on" "$LOGFILE" 2>/dev/null; then break; fi |
|
sleep 0.5 |
|
done |
|
grep -q "listening on" "$LOGFILE" 2>/dev/null || { |
|
warn "goosed did not report 'listening' yet. Last log lines:"; tail -20 "$LOGFILE"; die "startup failed" |
|
} |
|
|
|
# ----- 4. open the tunnel --------------------------------------------------- |
|
say "Opening mobile tunnel..." |
|
RESP="$(curl -sk -X POST "https://127.0.0.1:$PORT/tunnel/start" \ |
|
-H "X-Secret-Key: $SERVER_SECRET")" |
|
|
|
extract() { printf '%s' "$RESP" | sed -n "s/.*\"$1\":\"\([^\"]*\)\".*/\1/p"; } |
|
TUNNEL_URL="$(extract url)" |
|
TUNNEL_SECRET="$(extract secret)" |
|
|
|
[[ -n "$TUNNEL_URL" && -n "$TUNNEL_SECRET" ]] || { |
|
warn "Could not start tunnel. Response was: $RESP"; die "tunnel start failed" |
|
} |
|
|
|
# ----- 5. show pairing details ---------------------------------------------- |
|
# This is the exact payload the desktop used to encode into the pairing QR code. |
|
PAIR_JSON="{\"url\":\"$TUNNEL_URL\",\"secret\":\"$TUNNEL_SECRET\"}" |
|
# urlencode for the deeplink |
|
PAIR_ENC="$(printf '%s' "$PAIR_JSON" | python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.stdin.read()))' 2>/dev/null || printf '%s' "$PAIR_JSON")" |
|
DEEPLINK="goosechat://configure?data=$PAIR_ENC" |
|
|
|
cat <<EOF |
|
|
|
$(say "Tunnel is UP. Pair your goose mobile app with these details:") |
|
|
|
Tunnel URL : $TUNNEL_URL |
|
Secret : $TUNNEL_SECRET |
|
|
|
To pair, either: |
|
* Enter the URL + Secret manually in the mobile app, OR |
|
* Generate a QR code from this deeplink and scan it with the app: |
|
|
|
$DEEPLINK |
|
|
|
EOF |
|
|
|
# Optional: render a QR in the terminal if 'qrencode' is installed. |
|
if command -v qrencode >/dev/null 2>&1; then |
|
say "QR code (scan with the goose mobile app):" |
|
qrencode -t ANSIUTF8 "$DEEPLINK" |
|
else |
|
warn "Install 'qrencode' (brew install qrencode) to render a scannable QR here." |
|
fi |
|
|
|
cat <<EOF |
|
Keep this terminal open (goosed runs in the background, logs: $LOGFILE). |
|
When you're done: $0 --stop |
|
|
|
Notes: |
|
* Runs alongside your normal goose (own port $PORT, same config/SQLite). |
|
* If the mobile app can't connect, check the log: tail -f "$LOGFILE" |
|
* Uninstall: $0 --stop && rm -rf "$WORKDIR" |
|
EOF |