├── .gitignore ├── .git-blame-ignore-revs ├── proot-x86_64 ├── nix-user-chroot ├── Makefile └── main.cpp ├── Makefile ├── appimage.nix ├── test.sh ├── nix-strace.sh ├── appimage-top.nix ├── nix-installer.nix ├── test-appimage.nix ├── appdir.sh ├── nix2appimage.sh ├── nix-bootstrap.sh ├── LICENSE ├── release.nix ├── appimagetool.nix ├── nix-bundle.sh ├── flake.lock ├── flake.nix ├── appimage-bundle.nix ├── appdir.nix ├── nix-run.sh ├── install-nix-from-closure.sh ├── README.md ├── default.nix └── AppRun.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | result* -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Fmt 2 | 2cbaf00b7c7de9edd927e9ab9821d000952c0526 3 | -------------------------------------------------------------------------------- /proot-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-bundle/HEAD/proot-x86_64 -------------------------------------------------------------------------------- /nix-user-chroot/Makefile: -------------------------------------------------------------------------------- 1 | ENV_PATH ?= "" 2 | 3 | nix-user-chroot: main.cpp 4 | ${CXX} -o nix-user-chroot -DENV_PATH='$(ENV_PATH)' main.cpp 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr 2 | 3 | install: nix-bundle.sh nix-run.sh appdir.nix appimagetool.nix appimage.nix AppRun.c appimage-top.nix default.nix appdir.sh nix2appimage.sh nix-user-chroot/ 4 | mkdir -p ${PREFIX}/share/nix-bundle/ 5 | cp -r $^ ${PREFIX}/share/nix-bundle/ 6 | -------------------------------------------------------------------------------- /appimage.nix: -------------------------------------------------------------------------------- 1 | { stdenv, appimagetool }: 2 | dir: 3 | 4 | stdenv.mkDerivation { 5 | name = "appimage"; 6 | buildInputs = [ appimagetool ]; 7 | buildCommand = '' 8 | ARCH=x86_64 appimagetool ${dir}/*.AppDir 9 | mkdir $out 10 | cp *.AppImage $out 11 | ''; 12 | } 13 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | export NIX_PATH=channel:nixos-20.09 4 | 5 | echo "Test with attribute name" 6 | ./nix-bundle.sh hello /bin/hello 7 | 8 | echo "Test with store path" 9 | out=$(nix-build --no-out-link --expr '(import {})' -A hello) 10 | ./nix-bundle.sh "$out" /bin/hello 11 | -------------------------------------------------------------------------------- /nix-strace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pkg="$1" 4 | shift 5 | exe="$1" 6 | shift 7 | bin=$(mktemp) 8 | out=$(mktemp) 9 | ~/nix.sh ./nix-bundle.sh $pkg $exe > $bin 10 | chmod +x $bin 11 | strace -f -o $out $bin $@ 12 | cat $out | grep -E '^[0-9]+ open\("\.?\/nix' | grep -Ev " = -[0-9]+ [A-Z]+ \([a-zA-Z ]+\)$" | sed -E 's/^[0-9]+ open\("\.?([^\"]+)".*/\1/' 13 | -------------------------------------------------------------------------------- /appimage-top.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixpkgs' ? , 3 | }: 4 | 5 | let 6 | pkgs = import nixpkgs' { }; 7 | muslPkgs = import nixpkgs' { 8 | localSystem.config = "x86_64-unknown-linux-musl"; 9 | }; 10 | 11 | in 12 | rec { 13 | appimagetool = pkgs.callPackage ./appimagetool.nix { }; 14 | 15 | appimage = pkgs.callPackage ./appimage.nix { 16 | inherit appimagetool; 17 | }; 18 | 19 | appdir = pkgs.callPackage ./appdir.nix { inherit muslPkgs; }; 20 | } 21 | -------------------------------------------------------------------------------- /nix-installer.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenv, 3 | fetchFromGitHub, 4 | writeText, 5 | nix, 6 | cacert, 7 | }: 8 | 9 | stdenv.mkDerivation { 10 | name = "nix-installer"; 11 | 12 | propagatedBuildInputs = [ 13 | nix.out 14 | cacert 15 | ]; 16 | 17 | buildCommand = '' 18 | mkdir -p $out/bin/ 19 | substitute ${./install-nix-from-closure.sh} $out/install \ 20 | --subst-var-by nix ${nix.out} \ 21 | --subst-var-by cacert ${cacert} 22 | chmod +x $out/install 23 | ''; 24 | } 25 | -------------------------------------------------------------------------------- /test-appimage.nix: -------------------------------------------------------------------------------- 1 | { 2 | appimagefile, 3 | nixpkgs' ? , 4 | }: 5 | 6 | # nix build -f test-appimage.nix --arg appimagefile ./VLC*AppImage 7 | 8 | with import nixpkgs' { }; 9 | 10 | runCommandCC "patchelf" { } '' 11 | cp ${appimagefile} $out 12 | chmod +w $out 13 | patchelf \ 14 | --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \ 15 | --set-rpath ${ 16 | lib.makeLibraryPath [ 17 | stdenv.cc.libc 18 | fuse 19 | zlib 20 | glib 21 | ] 22 | } 23 | $out 24 | '' 25 | -------------------------------------------------------------------------------- /appdir.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if [ "$#" -lt 1 ]; then 4 | cat <, 3 | nixpkgs' ? import nixpkgs { }, 4 | }: 5 | with nixpkgs'; 6 | 7 | stdenv.mkDerivation rec { 8 | pname = "nix-bundle"; 9 | name = "${pname}-${version}"; 10 | version = "0.4.0"; 11 | 12 | src = ./.; 13 | 14 | # coreutils, gnutar is actually needed by nix for bootstrap 15 | buildInputs = [ 16 | nix 17 | coreutils 18 | makeWrapper 19 | gnutar 20 | gzip 21 | bzip2 22 | ]; 23 | 24 | nixBundlePath = lib.makeBinPath [ 25 | nix 26 | coreutils 27 | gnutar 28 | gzip 29 | bzip2 30 | ]; 31 | nixRunPath = lib.makeBinPath [ 32 | nix 33 | coreutils 34 | ]; 35 | 36 | makeFlags = [ "PREFIX=$(out)" ]; 37 | 38 | postInstall = '' 39 | mkdir -p $out/bin 40 | makeWrapper $out/share/nix-bundle/nix-bundle.sh $out/bin/nix-bundle \ 41 | --prefix PATH : ${nixBundlePath} 42 | makeWrapper $out/share/nix-bundle/nix-run.sh $out/bin/nix-run \ 43 | --prefix PATH : ${nixRunPath} 44 | ''; 45 | 46 | meta = with lib; { 47 | maintainers = [ maintainers.matthewbauer ]; 48 | platforms = platforms.all; 49 | description = "Create bundles from Nixpkgs attributes"; 50 | license = licenses.mit; 51 | homepage = "https://github.com/matthewbauer/nix-bundle"; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /appimagetool.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenv, 3 | lib, 4 | fetchurl, 5 | fuse, 6 | zlib, 7 | squashfsTools, 8 | glib, 9 | }: 10 | 11 | # This is from some binaries. 12 | 13 | # Ideally, this should be source based, 14 | # but I can't get it to build from GitHub 15 | 16 | stdenv.mkDerivation rec { 17 | name = "appimagekit"; 18 | 19 | src = fetchurl { 20 | url = "https://github.com/AppImage/AppImageKit/releases/download/10/appimagetool-x86_64.AppImage"; 21 | sha256 = "03zbiblj8a1yk1xsb5snxi4ckwn3diyldg1jh5hdjjhsmpw652ig"; 22 | }; 23 | 24 | buildInputs = [ 25 | squashfsTools 26 | ]; 27 | 28 | sourceRoot = "squashfs-root"; 29 | 30 | unpackPhase = '' 31 | cp $src appimagetool-x86_64.AppImage 32 | chmod u+wx appimagetool-x86_64.AppImage 33 | patchelf \ 34 | --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \ 35 | --set-rpath ${fuse}/lib:${zlib}/lib \ 36 | appimagetool-x86_64.AppImage 37 | 38 | ./appimagetool-x86_64.AppImage --appimage-extract 39 | ''; 40 | 41 | installPhase = '' 42 | mkdir -p $out 43 | cp -r usr/* $out 44 | 45 | for x in $out/bin/*; do 46 | patchelf \ 47 | --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \ 48 | --set-rpath ${ 49 | lib.makeLibraryPath [ 50 | zlib 51 | stdenv.cc.libc 52 | fuse 53 | glib 54 | ] 55 | } \ 56 | $x 57 | done 58 | ''; 59 | 60 | dontStrip = true; 61 | dontPatchELF = true; 62 | } 63 | -------------------------------------------------------------------------------- /nix-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if [ "$#" -lt 2 ]; then 4 | cat <&2 echo "$0 failed. Exiting." 56 | exit 1 57 | elif [ -t 1 ]; then 58 | filename=$(basename "$exec") 59 | echo "Nix bundle created at $filename." 60 | cp -f "$out" "$filename" 61 | else 62 | echo "$out" 63 | fi 64 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1756542300, 6 | "narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "d7600c775f877cd87b4f5a831c28aa94137377aa", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "utils": "utils" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1681028828, 28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 | "owner": "nix-systems", 30 | "repo": "default", 31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default", 37 | "type": "github" 38 | } 39 | }, 40 | "utils": { 41 | "inputs": { 42 | "systems": "systems" 43 | }, 44 | "locked": { 45 | "lastModified": 1731533236, 46 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 47 | "owner": "numtide", 48 | "repo": "flake-utils", 49 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "numtide", 54 | "repo": "flake-utils", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "The purely functional package manager"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = 10 | inputs: 11 | let 12 | inherit (inputs.nixpkgs) lib; 13 | 14 | getExe = 15 | x: 16 | lib.getExe' x ( 17 | x.meta.mainProgram or (lib.warn 18 | "nix-bundle: Package ${ 19 | lib.strings.escapeNixIdentifier x.meta.name or x.pname or x.name 20 | } does not have the meta.mainProgram attribute. Assuming you want '${lib.getName x}'." 21 | lib.getName 22 | x 23 | ) 24 | ); 25 | in 26 | inputs.utils.lib.eachDefaultSystem ( 27 | system: 28 | let 29 | nix-bundle-fun = 30 | { 31 | drv, 32 | programPath ? getExe drv, 33 | }: 34 | let 35 | nixpkgs = inputs.nixpkgs.legacyPackages.${system}; 36 | nix-bundle = import inputs.self { inherit nixpkgs; }; 37 | script = nixpkgs.writeScript "startup" '' 38 | #!/bin/sh 39 | .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- ${programPath} "$@" 40 | ''; 41 | in 42 | nix-bundle.makebootstrap { 43 | drvToBundle = drv; 44 | targets = [ script ]; 45 | startup = ".${builtins.unsafeDiscardStringContext script} '\"$@\"'"; 46 | }; 47 | in 48 | { 49 | bundlers = { 50 | default = inputs.self.bundlers.${system}.nix-bundle; 51 | nix-bundle = drv: nix-bundle-fun { inherit drv; }; 52 | }; 53 | 54 | formatter = inputs.nixpkgs.legacyPackages.${system}.nixfmt-tree; 55 | } 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /appimage-bundle.nix: -------------------------------------------------------------------------------- 1 | # use like this: 2 | # nix-build appimage-bundle.nix --argstr package hello --argstr exec hello 3 | # nix-build appimage-bundle.nix --arg package 'with import {}; writers.writePython3Bin "helloThere.py" {} "print(1)\n"' --argstr exec helloThere.py 4 | 5 | { 6 | nixpkgs ? import { }, 7 | package, 8 | exec, 9 | ... 10 | }: 11 | let 12 | appimage_src = 13 | drv: exec: 14 | with nixpkgs; 15 | self.stdenv.mkDerivation rec { 16 | name = drv.name + "-appdir"; 17 | env = buildEnv { 18 | inherit name; 19 | paths = buildInputs; 20 | }; 21 | src = env; 22 | inherit exec; 23 | buildInputs = [ drv ]; 24 | buildCommand = '' 25 | mkdir -p $out/share/icons/hicolor/256x256/apps 26 | mkdir -p $out/share/applications 27 | 28 | shopt -s extglob 29 | ln -s ${env}/!(share) $out/ 30 | ln -s ${env}/share/* $out/share/ 31 | 32 | touch $out/share/icons/hicolor/256x256/apps/${drv.name}.png 33 | touch $out/share/icons/${drv.name}.png 34 | 35 | cat < $out/share/applications/${drv.name}.desktop 36 | [Desktop Entry] 37 | Type=Application 38 | Version=1.0 39 | Name=${drv.name} 40 | Path=${env} 41 | Icon=${drv.name} 42 | Exec=$exec 43 | Terminal=true 44 | EOF 45 | ''; 46 | system = builtins.currentSystem; 47 | }; 48 | 49 | in 50 | let 51 | results = 52 | if (nixpkgs.lib.isDerivation package && !(nixpkgs.lib.isString package)) then 53 | { 54 | name = package.name; 55 | target = appimage_src package "${exec}"; 56 | extraTargets = [ ]; 57 | } 58 | else 59 | { 60 | name = nixpkgs."${package}".name; 61 | target = appimage_src (nixpkgs."${package}") "${exec}"; 62 | extraTargets = [ ]; 63 | }; 64 | in 65 | with (import (./appimage-top.nix) { nixpkgs' = nixpkgs.path; }); 66 | (appimage (appdir results)).overrideAttrs (old: { 67 | name = results.name; 68 | }) 69 | -------------------------------------------------------------------------------- /appdir.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenv, 3 | lib, 4 | fetchurl, 5 | muslPkgs, 6 | perl, 7 | closureInfo, 8 | fetchFromGitHub, 9 | coreutils, 10 | bash, 11 | }: 12 | 13 | let 14 | AppRun = 15 | targets: 16 | muslPkgs.stdenv.mkDerivation { 17 | name = "AppRun"; 18 | 19 | phases = [ 20 | "buildPhase" 21 | "installPhase" 22 | "fixupPhase" 23 | ]; 24 | 25 | buildPhase = '' 26 | CC="$CC -O2 -Wall -Wno-deprecated-declarations -Wno-unused-result -static" 27 | $CC ${./AppRun.c} -o AppRun -DENV_PATH='"${lib.makeBinPath targets}"' 28 | ''; 29 | 30 | installPhase = '' 31 | mkdir -p $out/bin 32 | cp AppRun $out/bin/AppRun 33 | ''; 34 | }; 35 | 36 | in 37 | 38 | { 39 | target, 40 | name, 41 | extraTargets ? [ 42 | coreutils 43 | bash 44 | ], 45 | }: 46 | let 47 | targets = ([ target ] ++ extraTargets); 48 | closure = closureInfo { rootPaths = targets; }; 49 | in 50 | stdenv.mkDerivation { 51 | name = "${name}.AppDir"; 52 | exportReferencesGraph = map (x: [ 53 | ("closure-" + baseNameOf x) 54 | x 55 | ]) targets; 56 | nativeBuildInputs = [ perl ]; 57 | buildCommand = '' 58 | # TODO use symlinks to shrink output size 59 | 60 | if [ ! -d ${target}/share/applications ]; then 61 | echo "--------------------------------------------------" 62 | echo "| /share/applications does not exist. |" 63 | echo "| AppImage only works with 'applications'. |" 64 | echo "| Try using nix-bundle.sh for command-line apps. |" 65 | echo "--------------------------------------------------" 66 | exit 1 67 | fi 68 | 69 | storePaths=$(cat ${closure}/store-paths) 70 | 71 | mkdir -p $out/${name}.AppDir 72 | cd $out/${name}.AppDir 73 | 74 | mkdir -p nix/store 75 | cp -r $storePaths nix/store 76 | 77 | ln -s .${target} usr 78 | 79 | if [ -d ${target}/share/appdata ]; then 80 | chmod a+w usr/share 81 | mkdir -p usr/share/metainfo 82 | for f in ${target}/share/appdata/*.xml; do 83 | ln -s ../appdata/$(basename $f) usr/share/metainfo/$(basename $f) 84 | done 85 | fi 86 | 87 | # .desktop 88 | desktop=$(find ${target}/share/applications -name "*.desktop" | head -n1) 89 | if ! [ -z "$desktop" ]; then 90 | cp .$desktop . 91 | fi 92 | 93 | 94 | # icons 95 | if [ -d ${target}/share/icons ]; then 96 | icon=$(find ${target}/share/icons -name "${name}*.png" | head -n1) 97 | if ! [ -z "$icon" ]; then 98 | ln -s .$icon 99 | ln -s .$icon .DirIcon 100 | else 101 | icon=$(find ${target}/share/icons -name "${name}*.svg" | head -n1) 102 | if ! [ -z "$icon" ]; then 103 | ln -s .$icon 104 | ln -s .$icon .DirIcon 105 | fi 106 | fi 107 | fi 108 | 109 | cp ${AppRun targets}/bin/AppRun AppRun 110 | ''; 111 | } 112 | -------------------------------------------------------------------------------- /nix-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # nix-run.sh provides an easy way to run executables from Nix derivations 4 | # without installing them. It will try to determine how to run the application 5 | # based on what files are installable. Currently, macOS apps, Freedesktop apps, 6 | # and ordinary binaries are handled. 7 | 8 | # Usage 9 | 10 | if [ -z "$1" ]; then 11 | >&2 echo "Need more than one argument." 12 | >&2 echo 13 | >&2 echo "Try:" 14 | >&2 echo "$ nix-run hello" 15 | >&2 echo 16 | >&2 echo "To run the hello program" 17 | >&2 echo "or substitute hello with another package in Nixpkgs" 18 | exit 1 19 | fi 20 | 21 | pkg="$1" 22 | shift 23 | 24 | # A second argument will provide a hint to run 25 | if [ -n "$1" ]; then 26 | name="$1" 27 | shift 28 | else 29 | name="$pkg" 30 | fi 31 | 32 | expr="with import {}; let x = ($pkg); in x" 33 | path=$(nix-instantiate --no-gc-warning -E "$expr") 34 | out=$(nix-store --no-gc-warning -r "$path") 35 | 36 | if [ -z "$out" ]; then 37 | >&2 echo "Could not evaluate $pkg to a Nix drv." 38 | exit 1 39 | fi 40 | 41 | # Run DIR as a Darwin application 42 | run_darwin_app () { 43 | dir="$1" 44 | shift 45 | 46 | open -a "$dir" --args "$@" 47 | } 48 | 49 | # Run FILE as a Freedesktop application 50 | # taken from: 51 | # https://askubuntu.com/questions/5172/running-a-desktop-file-in-the-terminal/5174 52 | run_linux_desktop_app () { 53 | file="$1" 54 | shift 55 | 56 | cmd=$(grep '^Exec' "$file" | tail -1 | \ 57 | sed 's/Exec=//;s/^"//;s/" *$//') 58 | 59 | if [ "$#" -gt 0 ]; then 60 | cmd=$(echo "$cmd" | sed "s/%[fu]/$1/;s/%[FU]/$*/") 61 | fi 62 | 63 | cmd=$(echo "$cmd" | sed "s/%k/$desktop/;s/%.//") 64 | 65 | "$cmd" "$@" 66 | } 67 | 68 | # Run FILE as an ordinary binary 69 | run_bin () { 70 | file="$1" 71 | shift 72 | 73 | "$file" "$@" 74 | } 75 | 76 | if [ -x "$out/nix-support/run" ]; then 77 | run_bin "$out/nix-support/run" "$@" 78 | elif [ -x "$out/bin/run" ]; then 79 | run_bin "$out/bin/run" "$@" 80 | elif [ "$(uname)" = Darwin ] && [ -d "$out/Applications/$name.app" ]; then 81 | run_darwin_app "$out/Applications/$name.app" "$@" 82 | elif [ "$(uname)" = Darwin ] && [ -d "$out"/Applications/*.app ]; then 83 | for f in "$out"/Applications/*.app; do 84 | run_darwin_app "$f" "$@" 85 | done 86 | elif [ -f "$out/share/applications/$name.desktop" ]; then 87 | run_linux_desktop_app "$out/share/applications/$name.desktop" "$@" 88 | elif [ -d "$out"/share/applications ]; then 89 | for f in "$out"/share/applications/*.desktop; do 90 | run_linux_desktop_app "$f" 91 | done 92 | elif [ -x "$out/bin/$name" ]; then 93 | run_bin "$out/bin/$name" "$@" 94 | elif [ -d "$out/bin" ]; then 95 | for bin in "$out"/bin/*; do 96 | run_bin "$bin" "$@" 97 | done 98 | else 99 | >&2 echo "Cannot find a way to run path $out." 100 | exit 1 101 | fi 102 | -------------------------------------------------------------------------------- /install-nix-from-closure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | dest="/nix" 6 | self="." 7 | nix="@nix@" 8 | cacert="@cacert@" 9 | 10 | if ! [ -e $self/.reginfo ]; then 11 | echo "$0: incomplete installer (.reginfo is missing)" >&2 12 | exit 1 13 | fi 14 | 15 | if [ -z "$USER" ]; then 16 | echo "$0: \$USER is not set" >&2 17 | exit 1 18 | fi 19 | 20 | if [ "$(id -u)" -eq 0 ]; then 21 | printf '\e[1;31mwarning: installing Nix as root is not supported by this script!\e[0m\n' 22 | fi 23 | 24 | echo "performing a single-user installation of Nix..." >&2 25 | 26 | if ! [ -e $dest ]; then 27 | cmd="mkdir -m 0755 $dest && chown $USER $dest" 28 | echo "directory $dest does not exist; creating it by running '$cmd' using sudo" >&2 29 | if ! sudo sh -c "$cmd"; then 30 | echo "$0: please manually run ‘$cmd’ as root to create $dest" >&2 31 | exit 1 32 | fi 33 | fi 34 | 35 | if ! [ -w $dest ]; then 36 | echo "$0: directory $dest exists, but is not writable by you. This could indicate that another user has already performed a single-user installation of Nix on this system. If you wish to enable multi-user support see http://nixos.org/nix/manual/#ssec-multi-user. If you wish to continue with a single-user install for $USER please run ‘chown -R $USER $dest’ as root." >&2 37 | exit 1 38 | fi 39 | 40 | mkdir -p $dest/store 41 | 42 | echo -n "copying Nix to $dest/store..." >&2 43 | 44 | for i in $(cd $self/store >/dev/null && echo *); do 45 | echo -n "." >&2 46 | i_tmp="$dest/store/$i.$$" 47 | if [ -e "$i_tmp" ]; then 48 | rm -rf "$i_tmp" 49 | fi 50 | if ! [ -e "$dest/store/$i" ]; then 51 | cp -Rp "$self/store/$i" "$i_tmp" 52 | chmod -R a-w "$i_tmp" 53 | chmod +w "$i_tmp" 54 | mv "$i_tmp" "$dest/store/$i" 55 | chmod -w "$dest/store/$i" 56 | fi 57 | done 58 | echo "" >&2 59 | 60 | echo "initialising Nix database..." >&2 61 | if ! $nix/bin/nix-store --init; then 62 | echo "$0: failed to initialize the Nix database" >&2 63 | exit 1 64 | fi 65 | 66 | if ! $nix/bin/nix-store --load-db < $self/.reginfo; then 67 | echo "$0: unable to register valid paths" >&2 68 | exit 1 69 | fi 70 | 71 | . $nix/etc/profile.d/nix.sh 72 | 73 | if ! $nix/bin/nix-env -i "$nix"; then 74 | echo "$0: unable to install Nix into your default profile" >&2 75 | exit 1 76 | fi 77 | 78 | # Install an SSL certificate bundle. 79 | if [ -z "$SSL_CERT_FILE" -o ! -f "$SSL_CERT_FILE" ]; then 80 | $nix/bin/nix-env -i "$cacert" 81 | export SSL_CERT_FILE="$HOME/.nix-profile/etc/ssl/certs/ca-bundle.crt" 82 | fi 83 | 84 | # Subscribe the user to the Nixpkgs channel and fetch it. 85 | if ! $nix/bin/nix-channel --list | grep -q "^nixpkgs "; then 86 | $nix/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable 87 | fi 88 | if [ -z "$_NIX_INSTALLER_TEST" ]; then 89 | $nix/bin/nix-channel --update nixpkgs 90 | fi 91 | 92 | # Make the shell source nix.sh during login. 93 | p=$HOME/.nix-profile/etc/profile.d/nix.sh 94 | 95 | added= 96 | for i in .bash_profile .bash_login .profile; do 97 | fn="$HOME/$i" 98 | if [ -w "$fn" ]; then 99 | if ! grep -q "$p" "$fn"; then 100 | echo "modifying $fn..." >&2 101 | echo "if [ -e $p ]; then . $p; fi # added by Nix installer" >> $fn 102 | fi 103 | added=1 104 | break 105 | fi 106 | done 107 | 108 | if [ -z "$added" ]; then 109 | cat >&2 <&2 < { }, 3 | }: 4 | 5 | with nixpkgs; 6 | 7 | rec { 8 | toStorePath = 9 | target: 10 | # If a store path has been given but is not a derivation, add the missing context 11 | # to it so it will be propagated properly as a build input. 12 | if !(lib.isDerivation target) && lib.isStorePath target then 13 | let 14 | path = toString target; 15 | in 16 | builtins.appendContext path { 17 | "${path}" = { 18 | path = true; 19 | }; 20 | } 21 | # Otherwise, add to the store. This takes care of appending the store path 22 | # in the context automatically. 23 | else 24 | "${target}"; 25 | 26 | arx = 27 | { 28 | drvToBundle, 29 | archive, 30 | startup, 31 | }: 32 | stdenv.mkDerivation { 33 | name = if drvToBundle != null then "${drvToBundle.pname}-arx" else "arx"; 34 | passthru = { 35 | inherit drvToBundle; 36 | }; 37 | buildCommand = '' 38 | # tmpdir has a additional `/` in the beginning to work around `QualifiedPath` checking for `|/|./|../|` 39 | ${haskellPackages.arx}/bin/arx tmpx \ 40 | --tmpdir '/$HOME/.cache' \ 41 | --shared \ 42 | -rm! ${archive} \ 43 | -o $out // ${startup} 44 | chmod +x $out 45 | ''; 46 | }; 47 | 48 | maketar = 49 | { targets }: 50 | let 51 | closure = closureInfo { rootPaths = targets; }; 52 | in 53 | stdenv.mkDerivation { 54 | name = "maketar"; 55 | buildInputs = [ perl ]; 56 | exportReferencesGraph = map (x: [ 57 | ("closure-" + baseNameOf x) 58 | x 59 | ]) targets; 60 | buildCommand = '' 61 | storePaths=$(cat ${closure}/store-paths) 62 | 63 | # https://reproducible-builds.org/docs/archives 64 | tar -cf - \ 65 | --owner=0 --group=0 --mode=u+rw,uga+r \ 66 | --hard-dereference \ 67 | --mtime="@$SOURCE_DATE_EPOCH" \ 68 | --format=gnu \ 69 | --sort=name \ 70 | $storePaths | bzip2 -z > $out 71 | ''; 72 | }; 73 | 74 | # TODO: eventually should this go in nixpkgs? 75 | nix-user-chroot = lib.makeOverridable stdenv.mkDerivation { 76 | name = "nix-user-chroot-2c52b5f"; 77 | src = ./nix-user-chroot; 78 | 79 | buildInputs = [ 80 | stdenv.cc.cc.libgcc or null 81 | ]; 82 | 83 | makeFlags = [ ]; 84 | 85 | # hack to use when /nix/store is not available 86 | postFixup = '' 87 | exe=$out/bin/nix-user-chroot 88 | patchelf \ 89 | --set-interpreter .$(patchelf --print-interpreter $exe) \ 90 | --set-rpath $(patchelf --print-rpath $exe | sed 's|/nix/store/|./nix/store/|g') \ 91 | $exe 92 | ''; 93 | 94 | installPhase = '' 95 | runHook preInstall 96 | 97 | mkdir -p $out/bin/ 98 | cp nix-user-chroot $out/bin/nix-user-chroot 99 | 100 | runHook postInstall 101 | ''; 102 | 103 | meta.platforms = lib.platforms.linux; 104 | }; 105 | 106 | makebootstrap = 107 | { 108 | targets, 109 | startup, 110 | drvToBundle ? null, 111 | }: 112 | arx { 113 | inherit drvToBundle startup; 114 | archive = maketar { 115 | inherit targets; 116 | }; 117 | }; 118 | 119 | makeStartup = 120 | { 121 | target, 122 | nixUserChrootFlags, 123 | nix-user-chroot', 124 | run, 125 | initScript, 126 | }: 127 | let 128 | # Avoid re-adding a store path into the store 129 | path = toStorePath target; 130 | in 131 | writeScript "startup" '' 132 | #!/bin/sh 133 | ${initScript} 134 | .${nix-user-chroot'}/bin/nix-user-chroot -n ./nix ${nixUserChrootFlags} -- ${path}${run} "$@" 135 | ''; 136 | 137 | nix-bootstrap = 138 | { 139 | target, 140 | extraTargets ? [ ], 141 | run, 142 | nix-user-chroot' ? nix-user-chroot, 143 | nixUserChrootFlags ? "", 144 | initScript ? "", 145 | }: 146 | let 147 | script = makeStartup { 148 | inherit 149 | target 150 | nixUserChrootFlags 151 | nix-user-chroot' 152 | run 153 | initScript 154 | ; 155 | }; 156 | in 157 | makebootstrap { 158 | startup = ".${script} '\"$@\"'"; 159 | targets = [ "${script}" ] ++ extraTargets; 160 | }; 161 | 162 | nix-bootstrap-nix = 163 | { 164 | target, 165 | run, 166 | extraTargets ? [ ], 167 | }: 168 | nix-bootstrap-path { 169 | inherit target run; 170 | extraTargets = [ 171 | gnutar 172 | bzip2 173 | xz 174 | gzip 175 | coreutils 176 | bash 177 | ] 178 | ++ extraTargets; 179 | }; 180 | 181 | # special case adding path to the environment before launch 182 | nix-bootstrap-path = 183 | let 184 | nix-user-chroot'' = 185 | targets: 186 | nix-user-chroot.overrideDerivation (o: { 187 | buildInputs = o.buildInputs ++ targets; 188 | makeFlags = o.makeFlags ++ [ 189 | ''ENV_PATH="${lib.makeBinPath targets}"'' 190 | ]; 191 | }); 192 | in 193 | { 194 | target, 195 | extraTargets ? [ ], 196 | run, 197 | initScript ? "", 198 | }: 199 | nix-bootstrap { 200 | inherit 201 | target 202 | extraTargets 203 | run 204 | initScript 205 | ; 206 | nix-user-chroot' = nix-user-chroot'' extraTargets; 207 | }; 208 | } 209 | -------------------------------------------------------------------------------- /AppRun.c: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | 3 | Copyright (c) 2004-16 Simon Peter 4 | Portions Copyright (c) 2010 RazZziel 5 | 6 | All Rights Reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | **************************************************************************/ 27 | 28 | #define _GNU_SOURCE 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #define die(...) \ 42 | do { \ 43 | fprintf(stderr, "Error: " __VA_ARGS__); \ 44 | exit(1); \ 45 | } while (0); 46 | 47 | #define PATH_MAX 4096 48 | 49 | #define LINE_SIZE 255 50 | 51 | #define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); } 52 | 53 | int filter (const struct dirent *dir) { 54 | char *p = (char*) &dir->d_name; 55 | p = strrchr(p, '.'); 56 | return p && !strcmp(p, ".desktop"); 57 | } 58 | 59 | static void update_map(char *mapping, char *map_file) { 60 | int fd; 61 | 62 | fd = open(map_file, O_WRONLY); 63 | if (fd < 0) { 64 | err_exit("map open"); 65 | } 66 | 67 | int map_len = strlen(mapping); 68 | if (write(fd, mapping, map_len) != map_len) { 69 | err_exit("map write"); 70 | } 71 | 72 | close(fd); 73 | } 74 | 75 | static void add_path(const char* name, const char* rootdir) { 76 | char path_buf[PATH_MAX]; 77 | snprintf(path_buf, sizeof(path_buf), "/%s", name); 78 | 79 | struct stat statbuf; 80 | if (stat(path_buf, &statbuf) < 0) { 81 | fprintf(stderr, "Cannot stat %s: %s\n", path_buf, strerror(errno)); 82 | return; 83 | } 84 | 85 | char path_buf2[PATH_MAX]; 86 | snprintf(path_buf2, sizeof(path_buf2), "%s/%s", rootdir, name); 87 | 88 | mkdir(path_buf2, statbuf.st_mode & ~S_IFMT); 89 | if (mount(path_buf, path_buf2, "none", MS_BIND | MS_REC, NULL) < 0) { 90 | fprintf(stderr, "Cannot bind mount %s to %s: %s\n", path_buf, path_buf2, strerror(errno)); 91 | } 92 | } 93 | 94 | #define SAVE_ENV_VAR(x) char *x = getenv(#x) 95 | #define LOAD_ENV_VAR(x) do { if (x != NULL) setenv(#x, x, 1); } while(0) 96 | 97 | int main(int argc, char *argv[]) { 98 | char *appdir = dirname(realpath("/proc/self/exe", NULL)); 99 | if (!appdir) 100 | die("Could not access /proc/self/exe\n"); 101 | 102 | char *tmpdir = getenv("TMPDIR"); 103 | if (!tmpdir) { 104 | tmpdir = "/tmp"; 105 | } 106 | 107 | char template[PATH_MAX]; 108 | int needed = snprintf(template, PATH_MAX, "%s/nixXXXXXX", tmpdir); 109 | if (needed < 0) { 110 | err_exit("TMPDIR too long: '%s'", tmpdir); 111 | } 112 | 113 | char *rootdir = mkdtemp(template); 114 | if (!rootdir) { 115 | err_exit("mkdtemp(%s)", template); 116 | } 117 | 118 | int ret; 119 | 120 | struct dirent **namelist; 121 | 122 | ret = scandir(appdir, &namelist, filter, NULL); 123 | 124 | if (ret == 0) { 125 | die("No .desktop files found\n"); 126 | } else if(ret == -1) { 127 | die("Could not scan directory %s\n", appdir); 128 | } 129 | 130 | /* Extract executable from .desktop file */ 131 | 132 | FILE *f; 133 | char *desktop_file = malloc(LINE_SIZE); 134 | snprintf(desktop_file, LINE_SIZE-1, "%s/%s", appdir, namelist[0]->d_name); 135 | f = fopen(desktop_file, "r"); 136 | 137 | char *line = malloc(LINE_SIZE); 138 | size_t n = LINE_SIZE; 139 | int found = 0; 140 | 141 | while (getline(&line, &n, f) != -1) 142 | { 143 | if (!strncmp(line,"Exec=",5)) 144 | { 145 | char *p = line+5; 146 | while (*++p && *p != ' ' && *p != '%' && *p != '\n'); 147 | *p = 0; 148 | found = 1; 149 | break; 150 | } 151 | } 152 | 153 | fclose(f); 154 | 155 | if (!found) 156 | die("Executable not found, make sure there is a line starting with 'Exec='\n"); 157 | 158 | /* Execution */ 159 | char *executable = basename(line+5); 160 | 161 | char full_exec[PATH_MAX]; 162 | snprintf(full_exec, sizeof(full_exec), "/usr/bin/%s", executable); 163 | 164 | // get uid, gid before going to new namespace 165 | uid_t uid = getuid(); 166 | gid_t gid = getgid(); 167 | 168 | // "unshare" into new namespace 169 | if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) { 170 | err_exit("unshare()"); 171 | } 172 | 173 | // add necessary system stuff to rootdir namespace 174 | add_path("dev", rootdir); 175 | add_path("proc", rootdir); 176 | add_path("sys", rootdir); 177 | add_path("run", rootdir); 178 | add_path("etc", rootdir); 179 | add_path("home", rootdir); 180 | 181 | // setup skeleton 182 | char path_buf[PATH_MAX]; 183 | snprintf(path_buf, sizeof(path_buf), "%s/tmp", rootdir); 184 | mkdir(path_buf, ~0); 185 | snprintf(path_buf, sizeof(path_buf), "%s/var", rootdir); 186 | mkdir(path_buf, ~0); 187 | 188 | // make sure nixdir exists 189 | struct stat statbuf2; 190 | if (stat(appdir, &statbuf2) < 0) { 191 | err_exit("stat(%s)", appdir); 192 | } 193 | 194 | char nixdir[PATH_MAX]; 195 | snprintf(nixdir, sizeof(nixdir), "%s/nix", appdir); 196 | snprintf(path_buf, sizeof(path_buf), "%s/nix", rootdir); 197 | mkdir(path_buf, statbuf2.st_mode & ~S_IFMT); 198 | if (mount(nixdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) { 199 | err_exit("mount(%s, %s)", nixdir, path_buf); 200 | } 201 | 202 | char usrdir[PATH_MAX]; 203 | snprintf(usrdir, sizeof(usrdir), "%s/usr", appdir); 204 | snprintf(path_buf, sizeof(path_buf), "%s/usr", rootdir); 205 | mkdir(path_buf, statbuf2.st_mode & ~S_IFMT); 206 | if (mount(usrdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) { 207 | err_exit("mount(%s, %s)", usrdir, path_buf); 208 | } 209 | 210 | snprintf(path_buf, sizeof(path_buf), "%s/bin", rootdir); 211 | if (symlink("/usr/bin", path_buf) < 0) { 212 | err_exit("symlink(/usr/bin, %s)", path_buf); 213 | } 214 | 215 | // fixes issue #1 where writing to /proc/self/gid_map fails 216 | // see user_namespaces(7) for more documentation 217 | int fd_setgroups = open("/proc/self/setgroups", O_WRONLY); 218 | if (fd_setgroups > 0) { 219 | write(fd_setgroups, "deny", 4); 220 | } 221 | 222 | // map the original uid/gid in the new ns 223 | char map_buf[1024]; 224 | snprintf(map_buf, sizeof(map_buf), "%d %d 1", uid, uid); 225 | update_map(map_buf, "/proc/self/uid_map"); 226 | snprintf(map_buf, sizeof(map_buf), "%d %d 1", gid, gid); 227 | update_map(map_buf, "/proc/self/gid_map"); 228 | 229 | // chroot to rootdir 230 | if (chroot(rootdir) < 0) { 231 | err_exit("chroot(%s)", rootdir); 232 | } 233 | 234 | char *pwddir = getenv("PWD"); 235 | chdir(pwddir); 236 | 237 | SAVE_ENV_VAR(PWD); 238 | SAVE_ENV_VAR(DBUS_SESSION_BUS_ADDRESS); 239 | SAVE_ENV_VAR(USER); 240 | SAVE_ENV_VAR(HOSTNAME); 241 | SAVE_ENV_VAR(LANG); 242 | SAVE_ENV_VAR(LC_ALL); 243 | SAVE_ENV_VAR(TERM); 244 | SAVE_ENV_VAR(DISPLAY); 245 | SAVE_ENV_VAR(XDG_RUNTIME_DIR); 246 | SAVE_ENV_VAR(XAUTHORITY); 247 | SAVE_ENV_VAR(XDG_SESSION_ID); 248 | SAVE_ENV_VAR(XDG_SEAT); 249 | SAVE_ENV_VAR(HOME); 250 | 251 | clearenv(); 252 | 253 | LOAD_ENV_VAR(PWD); 254 | LOAD_ENV_VAR(DBUS_SESSION_BUS_ADDRESS); 255 | LOAD_ENV_VAR(USER); 256 | LOAD_ENV_VAR(HOSTNAME); 257 | LOAD_ENV_VAR(LANG); 258 | LOAD_ENV_VAR(LC_ALL); 259 | LOAD_ENV_VAR(TERM); 260 | LOAD_ENV_VAR(DISPLAY); 261 | LOAD_ENV_VAR(XDG_RUNTIME_DIR); 262 | LOAD_ENV_VAR(XAUTHORITY); 263 | LOAD_ENV_VAR(XDG_SESSION_ID); 264 | LOAD_ENV_VAR(XDG_SEAT); 265 | LOAD_ENV_VAR(HOME); 266 | 267 | setenv("PATH", ENV_PATH, 1); 268 | setenv("TMPDIR", "/tmp", 1); 269 | 270 | /* Run */ 271 | // FIXME: What about arguments in the Exec= line of the desktop file? 272 | ret = execvp(full_exec, argv); 273 | 274 | if (ret == -1) 275 | die("Error executing '%s'; return code: %d\n", full_exec, ret); 276 | 277 | free(line); 278 | free(desktop_file); 279 | return 0; 280 | } 281 | -------------------------------------------------------------------------------- /nix-user-chroot/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is based on @lethalman's nix-user-chroot. This file has 3 | * diverged from it though. 4 | * 5 | * Usage: nix-user-chroot 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | #define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); } 29 | static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env, list dirMappings, list envMappings, const char *executable, char * const new_argv[]); 30 | 31 | volatile uint8_t child_died = 0; 32 | int child_pid = 0; 33 | 34 | static void usage(const char *pname) { 35 | fprintf(stderr, "Usage: %s -n -- \n", pname); 36 | fprintf(stderr, "\t-c\tclear all env vars\n"); 37 | fprintf(stderr, "\t-m :\tmap src on the host to dest in the sandbox\n"); 38 | fprintf(stderr, "\t-d\tdelete all default dir mappings, may break things\n"); 39 | fprintf(stderr, "\t-p \tpreserve the value of a variable across the -c clear\n"); 40 | fprintf(stderr, "\t-e\tadd an /escape-hatch to the sandbox, and run (outside the sandbox) any strings written to it\n"); 41 | 42 | exit(EXIT_FAILURE); 43 | } 44 | 45 | static void update_map(const char *mapping, const char *map_file) { 46 | int fd; 47 | 48 | fd = open(map_file, O_WRONLY); 49 | if (fd < 0) { 50 | err_exit("map open"); 51 | } 52 | 53 | int map_len = strlen(mapping); 54 | if (write(fd, mapping, map_len) != map_len) { 55 | err_exit("map write"); 56 | } 57 | 58 | close(fd); 59 | } 60 | 61 | static void add_path(string src, string dest, string rootdir) { 62 | string path_buf2; 63 | 64 | struct stat statbuf; 65 | if (stat(src.c_str(), &statbuf) < 0) { 66 | fprintf(stderr, "Cannot stat %s: %s\n", src.c_str(), strerror(errno)); 67 | return; 68 | } 69 | 70 | path_buf2 = rootdir + "/" + dest; 71 | 72 | if (S_ISDIR(statbuf.st_mode)) { 73 | mkdir(path_buf2.c_str(), statbuf.st_mode & ~S_IFMT); 74 | if (mount(src.c_str(), path_buf2.c_str(), "none", MS_BIND | MS_REC, NULL) < 0) { 75 | fprintf(stderr, "Cannot bind mount %s to %s: %s\n", src.c_str(), path_buf2.c_str(), strerror(errno)); 76 | } 77 | } else if (S_ISREG(statbuf.st_mode)) { 78 | printf("bind-mounting file %s not supported", src.c_str()); 79 | } 80 | } 81 | 82 | struct DirMapping { 83 | string src; 84 | string dest; 85 | }; 86 | 87 | struct SetEnv { 88 | string key; 89 | string value; 90 | }; 91 | 92 | struct DirMapping parseMapping(string input) { 93 | auto pos = input.find(":"); 94 | string src = input.substr(0, pos); 95 | string dest = input.substr(pos + 1); 96 | return (struct DirMapping){ src, dest }; 97 | } 98 | 99 | static void handle_child_death(int signo, siginfo_t *info, void *context) { 100 | if ( (child_pid == 0) || (info->si_pid == child_pid) ) { 101 | child_died = 1; 102 | } 103 | } 104 | 105 | int main(int argc, char *argv[]) { 106 | uint8_t clear_env = 0; 107 | uint8_t enable_escape_hatch = 0; 108 | char *nixdir = NULL; 109 | list dirMappings; 110 | list envMappings; 111 | const char *t; 112 | 113 | #define x(y) dirMappings.push_back({ "/" y, y }) 114 | x("dev"); 115 | x("proc"); 116 | x("sys"); 117 | x("run"); 118 | x("tmp"); 119 | x("var"); 120 | x("etc"); 121 | x("usr"); 122 | x("home"); 123 | x("root"); 124 | #undef x 125 | 126 | int opt; 127 | while ((opt = getopt(argc, argv, "cen:m:dp:")) != -1) { 128 | switch (opt) { 129 | case 'c': 130 | clear_env = 1; 131 | break; 132 | case 'e': 133 | enable_escape_hatch = 1; 134 | break; 135 | case 'n': 136 | // determine absolute directory for nix dir 137 | nixdir = realpath(optarg, NULL); 138 | if (!nixdir) { 139 | err_exit("realpath(%s)", optarg); 140 | } 141 | break; 142 | case 'm': 143 | dirMappings.push_back(parseMapping(optarg)); 144 | break; 145 | case 'd': 146 | dirMappings.clear(); 147 | break; 148 | case 'p': 149 | t = getenv(optarg); 150 | if (t) { 151 | envMappings.push_back({ optarg, t }); 152 | } 153 | break; 154 | } 155 | } 156 | 157 | if (!nixdir) { 158 | fprintf(stderr, "-n is required\n"); 159 | exit(EXIT_FAILURE); 160 | } 161 | 162 | if (argc <= optind) { 163 | usage(argv[0]); 164 | } 165 | 166 | const char *tmpdir = getenv("TMPDIR"); 167 | if (!tmpdir) { 168 | tmpdir = "/tmp"; 169 | } 170 | 171 | char template_[PATH_MAX]; 172 | int needed = snprintf(template_, PATH_MAX, "%s/nixXXXXXX", tmpdir); 173 | if (needed < 0) { 174 | err_exit("TMPDIR too long: '%s'", tmpdir); 175 | } 176 | 177 | char *rootdir = mkdtemp(template_); 178 | if (!rootdir) { 179 | err_exit("mkdtemp(%s)", template_); 180 | } 181 | 182 | int unrace[2]; 183 | 184 | if (pipe(unrace)) { 185 | err_exit("pipe()"); 186 | } 187 | 188 | struct sigaction handle_child; 189 | handle_child.sa_sigaction = handle_child_death; 190 | handle_child.sa_flags = SA_SIGINFO; 191 | 192 | struct sigaction old_handler; 193 | if (sigaction(SIGCHLD, &handle_child, &old_handler)) { 194 | err_exit("sigaction()"); 195 | } 196 | 197 | int child; 198 | child = child_pid = fork(); 199 | if (child < 0) { 200 | err_exit("fork()"); 201 | } else if (child == 0) { 202 | sigaction(SIGCHLD, &old_handler, NULL); 203 | close(unrace[1]); 204 | char buf[10]; 205 | read(unrace[0], buf, 10); 206 | close(unrace[0]); 207 | return child_proc(rootdir, nixdir, clear_env, dirMappings, envMappings, argv[optind], argv + optind); 208 | } else { 209 | close(unrace[0]); 210 | char fifopath[PATH_MAX]; 211 | if (enable_escape_hatch) { 212 | snprintf(fifopath, PATH_MAX, "%s/escape-hatch", rootdir); 213 | mkfifo(fifopath, 0600); 214 | } 215 | close(unrace[1]); 216 | if (enable_escape_hatch) { 217 | char buffer[1024]; 218 | while (!child_died) { 219 | int fd = open(fifopath, O_RDONLY); 220 | if (fd < 0) { 221 | if (errno == EINTR) continue; 222 | fprintf(stderr, "error opening escape-hatch: %s\n", strerror(errno)); 223 | continue; 224 | } 225 | int size = read(fd, buffer, 1024); 226 | buffer[size] = 0; 227 | system(buffer); 228 | close(fd); 229 | } 230 | } 231 | int status; 232 | int ret = waitpid(child, &status, 0); 233 | return WEXITSTATUS(status); 234 | } 235 | } 236 | 237 | static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env, list dirMappings, list envMappings, const char *executable, char * const new_argv[]) { 238 | // get uid, gid before going to new namespace 239 | uid_t uid = getuid(); 240 | gid_t gid = getgid(); 241 | 242 | // "unshare" into new namespace 243 | if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) { 244 | if (errno == EPERM) { 245 | fputs("Run the following to enable unprivileged namespace use:\nsudo bash -c \"sysctl -w kernel.unprivileged_userns_clone=1 ; echo kernel.unprivileged_userns_clone=1 > /etc/sysctl.d/nix-user-chroot.conf\"\n\n", stderr); 246 | exit(EXIT_FAILURE); 247 | } else { 248 | err_exit("unshare()"); 249 | } 250 | } 251 | 252 | // add necessary system stuff to rootdir namespace 253 | for (list::iterator it = dirMappings.begin(); 254 | it != dirMappings.end(); ++it) { 255 | struct DirMapping m = *it; 256 | add_path(m.src, m.dest, rootdir); 257 | } 258 | 259 | // make sure nixdir exists 260 | struct stat statbuf2; 261 | if (stat(nixdir, &statbuf2) < 0) { 262 | err_exit("stat(%s)", nixdir); 263 | } 264 | 265 | char path_buf[PATH_MAX]; 266 | // mount /nix to new namespace 267 | snprintf(path_buf, sizeof(path_buf), "%s/nix", rootdir); 268 | mkdir(path_buf, statbuf2.st_mode & ~S_IFMT); 269 | if (mount(nixdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) { 270 | err_exit("mount(%s, %s)", nixdir, path_buf); 271 | } 272 | 273 | // fixes issue #1 where writing to /proc/self/gid_map fails 274 | // see user_namespaces(7) for more documentation 275 | int fd_setgroups = open("/proc/self/setgroups", O_WRONLY); 276 | if (fd_setgroups > 0) { 277 | write(fd_setgroups, "deny", 4); 278 | close(fd_setgroups); 279 | } 280 | 281 | // map the original uid/gid in the new ns 282 | char map_buf[1024]; 283 | snprintf(map_buf, sizeof(map_buf), "%d %d 1", uid, uid); 284 | update_map(map_buf, "/proc/self/uid_map"); 285 | snprintf(map_buf, sizeof(map_buf), "%d %d 1", gid, gid); 286 | update_map(map_buf, "/proc/self/gid_map"); 287 | 288 | // chroot to rootdir 289 | if (chroot(rootdir) < 0) { 290 | err_exit("chroot(%s)", rootdir); 291 | } 292 | 293 | chdir("/"); 294 | 295 | if (clear_env) clearenv(); 296 | setenv("PATH", ENV_PATH, 1); 297 | 298 | for (list::iterator it = envMappings.begin(); 299 | it != envMappings.end(); ++it) { 300 | struct SetEnv e = *it; 301 | setenv(e.key.c_str(), e.value.c_str(), 1); 302 | } 303 | 304 | // execute the command 305 | execvp(executable, new_argv); 306 | err_exit("execvp(%s)", executable); 307 | } 308 | --------------------------------------------------------------------------------