#! /usr/bin/env -S guix shell python python-wrapper python-requests python-feedparser -- python # Usage: ./chrome_update.py # Most of this script is based on the update.py script from Nixpkgs: # https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/networking/browsers/chromium/update.py import json import re import subprocess import textwrap from collections import OrderedDict from urllib.request import urlopen import feedparser import requests RELEASES_URL = 'https://versionhistory.googleapis.com/v1/chrome/platforms/linux/channels/all/versions/all/releases' DEB_URL = 'https://dl.google.com/linux/chrome/deb/pool/main/g' HTML_TAGS = re.compile(r'<[^>]+>') def guix_download(url): """Prefetches the content of the given URL and returns its hash.""" out = subprocess.check_output(['guix', 'download', url], stderr=subprocess.DEVNULL).decode().splitlines()[-1] return out def get_package_version(package): out = subprocess.check_output(['bash', '-c', f"guix show {package} | guix shell recutils -- recsel -p version"], stderr=subprocess.DEVNULL).decode().splitlines()[-1] return out.replace("version: ", "") def print_cves(target_version): feed = feedparser.parse('https://chromereleases.googleblog.com/feeds/posts/default') for entry in feed.entries: url = requests.get(entry.link).url.split('?')[0] if re.search(r'Stable Channel Update for Desktop', entry.title): if target_version and entry.title == '': # Workaround for a special case (Chrome Releases bug?): if 'the-stable-channel-has-been-updated-to' not in url: continue else: continue content = entry.content[0].value content = HTML_TAGS.sub('', content) # Remove any HTML tags if re.search(r'Linux', content) is None: continue # print(url) # For debugging purposes version = re.search(r'\d+(\.\d+){3}', content).group(0) if target_version: if version != target_version: continue if fixes := re.search(r'This update includes .+ security fix(es)?\.', content): fixes = fixes.group(0) if zero_days := re.search(r'Google is aware( of reports)? th(e|at) .+ in the wild\.', content): fixes += " " + zero_days.group(0) print('\n' + '\n'.join(textwrap.wrap(fixes, width=72))) if cve_list := re.findall(r'CVE-[^: ]+', content): cve_list = list(OrderedDict.fromkeys(cve_list)) # Remove duplicates but preserve the order cve_string = ', '.join(cve_list) print("\nFixes " + '\n'.join(textwrap.wrap(cve_string, width=72)) + ".") break with urlopen(RELEASES_URL) as resp: releases = json.load(resp)['releases'] for release in releases: if "endTime" in release["serving"]: continue channel_name = re.findall("chrome\/platforms\/linux\/channels\/(.*)\/versions\/", release['name'])[0] channel = {'version': release['version']} cves = "" if channel_name == 'dev': google_chrome_suffix = 'unstable' elif channel_name == 'ungoogled-chromium': google_chrome_suffix = 'stable' else: google_chrome_suffix = channel_name channel['name'] = f"google-chrome-{google_chrome_suffix}" try: channel['hash'] = guix_download( f'{DEB_URL}/{channel["name"]}/' + f'{channel["name"]}_{release["version"]}-1_amd64.deb') except subprocess.CalledProcessError: # This release isn't actually available yet. Continue to # the next one. continue print(f"====================================== {channel['name']} ===================================") print(f"Current version: {get_package_version(channel['name'])}") print(f"Pulled version: {channel['version']}") print(f"Hash: {channel['hash']}") print("Commit message:\n\n") print(f"nongnu: {channel['name']}: Update to {channel['version']}. ") if channel_name == "stable": print_cves(channel['version']) print("") print(f"* nongnu/packages/chrome.scm ({channel['name']}): Update to {channel['version']}.") print("")