|  |  | @ -40,6 +40,7 @@ from common.params import Params | 
			
		
	
		
		
			
				
					
					|  |  |  | from selfdrive.hardware import EON, TICI, HARDWARE |  |  |  | from selfdrive.hardware import EON, TICI, HARDWARE | 
			
		
	
		
		
			
				
					
					|  |  |  | from selfdrive.swaglog import cloudlog |  |  |  | from selfdrive.swaglog import cloudlog | 
			
		
	
		
		
			
				
					
					|  |  |  | from selfdrive.controls.lib.alertmanager import set_offroad_alert |  |  |  | from selfdrive.controls.lib.alertmanager import set_offroad_alert | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | from selfdrive.version import get_tested_branch | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") |  |  |  | LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") | 
			
		
	
		
		
			
				
					
					|  |  |  | STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") |  |  |  | STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") | 
			
		
	
	
		
		
			
				
					|  |  | @ -51,6 +52,8 @@ OVERLAY_METADATA = os.path.join(STAGING_ROOT, "metadata") | 
			
		
	
		
		
			
				
					
					|  |  |  | OVERLAY_MERGED = os.path.join(STAGING_ROOT, "merged") |  |  |  | OVERLAY_MERGED = os.path.join(STAGING_ROOT, "merged") | 
			
		
	
		
		
			
				
					
					|  |  |  | FINALIZED = os.path.join(STAGING_ROOT, "finalized") |  |  |  | FINALIZED = os.path.join(STAGING_ROOT, "finalized") | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | DAYS_NO_CONNECTIVITY_MAX = 14     # do not allow to engage after this many days | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | DAYS_NO_CONNECTIVITY_PROMPT = 10  # send an offroad prompt after this many days | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | class WaitTimeHelper: |  |  |  | class WaitTimeHelper: | 
			
		
	
		
		
			
				
					
					|  |  |  |   def __init__(self, proc): |  |  |  |   def __init__(self, proc): | 
			
		
	
	
		
		
			
				
					|  |  | @ -102,15 +105,24 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) - | 
			
		
	
		
		
			
				
					
					|  |  |  |   params = Params() |  |  |  |   params = Params() | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   params.put("UpdateFailedCount", str(failed_count)) |  |  |  |   params.put("UpdateFailedCount", str(failed_count)) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   last_update = datetime.datetime.utcnow() | 
			
		
	
		
		
			
				
					
					|  |  |  |   if failed_count == 0: |  |  |  |   if failed_count == 0: | 
			
		
	
		
		
			
				
					
					|  |  |  |     t = datetime.datetime.utcnow().isoformat() |  |  |  |     t = last_update.isoformat() | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     params.put("LastUpdateTime", t.encode('utf8')) |  |  |  |     params.put("LastUpdateTime", t.encode('utf8')) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   else: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     try: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       t = params.get("LastUpdateTime", encoding='utf8') | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       last_update = datetime.datetime.fromisoformat(t) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     except (TypeError, ValueError): | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       pass | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   if exception is None: |  |  |  |   if exception is None: | 
			
		
	
		
		
			
				
					
					|  |  |  |     params.delete("LastUpdateException") |  |  |  |     params.delete("LastUpdateException") | 
			
		
	
		
		
			
				
					
					|  |  |  |   else: |  |  |  |   else: | 
			
		
	
		
		
			
				
					
					|  |  |  |     params.put("LastUpdateException", exception) |  |  |  |     params.put("LastUpdateException", exception) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   # Write out release notes for new versions | 
			
		
	
		
		
			
				
					
					|  |  |  |   if new_version: |  |  |  |   if new_version: | 
			
		
	
		
		
			
				
					
					|  |  |  |     try: |  |  |  |     try: | 
			
		
	
		
		
			
				
					
					|  |  |  |       with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f: |  |  |  |       with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f: | 
			
		
	
	
		
		
			
				
					|  |  | @ -123,6 +135,24 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) - | 
			
		
	
		
		
			
				
					
					|  |  |  |       params.put("ReleaseNotes", "") |  |  |  |       params.put("ReleaseNotes", "") | 
			
		
	
		
		
			
				
					
					|  |  |  |     params.put_bool("UpdateAvailable", True) |  |  |  |     params.put_bool("UpdateAvailable", True) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   # Handle user prompt | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"): | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     set_offroad_alert(alert, False) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   now = datetime.datetime.utcnow() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   dt = now - last_update | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   if failed_count > 15 and exception is not None: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     if get_tested_branch(): | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       extra_text = "Ensure the software is correctly installed" | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     else: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       extra_text = exception | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   elif dt.days > DAYS_NO_CONNECTIVITY_MAX and failed_count > 1: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     set_offroad_alert("Offroad_ConnectivityNeeded", True) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | def setup_git_options(cwd: str) -> None: |  |  |  | def setup_git_options(cwd: str) -> None: | 
			
		
	
		
		
			
				
					
					|  |  |  |   # We sync FS object atimes (which NEOS doesn't use) and mtimes, but ctimes |  |  |  |   # We sync FS object atimes (which NEOS doesn't use) and mtimes, but ctimes | 
			
		
	
	
		
		
			
				
					|  |  | @ -344,16 +374,14 @@ def main(): | 
			
		
	
		
		
			
				
					
					|  |  |  |   params = Params() |  |  |  |   params = Params() | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   if params.get_bool("DisableUpdates"): |  |  |  |   if params.get_bool("DisableUpdates"): | 
			
		
	
		
		
			
				
					
					|  |  |  |     raise RuntimeError("updates are disabled by the DisableUpdates param") |  |  |  |     cloudlog.warning("updates are disabled by the DisableUpdates param") | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |     exit(0) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   if EON and os.geteuid() != 0: |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     raise RuntimeError("updated must be launched as root!") |  |  |  |  | 
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   ov_lock_fd = open(LOCK_FILE, 'w') |  |  |  |   ov_lock_fd = open(LOCK_FILE, 'w') | 
			
		
	
		
		
			
				
					
					|  |  |  |   try: |  |  |  |   try: | 
			
		
	
		
		
			
				
					
					|  |  |  |     fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) |  |  |  |     fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) | 
			
		
	
		
		
			
				
					
					|  |  |  |   except IOError as e: |  |  |  |   except IOError as e: | 
			
		
	
		
		
			
				
					
					|  |  |  |     raise RuntimeError("couldn't get overlay lock; is another updated running?") from e |  |  |  |     raise RuntimeError("couldn't get overlay lock; is another instance running?") from e | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   # Set low io priority |  |  |  |   # Set low io priority | 
			
		
	
		
		
			
				
					
					|  |  |  |   proc = psutil.Process() |  |  |  |   proc = psutil.Process() | 
			
		
	
	
		
		
			
				
					|  |  | @ -368,10 +396,6 @@ def main(): | 
			
		
	
		
		
			
				
					
					|  |  |  |     t = datetime.datetime.utcnow().isoformat() |  |  |  |     t = datetime.datetime.utcnow().isoformat() | 
			
		
	
		
		
			
				
					
					|  |  |  |     params.put("InstallDate", t.encode('utf8')) |  |  |  |     params.put("InstallDate", t.encode('utf8')) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   # Wait for IsOffroad to be set before our first update attempt |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   wait_helper = WaitTimeHelper(proc) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   wait_helper.sleep(30) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   overlay_init = Path(os.path.join(BASEDIR, ".overlay_init")) |  |  |  |   overlay_init = Path(os.path.join(BASEDIR, ".overlay_init")) | 
			
		
	
		
		
			
				
					
					|  |  |  |   overlay_init.unlink(missing_ok=True) |  |  |  |   overlay_init.unlink(missing_ok=True) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -379,6 +403,13 @@ def main(): | 
			
		
	
		
		
			
				
					
					|  |  |  |   last_fetch_time = 0 |  |  |  |   last_fetch_time = 0 | 
			
		
	
		
		
			
				
					
					|  |  |  |   update_failed_count = 0 |  |  |  |   update_failed_count = 0 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   # Set initial params for offroad alerts | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   set_params(False, 0, None) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   # Wait for IsOffroad to be set before our first update attempt | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   wait_helper = WaitTimeHelper(proc) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   wait_helper.sleep(30) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   # Run the update loop |  |  |  |   # Run the update loop | 
			
		
	
		
		
			
				
					
					|  |  |  |   #  * every 1m, do a lightweight internet/update check |  |  |  |   #  * every 1m, do a lightweight internet/update check | 
			
		
	
		
		
			
				
					
					|  |  |  |   #  * every 10m, do a full git fetch |  |  |  |   #  * every 10m, do a full git fetch | 
			
		
	
	
		
		
			
				
					|  |  | 
 |