| 
						
						
							
								
							
						
						
					 | 
					 | 
					@ -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 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					 | 
					
  |