Skip to content

Commit 05a4d37

Browse files
committed
feat(scripts): Add get-debian-linux-config
Downloads a linux-image package and extracts the config file from /boot. Signed-off-by: Loïc Minier <[email protected]>
1 parent 3e8ce39 commit 05a4d37

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

scripts/get-debian-linux-config.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import re
5+
import subprocess
6+
import sys
7+
import tempfile
8+
from pathlib import Path
9+
10+
ARCH = "arm64"
11+
12+
13+
def run(cmd, **kwargs):
14+
"""Run command with verbose logging"""
15+
if isinstance(cmd, str):
16+
print(f"+ {cmd}", file=sys.stderr)
17+
else:
18+
print("+", " ".join(cmd), file=sys.stderr)
19+
return subprocess.run(cmd, check=True, **kwargs)
20+
21+
22+
def ensure_dirs(*paths):
23+
for path in paths:
24+
Path(path).mkdir(parents=True, exist_ok=True)
25+
26+
27+
def write_file(path: Path, content: str):
28+
path.parent.mkdir(parents=True, exist_ok=True)
29+
path.write_text(content, encoding="utf-8")
30+
31+
32+
def setup_isolated_apt_root(root: Path, sources_content: str, arch: str):
33+
etc_apt_sources_list_d = root / "etc" / "apt" / "sources.list.d"
34+
var_lib_apt_lists = root / "var" / "lib" / "apt" / "lists"
35+
36+
ensure_dirs(
37+
etc_apt_sources_list_d,
38+
var_lib_apt_lists / "partial",
39+
)
40+
41+
write_file(etc_apt_sources_list_d / "sources.sources", sources_content)
42+
43+
44+
def apt_env_options(root: Path, arch: str):
45+
options = [
46+
f"Dir={root}",
47+
f"APT::Architecture={arch}",
48+
"Acquire::Languages=none",
49+
"Debug::NoLocking=false",
50+
]
51+
result = []
52+
for option in options:
53+
result.extend(["-o", option])
54+
return result
55+
56+
57+
def apt_update(root: Path, arch: str):
58+
opts = apt_env_options(root, arch)
59+
run(["apt-get", *opts, "update"])
60+
61+
62+
def resolve_versioned_kernel_pkg(
63+
root: Path, arch: str, package_name: str
64+
) -> str:
65+
opts = apt_env_options(root, arch)
66+
res = run(
67+
["apt-cache", *opts, "depends", package_name],
68+
capture_output=True,
69+
text=True,
70+
)
71+
72+
depends_pattern = re.compile(r"^\s*Depends:\s+(linux-image-\S+)")
73+
candidates = [
74+
match.group(1)
75+
for line in res.stdout.splitlines()
76+
if (match := depends_pattern.match(line))
77+
]
78+
79+
# If no candidates found, assume the provided package is already specific
80+
if not candidates:
81+
return package_name
82+
83+
# Prefer non-unsigned packages
84+
for pkg in candidates:
85+
if "-unsigned-" not in pkg:
86+
return pkg
87+
88+
# Fall back to first candidate if all are unsigned
89+
return candidates[0]
90+
91+
92+
def apt_download_pkg(
93+
root: Path, arch: str, pkg: str, download_dir: Path
94+
) -> Path:
95+
opts = apt_env_options(root, arch)
96+
ensure_dirs(download_dir)
97+
run(["apt-get", *opts, "download", pkg], cwd=str(download_dir))
98+
99+
debs = sorted(download_dir.glob(f"{pkg}_*.deb"))
100+
if not debs:
101+
debs = sorted(download_dir.glob(f"{pkg}*.deb"))
102+
if not debs:
103+
raise SystemExit(
104+
f"Download succeeded but .deb not found for {pkg} in {download_dir}"
105+
)
106+
return debs[-1]
107+
108+
109+
def extract_kernel_config_from_deb(deb_path: Path, output: Path):
110+
# List content to find the exact config path using subprocess pipeline
111+
with subprocess.Popen(
112+
["dpkg-deb", "--fsys-tarfile", str(deb_path)], stdout=subprocess.PIPE
113+
) as dpkg_proc:
114+
tar_result = subprocess.run(
115+
["tar", "-t"],
116+
stdin=dpkg_proc.stdout,
117+
capture_output=True,
118+
text=True,
119+
check=True,
120+
)
121+
122+
paths = [line.strip() for line in tar_result.stdout.splitlines()]
123+
# Look for './boot/config-*' (tar usually prefixes './')
124+
cfg_candidates = [
125+
path for path in paths if path.startswith("./boot/config-")
126+
]
127+
if not cfg_candidates:
128+
raise SystemExit(f"No ./boot/config-* found in {deb_path}")
129+
cfg_path = cfg_candidates[0]
130+
print(f"# Found config path in deb: {cfg_path}", file=sys.stderr)
131+
132+
# Stream-extract the config using subprocess pipeline
133+
with subprocess.Popen(
134+
["dpkg-deb", "--fsys-tarfile", str(deb_path)], stdout=subprocess.PIPE
135+
) as dpkg_proc:
136+
tar_result = subprocess.run(
137+
["tar", "-xO", cfg_path],
138+
stdin=dpkg_proc.stdout,
139+
capture_output=True,
140+
check=True,
141+
)
142+
143+
# Write the extracted config to output file
144+
output.write_bytes(tar_result.stdout)
145+
146+
147+
def main():
148+
parser = argparse.ArgumentParser(
149+
description="Download a linux-image package and extract its config"
150+
)
151+
parser.add_argument(
152+
"--sources", required=True, help="path to deb822 style sources file"
153+
)
154+
parser.add_argument(
155+
"--output",
156+
default=None,
157+
help="output file (default: <resolved-pkg>.config)",
158+
)
159+
parser.add_argument(
160+
"--debug",
161+
action="store_true",
162+
help="keep working temporary directory",
163+
)
164+
parser.add_argument(
165+
"--package",
166+
default=f"linux-image-{ARCH}",
167+
help=f"package or metapackage name (default: linux-image-{ARCH})",
168+
)
169+
args = parser.parse_args()
170+
171+
sources_path = Path(args.sources)
172+
if not sources_path.exists():
173+
sys.exit(f"Error: sources file '{args.sources}' not found")
174+
175+
try:
176+
sources_content = sources_path.read_text(encoding="utf-8")
177+
except Exception as e:
178+
sys.exit(f"Error reading sources file '{args.sources}': {e}")
179+
180+
top_tmp = tempfile.TemporaryDirectory(
181+
prefix="get-debian-linux-config-", delete=not (args.debug)
182+
)
183+
work_root = Path(top_tmp.name)
184+
apt_root = work_root / "aptroot"
185+
dl_dir = work_root / "downloads"
186+
187+
print(
188+
f"==> Setting up isolated APT environment in {work_root}...",
189+
file=sys.stderr,
190+
)
191+
setup_isolated_apt_root(apt_root, sources_content, ARCH)
192+
193+
print("\n==> Updating package lists...", file=sys.stderr)
194+
apt_update(apt_root, ARCH)
195+
196+
print("\n==> Resolving kernel package...", file=sys.stderr)
197+
image_pkg = resolve_versioned_kernel_pkg(apt_root, ARCH, args.package)
198+
print(f"==> Resolved kernel package: {image_pkg}", file=sys.stderr)
199+
200+
print(f"\n==> Downloading package: {image_pkg}", file=sys.stderr)
201+
deb_path = apt_download_pkg(apt_root, ARCH, image_pkg, dl_dir)
202+
203+
print(f"\n==> Extracting config from {deb_path.name}", file=sys.stderr)
204+
output_path = (
205+
Path(args.output)
206+
if args.output
207+
else Path.cwd() / f"{image_pkg}.config"
208+
)
209+
extract_kernel_config_from_deb(deb_path, output_path)
210+
211+
print(f"\nWrote: {output_path}")
212+
213+
214+
if __name__ == "__main__":
215+
main()

0 commit comments

Comments
 (0)