Archiving all Rosetta 2 installers
With Rosetta 2 support winding down, time to revisit offline installer backups (Apple’s legacy software availability notwithstanding).
This update covers batch downloading RosettaUpdateAuto.pkg for all macOS versions from 11 through 26 beta, comprising 472 files totaling just under 150MB.
Install Python (
python3
will trigger installation if necessary) and requests (pip3 install requests
)Run the following script1 to:
- download all Rosetta 2 PKGs from Apple to ./RosettaUpdates/ (skipping existing files),
- organize PKGs by macOS version, and
- generate a timestamped report (rosetta_recommendation_date_time.txt in the current directory) recommending the latest versions by PostDate.2
#!/usr/bin/env python3 import plistlib import requests import re from pathlib import Path from datetime import datetime def download_file_with_progress(url, filepath): """Downloads a file from a URL to a given path with a progress indicator.""" try: response = requests.get(url, stream=True) response.raise_for_status() total_size = int(response.headers.get('content-length', 0)) downloaded = 0 print(f" -> Downloading {filepath.name} ({total_size / 1_000_000:.2f} MB)...") with open(filepath, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk) downloaded += len(chunk) if total_size > 0: percent = (downloaded / total_size) * 100 print(f"\r Progress: {percent:.1f}%", end='', flush=True) print("\r ✓ Download complete. ") return True except requests.exceptions.RequestException as e: print(f"\n ✗ Error downloading: {e}") return False def get_macos_version_from_metadata(product_data): """ Attempts to determine the macOS version from product metadata, checking ServerMetadataURL and then Distribution files. """ # First, try the ServerMetadataURL for a direct answer if 'ServerMetadataURL' in product_data: try: res = requests.get(product_data['ServerMetadataURL']) if res.status_code == 200: metadata = plistlib.loads(res.content) version_keys = ['OSVersion', 'ProductVersion', 'CFBundleShortVersionString'] for key in version_keys: if key in metadata: return metadata[key] except Exception: pass # Ignore errors and try the next method # Fallback: check the distribution file XML for version strings if 'Distributions' in product_data: for dist_url in product_data['Distributions'].values(): try: res = requests.get(dist_url) if res.status_code == 200: # Search for common version patterns in the XML match = re.search(r'MacOSX\.version\.major" content="([0-9]+)"', res.text) if match: return match.group(1) except Exception: continue # Ignore errors and try the next distribution return "Unknown" def generate_and_save_report(all_packages_by_version): """Generates the recommendation report and saves it to a text file.""" report_lines = [] report_lines.append("=" * 70) report_lines.append("Rosetta 2 - CHRONOLOGICAL RECOMMENDATIONS (Based on Apple PostDate)") report_lines.append(f"Report generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") report_lines.append("=" * 70) if not all_packages_by_version: report_lines.append("No Rosetta packages were found or downloaded.") for version, packages in sorted(all_packages_by_version.items()): report_lines.append(f"\nmacOS {version.replace('macOS_', '')}:") packages_with_dates = [p for p in packages if p.get('post_date')] if not packages_with_dates: report_lines.append(" No 'PostDate' metadata available to determine the latest.") for pkg in packages: report_lines.append(f" - {pkg['filename']}") continue packages_with_dates.sort(key=lambda p: p['post_date']) recommended = packages_with_dates[-1] report_lines.append(f" RECOMMENDED: {recommended['filename']}") report_lines.append(f" Date: {recommended['post_date'].strftime('%Y-%m-%d %H:%M:%S')}") if recommended['build_version']: report_lines.append(f" Build: {recommended['build_version']}") if len(packages_with_dates) > 1: order = ' → '.join([p['product_id'] for p in packages_with_dates]) report_lines.append(f" Update Path: {order}") # Write the collected report lines to a file report_filename = f"rosetta_recommendations_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" try: with open(report_filename, 'w', encoding='utf-8') as f: f.write("\n".join(report_lines)) print("\n" + "=" * 70) print(f"SUCCESS: Recommendation report saved to '{report_filename}'") print("=" * 70) except IOError as e: print(f"\nERROR: Could not write report file. {e}") def main(): """ Main function to download Rosetta updates and generate recommendations. """ catalog_url = "https://swscan.apple.com/content/catalogs/others/index-rosettaupdateauto-1.sucatalog.gz" # (H/T: https://github.com/mani2care/JAMF-Script/blob/main/Rosetta2/Rosetta2-install-via-pkg.sh) base_dir = Path("RosettaUpdates") base_dir.mkdir(exist_ok=True) print(f"Rosetta 2 Package Fetcher and Analyzer - {datetime.now().strftime('%Y-%m-%d')}") print("=" * 70) # --- 1. Download and parse the software update catalog --- try: print("Fetching software catalog from Apple...") response = requests.get(catalog_url) response.raise_for_status() catalog = plistlib.loads(response.content) print("Catalog parsed successfully.") except (requests.exceptions.RequestException, plistlib.InvalidFileException) as e: print(f"Fatal Error: Could not fetch or parse the Apple catalog. {e}") return # --- 2. Process all Rosetta products from the catalog --- all_packages_by_version = {} products = catalog.get('Products', {}) total_products = len(products) processed_count = 0 print("\nProcessing all Rosetta packages found in the catalog...") for product_id, product_data in products.items(): processed_count += 1 # Update progress on a single line print(f"\r Scanning catalog: {processed_count}/{total_products} products...", end="") pkg_info = next((p for p in product_data.get('Packages', []) if 'RosettaUpdateAuto.pkg' in p.get('URL', '')), None) if not pkg_info: continue # If we found a Rosetta package, clear the progress line and print details print(f"\r{' ' * 70}\r", end="") # Clear the line version = get_macos_version_from_metadata(product_data) print(f"-> Found Rosetta package '{product_id}' for macOS {version}") version_clean = re.sub(r'[^\w\.-]', '_', version) version_dir = base_dir / f"macOS_{version_clean}" version_dir.mkdir(exist_ok=True) filename = f"{product_id}_RosettaUpdateAuto.pkg" filepath = version_dir / filename if not filepath.exists(): download_file_with_progress(pkg_info['URL'], filepath) else: print(f" - Already exists locally: {filename}") if version_clean not in all_packages_by_version: all_packages_by_version[version_clean] = [] all_packages_by_version[version_clean].append({ 'product_id': product_id, 'filename': filename, 'post_date': product_data.get('PostDate'), 'build_version': product_data.get('ExtendedMetaInfo', {}).get('BuildVersion'), }) print(f"\r{' ' * 70}\rCatalog scan complete. {len(all_packages_by_version)} unique Rosetta versions identified.") # --- 3. Generate and save the final report to a text file --- generate_and_save_report(all_packages_by_version) print(f"All package files are located in the '{base_dir}' directory.") if __name__ == "__main__": main()
Footnotes
H/T: Kagi's Code Assistant & ChatGPT integration along with Google Gemini ↩
Some PKG versions have an older PostDate but contain newer files internally, such as with macOS 11.3: ↩
Filename PostDate Newest file 001-29948_RosettaUpdateAuto.pkg 2021-04-26 2021-02-28 071-29199_RosettaUpdateAuto.pkg 2021-04-20 2021-02-28 071-27946_RosettaUpdateAuto.pkg 2021-04-13 2021-03-25 071-26758_RosettaUpdateAuto.pkg 2021-04-08 2021-03-25 071-21342_RosettaUpdateAuto.pkg 2021-03-31 2021-03-25 071-20136_RosettaUpdateAuto.pkg 2021-03-23 2021-03-03 071-17606_RosettaUpdateAuto.pkg 2021-03-15 2021-03-03 071-08922_RosettaUpdateAuto.pkg 2021-03-02 2021-02-24 071-07131_RosettaUpdateAuto.pkg 2021-02-18 2021-02-10 071-05229_RosettaUpdateAuto.pkg 2021-02-17 2021-02-10 071-02780_RosettaUpdateAuto.pkg 2021-02-02 2021-01-22
❧ 2025-06-09