Skip to content

Instantly share code, notes, and snippets.

@istudyatuni
Last active June 16, 2026 06:01
Show Gist options
  • Select an option

  • Save istudyatuni/2ba5d6e8c0784693444569bb34c463f8 to your computer and use it in GitHub Desktop.

Select an option

Save istudyatuni/2ba5d6e8c0784693444569bb34c463f8 to your computer and use it in GitHub Desktop.
Run binary in isolation

Run binary in isolation

Uses bubblewrap under the hood. Targeting NixOS, but can be modified to be run on other distributions

Usage

chmod +x run-wrap.fish
./run-wrap.fish <exe>

# check mounted paths
./run-wrap.fish findmnt
#!/usr/bin/env -S nix shell nixpkgs#fish nixpkgs#bubblewrap --command fish
# Run executable in isolated environment
# - share network
# - inherit /nix/store paths from $PATH
# - inherit some env variables
# - ro bind /nix, /bin, /usr/bin folders, ssl certs
# - rw bind current directory
# - ro bind .git and .jj
# - hide git-ignored paths in current directory (bind as empty dirs/files).
# can be overriden by profiles
# - when running inside git-ignored dir, bind it as-is
# - bind known dirs/files for some profiles. currently for:
# - go
# - nodejs
# - nodejs-npm
# - nodejs-yarn
# - rust
# utils
function echo-lines; for a in $argv; echo $a; end; end
function ro-bind -a name; echo-lines --ro-bind (realpath $name) $name; end
function ro-bind-exists -a name
if test -e $name; ro-bind $name; end
end
function passenv -a name; echo-lines --setenv "$name" (eval "echo \$$name"); end
function with_color -a color
set_color $color
echo -n $argv[2..]
set_color normal
end
function log; echo $argv >&2; end
function warn; log (with_color yellow $argv); end
# help
if test "$argv[1]" = '-h'
set -l name (status basename)
log "Run executable in isolated environment
Usage:
$name <exe>
$name <exe> [exe args..]
$name <exe> -- [bwrap args..]
$name <exe> [exe args..] -- [bwrap args..]"
exit
end
# parse args
set -l exe $argv[1]
if test -z $exe
log Executable is required: $(status basename) '<exe>'
exit 1
end
set -l argv $argv[2..]
set -l exe_args
set -l bwrap_args
set -l arg_sep_found false
for arg in $argv
if test $arg = '--'
set arg_sep_found true
continue
end
if $arg_sep_found
set -a bwrap_args $arg
else
set -a exe_args $arg
end
end
# basic variables
set -l dir (pwd)
set -l user (whoami)
set -l uid (id -u)
set -l home $HOME
# -g so variable is visible in functions
set -g tmpdir /tmp/tmp.run-wrap
mkdir -p $tmpdir
getent passwd $uid 65534 > $tmpdir/passwd
getent group (id -g) 65534 > $tmpdir/group
touch $tmpdir/empty
mkdir -p $tmpdir/emptydir
echo 'experimental-features = nix-command flakes' > $tmpdir/nix.conf
function _hide -a empty name; echo-lines --ro-bind $empty $name; end
function hide-dir -a name; _hide $tmpdir/emptydir $name; end
function hide-file -a name; _hide $tmpdir/empty $name; end
# extra bwrap args
set -l args
# list of git-ignored paths that will be handled by profile
set -l allow_ignored
set -l profile unset
# detect profile
if test -f Cargo.toml; set profile rust; end
if test -f go.mod; set profile golang; end
if test -f package.json;
if test -f package-lock.json; set profile nodejs-npm; end
if test -f yarn.lock; set profile nodejs-yarn; end
end
# profile-specific binds
if test $profile = golang
set -a args (ro-bind $home/go)
end
if test $profile = nodejs-npm
set -a args (ro-bind $home/.npm)
end
if test $profile = nodejs-yarn
set -a args (ro-bind $home/.config/yarn)
set -a args (ro-bind $home/.yarn)
end
if test $profile = rust
set -a allow_ignored target/
set -a args (ro-bind $home/.cargo)
set -a args (hide-file $home/.cargo/credentials.toml)
set -a args (ro-bind $home/.rustup)
end
# hide .git and git-ignored dirs/files in current dir. profiles can override it
set -l gitroot
set -l gitignored
set -l ingit false
set -l ingitignored false
if git rev-parse &>/dev/null
set gitroot (git rev-parse --show-toplevel)
set ingit true
function ignored
git status --ignored --porcelain=v1 | string match -r --groups-only '^!! (.+)'
end
if git check-ignore --quiet $dir
# current directory is ignored
set ingitignored true
else if test $gitroot = $dir
set gitignored (ignored)
else
# path relative to git root
set -l subdir (realpath --relative-to=$gitroot $dir)
# ignored paths relative to the current directory
for path in (ignored)
set -l prefix "^$subdir/"
if string match --quiet -r $prefix $path
set -a gitignored (string replace -r $prefix '' $path)
end
end
end
end
if test -e .git; set -a args (ro-bind $dir/.git); end
for path in $gitignored
if contains $path $allow_ignored
continue
else if contains $path .jj/
set -a args (ro-bind $dir/$path)
continue
end
set -l path $dir/$path
if test -d $path
set -a args (hide-dir $path)
else if test -f $path
# todo: also mount symlink. currently not possible, see
# https://github.com/containers/bubblewrap/issues/390
set -a args (hide-file $path)
end
end
# inherit paths from PATH pointing directly to /nix/store, they're from nix shell
set -l --path path /bin /usr/bin
# skip binaries from this script
set -l skip_nix fish bubblewrap
set -l semver_regex '\d+\.\d+\.\d+'
for p in $PATH
set -l skip false
for name in $skip_nix
if string match --quiet -r "^/nix/store.+$name-$semver_regex/bin\$" $p
set skip true
break
end
end
if $skip; continue; end
if string match -r '^/nix/store' $p --quiet
set -a path $p
end
end
log Running (with_color cyan $exe) in isolation
if ! $ingit
warn Running outside of git repo
else if $ingitignored
warn Current directory is git-ignored, mounting as-is
end
exec bwrap \
--die-with-parent \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
(ro-bind /nix) \
--ro-bind /run/current-system/sw/bin /bin \
--ro-bind /etc/profiles/per-user/$user/bin /usr/bin \
--unshare-all \
--share-net \
# network settings
(ro-bind /etc/resolv.conf) \
# ssl certs
(ro-bind /etc/ssl/certs/ca-bundle.crt) \
(ro-bind /etc/ssl/certs/ca-certificates.crt) \
# basic user info
--ro-bind $tmpdir/passwd /etc/passwd \
--ro-bind $tmpdir/group /etc/group \
# nix config
--ro-bind $tmpdir/nix.conf $home/.config/nix/nix.conf \
# shell configs
# fish
(ro-bind-exists $home/.config/fish) \
(ro-bind-exists /etc/config.fish) \
(ro-bind-exists /etc/generated_completions) \
# other
(ro-bind-exists $home/.config/starship.toml) \
--clearenv \
# $path should be quoted so its values are separated with ":"
--setenv PATH "$path" \
(passenv HOME) \
(passenv NIX_PATH) \
(passenv EDITOR) \
(passenv LS_COLORS) \
(passenv TERM) \
--setenv SHLVL (math max 0, (math $SHLVL - 1)) \
--bind $dir $dir \
$bwrap_args $args $exe $exe_args
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment