Parsing the Unparsable: How One Bash One-Liner Rescued a Kernel Build from Dependency Hell

Introduction

In the sprawling narrative of provisioning a high-performance Proxmox host with 8× Blackwell RTX PRO 6000 GPUs, there is a quiet, unassuming moment that exemplifies the craft of systems engineering. Message 8484 is not flashy. It contains no dramatic breakthroughs, no hardware revelations, no architectural epiphanies. It is a single bash command, executed over SSH, that extracts build dependencies from a Debian control file and installs them. Yet within this modest action lies a concentrated lesson in pragmatic problem-solving, toolchain discipline, and the art of working around brittle automation.

The Context: A System Recovered from Self-Inflicted Wounds

To understand message 8484, one must first understand the catastrophe that preceded it. Earlier in the session, the assistant had attempted to install a community-built 6.19 kernel on kpro6—a kernel compiled with GCC 14 from Debian Trixie, while the host ran Bookworm's GCC 12.2.0. This toolchain mismatch triggered a cascade of incompatibilities. The assistant attempted to paper over the cracks with a GLIBC_2.38 shim library—a shared object that intercepted glibc symbol lookups. The shim worked briefly, then catastrophically failed: it poisoned the system's dynamic linker, causing every SSH login to crash because bash itself could not load. The system was effectively bricked for remote access.

The user physically rescued the machine using a live ISO, then delivered a clear directive: "try not to do hacks; open nvidia can probably just be built locally without hacky repos, same with the kernel, all using correct gcc?" The assistant took this instruction to heart. It pivoted entirely away from binary patches and community kernels. The new plan was pristine: clone the official Proxmox VE kernel repository, build the kernel from source using the system's native GCC 12.2.0, then compile the NVIDIA open-gpu-kernel-modules against the freshly built kernel headers—all with a single, consistent toolchain.

The Problem: mk-build-deps Meets Its Match

By message 8484, the assistant had already cloned the bookworm-6.14 branch of the PVE kernel repository into /scratch/pve-kernel and run make build-dir-fresh, which successfully prepared the build directory. The next logical step was to install the build dependencies. In Debian packaging, the standard tool for this is mk-build-deps, which reads a debian/control file, generates a metapackage that depends on all the build dependencies, and installs it. It is a clean, automated workflow—when it works.

But it did not work. The assistant attempted mk-build-deps three times across messages 8478–8481, and each time it failed with the same error:

E: You must put some 'deb-src' URIs in your sources.list
mk-build-deps: Unable to find package name in `apt-cache showsrc proxmox-kernel-6.14.11/debian/control'

The root cause was subtle. mk-build-deps internally calls apt-cache showsrc to resolve source package names. This requires deb-src entries in the apt sources list. The Proxmox VE repository—http://download.proxmox.com/debian/pve bookworm pve-no-subscription—does not provide a source repository. Adding a deb-src line for it produced only a warning: "repository does not seem to provide it (sources.list entry misspelt?)". The assistant was stuck in a loop: the tool demanded a source repo that did not exist, and the control file format was opaque to mk-build-deps without that repo.

The Pivot: From Automation to Ad-Hoc Parsing

Message 8484 represents the moment the assistant abandoned the automated tool and reached for raw shell scripting. The reasoning is visible in the structure of the command itself. Rather than continuing to fight mk-build-deps, the assistant reasoned: the build dependencies are right there in the control file; I can read them myself.

The command is a single pipeline, executed over SSH on the remote host:

deps=$(grep -A100 "^Build-Depends:" proxmox-kernel-6.14.11/debian/control | sed "/^[A-Z]/d;/^$/q" | tr "," "\n" | sed "s/([^)]*)//g;s/\[.*\]//g" | sed "s/^\s*//;s/\s*$//" | grep -v "^$" | grep -v "^Build-Depends:")

This is a bespoke parser for Debian control file syntax, built from five chained text-processing commands. Let us examine each stage:

  1. grep -A100 "^Build-Depends:" — Finds the Build-Depends: header and prints the next 100 lines. This captures the multi-line dependency list, which in Debian control files can span many lines with continuation indentation.
  2. sed "/^[A-Z]/d;/^$/q" — Removes lines that start with an uppercase letter (which would be another field header like Standards-Version: or Homepage:), and quits on the first empty line. This effectively truncates the output to just the dependency block.
  3. tr "," "\n" — Splits the comma-separated dependency list into one line per dependency.
  4. sed "s/([^)]*)//g;s/\[.*\]//g" — Strips version constraints like (>= 1.2.3) and architecture restrictions like [linux-any]. This is critical because apt-get install cannot accept parenthesized version requirements.
  5. sed "s/^\s*//;s/\s*$//" — Trims leading and trailing whitespace from each dependency name.
  6. grep -v "^$" — Removes empty lines.
  7. grep -v "^Build-Depends:" — Removes the header line itself, which may have survived the earlier filters. The result is a clean, space-separated list of 28 package names: automake bc bison cpio debhelper-compat dh-python dwarves file flex gawk gcc git kmod libdw-dev libelf-dev libiberty-dev libnuma-dev libslang2-dev libssl-dev libtool lintian lz4 python3-dev python3-minimal rsync sphinx-common xmlto zlib1g-dev zstd.

Assumptions and Risks

This approach makes several assumptions. First, it assumes the control file follows the standard Debian format where Build-Depends: is followed by continuation lines indented with spaces. The PVE kernel control file does, but a differently formatted file could break the parser. Second, it assumes no dependency name contains parentheses or brackets as part of its name (they do not in Debian). Third, it assumes the dependency list fits within 100 lines—a generous margin, but a hard cutoff. Fourth, it assumes the sed "/^[A-Z]/d" heuristic correctly identifies field headers; a dependency name that starts with an uppercase letter (unlikely but possible) would be silently dropped.

These are reasonable assumptions for Debian packaging conventions, but they are not guaranteed. The assistant chose pragmatism over robustness: a 95% solution that works now, rather than a 100% solution that requires another toolchain detour.

The Output: A Working Build Environment

The command succeeded. The output shows all 28 dependencies were resolved and installed. The tail of the installation log shows packages like libslang2-dev, python3.11, libpython3.11-dev, and libpython3.11 being configured. The build environment is now ready.

This output knowledge—a functioning set of build dependencies—is the immediate product of message 8484. But the deeper output is methodological: the assistant demonstrated that when automated tooling fails due to repository constraints, the information needed is often still accessible in plain text. The control file is right there on disk. The dependencies are listed in a human-readable format. A few shell commands can extract them.

The Thinking Process

The reasoning visible in this message is a chain of escalating specificity. The assistant did not jump straight to manual parsing. It tried the standard tool (mk-build-deps) three times, each with a different invocation variant: first with -ir, then with --install --remove, then with -t "apt-get -y". Each failed identically. The assistant then tried adding a deb-src source list entry, which also failed because PVE does not publish source packages. Only after exhausting these avenues did the assistant pivot to parsing the control file directly.

This is characteristic of experienced systems work: try the standard path first, debug the standard path, and only when the standard path is structurally blocked (not just buggy) do you reach for a custom solution. The custom solution is not elegant—it is a bash pipeline of grep, sed, and tr—but it is correct enough to proceed.

Conclusion

Message 8484 is a small victory in a much larger campaign. It is the moment when a complex kernel build, threatened by a toolchain incompatibility and a broken automation path, gets back on track through a simple act of textual extraction. The assistant did not write a Python script, did not install a parsing library, did not file a bug report against mk-build-deps. It read a file, extracted what it needed, and moved on. This is the essence of practical engineering: not the most elegant solution, but the one that works within the constraints of the moment.