breakpointHook: re-implement using nsenter (#370184)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# breakpointHook {#breakpointhook}
|
||||
|
||||
This hook will make a build pause instead of stopping when a failure happens. It prevents nix from cleaning up the build environment immediately and allows the user to attach to a build environment using the `cntr` command. Upon build error it will print instructions on how to use `cntr`, which can be used to enter the environment for debugging. Installing cntr and running the command will provide shell access to the build sandbox of failed build. At `/var/lib/cntr` the sandboxed filesystem is mounted. All commands and files of the system are still accessible within the shell. To execute commands from the sandbox use the cntr exec subcommand. `cntr` is only supported on Linux-based platforms. To use it first add `cntr` to your `environment.systemPackages` on NixOS or alternatively to the root user on non-NixOS systems. Then in the package that is supposed to be inspected, add `breakpointHook` to `nativeBuildInputs`.
|
||||
This hook makes a build pause instead of stopping when a failure occurs. It prevents Nix from cleaning up the build environment immediately and allows the user to attach to the build environment. Upon a build error, it will print instructions that can be used to enter the environment for debugging. breakpointHook is only available on Linux. To use it, add `breakpointHook` to `nativeBuildInputs` in the package to be inspected.
|
||||
|
||||
```nix
|
||||
{
|
||||
@@ -8,10 +8,10 @@ This hook will make a build pause instead of stopping when a failure happens. It
|
||||
}
|
||||
```
|
||||
|
||||
When a build failure happens there will be an instruction printed that shows how to attach with `cntr` to the build sandbox.
|
||||
When a build failure occurs, an instruction will be printed showing how to attach to the build sandbox.
|
||||
|
||||
::: {.note}
|
||||
Caution with remote builds
|
||||
|
||||
This won’t work with remote builds as the build environment is on a different machine and can’t be accessed by `cntr`. Remote builds can be turned off by setting `--option builders ''` for `nix-build` or `--builders ''` for `nix build`.
|
||||
For remote builds, the printed instructions need to be run on the remote machine, as the build sandbox is only accessible on the machine running the builds. Remote builds can be turned off by setting `--option builders ''` for `nix-build` or `--builders ''` for `nix build`. :::
|
||||
:::
|
||||
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
id="$1"
|
||||
pids="$(pgrep -f "sleep $id" || :)"
|
||||
if [ -z "$pids" ]; then
|
||||
echo "Error: No process found for 'sleep $id'. The build must still be running in order to attach. Also make sure it's not on a remote builder." >&2
|
||||
exit 1
|
||||
elif [ "$(echo "$pids" | wc -l)" -ne 1 ]; then
|
||||
echo "Error: Multiple processes found matching 'sleep $id'" >&2
|
||||
exit 1
|
||||
fi
|
||||
pid="$(echo "$pids" | head -n1)"
|
||||
|
||||
# helper to extract variables from the build env
|
||||
getVar(){
|
||||
while IFS= read -r -d $'\0' line; do
|
||||
case "$line" in
|
||||
*"$1="* )
|
||||
echo "$line"
|
||||
;;
|
||||
esac
|
||||
done < /proc/$pid/environ \
|
||||
| cut -d "=" -f 2
|
||||
}
|
||||
|
||||
# bash is needed to load the env vars, as we do not know the syntax of the debug shell.
|
||||
# bashInteractive is used instead of bash, as we depend on it anyways, due to it being
|
||||
# the default debug shell
|
||||
bashInteractive="$(getVar bashInteractive)"
|
||||
# the debug shell will be started as interactive shell after loading the env vars
|
||||
debugShell="$(getVar debugShell)"
|
||||
# to drop the user into the working directory at the point of failure
|
||||
pwd="$(readlink /proc/$pid/cwd)"
|
||||
|
||||
# enter the namespace of the failed build
|
||||
exec nsenter --mount --net --target "$pid" "$bashInteractive" -c "
|
||||
set -eu -o pipefail
|
||||
while IFS= read -r -d \$'\0' line; do
|
||||
export \"\$line\"
|
||||
done <&3
|
||||
exec 3>&-
|
||||
cd \"$pwd\"
|
||||
exec \"$debugShell\"
|
||||
" 3< /proc/$pid/environ
|
||||
@@ -1,9 +1,20 @@
|
||||
breakpointHook() {
|
||||
local red='\033[0;31m'
|
||||
local cyan='\033[0;36m'
|
||||
local green='\033[0;32m'
|
||||
local no_color='\033[0m'
|
||||
|
||||
echo -e "${red}build failed in ${curPhase} with exit code ${exitCode}${no_color}"
|
||||
printf "To attach install cntr and run the following command as root:\n\n"
|
||||
sh -c "echo ' cntr attach -t command cntr-${out}'; while true; do sleep 99999999; done"
|
||||
# provide the user with an interactive shell for better experience
|
||||
export bashInteractive="@bashInteractive@"
|
||||
if [ -z "$debugShell" ]; then
|
||||
export debugShell="@bashInteractive@"
|
||||
fi
|
||||
|
||||
local id
|
||||
id="$(shuf -i 999999-9999999 -n1)"
|
||||
echo -e "${red}build for ${cyan}${name:-unknown}${red} failed in ${curPhase:-unknown} with exit code ${exitCode:-unknown}${no_color}"
|
||||
echo -e "${green}To attach, run the following command:${no_color}"
|
||||
echo -e "${green} sudo @attach@ $id${no_color}"
|
||||
sleep "$id"
|
||||
}
|
||||
failureHooks+=(breakpointHook)
|
||||
|
||||
@@ -1,6 +1,38 @@
|
||||
{ stdenv, makeSetupHook }:
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
|
||||
bash,
|
||||
bashInteractive,
|
||||
coreutils,
|
||||
makeSetupHook,
|
||||
procps,
|
||||
util-linux,
|
||||
writeShellScriptBin,
|
||||
}:
|
||||
|
||||
let
|
||||
attach = writeShellScriptBin "attach" ''
|
||||
export PATH="${
|
||||
lib.makeBinPath [
|
||||
bash
|
||||
coreutils
|
||||
procps # needed for pgrep
|
||||
util-linux # needed for nsenter
|
||||
]
|
||||
}"
|
||||
exec bash ${./attach.sh} "$@"
|
||||
'';
|
||||
in
|
||||
|
||||
makeSetupHook {
|
||||
name = "breakpoint-hook";
|
||||
meta.broken = !stdenv.buildPlatform.isLinux;
|
||||
substitutions = {
|
||||
attach = "${attach}/bin/attach";
|
||||
# The default interactive shell in case $debugShell is not set in the derivation.
|
||||
# Can be overridden to zsh or fish, etc.
|
||||
# This shell is also used to load the env variables before the $debugShell is started.
|
||||
bashInteractive = lib.getExe bashInteractive;
|
||||
};
|
||||
} ./breakpoint-hook.sh
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
breakpointHook() {
|
||||
local red='\033[0;31m'
|
||||
local no_color='\033[0m'
|
||||
|
||||
echo -e "${red}build failed in ${curPhase} with exit code ${exitCode}${no_color}"
|
||||
printf "To attach install cntr and run the following command as root:\n\n"
|
||||
sh -c "echo ' cntr attach -t command cntr-${out}'; while true; do sleep 99999999; done"
|
||||
}
|
||||
failureHooks+=(breakpointHook)
|
||||
@@ -0,0 +1,6 @@
|
||||
{ stdenv, makeSetupHook }:
|
||||
|
||||
makeSetupHook {
|
||||
name = "breakpoint-hook";
|
||||
meta.broken = !stdenv.buildPlatform.isLinux;
|
||||
} ./breakpoint-hook.sh
|
||||
Reference in New Issue
Block a user