# Designing relocatable shebangs

**URL:** https://discourse.nixos.org/t/designing-relocatable-shebangs/80755
**Category:** Development
**Created:** [October 25, 2026, 1:14pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755 "2026-10-25T13:14:36Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![ssoss](https://discourse.nixos.org/user_avatar/discourse.nixos.org/ssoss/32/27356_2.png) [@ssoss](https://discourse.nixos.org/u/ssoss)
#### Post date: [October 25, 2026, 1:14pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/1 "2026-10-25T13:14:36Z")

</div>

Linux 7.3 brings an incredibly powerful feature: eBPF binfmt\_misc hooks. You can see some discussion on this [here](https://discourse.nixos.org/t/linux-7-3-brings-more-powerful-binfmt-misc-for-sophisticated-distros-eg-nixos/80623), especially the blog post by @fzakaria [here](https://fzakaria.com/2026/10/17/one-store-two-frontends).

One idea is to make the `PT_INTERP` elf header support `$ORIGIN`, so that binaries are completely relocatable. However, this breaks compatibility with older Linux kernels, and with users that otherwise cannot enable BPF.

A solution is to define the normal `PT_INTERP` as `/nix/store/...`, but to additionally define a separate `PT_INTERP_NIX` section that replaces the absolute path with a relative path. On a Linux 7.3+ kernel with the bpf program loaded, the binary will be relocatable, but on older systems everything would still work as long as the binary is installed into the global path. I think this is a good path forward for Nix.

But for full compatibility, the same thing needs to happen for shebangs.  
The binfmt\_misc program can also recognize the execution of scripts that start with `#!`, and can rewrite them as necessary. One option is to just substitute `$ORIGIN` in the shebang, but this again would lose non-bpf compatibility.

I propose the following protocol: binaries that start with `#!\t ` (that is 0x23, 0x21, 0x09, 0x20) will be marked as “relocatable”. Ie. the bpf `match` program will return `true` when the file starts with those 4 characters.

When the bpf program recognizes a script, the interpretation rules are: skip all characters from the start of input until the first newline. Then skip the following sequence of non-whitespace characters, then the next sequence of whitespace characters. After that, the rest of that line will be parsed as a shebang, but will have `$ORIGIN` replaced.

For example:

```python
#! /nix/store/xxxxxxxxxx-python3-3.14.8/bin/python3.14
# $ORIGIN/../../xxxxxxxxxx-python3-3.14.8/bin/python3.14
print("Hello from Python!")

```

```haskell
#! /nix/store/xxxxxxxxxx-lua5_5-5.5.1/bin/lua
-- $ORIGIN/../../xxxxxxxxxx-lua5_5-5.5.1/bin/lua
print "Hello from Lua!"

```

```bash
#! /nix/store/xxxxxxxxxx-bash-5.3p20/bin/bash -e
# $ORIGIN/../../xxxxxxxxxx-bash-5.3p20/bin/bash -e
echo "Hello from Bash!"

```

I think that using a recognizable yet uncommon sequence of whitespace (`\t `) is a good way to implement this feature, but let me know if you have any thoughts!

---

<div class="post-metadata">

### Author: ![fzakaria](https://discourse.nixos.org/user_avatar/discourse.nixos.org/fzakaria/32/2643_2.png) [@fzakaria](https://discourse.nixos.org/u/fzakaria)
#### Post date: [October 25, 2026, 6:06pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/2 "2026-10-25T18:06:57Z")

</div>

That makes sense to me; we should probably upstream the new eBPF PT\_INTERP\_NIX BPF Handler as a NixOS module now that 7.3 is out;  
I had been meaning to do that…  
If you want to take a stab at that, you can add me as a reviewer?  
(We can then propose the same to Debian and Fedora?)

The kernel should include an example for Nix BPF in the source AFAIR (it was part of the patch series we worked on)

Here is the eBPF for the shebang: [shebang\_origin.bpf.c · GitHub](https://gist.github.com/fzakaria/2e1e1c44fa488a951674f8761c672366)

---

<div class="post-metadata">

### Author: ![twoolie](https://discourse.nixos.org/user_avatar/discourse.nixos.org/twoolie/32/2544_2.png) [@twoolie](https://discourse.nixos.org/u/twoolie)
#### Post date: [October 25, 2026, 11:56pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/3 "2026-10-25T23:56:06Z")

</div>

> [@ssoss](#):
>
> I propose the following protocol: binaries that start with `#!\t `(that is 0x23, 0x21, 0x09, 0x20) will be marked as “relocatable”. Ie. the bpf `match` program will return `true` when the file starts with those 4 characters.

Do we need to specially mark them? Can’t we just have the bpf program look for `#!/nix/store/` and substitute in `#!$ORIGIN`? I can’t think of a case where this would break scripts that haven’t opted in, but that could of course be a failure of imagination on my part.

---

<div class="post-metadata">

### Author: ![rszyma](https://discourse.nixos.org/user_avatar/discourse.nixos.org/rszyma/32/17928_2.png) [@rszyma](https://discourse.nixos.org/u/rszyma)
#### Post date: [October 26, 2026, 12:16am UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/4 "2026-10-26T00:16:20Z")

</div>

I don’t have better idea, but I imagine marking with tab (\t) could break (in a non-obvious way) when converting between spaces and tabs in editor. Also inputting \t would be slightly annoying if editor uses spaces as tabs.

Edit: I just realized that these sequences would be written by `patchShebangs`, and the file kept read-only in nix store, so my concerns are probably not an issue.

---

<div class="post-metadata">

### Author: ![ssoss](https://discourse.nixos.org/user_avatar/discourse.nixos.org/ssoss/32/27356_2.png) [@ssoss](https://discourse.nixos.org/u/ssoss)
#### Post date: [October 26, 2026, 12:55pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/5 "2026-10-26T12:55:18Z")

</div>

I think it could be a speed improvement, as a Linux 7.3 system will still not have that many fully-relocatable scripts on it while it will definitely have a lot of scripts with a `#!/nix/store` shebang.

I could also imagine someone wanting to quickly edit a script in the store, so they want to `cp /nix/store/xxxx/script.py ./script.py`, edit, then run. By only performing the replacement for the `#!\t ` sequence, you can prevent the bpf replacement from running by just deleting one of those whitespace characters.

---

<div class="post-metadata">

### Author: ![ssoss](https://discourse.nixos.org/user_avatar/discourse.nixos.org/ssoss/32/27356_2.png) [@ssoss](https://discourse.nixos.org/u/ssoss)
#### Post date: [October 26, 2026, 12:58pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/6 "2026-10-26T12:58:17Z")

</div>

This is a good point. And while NixOS will only be writing these files using `patchShebangs`, I think the concept is much more general and could be hand-written for AppImage-style uses.

Maybe we could also recognize sequences like “17 space characters” or “7 tab characters”, where no human would write it themselves but would survive code editors.

---

<div class="post-metadata">

### Author: ![ssoss](https://discourse.nixos.org/user_avatar/discourse.nixos.org/ssoss/32/27356_2.png) [@ssoss](https://discourse.nixos.org/u/ssoss)
#### Post date: [October 26, 2026, 1:04pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/7 "2026-10-26T13:04:38Z")

</div>

I think we should try to name it `PT_INTERP_ORIGIN` or `PT_INTERP_RELOC`, since it’s useful beyond Nix (I know the GNOME OS folks were at least open to using it with Toolpak, and IIRC bazel was also interested).

And I can try to take a stab at it next week.

---

<div class="post-metadata">

### Author: ![iwanb](https://discourse.nixos.org/user_avatar/discourse.nixos.org/iwanb/32/4268_2.png) [@iwanb](https://discourse.nixos.org/u/iwanb)
#### Post date: [October 26, 2026, 7:32pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/8 "2026-10-26T19:32:42Z")

</div>

Wouldn’t the eBPF hook check that the script itself is in /nix/store/ before doing a replacement? So copying it would automatically disable it.

EDIT: of course thinking about it for another second, that breaks the relocatability which is the whole point… But you could still match on `/<hash>-.../` which is probably enough

---

<div class="post-metadata">

### Author: ![ssoss](https://discourse.nixos.org/user_avatar/discourse.nixos.org/ssoss/32/27356_2.png) [@ssoss](https://discourse.nixos.org/u/ssoss)
#### Post date: [October 26, 2026, 10:09pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/9 "2026-10-26T22:09:54Z")

</div>

I think its a bad idea to strongly couple the bpf program with Nix, let alone `/nix/store`. I think it’s much less of a challenge to get the feature added to Ubuntu/Fedora if Bazel/Buck2/Toolpak are all interested in the same feature.

---

<div class="post-metadata">

### Author: ![fzakaria](https://discourse.nixos.org/user_avatar/discourse.nixos.org/fzakaria/32/2643_2.png) [@fzakaria](https://discourse.nixos.org/u/fzakaria)
#### Post date: [October 26, 2026, 10:32pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/10 "2026-10-26T22:32:22Z")

</div>

the BPF program can be NixOS-specific I think; if there is something to leverage a particular distribution to do in the BPF that is smart.

I agree though that there can also be a “general” approach; it’s a handler chain so there can be the “default BPF $ORIGIN” handling and if one is needed, a NixOS optimized one?

Feel free to reach out to me if you want to collaborate.  
I am expecting baby #4 (this week!) but I still like to hack 😛

---

<div class="post-metadata">

### Author: ![numinit](https://discourse.nixos.org/user_avatar/discourse.nixos.org/numinit/32/2313_2.png) [@numinit](https://discourse.nixos.org/u/numinit)
#### Post date: [October 26, 2026, 10:56pm UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/11 "2026-10-26T22:56:49Z")

</div>

We ought to try to get whatever we pick into FHS 4. @zwol any thoughts? 😀

See “store-based” here:

> **[Begin drafting FHS 4 by introducing concepts of profiles and installation...](https://gitlab.freedesktop.org/FHS/fhs-spec/-/merge_requests/4)**
>
> Last y’all heard from me, I was saying I thought there should be FHS 3.5 that minimally addressed the outstanding issues. Then I disappeared for two months and...

> **[FHS draft - - (zwol.fhs4-reorg)](https://fhs.pages.freedesktop.org/-/fhs-spec/-/jobs/93479708/artifacts/build/fhs/index.html)**

> Distributions conforming to the “store-based” profile use a unique [installation prefix](https://fhs.pages.freedesktop.org/-/fhs-spec/-/jobs/93479708/artifacts/build/fhs/xmlto-html/fhs-onepage.html#install-prefix) for each software package that they distribute. An installed system that uses a store-based distribution will rely on [search paths](https://fhs.pages.freedesktop.org/-/fhs-spec/-/jobs/93479708/artifacts/build/fhs/xmlto-html/fhs-onepage.html#runtime-search-paths) and symlink farms to present a unified view of available software.

Maybe this could use something about $ORIGIN? It seems to prefer absolute paths…

> In a store-based distribution, each package’s installation prefix is a subdirectory of the “store root,” an absolute path defined by the specific store-based distribution.

In any case, it will be much easier for FHS distros to support if this is just canonized in the FHS.

---

<div class="post-metadata">

### Author: ![zwol](https://discourse.nixos.org/user_avatar/discourse.nixos.org/zwol/32/31829_2.png) [@zwol](https://discourse.nixos.org/u/zwol)
#### Post date: [October 27, 2026, 1:02am UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/12 "2026-10-27T01:02:40Z")

</div>

FHS 4 seems to have stalled out. I would still like to see it happen but, well, I posted that draft and got _one_ piece of feedback, from the guy who originally wanted to do FHS 4, which boiled down to “you and I have very different visions for what the standard should even _be_”, and since then nothing, for the past eight months.

The vagueness about what path the “store root” actually _is_, is intentional, because there’s at least two candidates in use “in the wild” (`/nix/store` for Nix and `/gnu/store` for Guix), and it isn’t, IMNSHO, FHS’s place to pick a winner. Genericizing it (`/store` perhaps) isn’t a solution; that would break _both_ Nix and Guix, and even if it didn’t, would rule out future use of either as an overlay upon the other.

* * *

Side note, I firmly believe that eBPF is a security disaster waiting to happen and I always turn it off at kernel build time. I would turn off _original_ BPF as well if kconfig would let me. I do not want to see any technology based on eBPF become a hard requirement for NixOS.

---

<div class="post-metadata">

### Author: ![numinit](https://discourse.nixos.org/user_avatar/discourse.nixos.org/numinit/32/2313_2.png) [@numinit](https://discourse.nixos.org/u/numinit)
#### Post date: [October 27, 2026, 3:31am UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/13 "2026-10-27T03:31:56Z")

</div>

FWIW, my immediate thought is Android, which requires hideous hacks to support /nix/store with `proot` for nix-on-droid, and would also never enable BPF to do this kind of thing in a million years, either. (So they are forcing new, worse hacks…)

I do wonder if they’d be amenible to a Kconfig option to enable it, then vendors could opt in. We’d have to convince Google for Android but I ~~feel like that’d be easier than convincing them to turn on BPF.~~

edit: I was mistaken, they do actually enable it: [https://source.android.com/docs/core/architecture/kernel/bpf](https://source.android.com/docs/core/architecture/kernel/bpf) - that’s how vendors do things like traffic classification, actually

---

<div class="post-metadata">

### Author: ![twoolie](https://discourse.nixos.org/user_avatar/discourse.nixos.org/twoolie/32/2544_2.png) [@twoolie](https://discourse.nixos.org/u/twoolie)
#### Post date: [October 27, 2026, 6:26am UTC](https://discourse.nixos.org/t/designing-relocatable-shebangs/80755/14 "2026-10-27T06:26:23Z")

</div>

> [@ssoss](#):
>
> I think it could be a speed improvement, as a Linux 7.3 system will still not have that many fully-relocatable scripts on it while it will definitely have a lot of scripts with a `#!/nix/store` shebang.

I don’t see how this is a meaningful speed improvement, since the ebpf hook will always need to run to check for `#/\t ` so searching for or replacing a longer string is a difference of a few microseconds.

> [@ssoss](#):
>
> I could also imagine someone wanting to quickly edit a script in the store, so they want to `cp /nix/store/xxxx/script.py ./script.py`, edit, then run. By only performing the replacement for the `#!\t `sequence, you can prevent the bpf replacement from running by just deleting one of those whitespace characters.

Why would you want to disable the replacement when running an external script? surely you still want your copied script to replace /nix/store/… with wherever your local store actually is so it actually still works.

I guess besides the ick factor of the hack that is using `#!\t ` as the magic bytes for PT\_INTERP shenanigans (`!#?!`), I’m really failing to see why this is needed. That’s not to say that this proposal is unnecessary or misguided, just that I’m failing to grok why this design is the one to pursue.

Alternatively, one could use the same [trick that `nix-shell` uses to wrap scripts](https://wiki.nixos.org/wiki/Nix-shell_shebang), providing a chain-loading program makes it 100% clear to to the reader that the interpreter is being redirected

```auto
#! /usr/bin/env nix-chain-load
#! $ORIGIN/../../xxxxxxxxxx-python3-3.14.8/bin/python3.14

```
