From cabfa7b735fd89e11beb142a663d15c0d67f9859 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:45:11 -0800 Subject: [PATCH] Revert "esim: remove bootstrap and delete (#36732)" (#36747) This reverts commit 6d04251517cadb8ad5001bc1e223f93133ce041f. --- system/hardware/base.py | 11 +++++++++++ system/hardware/esim.py | 36 +++++++++++++++++++++++++++++++++++- system/hardware/tici/esim.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 4163b33791..17d0ec1614 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -65,6 +65,10 @@ class ThermalConfig: return ret class LPABase(ABC): + @abstractmethod + def bootstrap(self) -> None: + pass + @abstractmethod def list_profiles(self) -> list[Profile]: pass @@ -73,6 +77,10 @@ class LPABase(ABC): def get_active_profile(self) -> Profile | None: pass + @abstractmethod + def delete_profile(self, iccid: str) -> None: + pass + @abstractmethod def download_profile(self, qr: str, nickname: str | None = None) -> None: pass @@ -85,6 +93,9 @@ class LPABase(ABC): def switch_profile(self, iccid: str) -> None: pass + def is_comma_profile(self, iccid: str) -> bool: + return any(iccid.startswith(prefix) for prefix in ('8985235',)) + class HardwareBase(ABC): @staticmethod def get_cmdline() -> dict[str, str]: diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 1c98bb1e4e..909ad41e03 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -3,21 +3,55 @@ import argparse import time from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware.base import LPABase + + +def bootstrap(lpa: LPABase) -> None: + print('┌──────────────────────────────────────────────────────────────────────────────┐') + print('│ WARNING, PLEASE READ BEFORE PROCEEDING │') + print('│ │') + print('│ this is an irreversible operation that will remove the comma-provisioned │') + print('│ profile. │') + print('│ │') + print('│ after this operation, you must purchase a new eSIM from comma in order to │') + print('│ use the comma prime subscription again. │') + print('└──────────────────────────────────────────────────────────────────────────────┘') + print() + for severity in ('sure', '100% sure'): + print(f'are you {severity} you want to proceed? (y/N) ', end='') + confirm = input() + if confirm != 'y': + print('aborting') + exit(0) + lpa.bootstrap() if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') + parser.add_argument('--bootstrap', action='store_true', help='bootstrap the eUICC (required before downloading profiles)') parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') parser.add_argument('--switch', metavar='iccid', help='switch to profile') + parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)') parser.add_argument('--nickname', nargs=2, metavar=('iccid', 'name'), help='update the nickname for a profile') args = parser.parse_args() mutated = False lpa = HARDWARE.get_sim_lpa() - if args.switch: + if args.bootstrap: + bootstrap(lpa) + mutated = True + elif args.switch: lpa.switch_profile(args.switch) mutated = True + elif args.delete: + confirm = input('are you sure you want to delete this profile? (y/N) ') + if confirm == 'y': + lpa.delete_profile(args.delete) + mutated = True + else: + print('cancelled') + exit(0) elif args.download: lpa.download_profile(args.download[0], args.download[1]) elif args.nickname: diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py index 8896117694..391ba45531 100644 --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -31,7 +31,16 @@ class TiciLPA(LPABase): def get_active_profile(self) -> Profile | None: return next((p for p in self.list_profiles() if p.enabled), None) + def delete_profile(self, iccid: str) -> None: + self._validate_profile_exists(iccid) + latest = self.get_active_profile() + if latest is not None and latest.iccid == iccid: + raise LPAError('cannot delete active profile, switch to another profile first') + self._validate_successful(self._invoke('profile', 'delete', iccid)) + self._process_notifications() + def download_profile(self, qr: str, nickname: str | None = None) -> None: + self._check_bootstrapped() msgs = self._invoke('profile', 'download', '-a', qr) self._validate_successful(msgs) new_profile = next((m for m in msgs if m['payload']['message'] == 'es8p_meatadata_parse'), None) @@ -46,6 +55,7 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) def switch_profile(self, iccid: str) -> None: + self._check_bootstrapped() self._validate_profile_exists(iccid) latest = self.get_active_profile() if latest and latest.iccid == iccid: @@ -53,10 +63,33 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'enable', iccid)) self._process_notifications() + def bootstrap(self) -> None: + """ + find all comma-provisioned profiles and delete them. they conflict with user-provisioned profiles + and must be deleted. + + **note**: this is a **very** destructive operation. you **must** purchase a new comma SIM in order + to use comma prime again. + """ + if self._is_bootstrapped(): + return + + for p in self.list_profiles(): + if self.is_comma_profile(p.iccid): + self._disable_profile(p.iccid) + self.delete_profile(p.iccid) + def _disable_profile(self, iccid: str) -> None: self._validate_successful(self._invoke('profile', 'disable', iccid)) self._process_notifications() + def _check_bootstrapped(self) -> None: + assert self._is_bootstrapped(), 'eUICC is not bootstrapped, please bootstrap before performing this operation' + + def _is_bootstrapped(self) -> bool: + """ check if any comma provisioned profiles are on the eUICC """ + return not any(self.is_comma_profile(iccid) for iccid in (p.iccid for p in self.list_profiles())) + def _invoke(self, *cmd: str): proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) try: