linux/hardened/update.py: add type annotations
This commit is contained in:
parent
d6fe0a4e2d
commit
e77d174fcd
|
@ -1,17 +1,44 @@
|
||||||
#! /usr/bin/env nix-shell
|
#! /usr/bin/env nix-shell
|
||||||
#! nix-shell -i python -p "python3.withPackages (ps: [ps.PyGithub])" git gnupg
|
#! nix-shell -i python -p "python38.withPackages (ps: [ps.PyGithub])" git gnupg
|
||||||
|
|
||||||
# This is automatically called by ../update.sh.
|
# This is automatically called by ../update.sh.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
TypedDict,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
from github import Github
|
from github import Github
|
||||||
|
from github.GitRelease import GitRelease
|
||||||
|
|
||||||
|
VersionComponent = Union[int, str]
|
||||||
|
Version = List[VersionComponent]
|
||||||
|
|
||||||
|
|
||||||
|
Patch = TypedDict("Patch", {"name": str, "url": str, "sha256": str})
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReleaseInfo:
|
||||||
|
version: Version
|
||||||
|
release: GitRelease
|
||||||
|
|
||||||
|
|
||||||
HERE = Path(__file__).resolve().parent
|
HERE = Path(__file__).resolve().parent
|
||||||
NIXPKGS_KERNEL_PATH = HERE.parent
|
NIXPKGS_KERNEL_PATH = HERE.parent
|
||||||
|
@ -19,17 +46,13 @@ NIXPKGS_PATH = HERE.parents[4]
|
||||||
HARDENED_GITHUB_REPO = "anthraxx/linux-hardened"
|
HARDENED_GITHUB_REPO = "anthraxx/linux-hardened"
|
||||||
HARDENED_TRUSTED_KEY = HERE / "anthraxx.asc"
|
HARDENED_TRUSTED_KEY = HERE / "anthraxx.asc"
|
||||||
HARDENED_PATCHES_PATH = HERE / "patches.json"
|
HARDENED_PATCHES_PATH = HERE / "patches.json"
|
||||||
MIN_KERNEL_VERSION = [4, 14]
|
MIN_KERNEL_VERSION: Version = [4, 14]
|
||||||
|
|
||||||
|
|
||||||
def run(*args, **kwargs):
|
def run(*args: Union[str, Path]) -> subprocess.CompletedProcess[bytes]:
|
||||||
try:
|
try:
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
args,
|
args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
**kwargs,
|
|
||||||
check=True,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError as err:
|
except subprocess.CalledProcessError as err:
|
||||||
print(
|
print(
|
||||||
|
@ -42,13 +65,15 @@ def run(*args, **kwargs):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def nix_prefetch_url(url):
|
def nix_prefetch_url(url: str) -> Tuple[str, Path]:
|
||||||
output = run("nix-prefetch-url", "--print-path", url).stdout
|
output = run("nix-prefetch-url", "--print-path", url).stdout
|
||||||
sha256, path = output.decode("utf-8").strip().split("\n")
|
sha256, path = output.decode("utf-8").strip().split("\n")
|
||||||
return sha256, Path(path)
|
return sha256, Path(path)
|
||||||
|
|
||||||
|
|
||||||
def verify_openpgp_signature(*, name, trusted_key, sig_path, data_path):
|
def verify_openpgp_signature(
|
||||||
|
*, name: str, trusted_key: Path, sig_path: Path, data_path: Path,
|
||||||
|
) -> bool:
|
||||||
with TemporaryDirectory(suffix=".nixpkgs-gnupg-home") as gnupg_home_str:
|
with TemporaryDirectory(suffix=".nixpkgs-gnupg-home") as gnupg_home_str:
|
||||||
gnupg_home = Path(gnupg_home_str)
|
gnupg_home = Path(gnupg_home_str)
|
||||||
run("gpg", "--homedir", gnupg_home, "--import", trusted_key)
|
run("gpg", "--homedir", gnupg_home, "--import", trusted_key)
|
||||||
|
@ -69,14 +94,15 @@ def verify_openpgp_signature(*, name, trusted_key, sig_path, data_path):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def fetch_patch(*, name, release):
|
def fetch_patch(*, name: str, release: GitRelease) -> Optional[Patch]:
|
||||||
def find_asset(filename):
|
def find_asset(filename: str) -> str:
|
||||||
try:
|
try:
|
||||||
return next(
|
it: Iterator[str] = (
|
||||||
asset.browser_download_url
|
asset.browser_download_url
|
||||||
for asset in release.get_assets()
|
for asset in release.get_assets()
|
||||||
if asset.name == filename
|
if asset.name == filename
|
||||||
)
|
)
|
||||||
|
return next(it)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise KeyError(filename)
|
raise KeyError(filename)
|
||||||
|
|
||||||
|
@ -99,15 +125,11 @@ def fetch_patch(*, name, release):
|
||||||
if not sig_ok:
|
if not sig_ok:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return {
|
return Patch(name=patch_filename, url=patch_url, sha256=sha256)
|
||||||
"name": patch_filename,
|
|
||||||
"url": patch_url,
|
|
||||||
"sha256": sha256,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_version(version_str):
|
def parse_version(version_str: str) -> Version:
|
||||||
version = []
|
version: Version = []
|
||||||
for component in version_str.split("."):
|
for component in version_str.split("."):
|
||||||
try:
|
try:
|
||||||
version.append(int(component))
|
version.append(int(component))
|
||||||
|
@ -116,15 +138,15 @@ def parse_version(version_str):
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
def version_string(version):
|
def version_string(version: Version) -> str:
|
||||||
return ".".join(str(component) for component in version)
|
return ".".join(str(component) for component in version)
|
||||||
|
|
||||||
|
|
||||||
def major_kernel_version_key(kernel_version):
|
def major_kernel_version_key(kernel_version: Version) -> str:
|
||||||
return version_string(kernel_version[:-1])
|
return version_string(kernel_version[:-1])
|
||||||
|
|
||||||
|
|
||||||
def commit_patches(*, kernel_key, message):
|
def commit_patches(*, kernel_key: str, message: str) -> None:
|
||||||
new_patches_path = HARDENED_PATCHES_PATH.with_suffix(".new")
|
new_patches_path = HARDENED_PATCHES_PATH.with_suffix(".new")
|
||||||
with open(new_patches_path, "w") as new_patches_file:
|
with open(new_patches_path, "w") as new_patches_file:
|
||||||
json.dump(patches, new_patches_file, indent=4, sort_keys=True)
|
json.dump(patches, new_patches_file, indent=4, sort_keys=True)
|
||||||
|
@ -144,6 +166,7 @@ def commit_patches(*, kernel_key, message):
|
||||||
|
|
||||||
|
|
||||||
# Load the existing patches.
|
# Load the existing patches.
|
||||||
|
patches: Dict[str, Patch]
|
||||||
with open(HARDENED_PATCHES_PATH) as patches_file:
|
with open(HARDENED_PATCHES_PATH) as patches_file:
|
||||||
patches = json.load(patches_file)
|
patches = json.load(patches_file)
|
||||||
|
|
||||||
|
@ -177,7 +200,6 @@ for kernel_key in sorted(patches.keys() - kernel_versions.keys()):
|
||||||
|
|
||||||
g = Github(os.environ.get("GITHUB_TOKEN"))
|
g = Github(os.environ.get("GITHUB_TOKEN"))
|
||||||
repo = g.get_repo(HARDENED_GITHUB_REPO)
|
repo = g.get_repo(HARDENED_GITHUB_REPO)
|
||||||
|
|
||||||
failures = False
|
failures = False
|
||||||
|
|
||||||
# Match each kernel version with the best patch version.
|
# Match each kernel version with the best patch version.
|
||||||
|
@ -195,10 +217,7 @@ for release in repo.get_releases():
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
release_info = {
|
release_info = ReleaseInfo(version=version, release=release)
|
||||||
"version": version,
|
|
||||||
"release": release,
|
|
||||||
}
|
|
||||||
|
|
||||||
if kernel_version == packaged_kernel_version:
|
if kernel_version == packaged_kernel_version:
|
||||||
releases[kernel_key] = release_info
|
releases[kernel_key] = release_info
|
||||||
|
@ -208,18 +227,20 @@ for release in repo.get_releases():
|
||||||
if kernel_version > packaged_kernel_version:
|
if kernel_version > packaged_kernel_version:
|
||||||
continue
|
continue
|
||||||
elif (
|
elif (
|
||||||
kernel_key not in releases
|
kernel_key not in releases or releases[kernel_key].version < version
|
||||||
or releases[kernel_key]["version"] < version
|
|
||||||
):
|
):
|
||||||
releases[kernel_key] = release_info
|
releases[kernel_key] = release_info
|
||||||
|
|
||||||
# Update hardened-patches.json for each release.
|
# Update hardened-patches.json for each release.
|
||||||
for kernel_key, release_info in releases.items():
|
for kernel_key, release_info in releases.items():
|
||||||
release = release_info["release"]
|
release = release_info.release
|
||||||
version = release_info["version"]
|
version = release_info.version
|
||||||
version_str = release.tag_name
|
version_str = release.tag_name
|
||||||
name = f"linux-hardened-{version_str}"
|
name = f"linux-hardened-{version_str}"
|
||||||
|
|
||||||
|
old_version: Optional[Version] = None
|
||||||
|
old_version_str: Optional[str] = None
|
||||||
|
update: bool
|
||||||
try:
|
try:
|
||||||
old_filename = patches[kernel_key]["name"]
|
old_filename = patches[kernel_key]["name"]
|
||||||
old_version_str = old_filename.replace("linux-hardened-", "").replace(
|
old_version_str = old_filename.replace("linux-hardened-", "").replace(
|
||||||
|
@ -229,7 +250,6 @@ for kernel_key, release_info in releases.items():
|
||||||
update = old_version < version
|
update = old_version < version
|
||||||
except KeyError:
|
except KeyError:
|
||||||
update = True
|
update = True
|
||||||
old_version = None
|
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
patch = fetch_patch(name=name, release=release)
|
patch = fetch_patch(name=name, release=release)
|
||||||
|
|
Loading…
Reference in New Issue