bump pymd3

This commit is contained in:
2026-04-04 22:30:27 -04:00
parent 94cb2441e2
commit 5dbbeb3394
4 changed files with 137 additions and 112 deletions

Binary file not shown.

View File

@@ -11,7 +11,7 @@ dependencies = [
"numpy==2.4.3", "numpy==2.4.3",
"pydantic==2.12.5", "pydantic==2.12.5",
"pyicloud>=2.4.1", "pyicloud>=2.4.1",
"pymobiledevice3==9.0.0", "pymobiledevice3==9.8.1",
"python-dotenv>=1.2.2", "python-dotenv>=1.2.2",
"python-socketio==5.16.1", "python-socketio==5.16.1",
"sqlalchemy>=2.0.48", "sqlalchemy>=2.0.48",

View File

@@ -11,16 +11,17 @@ import random
import math import math
import socketio import socketio
import httpx import httpx
from contextlib import asynccontextmanager, suppress from contextlib import suppress
from typing import Optional, Dict from typing import Optional, Dict
from dotenv import load_dotenv from dotenv import load_dotenv
with warnings.catch_warnings(): with warnings.catch_warnings():
# Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater." # Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater."
warnings.simplefilter("ignore", category=UserWarning) warnings.simplefilter("ignore", category=UserWarning)
warnings.simplefilter("ignore", category=UserWarning)
import fastapi import fastapi
import uvicorn import uvicorn
from fastapi import FastAPI, APIRouter, Request from fastapi import FastAPI, APIRouter, Request, Response
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@@ -140,16 +141,6 @@ class TunneldRunnerSio:
usbmux_monitor: bool = True, usbmux_monitor: bool = True,
mobdev2_monitor: bool = True, mobdev2_monitor: bool = True,
) -> None: ) -> None:
# instance = cls(
# host,
# port,
# protocol=protocol,
# usb_monitor=usb_monitor,
# wifi_monitor=wifi_monitor,
# usbmux_monitor=usbmux_monitor,
# mobdev2_monitor=mobdev2_monitor,
# context=context,)
# asyncio.run(instance._run_app())
cls( cls(
host, host,
port, port,
@@ -158,7 +149,8 @@ class TunneldRunnerSio:
wifi_monitor=wifi_monitor, wifi_monitor=wifi_monitor,
usbmux_monitor=usbmux_monitor, usbmux_monitor=usbmux_monitor,
mobdev2_monitor=mobdev2_monitor, mobdev2_monitor=mobdev2_monitor,
context=context)._run_app() context=context,
)._run_app()
def __init__( def __init__(
self, self,
@@ -171,11 +163,12 @@ class TunneldRunnerSio:
usbmux_monitor: bool = True, usbmux_monitor: bool = True,
mobdev2_monitor: bool = True, mobdev2_monitor: bool = True,
): ):
@asynccontextmanager async def app_startup() -> None:
async def lifespan(app: FastAPI): logger.info("Application startup: starting tunneld core and watcher")
self._tunneld_core.start() self._tunneld_core.start()
await start_tunnel_watcher() await start_tunnel_watcher()
yield
async def app_shutdown() -> None:
logger.info("Closing tunneld tasks...") logger.info("Closing tunneld tasks...")
await end_tunnel_watcher() await end_tunnel_watcher()
await end_icloud_monitor() await end_icloud_monitor()
@@ -195,10 +188,14 @@ class TunneldRunnerSio:
self._vue_dist = os.getenv("VUE_DIST") self._vue_dist = os.getenv("VUE_DIST")
self._vue_app = FastAPI( self._vue_app = FastAPI(
title="iOS Device Management API", title="iOS Device Management API",
lifespan=lifespan,
cors_allowed_origins="*", cors_allowed_origins="*",
) )
self._asgi_app = socketio.ASGIApp(self.context.sio, self._vue_app) self._asgi_app = socketio.ASGIApp(
self.context.sio,
self._vue_app,
on_startup=app_startup,
on_shutdown=app_shutdown,
)
self.context.icloud_monitor.sio = self.context.sio self.context.icloud_monitor.sio = self.context.sio
self.context.icloud_monitor.get_client_sids = lambda: list( self.context.icloud_monitor.get_client_sids = lambda: list(
self.context.connected_clients self.context.connected_clients
@@ -347,6 +344,7 @@ class TunneldRunnerSio:
async def tunnel_watcher_loop() -> None: async def tunnel_watcher_loop() -> None:
previous = collect_active_tunnels() previous = collect_active_tunnels()
while True: while True:
try:
await asyncio.sleep(1) await asyncio.sleep(1)
current = collect_active_tunnels() current = collect_active_tunnels()
added_keys = set(current.keys()) - set(previous.keys()) added_keys = set(current.keys()) - set(previous.keys())
@@ -354,6 +352,7 @@ class TunneldRunnerSio:
added = [current[k] for k in sorted(added_keys)] added = [current[k] for k in sorted(added_keys)]
removed = [previous[k] for k in sorted(removed_keys)] removed = [previous[k] for k in sorted(removed_keys)]
for item in added: for item in added:
try:
logger.info( logger.info(
"Tunnel discovered interface=%s udid=%s address=%s port=%s transport=%s", "Tunnel discovered interface=%s udid=%s address=%s port=%s transport=%s",
item.get("interface"), item.get("interface"),
@@ -368,25 +367,33 @@ class TunneldRunnerSio:
if self.context.udid is None: if self.context.udid is None:
self.context.udid = item.get("udid") self.context.udid = item.get("udid")
device_name = await get_device_name() device_name = await get_device_name()
if device_name != self.context.device_name: selected_device_name = self.context.device_name
if selected_device_name and device_name != selected_device_name:
wrong_udid = self.context.udid
self._tunneld_core.cancel(udid=item.get("udid")) self._tunneld_core.cancel(udid=item.get("udid"))
logger.warning( logger.warning(
"Tunnel established to wrong device. Dropping tunnel. wrong_udid=%s for device: %s", "Tunnel established to wrong device. Dropping tunnel. wrong_udid=%s for device: %s",
self.context.udid, self.context.device_name) wrong_udid, selected_device_name)
self.context.udid = None self.context.udid = None
await safe_sio_emit( await safe_sio_emit(
"appError", "appError",
{ {
"type": "tunnel_wrong_device", "type": "tunnel_wrong_device",
"message": "Tunnel established to wrong device. Dropping tunnel.", "message": "Tunnel established to wrong device. Dropping tunnel.",
"wrong_udid": self.context.udid, "wrong_udid": wrong_udid,
"device_name": device_name, "device_name": device_name,
}, },
) )
await get_tun(item.get("udid"), max_retries=10, retry_delay=1.0) await get_tun(item.get("udid"), max_retries=10, retry_delay=1.0)
except Exception:
logger.exception(
"Tunnel watcher failed while handling added tunnel for udid=%s",
item.get("udid"),
)
if removed: if removed:
for item in removed: for item in removed:
try:
logger.warning( logger.warning(
"Tunnel disconnected interface=%s udid=%s address=%s port=%s", "Tunnel disconnected interface=%s udid=%s address=%s port=%s",
item.get("interface"), item.get("interface"),
@@ -395,12 +402,26 @@ class TunneldRunnerSio:
item.get("port"), item.get("port"),
) )
await safe_sio_emit("tunnel_device_disconnected", item) await safe_sio_emit("tunnel_device_disconnected", item)
except Exception:
logger.exception(
"Tunnel watcher failed while emitting disconnection for udid=%s",
item.get("udid"),
)
await handle_tunnel_drop(removed) await handle_tunnel_drop(removed)
previous = current previous = current
except asyncio.CancelledError:
raise
except Exception:
logger.exception("Tunnel watcher loop crashed; continuing")
async def start_tunnel_watcher() -> None: async def start_tunnel_watcher() -> None:
if self.context.tunnel_watcher_task is None or self.context.tunnel_watcher_task.done(): if self.context.tunnel_watcher_task is None or self.context.tunnel_watcher_task.done():
if self.context.tunnel_watcher_task is not None and self.context.tunnel_watcher_task.done():
with suppress(Exception):
exc = self.context.tunnel_watcher_task.exception()
if exc is not None:
logger.error("Previous tunnel watcher task exited with error: %s", exc)
self.context.tunnel_watcher_task = asyncio.create_task( self.context.tunnel_watcher_task = asyncio.create_task(
tunnel_watcher_loop(), tunnel_watcher_loop(),
name="tunnel-task-watcher", name="tunnel-task-watcher",
@@ -1303,17 +1324,49 @@ class TunneldRunnerSio:
logger.info("Reverse Geocoded %s to %s", coords, rev_geocode) logger.info("Reverse Geocoded %s to %s", coords, rev_geocode)
return generate_http_response(rev_geocode) return generate_http_response(rev_geocode)
@self._app.post("/proxy/ors/{full_path:path}") @self._app.get("/ors/status")
async def app_ors_status(request: Request) -> fastapi.Response:
logger.info("request: %s", request)
target_url = "https://ors.intrepidnet.org/ors/v2/status"
async with httpx.AsyncClient() as client:
# Forward the request to the external service
response = await client.get(target_url, params=request.query_params)
# Return the response content and status code back to the original client
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers)
)
@self._app.get("/ors/health")
async def app_ors_status(request: Request) -> fastapi.Response:
logger.info("request: %s", request)
target_url = "https://ors.intrepidnet.org/ors/v2/health"
async with httpx.AsyncClient() as client:
# Forward the request to the external service
response = await client.get(target_url, params=request.query_params)
# Return the response content and status code back to the original client
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers)
)
@self._app.post("/ors/proxy/{full_path:path}")
async def app_proxy_ors(full_path: str, request: Request): async def app_proxy_ors(full_path: str, request: Request):
logger.info("request: %s", request) logger.info("request: %s", request)
body = await request.body() body = await request.json()
headers = dict(request.headers) headers = dict(request.headers)
method = request.method method = request.method
url = "https://ors.intrepidnet.org/" + full_path url = "https://ors.intrepidnet.org/" + full_path
logger.info("body: %s", body)
logger.info("headers: %s", headers)
logger.info("method: %s", method)
logger.info("url: %s", url)
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.request(method, url, headers=headers, content=body) response = await client.request(method, url, json=body)
return response.content return response.json()
""" Socket.IO Functions""" """ Socket.IO Functions"""

52
uv.lock generated
View File

@@ -688,21 +688,20 @@ wheels = [
[[package]] [[package]]
name = "ipsw-parser" name = "ipsw-parser"
version = "1.5.0" version = "1.6.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cached-property" },
{ name = "click" },
{ name = "coloredlogs" }, { name = "coloredlogs" },
{ name = "construct" }, { name = "construct" },
{ name = "plumbum" }, { name = "plumbum" },
{ name = "pyimg4" }, { name = "pyimg4" },
{ name = "remotezip2" }, { name = "remotezip2" },
{ name = "requests" }, { name = "requests" },
{ name = "typer" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/2c/96/c820ec1e2f50d398c2442b2f8759448a896a300745c2920c4cf7ba89dd4c/ipsw_parser-1.5.0.tar.gz", hash = "sha256:5becd2000017b7b8549cc6b6e3f5149541792bf6353663d0f77932965bf26aad", size = 51957, upload-time = "2025-11-18T21:57:37.881Z" } sdist = { url = "https://files.pythonhosted.org/packages/fc/8a/bae825b78838c86b05bd7ca3bf26e98dfcf3d0955619fe717402a628dfa7/ipsw_parser-1.6.0.tar.gz", hash = "sha256:d3121ae9f2a12bae6604972a9e00f6168cdedb3ad3db6b178061b4995e8fd14f", size = 53471, upload-time = "2026-03-17T20:27:27.524Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/99/b5805555d14abf34d9534c9e8ba81d629107fa5d3bd7f110ca752a21d1c2/ipsw_parser-1.5.0-py3-none-any.whl", hash = "sha256:740248a1937d7b7dacc5d98e758b578ed88c357638126a8a71d4b0c100a3c9eb", size = 36611, upload-time = "2025-11-18T21:57:36.975Z" }, { url = "https://files.pythonhosted.org/packages/a8/a3/9671c80252fc7a8b870b01639c3ae821d39f9489ac346bce7d8bfbf168f4/ipsw_parser-1.6.0-py3-none-any.whl", hash = "sha256:0e1f2b726c053617c65b0cef04afeecdf73ad14cc17e7e58d6382209d6693466", size = 37623, upload-time = "2026-03-17T20:27:26.518Z" },
] ]
[[package]] [[package]]
@@ -834,21 +833,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/0d/9c59313ab43d0858a9a665e80763bd830dc78d5f379afc3815e123c486c2/keyrings.alt-5.0.2-py3-none-any.whl", hash = "sha256:6be74693192f3f37bbb752bfac9b86e6177076b17d2ac12a390f1d6abff8ac7c", size = 17930, upload-time = "2024-08-14T01:09:26.785Z" }, { url = "https://files.pythonhosted.org/packages/4a/0d/9c59313ab43d0858a9a665e80763bd830dc78d5f379afc3815e123c486c2/keyrings.alt-5.0.2-py3-none-any.whl", hash = "sha256:6be74693192f3f37bbb752bfac9b86e6177076b17d2ac12a390f1d6abff8ac7c", size = 17930, upload-time = "2024-08-14T01:09:26.785Z" },
] ]
[[package]]
name = "la-panic"
version = "0.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cached-property" },
{ name = "click" },
{ name = "coloredlogs" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/85/28/757e1ccd939162caa27c8a6173d490deb986c38a7fd73fe2f264f6d7485d/la-panic-0.5.0.tar.gz", hash = "sha256:5239025d1e96aaed1fbd1c4a5d35572fd70cf42ddd68839ff1e4f1d21e3e279b", size = 57320, upload-time = "2023-11-12T17:49:33.159Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/63/8a/59eb5256cb0449c6f052453a1ce1fc32eed77ebd93b6be43f0136a7428a8/la_panic-0.5.0-py3-none-any.whl", hash = "sha256:5e224e5d038a020897606baf36e3aecbd33a5e56d82d0ac9cd72bcc0d5c3007d", size = 51866, upload-time = "2023-11-12T17:49:31.277Z" },
]
[[package]] [[package]]
name = "loguru" name = "loguru"
version = "0.7.3" version = "0.7.3"
@@ -1191,16 +1175,14 @@ wheels = [
[[package]] [[package]]
name = "pycrashreport" name = "pycrashreport"
version = "1.2.7" version = "2.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cached-property" }, { name = "typer" },
{ name = "click" },
{ name = "la-panic" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d5/0b/534e02c6badef117b5a4054c065b50e6055d833b983b024d57afb425b618/pycrashreport-1.2.7.tar.gz", hash = "sha256:8f4d52d3292c1ec479fac589633d1477d19fbeb6ab01960969389c6a9c63ed7f", size = 100834, upload-time = "2025-08-25T07:15:42.872Z" } sdist = { url = "https://files.pythonhosted.org/packages/aa/9c/1e1c2d84f746972cbe4d5e65f816008c64e427fc680f55115aa313683c23/pycrashreport-2.0.0.tar.gz", hash = "sha256:31d5e32faa3a047fe01e923bde3eaf1ec86b23e264babf2fc8f7b61fe4812342", size = 102682, upload-time = "2026-03-30T22:35:19.705Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/09/ac/751f4e55f5f3de5f152a5891682da3c25a8c57c7058a2274ca5a430a9fe2/pycrashreport-1.2.7-py3-none-any.whl", hash = "sha256:70a448ec44b86b016ce81346552d32400cc2c51d7e1928420e98a37461319866", size = 33056, upload-time = "2025-08-25T07:15:41.323Z" }, { url = "https://files.pythonhosted.org/packages/ed/ec/be636536ab13a63e3385a82c841d08ce2a51712ffc2a337cd24afc12e038/pycrashreport-2.0.0-py3-none-any.whl", hash = "sha256:12feb922c3349cdb33b468d105915f5f829c53df25640c5c2241bc4182bdb422", size = 34244, upload-time = "2026-03-30T22:35:18.478Z" },
] ]
[[package]] [[package]]
@@ -1387,7 +1369,7 @@ requires-dist = [
{ name = "numpy", specifier = "==2.4.3" }, { name = "numpy", specifier = "==2.4.3" },
{ name = "pydantic", specifier = "==2.12.5" }, { name = "pydantic", specifier = "==2.12.5" },
{ name = "pyicloud", specifier = ">=2.4.1" }, { name = "pyicloud", specifier = ">=2.4.1" },
{ name = "pymobiledevice3", specifier = "==9.0.0" }, { name = "pymobiledevice3", specifier = "==9.8.1" },
{ name = "python-dotenv", specifier = ">=1.2.2" }, { name = "python-dotenv", specifier = ">=1.2.2" },
{ name = "python-socketio", specifier = "==5.16.1" }, { name = "python-socketio", specifier = "==5.16.1" },
{ name = "sqlalchemy", specifier = ">=2.0.48" }, { name = "sqlalchemy", specifier = ">=2.0.48" },
@@ -1399,12 +1381,11 @@ requires-dist = [
[[package]] [[package]]
name = "pymobiledevice3" name = "pymobiledevice3"
version = "9.0.0" version = "9.8.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asn1" }, { name = "asn1" },
{ name = "bpylist2" }, { name = "bpylist2" },
{ name = "click" },
{ name = "coloredlogs" }, { name = "coloredlogs" },
{ name = "construct" }, { name = "construct" },
{ name = "construct-typing" }, { name = "construct-typing" },
@@ -1445,9 +1426,9 @@ dependencies = [
{ name = "wsproto" }, { name = "wsproto" },
{ name = "xonsh" }, { name = "xonsh" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/1d/89/aed5abbb6a4ece29bed2b9e85ccfbc28a8197658a4923e8cd2d5193796d8/pymobiledevice3-9.0.0.tar.gz", hash = "sha256:e85c169d67cf17d1dcf4ce26e3a84a801e86d13a0144ff7fb57eb532745ddcfb", size = 735101, upload-time = "2026-03-11T08:37:05.172Z" } sdist = { url = "https://files.pythonhosted.org/packages/a1/0e/81b8c391929cb2e2c9c831968df5c9cabc5687ab5d45fc9c345fcdb48171/pymobiledevice3-9.8.1.tar.gz", hash = "sha256:621479ad545795e52af1955e0cc6c8e807b51d81442173b3386f9d1fef21308a", size = 758015, upload-time = "2026-03-31T15:00:55.143Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/ae/aaff1375b383c78729b6b25abac1458e9fd5c0c84b8da364bf204559dc16/pymobiledevice3-9.0.0-py3-none-any.whl", hash = "sha256:7366533cc8807299ef0b88c6c56a77120f7d984914ec6436191a7cc2991d3ae7", size = 789609, upload-time = "2026-03-11T08:37:01.731Z" }, { url = "https://files.pythonhosted.org/packages/1a/58/3275db73730e99a6345231a7deeef5b184bd6a26b5dd40adefcb5ad0f84d/pymobiledevice3-9.8.1-py3-none-any.whl", hash = "sha256:9e1a5c8277a8a7955544579f118da8645f83ac05a1a03e79a96307242089842a", size = 797514, upload-time = "2026-03-31T15:00:53.169Z" },
] ]
[[package]] [[package]]
@@ -1661,15 +1642,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
] ]
[[package]]
name = "setuptools"
version = "82.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" },
]
[[package]] [[package]]
name = "shellingham" name = "shellingham"
version = "1.5.4" version = "1.5.4"