Created
July 8, 2026 17:17
-
-
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
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
| #!/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