From 19dbf7d12215cea8c40be73888b18a6418071a37 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 3 Apr 2026 13:37:21 +0200 Subject: [PATCH 01/29] Add undocumented command category --- doc/manual/generate-manpage.nix | 6 +++++- src/libcmd/include/nix/cmd/command.hh | 1 + src/nix/main.cc | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix index efdf28cd7b53..7b2a02bd3f84 100644 --- a/doc/manual/generate-manpage.nix +++ b/doc/manual/generate-manpage.nix @@ -76,7 +76,11 @@ let subcommands = if length categories > 1 then listCategories else listSubcommands details.commands; categories = sort (x: y: x.id < y.id) ( - unique (map (cmd: { inherit (cmd.category) id description; }) (attrValues details.commands)) + unique ( + map (cmd: { inherit (cmd.category) id description; }) ( + builtins.filter (cmd: cmd.category.id != 103) (attrValues details.commands) + ) + ) ); listCategories = concatStrings (map showCategory categories); diff --git a/src/libcmd/include/nix/cmd/command.hh b/src/libcmd/include/nix/cmd/command.hh index ec2e0d9add4c..326b05c6274a 100644 --- a/src/libcmd/include/nix/cmd/command.hh +++ b/src/libcmd/include/nix/cmd/command.hh @@ -24,6 +24,7 @@ static constexpr Command::Category catHelp = -1; static constexpr Command::Category catSecondary = 100; static constexpr Command::Category catUtility = 101; static constexpr Command::Category catNixInstallation = 102; +static constexpr Command::Category catUndocumented = 103; static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)"; diff --git a/src/nix/main.cc b/src/nix/main.cc index 0711804a5447..f16840436635 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -121,6 +121,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs categories[catSecondary] = "Infrequently used commands"; categories[catUtility] = "Utility/scripting commands"; categories[catNixInstallation] = "Commands for upgrading or troubleshooting your Nix installation"; + categories[catUndocumented] = "Undocumented commands"; addFlag({ .longName = "help", From a41fc8ec50b5bdb08947fcb96f5381f820c8cb0e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 3 Apr 2026 13:38:53 +0200 Subject: [PATCH 02/29] Add `nix crash` command for testing crash reporting --- src/nix/crash.cc | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/nix/meson.build | 1 + 2 files changed, 49 insertions(+) create mode 100644 src/nix/crash.cc diff --git a/src/nix/crash.cc b/src/nix/crash.cc new file mode 100644 index 000000000000..aaa5c90d0a2e --- /dev/null +++ b/src/nix/crash.cc @@ -0,0 +1,48 @@ +#include "nix/cmd/command.hh" + +using namespace nix; + +struct CmdCrash : Command +{ + std::string type; + + CmdCrash() + { + expectArg("type", &type); + } + + Category category() override + { + return catUndocumented; + } + + std::string description() override + { + return "crash the program to test crash reporting"; + } + + void run() override + { + if (type == "segfault") { + printError("Triggering a segfault..."); + volatile int * p = nullptr; + *p = 123; + } + + else if (type == "assert") { + printError("Triggering an assertion failure..."); + assert(false && "This is an assertion failure"); + } + + else if (type == "logic-error") { + printError("Triggering a C++ logic error..."); + std::bitset<4>{"012"}; + } + + else { + throw Error("unknown crash type '%s'", type); + } + } +}; + +static auto rCrash = registerCommand("crash"); diff --git a/src/nix/meson.build b/src/nix/meson.build index 3b343614e421..36aafd959a6b 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -68,6 +68,7 @@ nix_sources = [ config_priv_h ] + files( 'config.cc', 'copy.cc', 'crash-handler.cc', + 'crash.cc', 'derivation-add.cc', 'derivation-show.cc', 'derivation.cc', From 46eb0067e98e0e038c5b3ed1df0315219051d4a3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Apr 2026 21:16:58 +0200 Subject: [PATCH 03/29] everything.nix: Add a debug output for convenience --- packaging/everything.nix | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packaging/everything.nix b/packaging/everything.nix index 3206b8ba4235..00d5aa5c6411 100644 --- a/packaging/everything.nix +++ b/packaging/everything.nix @@ -102,6 +102,7 @@ stdenv.mkDerivation (finalAttrs: { "dev" "doc" "man" + "debug" ]; /** @@ -153,9 +154,12 @@ stdenv.mkDerivation (finalAttrs: { installPhase = let devPaths = lib.mapAttrsToList (_k: lib.getDev) finalAttrs.finalPackage.libs; + debugPaths = lib.map (lib.getOutput "debug") ( + lib.attrValues finalAttrs.finalPackage.libs ++ [ nix-cli ] + ); in '' - mkdir -p $out $dev/nix-support + mkdir -p $out $dev/nix-support $debug/lib/debug # Custom files echo $libs >> $dev/nix-support/propagated-build-inputs @@ -168,6 +172,12 @@ stdenv.mkDerivation (finalAttrs: { lndir $lib $dev done + for d in ${lib.escapeShellArgs debugPaths}; do + if [[ -d $d/lib/debug ]]; then + lndir $d/lib/debug $debug/lib/debug + fi + done + # Forwarded outputs ln -sT ${nix-manual} $doc ln -sT ${nix-manual.man} $man From 241d16182eecc9f1a4734088ba2ff3abf621f3aa Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Apr 2026 21:35:10 +0200 Subject: [PATCH 04/29] Add script for uploading debug symbols to sentry --- maintainers/upload-debug-info-to-sentry.py | 140 +++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100755 maintainers/upload-debug-info-to-sentry.py diff --git a/maintainers/upload-debug-info-to-sentry.py b/maintainers/upload-debug-info-to-sentry.py new file mode 100755 index 000000000000..bc215f785a48 --- /dev/null +++ b/maintainers/upload-debug-info-to-sentry.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os +import re +import subprocess +import sys +import urllib.error +import urllib.parse +import urllib.request + +NAR_DIR = "/tmp/nars" +DEBUG_INFO_DIR = "/tmp/debug-info" + + +def get_dynamic_libraries(executable: str) -> list[str]: + result = subprocess.run(["ldd", executable], capture_output=True, text=True, check=True) + libs = [] + for line in result.stdout.splitlines(): + # ldd output lines look like: + # libfoo.so.1 => /nix/store/.../libfoo.so.1 (0x...) + # /lib64/ld-linux-x86-64.so.2 (0x...) + m = re.search(r"=> (/\S+)", line) + if m: + libs.append(m.group(1)) + elif line.strip().startswith("/"): + path = line.strip().split()[0] + libs.append(path) + return libs + + +def get_build_id(path: str) -> str | None: + result = subprocess.run(["readelf", "-n", path], capture_output=True, text=True) + m = re.search(r"Build ID:\s+([0-9a-f]+)", result.stdout) + return m.group(1) if m else None + + +def download_nar(build_id: str, archive: str) -> str: + """Download a NAR to /tmp/nars and return the local path. Skips if already present.""" + base_url = f"https://cache.nixos.org/debuginfo/{build_id}" + nar_url = urllib.parse.urljoin(base_url, archive) + filename = nar_url.split("/")[-1] + local_path = os.path.join(NAR_DIR, filename) + if not os.path.exists(local_path): + os.makedirs(NAR_DIR, exist_ok=True) + print(f" downloading {nar_url} ...", file=sys.stderr) + urllib.request.urlretrieve(nar_url, local_path) + else: + print(f" already have {filename}", file=sys.stderr) + return local_path + + +def extract_debug_symbols(nar_path: str, member: str, build_id: str) -> str: + """Extract a member from a .nar.xz into /tmp/debug-info/.debug. Returns the output path.""" + out_path = os.path.join(DEBUG_INFO_DIR, f"{build_id}.debug") + if os.path.exists(out_path): + print(f" already extracted {out_path}", file=sys.stderr) + return out_path + os.makedirs(DEBUG_INFO_DIR, exist_ok=True) + print(f" extracting {member} -> {out_path} ...", file=sys.stderr) + xz = subprocess.Popen(["xz", "-d"], stdin=open(nar_path, "rb"), stdout=subprocess.PIPE) + nar_cat = subprocess.run( + ["nix", "nar", "cat", "/dev/stdin", member], + stdin=xz.stdout, + capture_output=True, + check=True, + ) + xz.wait() + with open(out_path, "wb") as f: + f.write(nar_cat.stdout) + return out_path + + +def find_debug_file_in_dirs(build_id: str, debug_dirs: list[str]) -> str | None: + """Look for a .debug file by build ID under /lib/debug/.build-id/NN/NNN.debug.""" + subpath = os.path.join("lib", "debug", ".build-id", build_id[:2], build_id[2:] + ".debug") + for d in debug_dirs: + candidate = os.path.join(d, subpath) + if os.path.exists(candidate): + return candidate + return None + + +def fetch_debuginfo(build_id: str) -> dict | None: + url = f"https://cache.nixos.org/debuginfo/{build_id}" + try: + with urllib.request.urlopen(url) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as e: + if e.code == 404: + return None + raise + + +def main(): + parser = argparse.ArgumentParser( + description="Upload ELF debug symbols to Sentry." + ) + parser.add_argument("executable", help="Path to the ELF executable (e.g. ./result/bin/nix)") + parser.add_argument("--project", help="Sentry project ID") + parser.add_argument("--debug-dir", action="append", default=[], metavar="DIR", + help="Directory to search for debug files (may be repeated)") + args = parser.parse_args() + + debug_files = [] + + libs = [args.executable] + get_dynamic_libraries(args.executable) + print("ELF files to process:", file=sys.stderr) + for lib in libs: + build_id = get_build_id(lib) + if build_id is None: + print(f" {lib} (no build ID)", file=sys.stderr) + continue + + local = find_debug_file_in_dirs(build_id, args.debug_dir) + if local: + print(f" {lib} ({build_id}): found locally at {local}", file=sys.stderr) + debug_files.append(local) + continue + + debuginfo = fetch_debuginfo(build_id) + if debuginfo is None: + print(f" {lib} ({build_id}, no debug info in cache)", file=sys.stderr) + continue + print(f" {lib} ({build_id}): member={debuginfo['member']}", file=sys.stderr) + nar_path = download_nar(build_id, debuginfo["archive"]) + debug_file = extract_debug_symbols(nar_path, debuginfo["member"], build_id) + debug_files.append(debug_file) + + if debug_files: + print(f"Uploading {len(debug_files)} debug file(s) to Sentry...", file=sys.stderr) + cmd = ["sentry-cli", "debug-files", "upload"] + if args.project: + cmd += ["--project", args.project] + subprocess.run(cmd + debug_files, check=True) + + +if __name__ == "__main__": + main() From 289ac80b081fc394a4de5fccb5a2bb4040fec1b8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Apr 2026 21:53:59 +0200 Subject: [PATCH 05/29] WIP sentry support --- .../common/assert-fail/meson.build | 12 +++++------ packaging/dependencies.nix | 5 +++++ packaging/everything.nix | 6 +++++- src/libmain/unix/stack.cc | 2 +- src/nix/main.cc | 20 ++++++++++++++++++- src/nix/meson.build | 2 +- src/nix/package.nix | 2 ++ tests/functional/common/init.sh | 3 +++ 8 files changed, 42 insertions(+), 10 deletions(-) diff --git a/nix-meson-build-support/common/assert-fail/meson.build b/nix-meson-build-support/common/assert-fail/meson.build index 7539b3921326..426c0b59a32f 100644 --- a/nix-meson-build-support/common/assert-fail/meson.build +++ b/nix-meson-build-support/common/assert-fail/meson.build @@ -24,9 +24,9 @@ can_wrap_assert_fail = cxx.links( name : 'linker can wrap __assert_fail', ) -if can_wrap_assert_fail - deps_other += declare_dependency( - sources : 'wrap-assert-fail.cc', - link_args : wrap_assert_fail_args, - ) -endif +#if can_wrap_assert_fail +# deps_other += declare_dependency( +# sources : 'wrap-assert-fail.cc', +# link_args : wrap_assert_fail_args, +# ) +#endif diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index f5e39e6e005f..d08d87ee43cc 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -147,4 +147,9 @@ scope: { pslSupport = !stdenv.hostPlatform.isStatic; idnSupport = !stdenv.hostPlatform.isStatic; }; + + sentry-native = pkgs.sentry-native.override { + # Avoid having two curls in our closure. + inherit (scope) curl; + }; } diff --git a/packaging/everything.nix b/packaging/everything.nix index 00d5aa5c6411..6d0977ea2745 100644 --- a/packaging/everything.nix +++ b/packaging/everything.nix @@ -44,6 +44,10 @@ testers, patchedSrc ? null, + + curl, + boehmgc, + sentry-native, }: let @@ -155,7 +159,7 @@ stdenv.mkDerivation (finalAttrs: { let devPaths = lib.mapAttrsToList (_k: lib.getDev) finalAttrs.finalPackage.libs; debugPaths = lib.map (lib.getOutput "debug") ( - lib.attrValues finalAttrs.finalPackage.libs ++ [ nix-cli ] + lib.attrValues finalAttrs.finalPackage.libs ++ [ nix-cli curl boehmgc sentry-native ] ); in '' diff --git a/src/libmain/unix/stack.cc b/src/libmain/unix/stack.cc index 458693407270..21ba5fb4c853 100644 --- a/src/libmain/unix/stack.cc +++ b/src/libmain/unix/stack.cc @@ -45,7 +45,7 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) void detectStackOverflow() { -#if defined(SA_SIGINFO) && defined(SA_ONSTACK) +#if defined(SA_SIGINFO) && defined(SA_ONSTACK) && 0 /* Install a SIGSEGV handler to detect stack overflows. This requires an alternative stack, otherwise the signal cannot be delivered when we're out of stack space. */ diff --git a/src/nix/main.cc b/src/nix/main.cc index f16840436635..b30524d95619 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -29,6 +29,7 @@ #include #include #include +#include #ifndef _WIN32 # include @@ -378,7 +379,24 @@ void mainWrapped(int argc, char ** argv) { savedArgv = argv; - registerCrashHandler(); + bool sentryEnabled = false; + + if (getEnv("NIX_DISABLE_SENTRY").value_or("") != "1") { + sentry_options_t * options = sentry_options_new(); + sentry_options_set_dsn( + options, "https://ca42fa4b6b08ae1caf3d96b998af6bac@o4506062689927168.ingest.us.sentry.io/4511151087878144"); + sentry_options_set_database_path(options, (getCacheDir() / "sentry").string().c_str()); + sentry_options_set_release(options, fmt("nix@%s", determinateNixVersion).c_str()); + sentry_options_set_traces_sample_rate(options, 0); + sentry_options_set_auto_session_tracking(options, false); + sentry_init(options); + sentryEnabled = true; + } + + Finally cleanupSentry([]() { sentry_shutdown(); }); + + if (!sentryEnabled) + registerCrashHandler(); /* The chroot helper needs to be run before any threads have been started. */ diff --git a/src/nix/meson.build b/src/nix/meson.build index 36aafd959a6b..261a56619cb3 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -191,7 +191,7 @@ this_exe = executable( sources, dependencies : deps_private_subproject + deps_private + deps_other, include_directories : include_dirs, - link_args : linker_export_flags, + link_args : linker_export_flags + [ '-lsentry' ], install : true, cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], ) diff --git a/src/nix/package.nix b/src/nix/package.nix index 9c223bceb2ba..bda3a21373ec 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -8,6 +8,7 @@ nix-expr, nix-main, nix-cmd, + sentry-native, # Configuration Options @@ -70,6 +71,7 @@ mkMesonExecutable (finalAttrs: { nix-expr nix-main nix-cmd + sentry-native ] ++ lib.optional ( stdenv.cc.isClang diff --git a/tests/functional/common/init.sh b/tests/functional/common/init.sh index 6d98b7ae357e..2b8c781256f8 100755 --- a/tests/functional/common/init.sh +++ b/tests/functional/common/init.sh @@ -3,6 +3,9 @@ # for shellcheck : "${test_nix_conf_dir?}" "${test_nix_conf?}" +# Don't upload crashes from tests to Sentry. +export NIX_DISABLE_SENTRY=1 + if isTestOnNixOS; then mkdir -p "$test_nix_conf_dir" "$TEST_HOME" From 4f15bd53e2a70b1f6d8e10d5ed12fffd83d26078 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Apr 2026 22:01:14 +0200 Subject: [PATCH 06/29] Upload debug info to sentry --- .github/workflows/build.yml | 12 ++++++++++++ .github/workflows/ci.yml | 3 +++ maintainers/upload-debug-info-to-sentry.py | 3 ++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a179006ce18..bb9c8ae0b2fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,12 @@ on: required: false manual_netlify_site_id: required: false + sentry_auth_token: + required: false + sentry_org: + required: false + sentry_project: + required: false jobs: build: @@ -52,6 +58,12 @@ jobs: - uses: DeterminateSystems/flakehub-cache-action@main - run: nix build .#packages.${{ inputs.system }}.default .#packages.${{ inputs.system }}.binaryTarball --no-link -L - run: nix build .#packages.${{ inputs.system }}.binaryTarball --out-link tarball + - run: nix build .#^debug,out + - run: ./maintainers/upload-debug-info-to-sentry.py --debug-dir ./result-debug ./result/bin/nix + env: + SENTRY_AUTH_TOKEN: ${{ secrets.sentry_auth_token }} + SENTRY_ORG: ${{ secrets.sentry_org }} + SENTRY_PROJECT: ${{ secrets.sentry_project }} - uses: actions/upload-artifact@v4 with: name: ${{ inputs.system }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08000ac4c871..5116382e59c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,9 @@ jobs: secrets: manual_netlify_auth_token: ${{ secrets.NETLIFY_AUTH_TOKEN }} manual_netlify_site_id: ${{ secrets.NETLIFY_SITE_ID }} + sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry_org: ${{ secrets.SENTRY_ORG }} + sentry_project: ${{ secrets.SENTRY_PROJECT }} build_aarch64-linux: uses: ./.github/workflows/build.yml diff --git a/maintainers/upload-debug-info-to-sentry.py b/maintainers/upload-debug-info-to-sentry.py index bc215f785a48..30082f8864fc 100755 --- a/maintainers/upload-debug-info-to-sentry.py +++ b/maintainers/upload-debug-info-to-sentry.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python3 +#!/usr/bin/env nix +#!nix shell --inputs-from . nixpkgs#sentry-cli --command python3 import argparse import json From d7e597ae29fee19d976c46f465760780482d7d90 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 7 Apr 2026 22:12:29 +0200 Subject: [PATCH 07/29] Disable sentry on static builds --- packaging/everything.nix | 8 +++++++- src/nix/main.cc | 6 +++++- src/nix/meson.build | 5 ++++- src/nix/meson.options | 7 +++++++ src/nix/package.nix | 6 ++++-- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packaging/everything.nix b/packaging/everything.nix index 6d0977ea2745..a8b72b4e9815 100644 --- a/packaging/everything.nix +++ b/packaging/everything.nix @@ -159,7 +159,13 @@ stdenv.mkDerivation (finalAttrs: { let devPaths = lib.mapAttrsToList (_k: lib.getDev) finalAttrs.finalPackage.libs; debugPaths = lib.map (lib.getOutput "debug") ( - lib.attrValues finalAttrs.finalPackage.libs ++ [ nix-cli curl boehmgc sentry-native ] + lib.attrValues finalAttrs.finalPackage.libs + ++ [ + nix-cli + curl + boehmgc + sentry-native + ] ); in '' diff --git a/src/nix/main.cc b/src/nix/main.cc index b30524d95619..48b0eaa2ac22 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -29,7 +29,9 @@ #include #include #include -#include +#if HAVE_SENTRY +# include +#endif #ifndef _WIN32 # include @@ -381,6 +383,7 @@ void mainWrapped(int argc, char ** argv) bool sentryEnabled = false; +#if HAVE_SENTRY if (getEnv("NIX_DISABLE_SENTRY").value_or("") != "1") { sentry_options_t * options = sentry_options_new(); sentry_options_set_dsn( @@ -394,6 +397,7 @@ void mainWrapped(int argc, char ** argv) } Finally cleanupSentry([]() { sentry_shutdown(); }); +#endif if (!sentryEnabled) registerCrashHandler(); diff --git a/src/nix/meson.build b/src/nix/meson.build index 261a56619cb3..a28b15a61f86 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -50,6 +50,9 @@ mandir = get_option('mandir') mandir = fs.is_absolute(mandir) ? mandir : prefix / mandir configdata.set_quoted('NIX_MAN_DIR', mandir) +sentry_required = get_option('sentry') +configdata.set('HAVE_SENTRY', sentry_required.enabled().to_int()) + config_priv_h = configure_file( configuration : configdata, output : 'cli-config-private.hh', @@ -191,7 +194,7 @@ this_exe = executable( sources, dependencies : deps_private_subproject + deps_private + deps_other, include_directories : include_dirs, - link_args : linker_export_flags + [ '-lsentry' ], + link_args : linker_export_flags + (sentry_required.enabled() ? [ '-lsentry' ] : []), install : true, cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], ) diff --git a/src/nix/meson.options b/src/nix/meson.options index 0fc680cfe4c7..ec968c5c4344 100644 --- a/src/nix/meson.options +++ b/src/nix/meson.options @@ -7,3 +7,10 @@ option( value : 'etc/profile.d', description : 'the path to install shell profile files', ) + +option( + 'sentry', + type : 'feature', + value : 'auto', + description : 'Enable Sentry crash reporting', +) diff --git a/src/nix/package.nix b/src/nix/package.nix index bda3a21373ec..cbbc63ba0699 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -17,6 +17,7 @@ let inherit (lib) fileset; + enableSentry = !stdenv.hostPlatform.isStatic; in mkMesonExecutable (finalAttrs: { @@ -71,16 +72,17 @@ mkMesonExecutable (finalAttrs: { nix-expr nix-main nix-cmd - sentry-native ] ++ lib.optional ( stdenv.cc.isClang && stdenv.hostPlatform.isStatic && stdenv.cc.libcxx != null && stdenv.cc.libcxx.isLLVM - ) llvmPackages.libunwind; + ) llvmPackages.libunwind + ++ lib.optional enableSentry sentry-native; mesonFlags = [ + (lib.mesonEnable "sentry" enableSentry) ]; postInstall = lib.optionalString stdenv.hostPlatform.isStatic '' From 953049c85fbc4ab294d93e23b4fbe38d6f19f9d5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 15:08:45 +0200 Subject: [PATCH 08/29] Add sentry test --- tests/functional/meson.build | 1 + tests/functional/sentry.sh | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/functional/sentry.sh diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 42965995807d..d1777cb80dae 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -176,6 +176,7 @@ suites = [ 'symlinks.sh', 'external-builders.sh', 'wasm.sh', + 'sentry.sh', ], 'workdir' : meson.current_source_dir(), }, diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh new file mode 100644 index 000000000000..0f1268c88d8e --- /dev/null +++ b/tests/functional/sentry.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +source common.sh + +unset NIX_DISABLE_SENTRY + +ulimit -c 0 + +sentryDir="$TEST_HOME/.cache/nix/sentry" + +nix --version +if ! [[ -d $sentryDir ]]; then + skip "not built with sentry support" +fi + +for type in segfault assert logic-error; do + rm -rf "$sentryDir" + + (! nix crash "$type") + + [[ -e $sentryDir/last_crash ]] + + envelopes=("$sentryDir"/*.run/*.envelope) + if [[ ! -e "${envelopes[0]}" ]]; then + fail "No crash dump found in $sentryDir after crash" + fi +done From 37f092245bbbe6d8e71c70ad40659f142e7544d5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 15:27:54 +0200 Subject: [PATCH 09/29] Disable sentry on macOS --- src/nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/package.nix b/src/nix/package.nix index cbbc63ba0699..87ae87d7bc7b 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -17,7 +17,7 @@ let inherit (lib) fileset; - enableSentry = !stdenv.hostPlatform.isStatic; + enableSentry = stdenv.hostPlatform.isLinux && !stdenv.hostPlatform.isStatic; in mkMesonExecutable (finalAttrs: { From 957c52a6ccd3b07cf8413189af08516f0e9f4ecb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 15:43:04 +0200 Subject: [PATCH 10/29] nix crash -> nix __crash --- src/libutil/args.cc | 2 +- src/nix/crash.cc | 2 +- tests/functional/sentry.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index bd3dc9c95dfa..66210a3e56f4 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -632,7 +632,7 @@ MultiCommand::MultiCommand(std::string_view commandName, const Commands & comman }}, .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) { for (auto & [name, command] : commands) - if (hasPrefix(name, prefix)) + if (hasPrefix(name, prefix) && !hasPrefix(name, "__")) completions.add(name); }}}); diff --git a/src/nix/crash.cc b/src/nix/crash.cc index aaa5c90d0a2e..58f63b5d6691 100644 --- a/src/nix/crash.cc +++ b/src/nix/crash.cc @@ -45,4 +45,4 @@ struct CmdCrash : Command } }; -static auto rCrash = registerCommand("crash"); +static auto rCrash = registerCommand("__crash"); diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh index 0f1268c88d8e..d7372d9e6f57 100644 --- a/tests/functional/sentry.sh +++ b/tests/functional/sentry.sh @@ -16,7 +16,7 @@ fi for type in segfault assert logic-error; do rm -rf "$sentryDir" - (! nix crash "$type") + (! nix __crash "$type") [[ -e $sentryDir/last_crash ]] From ec810adcf9f031ca9269f1d07d593ba61353801c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 15:50:00 +0200 Subject: [PATCH 11/29] sigsegvHandler(): Restore previous signal handler This ensures the sentry signal handler is called. --- src/libmain/unix/stack.cc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libmain/unix/stack.cc b/src/libmain/unix/stack.cc index 21ba5fb4c853..0b0b5ae92888 100644 --- a/src/libmain/unix/stack.cc +++ b/src/libmain/unix/stack.cc @@ -10,6 +10,8 @@ namespace nix { +static struct sigaction savedSigsegvAction; + static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) { /* Detect stack overflows by comparing the faulting address with @@ -34,18 +36,14 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx) } } - /* Restore default behaviour (i.e. segfault and dump core). */ - struct sigaction act; - sigfillset(&act.sa_mask); - act.sa_handler = SIG_DFL; - act.sa_flags = 0; - if (sigaction(SIGSEGV, &act, 0)) + /* Restore the original SIGSEGV handler. */ + if (sigaction(SIGSEGV, &savedSigsegvAction, 0)) abort(); } void detectStackOverflow() { -#if defined(SA_SIGINFO) && defined(SA_ONSTACK) && 0 +#if defined(SA_SIGINFO) && defined(SA_ONSTACK) /* Install a SIGSEGV handler to detect stack overflows. This requires an alternative stack, otherwise the signal cannot be delivered when we're out of stack space. */ @@ -63,7 +61,7 @@ void detectStackOverflow() sigfillset(&act.sa_mask); act.sa_sigaction = sigsegvHandler; act.sa_flags = SA_SIGINFO | SA_ONSTACK; - if (sigaction(SIGSEGV, &act, 0)) + if (sigaction(SIGSEGV, &act, &savedSigsegvAction)) throw SysError("resetting SIGSEGV"); #endif } From 01b59e1f6b15a37692114d3f4a755033392b584b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 16:29:13 +0200 Subject: [PATCH 12/29] Fix eval on macOS --- packaging/everything.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/everything.nix b/packaging/everything.nix index a8b72b4e9815..1a9c948e8e2e 100644 --- a/packaging/everything.nix +++ b/packaging/everything.nix @@ -164,8 +164,8 @@ stdenv.mkDerivation (finalAttrs: { nix-cli curl boehmgc - sentry-native ] + ++ lib.optional (stdenv.hostPlatform.isLinux && !stdenv.hostPlatform.isStatic) sentry-native ); in '' From c0a02840ed0e09f5bddcea2fe69193564312480d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 17:55:56 +0200 Subject: [PATCH 13/29] Fix test --- tests/functional/sentry.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh index d7372d9e6f57..89c7bccf846d 100644 --- a/tests/functional/sentry.sh +++ b/tests/functional/sentry.sh @@ -10,7 +10,7 @@ sentryDir="$TEST_HOME/.cache/nix/sentry" nix --version if ! [[ -d $sentryDir ]]; then - skip "not built with sentry support" + skipTest "not built with sentry support" fi for type in segfault assert logic-error; do From 4b821b23b2dabafc0080755e4d6abe5cf643dae6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 18:49:02 +0200 Subject: [PATCH 14/29] Only upload debug symbols on Linux --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb9c8ae0b2fe..651af7bb906c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,9 @@ jobs: - run: nix build .#packages.${{ inputs.system }}.default .#packages.${{ inputs.system }}.binaryTarball --no-link -L - run: nix build .#packages.${{ inputs.system }}.binaryTarball --out-link tarball - run: nix build .#^debug,out - - run: ./maintainers/upload-debug-info-to-sentry.py --debug-dir ./result-debug ./result/bin/nix + - name: Upload debug info to Sentry + run: ./maintainers/upload-debug-info-to-sentry.py --debug-dir ./result-debug ./result/bin/nix + if: contains(inputs.system, 'linux') && secrets.sentry_auth_token != '' env: SENTRY_AUTH_TOKEN: ${{ secrets.sentry_auth_token }} SENTRY_ORG: ${{ secrets.sentry_org }} From fd2f0bf67bf6360fdb9b9e8c0b79452e7c283823 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 22:01:24 +0200 Subject: [PATCH 15/29] dev-shell.nix: Propagate mesonFlags from the nix component This ensures that meson flags defined in src/nix/package.nix are respected in the dev shell. --- packaging/dev-shell.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packaging/dev-shell.nix b/packaging/dev-shell.nix index 8f963f961fb1..d13a8d0e23cb 100644 --- a/packaging/dev-shell.nix +++ b/packaging/dev-shell.nix @@ -290,7 +290,8 @@ pkgs.nixComponents2.nix-util.overrideAttrs ( map (transformFlag "perl") (ignoreCrossFile pkgs.nixComponents2.nix-perl-bindings.mesonFlags) ) ++ map (transformFlag "libexpr") (ignoreCrossFile pkgs.nixComponents2.nix-expr.mesonFlags) - ++ map (transformFlag "libcmd") (ignoreCrossFile pkgs.nixComponents2.nix-cmd.mesonFlags); + ++ map (transformFlag "libcmd") (ignoreCrossFile pkgs.nixComponents2.nix-cmd.mesonFlags) + ++ map (transformFlag "nix") (ignoreCrossFile pkgs.nixComponents2.nix-cli.mesonFlags); nativeBuildInputs = let From 087b993200688979c64ef837c2b7b42f60825d61 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 22:19:55 +0200 Subject: [PATCH 16/29] Code review --- src/nix/crash.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nix/crash.cc b/src/nix/crash.cc index 58f63b5d6691..3ed491ce75e4 100644 --- a/src/nix/crash.cc +++ b/src/nix/crash.cc @@ -1,5 +1,7 @@ #include "nix/cmd/command.hh" +#include + using namespace nix; struct CmdCrash : Command From 70a66145e1bdd24d1fc1c3621bcc452fd05e8932 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 22:19:34 +0200 Subject: [PATCH 17/29] Use crashpad --- packaging/dependencies.nix | 2 +- packaging/sentry-native.nix | 38 +++++++++++++++++++++++++++++++++ src/nix/main.cc | 10 ++++++--- src/nix/meson.build | 8 +++++++ src/nix/meson.options | 7 ++++++ src/nix/package.nix | 5 ++++- tests/functional/common/init.sh | 2 +- tests/functional/sentry.sh | 5 +++-- 8 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 packaging/sentry-native.nix diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index d08d87ee43cc..3c68ed1e749b 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -148,7 +148,7 @@ scope: { idnSupport = !stdenv.hostPlatform.isStatic; }; - sentry-native = pkgs.sentry-native.override { + sentry-native = (pkgs.callPackage ./sentry-native.nix { }).override { # Avoid having two curls in our closure. inherit (scope) curl; }; diff --git a/packaging/sentry-native.nix b/packaging/sentry-native.nix new file mode 100644 index 000000000000..36eb1c4dc4c8 --- /dev/null +++ b/packaging/sentry-native.nix @@ -0,0 +1,38 @@ +{ + lib, + stdenv, + fetchgit, + cmake, + curl, + pkg-config, +}: + +stdenv.mkDerivation rec { + pname = "sentry-native"; + version = "0.13.5"; + + src = fetchgit { + url = "https://github.com/getsentry/sentry-native"; + tag = version; + hash = "sha256-vDBI6lB1DMLleAgRCfsHvTSdtmXOzvJSaNAt+NwOd3c="; + fetchSubmodules = true; + }; + + nativeBuildInputs = [ + cmake + pkg-config + ]; + + buildInputs = [ + curl + ]; + + cmakeBuildType = "RelWithDebInfo"; + + cmakeFlags = [ ]; + + outputs = [ + "out" + "dev" + ]; +} diff --git a/src/nix/main.cc b/src/nix/main.cc index 48b0eaa2ac22..e22fdabd4184 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -384,14 +384,18 @@ void mainWrapped(int argc, char ** argv) bool sentryEnabled = false; #if HAVE_SENTRY - if (getEnv("NIX_DISABLE_SENTRY").value_or("") != "1") { + auto sentryEndpoint = + getEnv("NIX_SENTRY_ENDPOINT") + .value_or( + "https://ca42fa4b6b08ae1caf3d96b998af6bac@o4506062689927168.ingest.us.sentry.io/4511151087878144"); + if (sentryEndpoint != "") { sentry_options_t * options = sentry_options_new(); - sentry_options_set_dsn( - options, "https://ca42fa4b6b08ae1caf3d96b998af6bac@o4506062689927168.ingest.us.sentry.io/4511151087878144"); + sentry_options_set_dsn(options, sentryEndpoint.c_str()); sentry_options_set_database_path(options, (getCacheDir() / "sentry").string().c_str()); sentry_options_set_release(options, fmt("nix@%s", determinateNixVersion).c_str()); sentry_options_set_traces_sample_rate(options, 0); sentry_options_set_auto_session_tracking(options, false); + sentry_options_set_handler_path(options, CRASHPAD_HANDLER_PATH); sentry_init(options); sentryEnabled = true; } diff --git a/src/nix/meson.build b/src/nix/meson.build index a28b15a61f86..137eb0767a23 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -52,6 +52,14 @@ configdata.set_quoted('NIX_MAN_DIR', mandir) sentry_required = get_option('sentry') configdata.set('HAVE_SENTRY', sentry_required.enabled().to_int()) +if sentry_required.enabled() + crashpad_handler_path = get_option('crashpad-handler') + assert( + crashpad_handler_path != '', + 'crashpad-handler path must be set when sentry is enabled', + ) + configdata.set_quoted('CRASHPAD_HANDLER_PATH', crashpad_handler_path) +endif config_priv_h = configure_file( configuration : configdata, diff --git a/src/nix/meson.options b/src/nix/meson.options index ec968c5c4344..61da4fad7eed 100644 --- a/src/nix/meson.options +++ b/src/nix/meson.options @@ -14,3 +14,10 @@ option( value : 'auto', description : 'Enable Sentry crash reporting', ) + +option( + 'crashpad-handler', + type : 'string', + value : '', + description : 'Path to the crashpad_handler binary (required when sentry is enabled)', +) diff --git a/src/nix/package.nix b/src/nix/package.nix index 87ae87d7bc7b..075e25cf3a74 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -83,7 +83,10 @@ mkMesonExecutable (finalAttrs: { mesonFlags = [ (lib.mesonEnable "sentry" enableSentry) - ]; + ] + ++ lib.optional enableSentry ( + lib.mesonOption "crashpad-handler" "${sentry-native}/bin/crashpad_handler" + ); postInstall = lib.optionalString stdenv.hostPlatform.isStatic '' mkdir -p $out/nix-support diff --git a/tests/functional/common/init.sh b/tests/functional/common/init.sh index 2b8c781256f8..9b2bed678ec4 100755 --- a/tests/functional/common/init.sh +++ b/tests/functional/common/init.sh @@ -4,7 +4,7 @@ : "${test_nix_conf_dir?}" "${test_nix_conf?}" # Don't upload crashes from tests to Sentry. -export NIX_DISABLE_SENTRY=1 +export NIX_SENTRY_ENDPOINT= if isTestOnNixOS; then diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh index 89c7bccf846d..a293ea2ba034 100644 --- a/tests/functional/sentry.sh +++ b/tests/functional/sentry.sh @@ -2,7 +2,8 @@ source common.sh -unset NIX_DISABLE_SENTRY +# This doesn't actually work, but it prevents sentry from uploading for real. +export NIX_SENTRY_ENDPOINT=file://$TEST_ROOT/sentry-endpoint ulimit -c 0 @@ -20,7 +21,7 @@ for type in segfault assert logic-error; do [[ -e $sentryDir/last_crash ]] - envelopes=("$sentryDir"/*.run/*.envelope) + envelopes=("$sentryDir"/pending/*.dmp) if [[ ! -e "${envelopes[0]}" ]]; then fail "No crash dump found in $sentryDir after crash" fi From 9e2138bd3711791725c04462e5ae4d6d32d09f71 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Apr 2026 22:44:15 +0200 Subject: [PATCH 18/29] Try on macOS --- src/nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/package.nix b/src/nix/package.nix index 075e25cf3a74..a1e94726d5f3 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -17,7 +17,7 @@ let inherit (lib) fileset; - enableSentry = stdenv.hostPlatform.isLinux && !stdenv.hostPlatform.isStatic; + enableSentry = !stdenv.hostPlatform.isStatic; in mkMesonExecutable (finalAttrs: { From 93bc0bdf4b28c78f94e1edaded9464579524186f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Apr 2026 10:45:38 +0200 Subject: [PATCH 19/29] Fix CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 651af7bb906c..92b5b03a4dde 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,7 +61,7 @@ jobs: - run: nix build .#^debug,out - name: Upload debug info to Sentry run: ./maintainers/upload-debug-info-to-sentry.py --debug-dir ./result-debug ./result/bin/nix - if: contains(inputs.system, 'linux') && secrets.sentry_auth_token != '' + if: contains(inputs.system, 'linux') && env.SENTRY_AUTH_TOKEN != '' env: SENTRY_AUTH_TOKEN: ${{ secrets.sentry_auth_token }} SENTRY_ORG: ${{ secrets.sentry_org }} From eb6fb7f01e0570197b6a44d0e4397f4d3c5b47ee Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Apr 2026 10:50:48 +0200 Subject: [PATCH 20/29] Fix warning --- src/nix/main.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nix/main.cc b/src/nix/main.cc index e22fdabd4184..74d39402ad43 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -400,7 +400,10 @@ void mainWrapped(int argc, char ** argv) sentryEnabled = true; } - Finally cleanupSentry([]() { sentry_shutdown(); }); + Finally cleanupSentry([&]() { + if (sentryEnabled) + sentry_shutdown(); + }); #endif if (!sentryEnabled) From 1572f306b3922a1a028e08d801131c8148d06309 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Apr 2026 03:05:22 -0700 Subject: [PATCH 21/29] Fix compilation error --- src/libfetchers/fetchers.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 19018aaff9d0..fa62cc32ab07 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -13,6 +13,7 @@ #include "nix/util/environment-variables.hh" #include +#include namespace nix::fetchers { From 911f3d68a03cb5558c7a432138e43f3d3c9a67c7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Apr 2026 03:05:38 -0700 Subject: [PATCH 22/29] Fix sentry-native build on macOS --- packaging/sentry-native.nix | 14 ++++++++++++++ tests/functional/sentry.sh | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packaging/sentry-native.nix b/packaging/sentry-native.nix index 36eb1c4dc4c8..06accdd5b18a 100644 --- a/packaging/sentry-native.nix +++ b/packaging/sentry-native.nix @@ -5,6 +5,8 @@ cmake, curl, pkg-config, + python3, + darwin, }: stdenv.mkDerivation rec { @@ -18,11 +20,23 @@ stdenv.mkDerivation rec { fetchSubmodules = true; }; + dontFixCmake = true; + nativeBuildInputs = [ cmake pkg-config + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ + python3 + darwin.bootstrap_cmds ]; + postPatch = '' + # Borrowed from psutil: stick to the old SDK name for now. + substituteInPlace external/crashpad/util/mac/mac_util.cc \ + --replace-fail kIOMainPortDefault kIOMasterPortDefault + ''; + buildInputs = [ curl ]; diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh index a293ea2ba034..c749cf04bca9 100644 --- a/tests/functional/sentry.sh +++ b/tests/functional/sentry.sh @@ -15,12 +15,12 @@ if ! [[ -d $sentryDir ]]; then fi for type in segfault assert logic-error; do + if [[ $type = logic-error && $(uname) = Darwin ]]; then continue; fi + rm -rf "$sentryDir" (! nix __crash "$type") - [[ -e $sentryDir/last_crash ]] - envelopes=("$sentryDir"/pending/*.dmp) if [[ ! -e "${envelopes[0]}" ]]; then fail "No crash dump found in $sentryDir after crash" From 05817390b85916f7a705097de1a1e833d9a77b0b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Apr 2026 03:36:50 -0700 Subject: [PATCH 23/29] upload-debug-info-to-sentry.py: Support macOS --- .github/workflows/build.yml | 2 +- maintainers/upload-debug-info-to-sentry.py | 106 ++++++++++++--------- 2 files changed, 64 insertions(+), 44 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92b5b03a4dde..d49e81213f71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,7 +61,7 @@ jobs: - run: nix build .#^debug,out - name: Upload debug info to Sentry run: ./maintainers/upload-debug-info-to-sentry.py --debug-dir ./result-debug ./result/bin/nix - if: contains(inputs.system, 'linux') && env.SENTRY_AUTH_TOKEN != '' + if: env.SENTRY_AUTH_TOKEN != '' env: SENTRY_AUTH_TOKEN: ${{ secrets.sentry_auth_token }} SENTRY_ORG: ${{ secrets.sentry_org }} diff --git a/maintainers/upload-debug-info-to-sentry.py b/maintainers/upload-debug-info-to-sentry.py index 30082f8864fc..e1c242fd2fee 100755 --- a/maintainers/upload-debug-info-to-sentry.py +++ b/maintainers/upload-debug-info-to-sentry.py @@ -4,6 +4,7 @@ import argparse import json import os +import platform import re import subprocess import sys @@ -16,19 +17,30 @@ def get_dynamic_libraries(executable: str) -> list[str]: - result = subprocess.run(["ldd", executable], capture_output=True, text=True, check=True) - libs = [] - for line in result.stdout.splitlines(): - # ldd output lines look like: - # libfoo.so.1 => /nix/store/.../libfoo.so.1 (0x...) - # /lib64/ld-linux-x86-64.so.2 (0x...) - m = re.search(r"=> (/\S+)", line) - if m: - libs.append(m.group(1)) - elif line.strip().startswith("/"): - path = line.strip().split()[0] - libs.append(path) - return libs + if platform.system() == "Darwin": + result = subprocess.run(["otool", "-L", executable], capture_output=True, text=True, check=True) + libs = [] + for line in result.stdout.splitlines()[1:]: # skip first line (the binary path itself) + # otool -L output lines look like: + # /nix/store/.../libfoo.dylib (compatibility version X.Y.Z, current version A.B.C) + m = re.match(r"\s+(\S+)\s+\(", line) + if m: + libs.append(m.group(1)) + return libs + else: + result = subprocess.run(["ldd", executable], capture_output=True, text=True, check=True) + libs = [] + for line in result.stdout.splitlines(): + # ldd output lines look like: + # libfoo.so.1 => /nix/store/.../libfoo.so.1 (0x...) + # /lib64/ld-linux-x86-64.so.2 (0x...) + m = re.search(r"=> (/\S+)", line) + if m: + libs.append(m.group(1)) + elif line.strip().startswith("/"): + path = line.strip().split()[0] + libs.append(path) + return libs def get_build_id(path: str) -> str | None: @@ -96,45 +108,53 @@ def fetch_debuginfo(build_id: str) -> dict | None: def main(): parser = argparse.ArgumentParser( - description="Upload ELF debug symbols to Sentry." + description="Upload debug symbols to Sentry." ) - parser.add_argument("executable", help="Path to the ELF executable (e.g. ./result/bin/nix)") + parser.add_argument("executable", help="Path to the executable (e.g. ./result/bin/nix)") parser.add_argument("--project", help="Sentry project ID") parser.add_argument("--debug-dir", action="append", default=[], metavar="DIR", - help="Directory to search for debug files (may be repeated)") + help="Directory to search for debug files (may be repeated, Linux only)") args = parser.parse_args() - debug_files = [] - libs = [args.executable] + get_dynamic_libraries(args.executable) - print("ELF files to process:", file=sys.stderr) - for lib in libs: - build_id = get_build_id(lib) - if build_id is None: - print(f" {lib} (no build ID)", file=sys.stderr) - continue - - local = find_debug_file_in_dirs(build_id, args.debug_dir) - if local: - print(f" {lib} ({build_id}): found locally at {local}", file=sys.stderr) - debug_files.append(local) - continue - - debuginfo = fetch_debuginfo(build_id) - if debuginfo is None: - print(f" {lib} ({build_id}, no debug info in cache)", file=sys.stderr) - continue - print(f" {lib} ({build_id}): member={debuginfo['member']}", file=sys.stderr) - nar_path = download_nar(build_id, debuginfo["archive"]) - debug_file = extract_debug_symbols(nar_path, debuginfo["member"], build_id) - debug_files.append(debug_file) - - if debug_files: - print(f"Uploading {len(debug_files)} debug file(s) to Sentry...", file=sys.stderr) + + if platform.system() == "Darwin": + # On macOS there are no separate debug info files; upload the binaries directly. + print("Files to upload:", file=sys.stderr) + for lib in libs: + print(f" {lib}", file=sys.stderr) + files_to_upload = libs + else: + debug_files = [] + print("ELF files to process:", file=sys.stderr) + for lib in libs: + build_id = get_build_id(lib) + if build_id is None: + print(f" {lib} (no build ID)", file=sys.stderr) + continue + + local = find_debug_file_in_dirs(build_id, args.debug_dir) + if local: + print(f" {lib} ({build_id}): found locally at {local}", file=sys.stderr) + debug_files.append(local) + continue + + debuginfo = fetch_debuginfo(build_id) + if debuginfo is None: + print(f" {lib} ({build_id}, no debug info in cache)", file=sys.stderr) + continue + print(f" {lib} ({build_id}): member={debuginfo['member']}", file=sys.stderr) + nar_path = download_nar(build_id, debuginfo["archive"]) + debug_file = extract_debug_symbols(nar_path, debuginfo["member"], build_id) + debug_files.append(debug_file) + files_to_upload = debug_files + + if files_to_upload: + print(f"Uploading {len(files_to_upload)} file(s) to Sentry...", file=sys.stderr) cmd = ["sentry-cli", "debug-files", "upload"] if args.project: cmd += ["--project", args.project] - subprocess.run(cmd + debug_files, check=True) + subprocess.run(cmd + files_to_upload, check=True) if __name__ == "__main__": From fb689134acbde3a067573179534e1dde8535d18b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 Apr 2026 05:00:51 -0700 Subject: [PATCH 24/29] Fix upload --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5116382e59c1..37711caa66a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,10 @@ jobs: runner: UbuntuLatest32Cores128GArm runner_for_virt: UbuntuLatest32Cores128GArm runner_small: UbuntuLatest32Cores128GArm + secrets: + sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry_org: ${{ secrets.SENTRY_ORG }} + sentry_project: ${{ secrets.SENTRY_PROJECT }} build_aarch64-darwin: uses: ./.github/workflows/build.yml @@ -73,6 +77,10 @@ jobs: runner: namespace-profile-mac-m2-12c28g runner_for_virt: namespace-profile-mac-m2-12c28g runner_small: macos-latest-xlarge + secrets: + sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} + sentry_org: ${{ secrets.SENTRY_ORG }} + sentry_project: ${{ secrets.SENTRY_PROJECT }} success: runs-on: ubuntu-latest From b60ca3bae2ffc461efd0fe210529fd44c3a53d67 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Apr 2026 12:08:23 +0200 Subject: [PATCH 25/29] Get the Sentry endpoint from /etc/nix/sentry-endpoint Also respect the DETSYS_IDS_TELEMETRY environment variable. --- src/nix/main.cc | 17 +++++++++++------ tests/functional/sentry.sh | 5 +++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/nix/main.cc b/src/nix/main.cc index 74d39402ad43..f14dbe9b2ed4 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -384,13 +384,18 @@ void mainWrapped(int argc, char ** argv) bool sentryEnabled = false; #if HAVE_SENTRY - auto sentryEndpoint = - getEnv("NIX_SENTRY_ENDPOINT") - .value_or( - "https://ca42fa4b6b08ae1caf3d96b998af6bac@o4506062689927168.ingest.us.sentry.io/4511151087878144"); - if (sentryEndpoint != "") { + auto sentryEndpoint = getEnv("NIX_SENTRY_ENDPOINT"); + + if (!sentryEndpoint && getEnv("DETSYS_IDS_TELEMETRY") != "disabled") { + try { + sentryEndpoint = trim(readFile(settings.nixConfDir / "sentry-endpoint")); + } catch (...) { + } + } + + if (sentryEndpoint && sentryEndpoint != "") { sentry_options_t * options = sentry_options_new(); - sentry_options_set_dsn(options, sentryEndpoint.c_str()); + sentry_options_set_dsn(options, sentryEndpoint->c_str()); sentry_options_set_database_path(options, (getCacheDir() / "sentry").string().c_str()); sentry_options_set_release(options, fmt("nix@%s", determinateNixVersion).c_str()); sentry_options_set_traces_sample_rate(options, 0); diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh index c749cf04bca9..5f61d86dc57d 100644 --- a/tests/functional/sentry.sh +++ b/tests/functional/sentry.sh @@ -2,8 +2,9 @@ source common.sh -# This doesn't actually work, but it prevents sentry from uploading for real. -export NIX_SENTRY_ENDPOINT=file://$TEST_ROOT/sentry-endpoint +# Enable sentry with a fake endpoint. +unset NIX_SENTRY_ENDPOINT +echo -n "file://$TEST_ROOT/sentry-endpoint" > "$NIX_CONF_DIR/sentry-endpoint" ulimit -c 0 From 0f6eb2f32417c0212b2826be9e0869dbb433be28 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Apr 2026 16:31:23 +0200 Subject: [PATCH 26/29] Print errors reading sentry-endpoint --- src/nix/main.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nix/main.cc b/src/nix/main.cc index f14dbe9b2ed4..a0f2455f7e51 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -388,8 +388,11 @@ void mainWrapped(int argc, char ** argv) if (!sentryEndpoint && getEnv("DETSYS_IDS_TELEMETRY") != "disabled") { try { - sentryEndpoint = trim(readFile(settings.nixConfDir / "sentry-endpoint")); + auto p = settings.nixConfDir / "sentry-endpoint"; + if (pathExists(p)) + sentryEndpoint = trim(readFile(p)); } catch (...) { + ignoreExceptionExceptInterrupt(); } } From 05e2b13f2bb860c1467ccd556530d25be26bbb0f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Apr 2026 17:39:19 +0200 Subject: [PATCH 27/29] Re-enable wrap-assert-fail --- .../common/assert-fail/meson.build | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix-meson-build-support/common/assert-fail/meson.build b/nix-meson-build-support/common/assert-fail/meson.build index 426c0b59a32f..7539b3921326 100644 --- a/nix-meson-build-support/common/assert-fail/meson.build +++ b/nix-meson-build-support/common/assert-fail/meson.build @@ -24,9 +24,9 @@ can_wrap_assert_fail = cxx.links( name : 'linker can wrap __assert_fail', ) -#if can_wrap_assert_fail -# deps_other += declare_dependency( -# sources : 'wrap-assert-fail.cc', -# link_args : wrap_assert_fail_args, -# ) -#endif +if can_wrap_assert_fail + deps_other += declare_dependency( + sources : 'wrap-assert-fail.cc', + link_args : wrap_assert_fail_args, + ) +endif From 82df58fab1e24641110a04ea4c9ae087da359af4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 14 Apr 2026 19:14:51 +0200 Subject: [PATCH 28/29] Don't include undocumented commands in the manual --- src/libcmd/include/nix/cmd/command.hh | 1 - src/libutil/args.cc | 2 ++ src/libutil/include/nix/util/args.hh | 1 + src/nix/main.cc | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libcmd/include/nix/cmd/command.hh b/src/libcmd/include/nix/cmd/command.hh index 326b05c6274a..ec2e0d9add4c 100644 --- a/src/libcmd/include/nix/cmd/command.hh +++ b/src/libcmd/include/nix/cmd/command.hh @@ -24,7 +24,6 @@ static constexpr Command::Category catHelp = -1; static constexpr Command::Category catSecondary = 100; static constexpr Command::Category catUtility = 101; static constexpr Command::Category catNixInstallation = 102; -static constexpr Command::Category catUndocumented = 103; static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)"; diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 66210a3e56f4..e7ea47e08143 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -671,6 +671,8 @@ nlohmann::json MultiCommand::toJSON() auto command = commandFun(); auto j = command->toJSON(); auto cat = nlohmann::json::object(); + if (command->category() == Command::catUndocumented) + continue; cat["id"] = command->category(); cat["description"] = trim(categories[command->category()]); cat["experimental-feature"] = command->experimentalFeature(); diff --git a/src/libutil/include/nix/util/args.hh b/src/libutil/include/nix/util/args.hh index d793411247b8..54dc0471c3f0 100644 --- a/src/libutil/include/nix/util/args.hh +++ b/src/libutil/include/nix/util/args.hh @@ -374,6 +374,7 @@ struct Command : virtual public Args using Category = int; static constexpr Category catDefault = 0; + static constexpr Category catUndocumented = 1; virtual std::optional experimentalFeature(); diff --git a/src/nix/main.cc b/src/nix/main.cc index a0f2455f7e51..c7c26d00d936 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -121,10 +121,10 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs categories.clear(); categories[catHelp] = "Help commands"; categories[Command::catDefault] = "Main commands"; + categories[Command::catUndocumented] = "Undocumented commands"; categories[catSecondary] = "Infrequently used commands"; categories[catUtility] = "Utility/scripting commands"; categories[catNixInstallation] = "Commands for upgrading or troubleshooting your Nix installation"; - categories[catUndocumented] = "Undocumented commands"; addFlag({ .longName = "help", From 4a4ef708fcf16c3cd7d3854fc8d8ea18fb226ff7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 14 Apr 2026 19:33:01 +0200 Subject: [PATCH 29/29] Fix VM test --- tests/functional/sentry.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/sentry.sh b/tests/functional/sentry.sh index 5f61d86dc57d..e8e22baa9cd5 100644 --- a/tests/functional/sentry.sh +++ b/tests/functional/sentry.sh @@ -4,7 +4,7 @@ source common.sh # Enable sentry with a fake endpoint. unset NIX_SENTRY_ENDPOINT -echo -n "file://$TEST_ROOT/sentry-endpoint" > "$NIX_CONF_DIR/sentry-endpoint" +echo -n "file://$TEST_ROOT/sentry-endpoint" > "$test_nix_conf_dir/sentry-endpoint" ulimit -c 0