Skip to content

Instantly share code, notes, and snippets.

@R44VC0RP
Created July 8, 2026 17:17
Show Gist options
  • Select an option

  • Save R44VC0RP/da505bcc925c91dee0160a4c7577c1a3 to your computer and use it in GitHub Desktop.

Select an option

Save R44VC0RP/da505bcc925c91dee0160a4c7577c1a3 to your computer and use it in GitHub Desktop.
Generate an OpenMobile onboarding QR for a managed OpenCode2 V2 service over private Tailscale Serve
#!/bin/sh
set -eu
HTTPS_PORT="${OPENMOBILE_HTTPS_PORT:-4096}"
STATE_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/opencode/service.json"
MODE="preview"
if [ "${1:-}" = "--terminal" ]; then
MODE="terminal"
elif [ "${1:-}" != "" ]; then
echo "Usage: $0 [--terminal]" >&2
exit 2
fi
fail() {
echo "openmobile-qr: $*" >&2
exit 1
}
command -v jq >/dev/null 2>&1 || fail "jq is required."
command -v curl >/dev/null 2>&1 || fail "curl is required."
[ -f "$STATE_FILE" ] || fail "OpenCode2 service state was not found. Run 'opencode2 service start' first."
if [ "$(uname -s)" = "Darwin" ] && [ -x /Applications/Tailscale.app/Contents/MacOS/Tailscale ]; then
TAILSCALE=/Applications/Tailscale.app/Contents/MacOS/Tailscale
elif command -v tailscale >/dev/null 2>&1; then
TAILSCALE=$(command -v tailscale)
else
fail "Tailscale is required and must be connected on this computer and phone."
fi
LOCAL_URL=$(jq -er '.url | select(type == "string")' "$STATE_FILE") || fail "Managed service URL is missing."
PASSWORD=$(jq -er '.password | select(type == "string")' "$STATE_FILE") || fail "Managed service password is missing."
case "$LOCAL_URL" in
http://127.0.0.1:*|http://localhost:*|http://\[::1\]:*) ;;
*) fail "Managed OpenCode2 service must be bound to loopback, got: $LOCAL_URL" ;;
esac
HEALTH=$(curl -fsS --max-time 10 --user "opencode:$PASSWORD" "$LOCAL_URL/api/health") || fail "OpenCode2 is not healthy at $LOCAL_URL."
printf '%s' "$HEALTH" | jq -e '.healthy == true' >/dev/null || fail "Endpoint is not an OpenCode V2 server."
TS_STATUS=$($TAILSCALE status --json) || fail "Unable to read Tailscale status."
printf '%s' "$TS_STATUS" | jq -e '.BackendState == "Running" and .Self.Online == true' >/dev/null || fail "Tailscale is not connected."
DNS_NAME=$(printf '%s' "$TS_STATUS" | jq -er '.Self.DNSName | rtrimstr(".") | select(length > 0)') || fail "Tailscale MagicDNS is unavailable."
REMOTE_URL="https://$DNS_NAME:$HTTPS_PORT"
SERVE_STATUS=$($TAILSCALE serve status --json 2>/dev/null || printf '{}')
AUTHORITY="$DNS_NAME:$HTTPS_PORT"
CURRENT_PROXY=$(printf '%s' "$SERVE_STATUS" | jq -r --arg authority "$AUTHORITY" '.Web[$authority].Handlers["/"].Proxy // empty')
FUNNEL=$(printf '%s' "$SERVE_STATUS" | jq -r --arg authority "$AUTHORITY" '.Web[$authority].AllowFunnel // false')
[ "$FUNNEL" != "true" ] || fail "Tailscale port $HTTPS_PORT is publicly exposed by Funnel. Disable Funnel first."
if [ "$CURRENT_PROXY" != "$LOCAL_URL" ]; then
case "$CURRENT_PROXY" in
""|http://127.0.0.1:*|http://localhost:*|http://\[::1\]:*) ;;
*) fail "Tailscale HTTPS port $HTTPS_PORT is already used by $CURRENT_PROXY." ;;
esac
$TAILSCALE serve --bg --https="$HTTPS_PORT" "$LOCAL_URL" >/dev/null || fail "Could not configure Tailscale Serve."
fi
curl -fsS --max-time 15 --user "opencode:$PASSWORD" "$REMOTE_URL/api/health" | jq -e '.healthy == true' >/dev/null || fail "V2 health check failed through $REMOTE_URL."
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/openmobile-qr.XXXXXX")
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT HUP INT TERM
PAYLOAD_FILE="$TMP_DIR/payload.json"
QR_FILE="$TMP_DIR/openmobile-v2.png"
umask 077
jq -n \
--arg url "$REMOTE_URL" \
--arg username "opencode" \
--arg password "$PASSWORD" \
'{type:"opencode.mobile.server",version:1,url:$url,username:$username,password:$password,name:"OpenCode V2"}' > "$PAYLOAD_FILE"
unset PASSWORD
if command -v qrcode >/dev/null 2>&1; then
QRCODE=$(command -v qrcode)
QRCODE_PREFIX=""
elif command -v npx >/dev/null 2>&1; then
QRCODE=npx
QRCODE_PREFIX="--yes --package qrcode@1.5.4 qrcode"
else
fail "Node.js npx or the qrcode CLI is required to render the QR code."
fi
render_terminal() {
if [ -n "$QRCODE_PREFIX" ]; then
# shellcheck disable=SC2086
"$QRCODE" $QRCODE_PREFIX --error M --qzone 4 "$(cat "$PAYLOAD_FILE")"
else
"$QRCODE" --error M --qzone 4 "$(cat "$PAYLOAD_FILE")"
fi
printf '\n%s · V2\n' "$REMOTE_URL"
}
if [ "$MODE" = "terminal" ] || [ "$(uname -s)" != "Darwin" ]; then
render_terminal
exit 0
fi
if [ -n "$QRCODE_PREFIX" ]; then
# shellcheck disable=SC2086
"$QRCODE" $QRCODE_PREFIX --type png --error M --qzone 6 --width 1200 --output "$QR_FILE" "$(cat "$PAYLOAD_FILE")"
else
"$QRCODE" --type png --error M --qzone 6 --width 1200 --output "$QR_FILE" "$(cat "$PAYLOAD_FILE")"
fi
rm -f "$PAYLOAD_FILE"
printf 'Opening OpenMobile QR for %s. Close Preview to delete the image.\n' "$REMOTE_URL"
/usr/bin/open -W -n -a Preview "$QR_FILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment