diff --git a/README.md b/README.md index 4bb1412..a249bf1 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,25 @@ Start the `ipsw` daemon: +### macOS + +```bash +brew install blacktop/tap/ipswd +brew services start blacktop/tap/ipswd +``` + +### Linux + +> ⚠️ UNTESTED ⚠️ + +```bash +sudo snap install ipswd +``` + +### Docker + ```bash -git clone -b feature/api https://github.com/blacktop/ipsw.git -cd ipsw -IPSW_DAEMON_PORT=8080 go run ./cmd/ipswd/main.go start +docker run -d -p 3993:3993 -v `pwd`:/data blacktop/ipswd start ``` ## Installing @@ -38,7 +53,7 @@ pip install ipsw ```python import ipsw -client = ipsw.IpswClient(base_url='tcp://127.0.0.1:8080') +client = ipsw.IpswClient(base_url='tcp://127.0.0.1:3993') info = client.info.get("iPhone15,2_16.5_20F5028e_Restore.ipsw") print(f'{info.version} ({info.build})') for device in info.devices: diff --git a/ipsw/api/client.py b/ipsw/api/client.py index fa8de82..adfbd3a 100644 --- a/ipsw/api/client.py +++ b/ipsw/api/client.py @@ -20,6 +20,7 @@ from ..utils.socket import consume_socket_output, demux_adaptor, frames_iter from .daemon import DaemonApiMixin from .info import InfoApiMixin +from .macho import MachoApiMixin try: from ..transport import NpipeHTTPAdapter @@ -35,7 +36,8 @@ class APIClient( requests.Session, DaemonApiMixin, - InfoApiMixin): + InfoApiMixin, + MachoApiMixin): """ A low-level client for the ipsw API. diff --git a/ipsw/api/macho.py b/ipsw/api/macho.py new file mode 100644 index 0000000..bc1fd7b --- /dev/null +++ b/ipsw/api/macho.py @@ -0,0 +1,14 @@ +class MachoApiMixin: + def macho_info(self, path=None, arch=None): + """ + Display MachO header information. Identical to the ``ipsw macho info --json`` + command. + + Returns: + (dict): The info as a dict + + Raises: + :py:class:`ipsw.errors.APIError` + If the server returns an error. + """ + return self._result(self._get(self._url("/macho/info"), params={"path": path, "arch": arch}), True) diff --git a/ipsw/client.py b/ipsw/client.py index a8ec376..64b764c 100644 --- a/ipsw/client.py +++ b/ipsw/client.py @@ -1,6 +1,7 @@ from .api.client import APIClient from .constants import (DEFAULT_TIMEOUT_SECONDS, DEFAULT_MAX_POOL_SIZE) from .models.info import InfoCollection +from .models.macho import MachoCollection from .utils import kwargs_from_env @@ -78,6 +79,13 @@ def info(self): An object for getting local/remote IPSW/OTA info. """ return InfoCollection(client=self) + + @property + def macho(self): + """ + An object for getting MachO info. + """ + return MachoCollection(client=self) # Top-level methods def ping(self, *args, **kwargs): diff --git a/ipsw/models/macho.py b/ipsw/models/macho.py new file mode 100644 index 0000000..d2aab16 --- /dev/null +++ b/ipsw/models/macho.py @@ -0,0 +1,49 @@ +import os + +from ..api import APIClient +from .resource import Collection, Model + + +class Macho(Model): + """ + MachO info. + """ + + def __repr__(self): + return "<{}: '{} {} ({})'>".format( + self.__class__.__name__, + self.magic, + self.cpu, + self.sub_cpu, + ) + + @property + def magic(self): + """ + The header magic. + """ + return self.attrs["info"]['header'].get("magic", None) + + @property + def cpu(self): + """ + The header CPU. + """ + return self.attrs["info"]['header'].get("cpu", None) + + @property + def sub_cpu(self): + """ + The header sub CPU. + """ + return self.attrs["info"]['header'].get("subcpu", None) + + +class MachoCollection(Collection): + model = Macho + + def get(self, path=None, arch=None): + """ + Get MachO info. + """ + return self.prepare_model(self.client.api.macho_info(path, arch)) \ No newline at end of file