diff --git a/apps/find-my-iphone/build/README.md b/apps/find-my-iphone/build/README.md new file mode 100644 index 0000000..bc0df38 --- /dev/null +++ b/apps/find-my-iphone/build/README.md @@ -0,0 +1,13 @@ +# find-my-iphone image + +Node/Express bridge exposing Alexa + REST endpoints over the find-my-iphone +module (icloudjs SRP auth). Dockerfile lives in the source repo - +https://git.alexzaw.dev/alexz/find-my-iphone (working copy: +~/projects/find-my-iphone). Build + push with ./build.sh. + +Gotchas: +- APPLE_ID/APPLE_PASSWORD/API_KEY are required at boot or the container exits. +- The 2FA trust token is written to /data (mounted from ${APP_DATA_DIR}/data); + wiping that volume forces a new MFA round via POST /api/mfa. +- /alexa signature verification requires the raw request body - never put a + JSON body parser in front of it (server.js mounts express.json on /api only). diff --git a/apps/find-my-iphone/build/build.sh b/apps/find-my-iphone/build/build.sh new file mode 100644 index 0000000..a7b5cde --- /dev/null +++ b/apps/find-my-iphone/build/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail +IMAGE_NAME="${IMAGE_NAME:-git.alexzaw.dev/alexz/find-my-iphone}" +IMAGE_TAG="${IMAGE_TAG:-latest}" +SRC="${SRC:-$HOME/projects/find-my-iphone}" +docker build "$@" -t "${IMAGE_NAME}:${IMAGE_TAG}" "$SRC" +docker push "${IMAGE_NAME}:${IMAGE_TAG}" diff --git a/apps/find-my-iphone/config.json b/apps/find-my-iphone/config.json new file mode 100644 index 0000000..640058e --- /dev/null +++ b/apps/find-my-iphone/config.json @@ -0,0 +1,66 @@ +{ + "$schema": "../app-info-schema.json", + "name": "Find My iPhone Bridge", + "id": "find-my-iphone", + "available": true, + "port": 8590, + "exposable": true, + "dynamic_config": true, + "no_gui": true, + "tipi_version": 1, + "version": "1.2.0", + "categories": [ + "automation", + "utilities" + ], + "description": "Self-hosted bridge that makes your iPhone ring via iCloud Find My. Exposes an Alexa custom-skill endpoint (/alexa, signature-verified) and API-key-protected REST endpoints (/api/alert, /api/devices, /api/location). Apple credentials stay on this server; 2FA trust token persists in the app volume.", + "short_desc": "Ring your iPhone from Alexa or curl", + "author": "alexz (fork of matt-kruse/find-my-iphone)", + "source": "https://git.alexzaw.dev/alexz/find-my-iphone", + "form_fields": [ + { + "type": "text", + "label": "Apple ID", + "env_variable": "APPLE_ID", + "required": true, + "hint": "The iCloud account email that owns the devices" + }, + { + "type": "password", + "label": "Apple Password", + "env_variable": "APPLE_PASSWORD", + "required": true, + "hint": "Stored only in Runtipi's app env - never leaves this server" + }, + { + "type": "random", + "label": "API Key", + "env_variable": "API_KEY", + "required": true, + "min": 32, + "encoding": "hex", + "hint": "Auto-generated. Send as X-Api-Key header on /api/* requests. View in Settings tab." + }, + { + "type": "text", + "label": "Alexa Skill ID", + "env_variable": "ALEXA_SKILL_ID", + "required": false, + "hint": "amzn1.ask.skill.xxxx from the Alexa developer console. Locks /alexa to your skill. Leave blank until the skill exists, then fill in and save." + }, + { + "type": "text", + "label": "Default Device Name", + "env_variable": "DEFAULT_DEVICE", + "required": false, + "hint": "Device to ring when none is specified (e.g. Alex's iPhone). Blank = first Find-My-capable device." + } + ], + "supported_architectures": [ + "amd64" + ], + "created_at": 1754993000000, + "updated_at": 1754993000000, + "deprecated": false, + "min_tipi_version": "4.5.0" +} diff --git a/apps/find-my-iphone/docker-compose.json b/apps/find-my-iphone/docker-compose.json new file mode 100644 index 0000000..0658724 --- /dev/null +++ b/apps/find-my-iphone/docker-compose.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": 2, + "$schema": "https://schemas.runtipi.io/dynamic-compose.json", + "services": [ + { + "name": "find-my-iphone", + "image": "git.alexzaw.dev/alexz/find-my-iphone:latest", + "isMain": true, + "internalPort": 8080, + "environment": [ + { "key": "PORT", "value": "8080" }, + { "key": "DATA_DIR", "value": "/data" }, + { "key": "APPLE_ID", "value": "${APPLE_ID}" }, + { "key": "APPLE_PASSWORD", "value": "${APPLE_PASSWORD}" }, + { "key": "API_KEY", "value": "${API_KEY}" }, + { "key": "ALEXA_SKILL_ID", "value": "${ALEXA_SKILL_ID:-}" }, + { "key": "DEFAULT_DEVICE", "value": "${DEFAULT_DEVICE:-}" } + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data", + "containerPath": "/data", + "readOnly": false + } + ], + "healthCheck": { + "test": "curl -f http://127.0.0.1:8080/health || exit 1", + "interval": "30s", + "timeout": "5s", + "retries": 3, + "startPeriod": "10s" + } + } + ] +} diff --git a/apps/find-my-iphone/metadata/description.md b/apps/find-my-iphone/metadata/description.md new file mode 100644 index 0000000..a6840e2 --- /dev/null +++ b/apps/find-my-iphone/metadata/description.md @@ -0,0 +1,23 @@ +# Find My iPhone Bridge + +Ring your iPhone by voice ("Alexa, open phone finder") or curl, self-hosted. + +## Endpoints + +| Route | Auth | Purpose | +|-------|------|---------| +| `POST /alexa` | Alexa signature + skill ID | Custom skill endpoint | +| `GET /api/status` | X-Api-Key | Auth state: ready / mfa_required / error | +| `POST /api/mfa` | X-Api-Key | One-time 2FA bootstrap `{"code":"123456"}` | +| `GET /api/devices` | X-Api-Key | Device list with battery/status/location | +| `POST /api/alert` | X-Api-Key | Ring a device `{"device":"name"}` (optional) | +| `GET /api/location?device=` | X-Api-Key | Reverse-geocoded address | + +## First run (one time) + +1. Install with your Apple ID + password. +2. `GET /api/status` shows `mfa_required`; a code push arrives on your Apple devices. +3. `curl -X POST https:///api/mfa -H 'X-Api-Key: ' -H 'Content-Type: application/json' -d '{"code":"123456"}'` +4. Trust token persists in the app volume - no more codes needed. + +Source: https://git.alexzaw.dev/alexz/find-my-iphone