on master help sign in
29 files changed+1569−67
since splitsnapshots
unifiedsplit
.gitignore+1−1 AppRun.c+280 LICENSE+21 Makefile+5 README.md+126−2 appdir.nix+81 appdir.sh+27 appimage-bundle.nix+61 appimage-top.nix+17 appimage.nix+12 appimagetool.nix+47 default.nix+109−12 flake.nix+26 install-nix-from-closure.sh+128 makedir.nix−16 makeself.nix−14 nix-bootstrap.nix−22 nix-bootstrap.sh+35 nix-bundle.sh+63 nix-installer.nix+15 nix-run.sh+101 nix-strace.sh+12 nix-user-chroot/Makefile+4 nix-user-chroot/main.cpp+307 nix2appimage.sh+33 proot-x86_64 release.nix+34 test-appimage.nix+14 test.sh+10
.gitignore+1−1
@@ -1,2 +1,2 @@
11*~
2−result
2+result*
AppRun.cnew file+280
@@ -0,0 +1,280 @@
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 <stdio.h>
30+#include <stdlib.h>
31+#include <unistd.h>
32+#include <libgen.h>
33+#include <dirent.h>
34+#include <string.h>
35+#include <sys/stat.h>
36+#include <sched.h>
37+#include <fcntl.h>
38+#include <sys/mount.h>
39+#include <errno.h>
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+}
LICENSEnew file+21
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2017 Matthew Justin Bauer
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
Makefilenew file+5
@@ -0,0 +1,5 @@
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/
README.md+126−2
@@ -1,7 +1,131 @@
11# nix-bundle
22
3−to run:
3+nix-bundle is a way to package Nix attributes into single-file executables.
4+
5+## Benefits
6+
7+* Single-file output
8+* Can be run by non-root users
9+* No runtime
10+* Distro agnostic
11+* No installation
12+
13+## Getting started
14+
15+Make sure you have installed Nix already. See http://nixos.org/nix/ for more details.
16+
17+Once you have a working Nix install, you can run:
18+
19+```sh
20+$ ./nix-bundle.sh hello /bin/hello
21+```
22+
23+```hello``` indicates the Nix derivation from NixPkgs that you want to use, while ```/bin/hello``` indicates the path of the executable relative to ```hello``` that you want to run. This will create the file "hello". Running it:
24+
25+```sh
26+$ ./hello
27+Hello, world!
28+```
29+
30+This is a standalone file that is completely portable! As long as you are running the same architecture Linux kernel and have a shell interpreter available it will run.
31+
32+Some others to try:
33+
34+```sh
35+./nix-bundle.sh nano /bin/nano
36+```
37+
38+```sh
39+./nix-bundle.sh emacs /bin/emacs
40+```
41+
42+Or if you want to try graphical applications:
43+
44+```sh
45+# Simple X game. Very few dependencies. Quick to build and load. ~13MB
46+./nix-bundle.sh xskat /bin/xskat
47+```
448
549```sh
6−nix-build
50+./nix-bundle.sh firefox /bin/firefox
751```
52+
53+```sh
54+# SDL-based game. ~228MB
55+./nix-bundle.sh ivan /bin/ivan
56+```
57+
58+
59+## Self-bundling (meta)
60+
61+Starting with v0.1.3, you can bundle nix-bundle! To do this, just use nix-bundle normally:
62+
63+```sh
64+NIX_PATH="nixpkgs=https://github.com/matthewbauer/nixpkgs/archive/nix-bundle.tar.gz" ./nix-bundle.sh nix-bundle /bin/nix-bundle
65+```
66+
67+## [Experimental] Create AppImage executables from Nix expressions
68+
69+"nix-bundle.sh" tends to create fairly large outputs. This is largely because nix-bundle.sh "extracts" its payload up front. AppImage uses a different method where extraction only takes place when the file is accessed (through FUSE and SquashFS). You can now create a compliant "AppImage" using the "nix2appimage.sh" script:
70+
71+```sh
72+./nix2appimage.sh emacs
73+```
74+
75+This will create a file at Emacs-x86_64.AppImage which you can execute.
76+
77+Notice that there is only one argument for nix2appimage.sh. This is because the target executable will be detected from the .desktop file in ```/share/applications/*.desktop```. As a side-effect, AppImage requires your package to have a .desktop file, so packages like "hello", "coreutils", etc. will not work.
78+
79+Some other examples to try:
80+
81+```sh
82+./nix2appimage.sh firefox
83+```
84+
85+```sh
86+./nix2appimage.sh vlc
87+```
88+
89+```sh
90+./nix2appimage.sh 0ad
91+```
92+
93+```sh
94+./nix2appimage.sh wireshark-gtk
95+```
96+
97+These may take a while because of the large closure size.
98+
99+Note that these do not currently work out of the box with NixOS. Other Linux distros should work.
100+
101+## Comparison with AppImage, FlatPak, Snappy
102+
103+| Name | Distro-agnostic | Runtime required | Root required | Storage |
104+| ---------- | --------------- | ---------------- | ------------- | ------- |
105+| nix-bundle | yes | no | no | Arx tarball |
106+| AppImage | yes | no | no | Squashfs w/ lzma compression |
107+| FlatPak | yes | yes | no | ? |
108+| Snappy | yes | yes | no | squashFS |
109+
110+## How it works
111+
112+Nix-bundle glues together four different projects to work correctly:
113+
114+* [Arx](https://github.com/solidsnack/arx) - an archive execution tool
115+* Creates single-file archive executable that can unpack themselves and then run some command. nix-bundle calls nix-user-chroot to bootstrap the Nix environment. It outputs a "./nix" folder.
116+* [nix-user-chroot](https://github.com/lethalman/nix-user-chroot) - a small bootstrap that uses Linux namespaces to call chroot
117+ * This will create sub namespace and bind mount the "./nix" to "/nix" so that the Nix references function properly.
118+* [Nix](https://nixos.org/nix/) - a functional package manager
119+ * Used to build runtime closures that are self-contained.
120+* [nixpkgs](https://nixos.org/nixpkgs/)
121+ * Provides lots of different packages to choose from.
122+
123+## Drawbacks
124+
125+Nix-bundle has some drawbacks that need to be worked on:
126+
127+* Slow startup
128+* Large files (Firefox 150MB)
129+* Only compatible Linux
130+* Outputs built on x86-64 will not run on i386
131+* Requires Linux kernel with CAP_SYS_USER_NS on and permissions setup correctly
appdir.nixnew file+81
@@ -0,0 +1,81 @@
1+{ stdenv, lib, fetchurl, muslPkgs, perl, pathsFromGraph, fetchFromGitHub, coreutils, bash }:
2+
3+let
4+ AppRun = targets: muslPkgs.stdenv.mkDerivation {
5+ name = "AppRun";
6+
7+ phases = [ "buildPhase" "installPhase" "fixupPhase" ];
8+
9+ buildPhase = ''
10+ CC="$CC -O2 -Wall -Wno-deprecated-declarations -Wno-unused-result -static"
11+ $CC ${./AppRun.c} -o AppRun -DENV_PATH='"${lib.makeBinPath targets}"'
12+ '';
13+
14+ installPhase = ''
15+ mkdir -p $out/bin
16+ cp AppRun $out/bin/AppRun
17+ '';
18+ };
19+
20+in
21+
22+ { target, name, extraTargets ? [ coreutils bash ] }: let targets = ([ target ] ++ extraTargets);
23+ in stdenv.mkDerivation {
24+ name = "${name}.AppDir";
25+ exportReferencesGraph = map (x: [("closure-" + baseNameOf x) x]) targets;
26+ nativeBuildInputs = [ perl ];
27+ buildCommand = ''
28+ # TODO use symlinks to shrink output size
29+
30+ if [ ! -d ${target}/share/applications ]; then
31+ echo "--------------------------------------------------"
32+ echo "| /share/applications does not exist. |"
33+ echo "| AppImage only works with 'applications'. |"
34+ echo "| Try using nix-bundle.sh for command-line apps. |"
35+ echo "--------------------------------------------------"
36+ exit 1
37+ fi
38+
39+ storePaths=$(${perl}/bin/perl ${pathsFromGraph} ./closure-*)
40+
41+ mkdir -p $out/${name}.AppDir
42+ cd $out/${name}.AppDir
43+
44+ mkdir -p nix/store
45+ cp -r $storePaths nix/store
46+
47+ ln -s .${target} usr
48+
49+ if [ -d ${target}/share/appdata ]; then
50+ chmod a+w usr/share
51+ mkdir -p usr/share/metainfo
52+ for f in ${target}/share/appdata/*.xml; do
53+ ln -s ../appdata/$(basename $f) usr/share/metainfo/$(basename $f)
54+ done
55+ fi
56+
57+ # .desktop
58+ desktop=$(find ${target}/share/applications -name "*.desktop" | head -n1)
59+ if ! [ -z "$desktop" ]; then
60+ cp .$desktop .
61+ fi
62+
63+
64+ # icons
65+ if [ -d ${target}/share/icons ]; then
66+ icon=$(find ${target}/share/icons -name "${name}*.png" | head -n1)
67+ if ! [ -z "$icon" ]; then
68+ ln -s .$icon
69+ ln -s .$icon .DirIcon
70+ else
71+ icon=$(find ${target}/share/icons -name "${name}*.svg" | head -n1)
72+ if ! [ -z "$icon" ]; then
73+ ln -s .$icon
74+ ln -s .$icon .DirIcon
75+ fi
76+ fi
77+ fi
78+
79+ cp ${AppRun targets}/bin/AppRun AppRun
80+ '';
81+ }
appdir.shnew file+27
@@ -0,0 +1,27 @@
1+#!/usr/bin/env sh
2+
3+if [ "$#" -lt 1 ]; then
4+ cat <<EOF
5+
6+Usage: $0 TARGET
7+
8+Create an appdir from the nixpkgs attribute "TARGET".
9+EOF
10+
11+ exit 1
12+fi
13+
14+target="$1"
15+
16+expr="with import <nixpkgs> {}; with import ./appimage-top.nix {}; appdir { name = \"$target\"; target = $target; }"
17+
18+out=$(nix-store --no-gc-warning -r $(nix-instantiate --no-gc-warning -E "$expr"))
19+
20+if [ -z "$out" ]; then
21+ echo "$0 failed. Exiting."
22+ exit 1
23+else
24+ rm -f result
25+ ln -sf $out/*.AppDir result
26+ echo "AppImage created at result"
27+fi
appimage-bundle.nixnew file+61
@@ -0,0 +1,61 @@
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 <nixpkgs>{}; writers.writePython3Bin "helloThere.py" {} "print(1)\n"' --argstr exec helloThere.py
4+
5+
6+{nixpkgs ? import <nixpkgs>{},
7+package,
8+exec,
9+... }:
10+let
11+ appimage_src = drv : exec : with nixpkgs;
12+ self.stdenv.mkDerivation rec {
13+ name = drv.name + "-appdir";
14+ env = buildEnv {
15+ inherit name;
16+ paths = buildInputs;
17+ };
18+ src = env;
19+ inherit exec;
20+ buildInputs = [ drv ];
21+ buildCommand = ''
22+ mkdir -p $out/share/icons/hicolor/256x256/apps
23+ mkdir -p $out/share/applications
24+
25+ shopt -s extglob
26+ ln -s ${env}/!(share) $out/
27+ ln -s ${env}/share/* $out/share/
28+
29+ touch $out/share/icons/hicolor/256x256/apps/${drv.name}.png
30+ touch $out/share/icons/${drv.name}.png
31+
32+ cat <<EOF > $out/share/applications/${drv.name}.desktop
33+ [Desktop Entry]
34+ Type=Application
35+ Version=1.0
36+ Name=${drv.name}
37+ Path=${env}
38+ Icon=${drv.name}
39+ Exec=$exec
40+ Terminal=true
41+ EOF
42+ '';
43+ system = builtins.currentSystem;
44+ };
45+
46+in
47+ let results =
48+ if (nixpkgs.lib.isDerivation package && !(nixpkgs.lib.isString package))
49+ then {
50+ name = package.name;
51+ target = appimage_src package "${exec}";
52+ extraTargets = [];
53+ }
54+ else {
55+ name = nixpkgs."${package}".name;
56+ target = appimage_src (nixpkgs."${package}") "${exec}";
57+ extraTargets = [];
58+ };
59+ in
60+ with (import (./appimage-top.nix){nixpkgs' = nixpkgs.path;});
61+ (appimage (appdir results )).overrideAttrs (old: {name = results.name;})
appimage-top.nixnew file+17
@@ -0,0 +1,17 @@
1+{ nixpkgs' ? <nixpkgs> }:
2+
3+let
4+ pkgs = import nixpkgs' { };
5+ muslPkgs = import nixpkgs' {
6+ localSystem.config = "x86_64-unknown-linux-musl";
7+ };
8+
9+in rec {
10+ appimagetool = pkgs.callPackage ./appimagetool.nix {};
11+
12+ appimage = pkgs.callPackage ./appimage.nix {
13+ inherit appimagetool;
14+ };
15+
16+ appdir = pkgs.callPackage ./appdir.nix { inherit muslPkgs; };
17+}
appimage.nixnew file+12
@@ -0,0 +1,12 @@
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+}
appimagetool.nixnew file+47
@@ -0,0 +1,47 @@
1+{ stdenv, lib, fetchurl, fuse, zlib, squashfsTools, glib }:
2+
3+# This is from some binaries.
4+
5+# Ideally, this should be source based,
6+# but I can't get it to build from GitHub
7+
8+let
9+ inherit (stdenv.cc.bintools) dynamicLinker;
10+in stdenv.mkDerivation rec {
11+ name = "appimagekit";
12+
13+ src = fetchurl {
14+ url = "https://github.com/AppImage/AppImageKit/releases/download/10/appimagetool-x86_64.AppImage";
15+ sha256 = "03zbiblj8a1yk1xsb5snxi4ckwn3diyldg1jh5hdjjhsmpw652ig";
16+ };
17+
18+ buildInputs = [
19+ squashfsTools
20+ ];
21+
22+ sourceRoot = "squashfs-root";
23+
24+ unpackPhase = ''
25+ cp $src appimagetool-x86_64.AppImage
26+ chmod u+wx appimagetool-x86_64.AppImage
27+ patchelf --set-interpreter ${dynamicLinker} \
28+ --set-rpath ${fuse}/lib:${zlib}/lib \
29+ appimagetool-x86_64.AppImage
30+ ./appimagetool-x86_64.AppImage --appimage-extract
31+ '';
32+
33+ installPhase = ''
34+ mkdir -p $out
35+ cp -r usr/* $out
36+
37+ for x in $out/bin/*; do
38+ patchelf \
39+ --set-interpreter ${dynamicLinker} \
40+ --set-rpath ${lib.makeLibraryPath [ zlib stdenv.glibc.out fuse glib ]} \
41+ $x
42+ done
43+ '';
44+
45+ dontStrip = true;
46+ dontPatchELF = true;
47+}
default.nix+109−12
@@ -3,20 +3,117 @@
33with nixpkgs;
44
55let
6+ arx' = haskellPackages.arx.overrideAttrs (o: {
7+ patchPhase = (o.patchPhase or "") + ''
8+ substituteInPlace model-scripts/tmpx.sh \
9+ --replace /tmp/ \$HOME/.cache/
10+ '';
11+ });
12+in rec {
13+ toStorePath = target:
14+ # If a store path has been given but is not a derivation, add the missing context
15+ # to it so it will be propagated properly as a build input.
16+ if !(lib.isDerivation target) && lib.isStorePath target then
17+ let path = toString target; in
18+ builtins.appendContext path { "${path}" = { path = true; }; }
19+ # Otherwise, add to the store. This takes care of appending the store path
20+ # in the context automatically.
21+ else "${target}";
622
7− makeself = callPackage ./makeself.nix {};
8− makedir = callPackage ./makedir.nix {};
9− nix-bootstrap = callPackage ./nix-bootstrap.nix {};
23+ arx = { archive, startup}:
24+ stdenv.mkDerivation {
25+ name = "arx";
26+ buildCommand = ''
27+ ${arx'}/bin/arx tmpx --shared -rm! ${archive} -o $out // ${startup}
28+ chmod +x $out
29+ '';
30+ };
1031
11− nixdir = makedir {
12− name = "nixdir";
13− toplevel = nix-bootstrap;
32+ maketar = { targets }:
33+ stdenv.mkDerivation {
34+ name = "maketar";
35+ buildInputs = [ perl ];
36+ exportReferencesGraph = map (x: [("closure-" + baseNameOf x) x]) targets;
37+ buildCommand = ''
38+ storePaths=$(perl ${pathsFromGraph} ./closure-*)
39+
40+ tar -cf - \
41+ --owner=0 --group=0 --mode=u+rw,uga+r \
42+ --hard-dereference \
43+ $storePaths | bzip2 -z > $out
44+ '';
45+ };
46+
47+ # TODO: eventually should this go in nixpkgs?
48+ nix-user-chroot = lib.makeOverridable stdenv.mkDerivation {
49+ name = "nix-user-chroot-2c52b5f";
50+ src = ./nix-user-chroot;
51+
52+ makeFlags = [];
53+
54+ # hack to use when /nix/store is not available
55+ postFixup = ''
56+ exe=$out/bin/nix-user-chroot
57+ patchelf \
58+ --set-interpreter .$(patchelf --print-interpreter $exe) \
59+ --set-rpath $(patchelf --print-rpath $exe | sed 's|/nix/store/|./nix/store/|g') \
60+ $exe
61+ '';
62+
63+ installPhase = ''
64+ runHook preInstall
65+
66+ mkdir -p $out/bin/
67+ cp nix-user-chroot $out/bin/nix-user-chroot
68+
69+ runHook postInstall
70+ '';
71+
72+ meta.platforms = lib.platforms.linux;
1473 };
1574
16−in
75+ makebootstrap = { targets, startup }:
76+ arx {
77+ inherit startup;
78+ archive = maketar {
79+ inherit targets;
80+ };
81+ };
82+
83+ makeStartup = { target, nixUserChrootFlags, nix-user-chroot', run, initScript }:
84+ let
85+ # Avoid re-adding a store path into the store
86+ path = toStorePath target;
87+ in
88+ writeScript "startup" ''
89+ #!/bin/sh
90+ ${initScript}
91+ .${nix-user-chroot'}/bin/nix-user-chroot -n ./nix ${nixUserChrootFlags} -- ${path}${run} "$@"
92+ '';
93+
94+ nix-bootstrap = { target, extraTargets ? [], run, nix-user-chroot' ? nix-user-chroot, nixUserChrootFlags ? "", initScript ? "" }:
95+ let
96+ script = makeStartup { inherit target nixUserChrootFlags nix-user-chroot' run initScript; };
97+ in makebootstrap {
98+ startup = ".${script} '\"$@\"'";
99+ targets = [ "${script}" ] ++ extraTargets;
100+ };
101+
102+ nix-bootstrap-nix = {target, run, extraTargets ? []}:
103+ nix-bootstrap-path {
104+ inherit target run;
105+ extraTargets = [ gnutar bzip2 xz gzip coreutils bash ];
106+ };
17107
18− makeself {
19− name = "nix-installer.sh";
20− startup = ".${nix-bootstrap}/install";
21− archive = nixdir;
22− }
108+ # special case adding path to the environment before launch
109+ nix-bootstrap-path = let
110+ nix-user-chroot'' = targets: nix-user-chroot.overrideDerivation (o: {
111+ buildInputs = o.buildInputs ++ targets;
112+ makeFlags = o.makeFlags ++ [
113+ ''ENV_PATH="${lib.makeBinPath targets}"''
114+ ];
115+ }); in { target, extraTargets ? [], run, initScript ? "" }: nix-bootstrap {
116+ inherit target extraTargets run initScript;
117+ nix-user-chroot' = nix-user-chroot'' extraTargets;
118+ };
119+}
flake.nixnew file+26
@@ -0,0 +1,26 @@
1+{
2+ description = "The purely functional package manager";
3+
4+ inputs.nixpkgs.url = "nixpkgs/nixos-20.03-small";
5+
6+ outputs = { self, nixpkgs }: let
7+ systems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
8+ forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
9+ in {
10+ bundlers = {
11+ nix-bundle = { program, system }: let
12+ nixpkgs' = nixpkgs.legacyPackages.${system};
13+ nix-bundle = import self { nixpkgs = nixpkgs'; };
14+ script = nixpkgs'.writeScript "startup" ''
15+ #!/bin/sh
16+ .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- ${program} "$@"
17+ '';
18+ in nix-bundle.makebootstrap {
19+ targets = [ script ];
20+ startup = ".${builtins.unsafeDiscardStringContext script} '\"$@\"'";
21+ };
22+ };
23+
24+ defaultBundler = self.bundlers.nix-bundle;
25+ };
26+}
install-nix-from-closure.shnew file+128
@@ -0,0 +1,128 @@
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 <<EOF
110+
111+Installation finished! To ensure that the necessary environment
112+variables are set, please add the line
113+
114+ . $p
115+
116+to your shell profile (e.g. ~/.profile).
117+EOF
118+else
119+ cat >&2 <<EOF
120+
121+Installation finished! To ensure that the necessary environment
122+variables are set, either log in again, or type
123+
124+ . $p
125+
126+in your shell.
127+EOF
128+fi
makedir.nixdeleted−16
@@ -1,16 +0,0 @@
1−{ stdenv, perl, pathsFromGraph }:
2−
3−{ name, toplevel }:
4−
5− stdenv.mkDerivation {
6− inherit toplevel name;
7− exportReferencesGraph = [ "closure" toplevel ];
8− buildInputs = [ perl ];
9− buildCommand = ''
10− storePaths=$(${perl}/bin/perl ${pathsFromGraph} ./closure)
11− mkdir -p $out
12− for path in $storePaths; do
13− cp --parents -r $path $out/
14− done
15− '';
16− }
makeself.nixdeleted−14
@@ -1,14 +0,0 @@
1−{ stdenv, makeself }:
2−
3−{ name
4−, archive
5−, startup
6−}:
7−
8− stdenv.mkDerivation {
9− inherit name archive startup;
10− nativeBuildInputs = [ makeself ];
11− buildCommand = ''
12− ${makeself}/bin/makeself $archive $out $name $startup
13− '';
14− }
nix-bootstrap.nixdeleted−22
@@ -1,22 +0,0 @@
1−{ stdenv, fetchFromGitHub, nix, cacert }:
2−
3− stdenv.mkDerivation {
4− name = "nix-bootstrap";
5−
6− src = fetchFromGitHub {
7− owner = "matthewbauer";
8− repo = "nix-bootstrap";
9− rev = "47bc67bbc5cd71fba9c9b0d07f967023b94e8ffa";
10− sha256 = "1jy52hhmbdn1hgc10x73kqx8dc38piz589mjbj07rp4vx0qc9alw";
11− };
12−
13− propagatedBuildInputs = [ nix cacert ];
14−
15− installPhase = ''
16− mkdir -p $out/
17− substitute install-nix-from-closure.sh $out/install \
18− --subst-var-by nix .${nix} \
19− --subst-var-by cacert .${cacert}
20− chmod +x $out/install
21− '';
22− }
nix-bootstrap.shnew file+35
@@ -0,0 +1,35 @@
1+#!/bin/sh
2+
3+if [ $# -gt 0 ]; then
4+ set -- sh
5+fi
6+
7+# should download this in the future
8+# but the mirror is down
9+proot=$(dirname "$0")/proot-$(uname -p)
10+export PROOT_NO_SECCOMP=1
11+
12+nixdir=$HOME/.nix
13+
14+OLD_NIX_PATH=$NIX_PATH
15+# shellcheck disable=SC1090
16+if [ -e "$HOME/.nix-profile/etc/profile.d/nix.sh" ]; then . "$HOME/.nix-profile/etc/profile.d/nix.sh"; fi
17+if [ -n "$OLD_NIX_PATH" ]; then NIX_PATH="$OLD_NIX_PATH"; fi
18+
19+if [ -z "$IN_PROOT" ]; then
20+ export IN_PROOT=1
21+
22+ if ! [ -d "$nixdir" ]; then
23+ mkdir -p "$nixdir"
24+ s=$(mktemp)
25+ curl https://nixos.org/nix/install -o "$s"
26+ $proot -b "$nixdir:/nix" "$0" sh "$s"
27+ fi
28+
29+ $proot -b "$nixdir:/nix" "$0" "$@"
30+
31+ export IN_PROOT=
32+ exit
33+elif [ $# -gt 0 ]; then
34+ exec "$@"
35+fi
nix-bundle.shnew file+63
@@ -0,0 +1,63 @@
1+#!/usr/bin/env sh
2+
3+if [ "$#" -lt 2 ]; then
4+ cat <<EOF
5+
6+Usage: $0 TARGET EXECUTABLE
7+
8+Create a single-file bundle from the nixpkgs attribute "TARGET".
9+EXECUTABLE should be relative to the TARGET's output path.
10+
11+The TARGET is either an attribute in nixpkgs, or an absolute path to the
12+store.
13+
14+For example:
15+
16+$ $0 hello /bin/hello
17+$ ./hello
18+Hello, world!
19+
20+EOF
21+
22+ exit 1
23+fi
24+
25+nix_file=$(dirname "$0")/default.nix
26+
27+target="$1"
28+shift
29+
30+extraTargets=
31+if [ "$#" -gt 1 ]; then
32+ while [ "$#" -gt 1 ]; do
33+ extraTargets="$extraTargets $1"
34+ shift
35+ done
36+fi
37+
38+exec="$1"
39+shift
40+
41+bootstrap=nix-bootstrap
42+if [ "$target" = "nix-bundle" ] || [ "$target" = "nixStable" ] || [ "$target" = "nixUnstable" ] || [ "$target" = "nix" ]; then
43+ bootstrap=nix-bootstrap-nix
44+elif [ -n "$extraTargets" ]; then
45+ bootstrap=nix-bootstrap-path
46+fi
47+
48+expr="with import <nixpkgs> {}; with import $nix_file {}; $bootstrap { target = $target; extraTargets = [ $extraTargets ]; run = \"$exec\"; }"
49+
50+drv=$(nix-instantiate --no-gc-warning -E "$expr")
51+
52+out=$(nix-store --no-gc-warning --realize "$drv")
53+
54+if [ -z "$out" ]; then
55+ >&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
nix-installer.nixnew file+15
@@ -0,0 +1,15 @@
1+{ stdenv, fetchFromGitHub, writeText, nix, cacert }:
2+
3+stdenv.mkDerivation {
4+ name = "nix-installer";
5+
6+ propagatedBuildInputs = [ nix.out cacert ];
7+
8+ buildCommand = ''
9+ mkdir -p $out/bin/
10+ substitute ${./install-nix-from-closure.sh} $out/install \
11+ --subst-var-by nix ${nix.out} \
12+ --subst-var-by cacert ${cacert}
13+ chmod +x $out/install
14+ '';
15+}
nix-run.shnew file+101
@@ -0,0 +1,101 @@
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 <nixpkgs> {}; 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
nix-strace.shnew file+12
@@ -0,0 +1,12 @@
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/'
nix-user-chroot/Makefilenew file+4
@@ -0,0 +1,4 @@
1+ENV_PATH ?= ""
2+
3+nix-user-chroot: main.cpp
4+ g++ -o nix-user-chroot -DENV_PATH='$(ENV_PATH)' main.cpp
nix-user-chroot/main.cppnew file+307
@@ -0,0 +1,307 @@
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 <nixpath> <command>
6+ */
7+
8+#include <sched.h>
9+#include <unistd.h>
10+#include <stdlib.h>
11+#include <sys/wait.h>
12+#include <signal.h>
13+#include <fcntl.h>
14+#include <stdio.h>
15+#include <string.h>
16+#include <limits.h>
17+#include <errno.h>
18+#include <sys/mount.h>
19+#include <sys/types.h>
20+#include <dirent.h>
21+#include <sys/stat.h>
22+#include <stdint.h>
23+#include <list>
24+#include <string>
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<struct DirMapping> dirMappings, list<struct SetEnv> 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 <nixpath> -- <command>\n", pname);
36+ fprintf(stderr, "\t-c\tclear all env vars\n");
37+ fprintf(stderr, "\t-m <src>:<src>\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 <var>\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<struct DirMapping> dirMappings;
110+ list<struct SetEnv> 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 <nixdir> 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<struct DirMapping> dirMappings, list<struct SetEnv> 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<struct DirMapping>::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<struct SetEnv>::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+}
nix2appimage.shnew file+33
@@ -0,0 +1,33 @@
1+#!/usr/bin/env sh
2+
3+if [ "$#" -lt 1 ]; then
4+ cat <<EOF
5+
6+Usage: $0 TARGET
7+
8+Create a single-file bundle from the nixpkgs attribute "TARGET".
9+
10+For example:
11+
12+$ $0 emacs
13+$ ./emacs
14+
15+EOF
16+
17+ exit 1
18+fi
19+
20+target="$1"
21+
22+expr="with import <nixpkgs> {}; with import ./appimage-top.nix {}; appimage (appdir { name = \"$target\"; target = $target; })"
23+
24+out=$(nix-store --no-gc-warning -r $(nix-instantiate --no-gc-warning -E "$expr"))
25+
26+if [ -z "$out" ]; then
27+ echo "$0 failed. Exiting."
28+ exit 1
29+else
30+ appimage=$out/*.AppImage
31+ cp -f $appimage .
32+ echo "AppImage created at $(basename $appimage)"
33+fi
proot-x86_64new file

Binary file not shown.

release.nixnew file+34
@@ -0,0 +1,34 @@
1+{ nixpkgs ? <nixpkgs>
2+, nixpkgs' ? import nixpkgs {}}: with nixpkgs';
3+
4+stdenv.mkDerivation rec {
5+ pname = "nix-bundle";
6+ name = "${pname}-${version}";
7+ version = "0.4.0";
8+
9+ src = ./.;
10+
11+ # coreutils, gnutar is actually needed by nix for bootstrap
12+ buildInputs = [ nix coreutils makeWrapper gnutar gzip bzip2 ];
13+
14+ nixBundlePath = lib.makeBinPath [ nix coreutils gnutar gzip bzip2 ];
15+ nixRunPath = lib.makeBinPath [ nix coreutils ];
16+
17+ makeFlags = [ "PREFIX=$(out)" ];
18+
19+ postInstall = ''
20+ mkdir -p $out/bin
21+ makeWrapper $out/share/nix-bundle/nix-bundle.sh $out/bin/nix-bundle \
22+ --prefix PATH : ${nixBundlePath}
23+ makeWrapper $out/share/nix-bundle/nix-run.sh $out/bin/nix-run \
24+ --prefix PATH : ${nixRunPath}
25+ '';
26+
27+ meta = with lib; {
28+ maintainers = [ maintainers.matthewbauer ];
29+ platforms = platforms.all;
30+ description = "Create bundles from Nixpkgs attributes";
31+ license = licenses.mit;
32+ homepage = https://github.com/matthewbauer/nix-bundle;
33+ };
34+}
test-appimage.nixnew file+14
@@ -0,0 +1,14 @@
1+{ appimagefile, nixpkgs' ? <nixpkgs> }:
2+
3+# nix build -f test-appimage.nix --arg appimagefile ./VLC*AppImage
4+
5+with import nixpkgs' {};
6+
7+runCommand "patchelf" {} ''
8+ cp ${appimagefile} $out
9+ chmod +w $out
10+ patchelf \
11+ --set-interpreter ${stdenv.cc.bintools.dynamicLinker} \
12+ --set-rpath ${stdenv.glibc.out}/lib:${fuse}/lib:${zlib}/lib:${glib}/lib \
13+ $out
14+''
test.shnew file+10
@@ -0,0 +1,10 @@
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 <nixpkgs> {})' -A hello)
10+./nix-bundle.sh "$out" /bin/hello