GoogleFindMyToolsWebUi

GoogleFindMyToolsWebUi

Docker app from Commuting-Reliably6-Hardsh's Repository

Overview

A self-hosted app that logs into your Google account, polls your Find My Device trackers and Android phones, and forwards each new location to your own Traccar or Nextcloud PhoneTrack server - so you keep your location history on your own infrastructure instead of nowhere. One container, a web UI, nothing else to run. CAUTION: this holds long-lived Google account tokens and live device location data. It defaults to plain HTTP with no auth, meant for a trusted LAN - set HTTP_USER/HTTP_PASSWORD and/or HTTPS_ENABLED below before exposing it any further. First-time Google sign-in installs Chrome/Xvfb/noVNC on demand inside the container (needs outbound internet to Debian's mirrors and storage.googleapis.com the first time only), then walks you through the actual login right in the web UI - no separate VNC client needed. GitHub: https://github.com/P6g9YHK6/GoogleFindMyToolsWebUi

GoogleFindMyToolsWebUi logo

GoogleFindMyToolsWebUi

Lint and test

A self-hosted app that logs into your Google account, keeps polling your Find My Device / Find Hub trackers and Android phones, and forwards each new location to your own Traccar or Nextcloud PhoneTrack server, so you keep your location history on your own infrastructure instead of nowhere. One Docker container, a web UI, nothing else to run.

It's built on top of GoogleFindMyTools, the original reverse-engineering work that figured out how to talk to Google's Find My Device / Find Hub network at the protocol level (querying trackers, decrypting end-to-end encrypted locations, registering custom ESP32/Zephyr trackers). That original tool is still in here and still works as a set of Python scripts - see Advanced: running it as a CLI tool below. Everything described in the rest of this README is a self-hosted app built around it: a web UI, a scheduler, multi-destination forwarding, and the operational stuff (logging, throttling, auth, encryption at rest) a service you actually leave running needs.

[!CAUTION] This holds long-lived Google account tokens and live device location data. It's meant for local/LAN use - see Security below before exposing it any further than that.

Features

  • Devices page - every tracker and phone on your account, in one list. Manual "Locate" and "Play sound" buttons, plus whatever the last scheduled poll found. Shows last-seen time for phones and tags alike.
  • Scheduled forwarding, not a one-off export - each device polls on its own cron schedule and forwards to as many destinations as you want, each with its own schedule and its own alias. Every destination is really the same generic HTTP request builder (method, URL - query string and all - headers, body, all with {{latitude}}-style placeholders) - Traccar, and Nextcloud PhoneTrack's OsmAnd, GpsLogger, Locus Map, uLogger, OwnTracks, and Overland-compatible log endpoints are just one-time presets that pre-fill it when you're setting up a new endpoint, so you can add a custom endpoint (a different self-hosted service, a webhook, whatever takes HTTP) without waiting on this project to add it by name.
  • Skip pointless updates - two independent, opt-in gates per destination: skip sending if the device hasn't moved far enough, and skip re-sending the same stale cached fix Google keeps returning. Both are local math (haversine distance), no external API calls, no extra cost.
  • Logging - every forwarding attempt and every warning/error anywhere in the app (a failed locate, an expired token, a forwarding failure) lands in a searchable in-app log, with errors also pushed out live through Apprise to whatever you already use (ntfy, Discord, Telegram, Pushover, email, 100+ others) - configured from the Config page, no restart needed.
  • Register your own trackers - pair a custom ESP32- or Zephyr-based BLE tracker straight from the web UI.
  • Build and flash ESP32 firmware from the browser - the Firmware page bakes a registered tracker's key into an ESP32/ESP32-C3 build (device name, advertising interval, TX power, and the unwanted-tracking-protection flag are all configurable), then flashes it over USB via Web Serial, or gives you a plain .bin to flash yourself. Still marked experimental in the UI - hasn't been exercised across every board/setup yet. Other Zephyr-supported boards link out to a manual build/flash guide instead.
  • Account-wide rate limiting - every call to Google's backend (device list, locate, sound, register) goes through one shared throttle, tunable live from the Config page, so a burst of manual clicks and every device's poll loop can never combine into something that gets your account flagged.
  • /metrics - a small set of Prometheus-format gauges (uptime, sign-in status, query-throttle queue depth, forwarding/system log entry counts by outcome) if you already scrape other self-hosted services and want this one in the same dashboard. Behind the same Basic Auth as everything else when configured.

Screenshots

Screenshot_20260810_183230 Screenshot_20260810_190013 Screenshot_20260810_184047

Quick start

docker compose up -d

This pulls the pre-built image from ghcr.io/p6g9yhk6/googlefindmytools - no local build, no Chrome install on the host. Then open http://localhost:4321 and go to Config → Sign in with Google.

Chrome and the Xvfb/x11vnc/noVNC stack needed for that one-time Google login aren't baked into the image - they install on demand into an in-memory directory the first time you sign in. The Config page shows live progress while this happens, and you complete the actual Google login in an embedded browser view right there on the page - no separate machine, no VNC client needed.

Building from source instead: docker compose -f docker-compose.dev.yml up --build.

Configuration

Copy .env.example to .env and fill in what you need, or pass these directly to docker run/docker compose. Everything is optional - the defaults are a reasonable, auth-free single-user setup.

Variable Default What it does
HTTP_USER / HTTP_PASSWORD unset Set both to require this username/password pair (HTTP Basic Auth) for the whole web UI, including the embedded login view.
SECRETS_ENCRYPTION_KEY unset Any string. When set, every credential in auth.yaml (OAuth tokens, FCM credentials, vault keys, ...) is encrypted at rest (AES-256-GCM). Unset keeps the old plain-text behavior and logs a one-time startup warning saying so. Losing/changing this makes existing encrypted values unreadable; you'd need to sign in again to regenerate them.
TZ UTC Timezone for timestamps shown in the UI and logs.
GFMT_DATA_DIR /data (in the container) Where all persisted state lives: device/forwarding config, credentials, location history, logs - one flat directory, just mount a volume onto it.
DEFAULT_POLL_INTERVAL_S 300 Fallback poll interval for a newly added device before you set its own cron schedule.
LOCATE_CONCURRENCY 5 Max number of devices being actively located at once.
LOCATE_TIMEOUT_S 60 How long to wait for a single locate before giving up.
QUERY_THROTTLE_MAX / QUERY_THROTTLE_WINDOW_S / QUERY_MIN_SPREAD_S 20 / 60 / 1 Account-wide rate limit against Google's backend. Also editable live from the Config page - no restart needed.
HTTPS_ENABLED unset Set to 1 to serve HTTPS instead of HTTP on the same port, using a self-signed certificate generated automatically on first start and reused after that (not regenerated every restart). See Security.
GFMT_TLS_CERT_PATH / GFMT_TLS_KEY_PATH unset Bring your own cert/key instead of the self-signed one - both must point at existing files, or startup fails rather than silently falling back to self-signed.
GFMT_TLS_SAN unset Comma-separated extra hostnames/IPs to add to the generated self-signed cert (it always covers localhost/127.0.0.1/::1) - set this to your LAN hostname or static IP if you reach the box by either.
GFMT_TLS_VALIDITY_DAYS 825 How long a generated self-signed cert is valid for. Defaults to Apple's ATS cap (Safari/iOS/macOS reject longer-lived certs even after you manually trust them) - raise it if you don't care about Safari.
DEMO_MODE unset Set to 1 for a public-showcase demo instance: every page shows a fixed set of fake devices instead of a real account, real Google sign-in and every outbound network call are disabled, Firmware Build and Debug Export's live query are disabled, and nothing a visitor does is ever persisted - see Demo mode below.

The Config page also has fields for the query throttle and Apprise notification settings, applied immediately without a restart.

Demo mode

Set DEMO_MODE=1 to run a public-facing instance that shows off the UI with no real Google account and no real data at all - useful for a public demo link, a quick local screenshot/dev aid, or a CI/screenshot-testing fixture. It's the same image everyone else runs, just this one env var - not a separate build.

With it on:

  • Every page (Devices, Firmware, Forwarding Settings, Staleness, Logs, Config) shows the same fixed set of ~10 fake devices (deterministic, same every restart), realistic Bay Area coordinates, no movement.
  • Locate/Play Sound/Register Tracker all return a fake success instantly - nothing real is contacted.
  • Forwarding Settings comes pre-filled with example endpoints pointed at obviously-fake (.invalid) hosts; saving or "Send now" always simulates success and is never actually sent or persisted.
  • Staleness tracking shows a realistic mix of fresh/stale devices from the fixed dataset; toggling tracking works but is never persisted.
  • The Logs page shows canned example history instead of this process's own logs.
  • Firmware Build is disabled outright (not simulated) - it's a real multi-minute compile with a real toolchain download, too heavy/real to fake safely.
  • Debug Export's "Live API query" is disabled outright - it would otherwise run a real, uncached device-list + locate-all query. The logs-only export still works, with the canned demo logs.
  • Real Google sign-in is disabled server-side (not just the greyed-out button) - a public instance can never be made to spin up the real embedded-browser login flow.
  • Every outbound network call this process could make is additionally hard-blocked at a low level, regardless of what a visitor types into any form - see webui/demo_network_guard.py.
  • Nothing a visitor does is ever written to disk; a small 🚩 Demo flag next to the build id in the footer is the only visual tell.

Independently of DEMO_MODE, any normal instance (the env var unset) with no Google account signed in yet shows the same fake devices on the Devices page only, as an onboarding placeholder instead of an empty table - real sign-in stays fully live, and this never affects Settings/Staleness/Logs/Firmware/write-actions.

Security

[!CAUTION] By default this is plain HTTP with no auth - meant for a trusted LAN (or behind your own reverse proxy/VPN if you need remote access), not exposed directly to the public internet. HTTPS_ENABLED=1 gets you transport encryption (see below), but self-signed TLS still isn't the same as a real reverse proxy or VPN for anything beyond casual LAN use. The one supported way to expose this app to the public internet is DEMO_MODE=1, which runs with no real account, no real data, and no outbound network access at all.

  • Set HTTP_USER/HTTP_PASSWORD to gate the whole UI, including /docs (FastAPI's interactive API explorer) - it's one more route behind the same middleware, not a separate hole.
  • Set HTTPS_ENABLED=1 to serve HTTPS with an automatically generated, persisted self-signed certificate - no separate reverse proxy needed. Your browser will show a one-time "not trusted" warning the first time (expected for any self-signed cert, not a sign something's wrong) - accept/pin it, or point GFMT_TLS_CERT_PATH/GFMT_TLS_KEY_PATH at a real cert instead if you have one. This defeats passive network snooping but gives no identity guarantee the way a CA-signed cert does. It's a toggle, not a dual mode - with it on, plain http:// to the same port gets a connection reset, not a redirect.
  • Set SECRETS_ENCRYPTION_KEY to encrypt credentials at rest instead of plain YAML. Back this up somewhere alongside (or independent of) your GFMT_DATA_DIR volume: it's not stored anywhere itself, and losing or changing it makes every already-encrypted value in auth.yaml permanently unreadable - your only recovery is signing in again from scratch, not restoring the key.
  • Everything - config, credentials, logs - lives in the one data directory you control; nothing phones home except to Google's own APIs and (if you configure it) your Apprise notification targets.

Advanced: running it as a CLI tool

The original scripts this project is built on still work standalone, without Docker or the web UI - useful for scripting or if you just want to query a device once.

[!CAUTION] Before starting, ensure Chrome and Python are up to date - if Chrome isn't current, this will not work.

  • Clone this repository: git clone or download the ZIP file
  • cd GoogleFindMyToolsWebUi
  • Optional: create/activate a venv (python -m venv venv, then venv\Scripts\activate on Windows or source venv/bin/activate on Linux/macOS)
  • pip install -r requirements.txt
  • Install the latest Google Chrome
  • python main.py

On first run this walks you through the same Google sign-in as the web UI, storing the result in Auth/auth.yaml - copy that file to run on a headless machine without Chrome. (Upgrading from an older version that still has Auth/secrets.json migrates it automatically the first time it's read; the old file is left in place, untouched.) SECRETS_ENCRYPTION_KEY (see Configuration) applies here too.

TODO

  • Finish the live data scraper: battery level, wifi, wifi signal strength, force a phone update and any other data found via this api.
  • AirTag support, with the help of a reverse-engineered locate app: AirtagAlex, demo video.

Install GoogleFindMyToolsWebUi on Unraid in a few clicks.

Find GoogleFindMyToolsWebUi in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for GoogleFindMyToolsWebUi Review the template variables and paths Click Install

Requirements

Outbound internet access to Debian's package mirrors and storage.googleapis.com on first Google sign-in, to fetch Chrome/Xvfb/noVNC on demand.

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/p6g9yhk6/googlefindmytools:latest
Last Updated2026-09-27
First Seen2026-08-10

Runtime arguments

Web UI
http://[IP]:[PORT:4321]
Network
bridge
Shell
sh
Privileged
false
Extra Params
--shm-size=1g --tmpfs /run/gfmt-browser:rw,exec,nosuid,size=1g

Template configuration

WebUI PortPorttcp

Port the web UI (and its API) listens on.

Target
4321
Default
4321
Value
4321
DataPathrw

Everything persisted lives flat in here: Google auth tokens, per-device forwarding config, location history, and logs. Back this directory up.

Target
/data
Default
/mnt/user/appdata/googlefindmytools
Value
/mnt/user/appdata/googlefindmytools
HTTP_USERVariable

Set together with HTTP_PASSWORD to require this username/password pair (HTTP Basic Auth) for the whole web UI, including the embedded Google login view. Leave both blank to run without auth (trusted LAN only).

HTTP_PASSWORDVariable

Paired with HTTP_USER above - see its description.

SECRETS_ENCRYPTION_KEYVariable

Any string. When set, every credential in auth.yaml (OAuth tokens, FCM credentials, vault keys) is encrypted at rest with AES-256-GCM instead of stored as plain text. Losing or changing this makes existing encrypted values unreadable - your only recovery is signing in again.

HTTPS_ENABLEDVariable

Set to 1 to serve HTTPS instead of HTTP on the same port, using a self-signed certificate generated automatically on first start and reused after that. See the Security section of the README before turning this on.

TZVariable

Timezone for timestamps shown in the UI and logs, e.g. America/Chicago.

GFMT_TLS_SANVariable

Comma-separated extra hostnames/IPs to add to the generated self-signed HTTPS cert (it always covers localhost/127.0.0.1/::1) - set this to your LAN hostname or static IP if you reach this box by either.

GFMT_TLS_CERT_PATHVariable

Bring your own cert instead of the self-signed one - must point at an existing file inside the container (e.g. under /data), or startup fails rather than silently falling back to self-signed. Set alongside GFMT_TLS_KEY_PATH.

GFMT_TLS_KEY_PATHVariable

Paired with GFMT_TLS_CERT_PATH above - see its description.

GFMT_TLS_VALIDITY_DAYSVariable

How long a generated self-signed cert is valid for. Defaults to Apple's ATS cap (Safari/iOS/macOS reject longer-lived certs even after you manually trust them) - raise it if you don't care about Safari.

Default
825
DEFAULT_POLL_INTERVAL_SVariable

Fallback poll interval (seconds) for a newly added device before you set its own cron schedule.

Default
300
LOCATE_CONCURRENCYVariable

Max number of devices being actively located at once.

Default
5
LOCATE_TIMEOUT_SVariable

How long (seconds) to wait for a single locate before giving up.

Default
60
QUERY_THROTTLE_MAXVariable

Account-wide rate limit against Google's backend: max calls per QUERY_THROTTLE_WINDOW_S. Also editable live from the Config page - no restart needed.

Default
20
QUERY_THROTTLE_WINDOW_SVariable

Window (seconds) the QUERY_THROTTLE_MAX limit applies over.

Default
60
QUERY_MIN_SPREAD_SVariable

Minimum spacing (seconds) enforced between individual calls to Google's backend.

Default
1