environment.scm 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014, 2015, 2018 David Thompson <davet@gnu.org>
  3. ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2018 Mike Gerwitz <mtg@gnu.org>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix scripts environment)
  21. #:use-module (guix ui)
  22. #:use-module (guix store)
  23. #:use-module (guix utils)
  24. #:use-module ((guix status) #:select (with-status-verbosity))
  25. #:use-module (guix grafts)
  26. #:use-module (guix derivations)
  27. #:use-module (guix packages)
  28. #:use-module (guix profiles)
  29. #:use-module (guix search-paths)
  30. #:use-module (guix build utils)
  31. #:use-module (guix monads)
  32. #:use-module ((guix gexp) #:select (lower-object))
  33. #:use-module (guix scripts)
  34. #:use-module (guix scripts build)
  35. #:use-module (guix transformations)
  36. #:use-module (gnu build linux-container)
  37. #:use-module (gnu build accounts)
  38. #:use-module ((guix build syscalls) #:select (set-network-interface-up))
  39. #:use-module (gnu system linux-container)
  40. #:use-module (gnu system file-systems)
  41. #:use-module (gnu packages)
  42. #:use-module (gnu packages bash)
  43. #:use-module ((gnu packages bootstrap)
  44. #:select (bootstrap-executable %bootstrap-guile))
  45. #:use-module (ice-9 match)
  46. #:use-module (srfi srfi-1)
  47. #:use-module (srfi srfi-11)
  48. #:use-module (srfi srfi-26)
  49. #:use-module (srfi srfi-37)
  50. #:use-module (srfi srfi-98)
  51. #:export (assert-container-features
  52. guix-environment))
  53. ;; Protect some env vars from purification. Borrowed from nix-shell.
  54. (define %precious-variables
  55. '("HOME" "USER" "LOGNAME" "DISPLAY" "TERM" "TZ" "PAGER"))
  56. (define %default-shell
  57. (or (getenv "SHELL") "/bin/sh"))
  58. (define (purify-environment white-list)
  59. "Unset all environment variables except those that match the regexps in
  60. WHITE-LIST and those listed in %PRECIOUS-VARIABLES. A small number of
  61. variables such as 'HOME' and 'USER' are left untouched."
  62. (for-each unsetenv
  63. (remove (lambda (variable)
  64. (or (member variable %precious-variables)
  65. (find (cut regexp-exec <> variable)
  66. white-list)))
  67. (match (get-environment-variables)
  68. (((names . _) ...)
  69. names)))))
  70. (define* (create-environment profile manifest
  71. #:key pure? (white-list '()))
  72. "Set the environment variables specified by MANIFEST for PROFILE. When
  73. PURE? is #t, unset the variables in the current environment except those that
  74. match the regexps in WHITE-LIST. Otherwise, augment existing environment
  75. variables with additional search paths."
  76. (when pure?
  77. (purify-environment white-list))
  78. (for-each (match-lambda
  79. ((($ <search-path-specification> variable _ separator) . value)
  80. (let ((current (getenv variable)))
  81. (setenv variable
  82. (if (and current (not pure?))
  83. (if separator
  84. (string-append value separator current)
  85. value)
  86. value)))))
  87. (profile-search-paths profile manifest))
  88. ;; Give users a way to know that they're in 'guix environment', so they can
  89. ;; adjust 'PS1' accordingly, for instance. Set it to PROFILE so users can
  90. ;; conveniently access its contents.
  91. (setenv "GUIX_ENVIRONMENT" profile))
  92. (define* (show-search-paths profile manifest #:key pure?)
  93. "Display the search paths of MANIFEST applied to PROFILE. When PURE? is #t,
  94. do not augment existing environment variables with additional search paths."
  95. (for-each (match-lambda
  96. ((search-path . value)
  97. (display
  98. (search-path-definition search-path value
  99. #:kind (if pure? 'exact 'prefix)))
  100. (newline)))
  101. (profile-search-paths profile manifest)))
  102. (define (input->manifest-entry input)
  103. "Return a manifest entry for INPUT, or #f if INPUT does not correspond to a
  104. package."
  105. (match input
  106. ((_ (? package? package))
  107. (package->manifest-entry package))
  108. ((_ (? package? package) output)
  109. (package->manifest-entry package output))
  110. (_
  111. #f)))
  112. (define (package-environment-inputs package)
  113. "Return a list of manifest entries corresponding to the transitive input
  114. packages for PACKAGE."
  115. ;; Remove non-package inputs such as origin records.
  116. (filter-map input->manifest-entry
  117. (bag-transitive-inputs (package->bag package))))
  118. (define (show-help)
  119. (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...]
  120. Build an environment that includes the dependencies of PACKAGE and execute
  121. COMMAND or an interactive shell in that environment.\n"))
  122. (display (G_ "
  123. -e, --expression=EXPR create environment for the package that EXPR
  124. evaluates to"))
  125. (display (G_ "
  126. -l, --load=FILE create environment for the package that the code within
  127. FILE evaluates to"))
  128. (display (G_ "
  129. -m, --manifest=FILE create environment with the manifest from FILE"))
  130. (display (G_ "
  131. -p, --profile=PATH create environment from profile at PATH"))
  132. (display (G_ "
  133. --ad-hoc include all specified packages in the environment instead
  134. of only their inputs"))
  135. (display (G_ "
  136. --pure unset existing environment variables"))
  137. (display (G_ "
  138. -E, --preserve=REGEXP preserve environment variables that match REGEXP"))
  139. (display (G_ "
  140. --search-paths display needed environment variable definitions"))
  141. (display (G_ "
  142. -s, --system=SYSTEM attempt to build for SYSTEM--e.g., \"i686-linux\""))
  143. (display (G_ "
  144. -r, --root=FILE make FILE a symlink to the result, and register it
  145. as a garbage collector root"))
  146. (display (G_ "
  147. -C, --container run command within an isolated container"))
  148. (display (G_ "
  149. -N, --network allow containers to access the network"))
  150. (display (G_ "
  151. -P, --link-profile link environment profile to ~/.guix-profile within
  152. an isolated container"))
  153. (display (G_ "
  154. -u, --user=USER instead of copying the name and home of the current
  155. user into an isolated container, use the name USER
  156. with home directory /home/USER"))
  157. (display (G_ "
  158. --no-cwd do not share current working directory with an
  159. isolated container"))
  160. (display (G_ "
  161. --share=SPEC for containers, share writable host file system
  162. according to SPEC"))
  163. (display (G_ "
  164. --expose=SPEC for containers, expose read-only host file system
  165. according to SPEC"))
  166. (display (G_ "
  167. -v, --verbosity=LEVEL use the given verbosity LEVEL"))
  168. (display (G_ "
  169. --bootstrap use bootstrap binaries to build the environment"))
  170. (newline)
  171. (show-build-options-help)
  172. (newline)
  173. (show-transformation-options-help)
  174. (newline)
  175. (display (G_ "
  176. -h, --help display this help and exit"))
  177. (display (G_ "
  178. -V, --version display version information and exit"))
  179. (newline)
  180. (show-bug-report-information))
  181. (define %default-options
  182. `((system . ,(%current-system))
  183. (substitutes? . #t)
  184. (offload? . #t)
  185. (graft? . #t)
  186. (print-build-trace? . #t)
  187. (print-extended-build-trace? . #t)
  188. (multiplexed-build-output? . #t)
  189. (debug . 0)
  190. (verbosity . 1)))
  191. (define (tag-package-arg opts arg)
  192. "Return a two-element list with the form (TAG ARG) that tags ARG with either
  193. 'ad-hoc' in OPTS has the 'ad-hoc?' key set to #t, or 'inputs' otherwise."
  194. ;; Normally, the transitive inputs to a package are added to an environment,
  195. ;; but the ad-hoc? flag changes the meaning of a package argument such that
  196. ;; the package itself is added to the environment instead.
  197. (if (assoc-ref opts 'ad-hoc?)
  198. `(ad-hoc-package ,arg)
  199. `(package ,arg)))
  200. (define %options
  201. ;; Specification of the command-line options.
  202. (cons* (option '(#\h "help") #f #f
  203. (lambda args
  204. (show-help)
  205. (exit 0)))
  206. (option '(#\V "version") #f #f
  207. (lambda args
  208. (show-version-and-exit "guix environment")))
  209. (option '("pure") #f #f
  210. (lambda (opt name arg result)
  211. (alist-cons 'pure #t result)))
  212. (option '(#\E "preserve") #t #f
  213. (lambda (opt name arg result)
  214. (alist-cons 'inherit-regexp
  215. (make-regexp* arg)
  216. result)))
  217. (option '("inherit") #t #f ;deprecated
  218. (lambda (opt name arg result)
  219. (warning (G_ "'--inherit' is deprecated, \
  220. use '--preserve' instead~%"))
  221. (alist-cons 'inherit-regexp
  222. (make-regexp* arg)
  223. result)))
  224. (option '("search-paths") #f #f
  225. (lambda (opt name arg result)
  226. (alist-cons 'search-paths #t result)))
  227. (option '(#\l "load") #t #f
  228. (lambda (opt name arg result)
  229. (alist-cons 'load
  230. (tag-package-arg result arg)
  231. result)))
  232. (option '(#\e "expression") #t #f
  233. (lambda (opt name arg result)
  234. (alist-cons 'expression
  235. (tag-package-arg result arg)
  236. result)))
  237. (option '(#\m "manifest") #t #f
  238. (lambda (opt name arg result)
  239. (alist-cons 'manifest
  240. arg
  241. result)))
  242. (option '("ad-hoc") #f #f
  243. (lambda (opt name arg result)
  244. (alist-cons 'ad-hoc? #t result)))
  245. (option '(#\n "dry-run") #f #f
  246. (lambda (opt name arg result)
  247. (alist-cons 'dry-run? #t result)))
  248. (option '(#\s "system") #t #f
  249. (lambda (opt name arg result)
  250. (alist-cons 'system arg
  251. (alist-delete 'system result eq?))))
  252. (option '(#\C "container") #f #f
  253. (lambda (opt name arg result)
  254. (alist-cons 'container? #t result)))
  255. (option '(#\N "network") #f #f
  256. (lambda (opt name arg result)
  257. (alist-cons 'network? #t result)))
  258. (option '(#\P "link-profile") #f #f
  259. (lambda (opt name arg result)
  260. (alist-cons 'link-profile? #t result)))
  261. (option '(#\p "profile") #t #f
  262. (lambda (opt name arg result)
  263. (alist-cons 'profile arg
  264. (alist-delete 'profile result eq?))))
  265. (option '(#\u "user") #t #f
  266. (lambda (opt name arg result)
  267. (alist-cons 'user arg
  268. (alist-delete 'user result eq?))))
  269. (option '("no-cwd") #f #f
  270. (lambda (opt name arg result)
  271. (alist-cons 'no-cwd? #t result)))
  272. (option '("share") #t #f
  273. (lambda (opt name arg result)
  274. (alist-cons 'file-system-mapping
  275. (specification->file-system-mapping arg #t)
  276. result)))
  277. (option '("expose") #t #f
  278. (lambda (opt name arg result)
  279. (alist-cons 'file-system-mapping
  280. (specification->file-system-mapping arg #f)
  281. result)))
  282. (option '(#\r "root") #t #f
  283. (lambda (opt name arg result)
  284. (alist-cons 'gc-root arg result)))
  285. (option '(#\v "verbosity") #t #f
  286. (lambda (opt name arg result)
  287. (let ((level (string->number* arg)))
  288. (alist-cons 'verbosity level
  289. (alist-delete 'verbosity result)))))
  290. (option '("bootstrap") #f #f
  291. (lambda (opt name arg result)
  292. (alist-cons 'bootstrap? #t result)))
  293. (append %transformation-options
  294. %standard-build-options)))
  295. (define (pick-all alist key)
  296. "Return a list of values in ALIST associated with KEY."
  297. (define same-key? (cut eq? key <>))
  298. (fold (lambda (pair memo)
  299. (match pair
  300. (((? same-key? k) . v)
  301. (cons v memo))
  302. (_ memo)))
  303. '() alist))
  304. (define (options/resolve-packages store opts)
  305. "Return OPTS with package specification strings replaced by manifest entries
  306. for the corresponding packages."
  307. (define (manifest-entry=? e1 e2)
  308. (and (eq? (manifest-entry-item e1) (manifest-entry-item e2))
  309. (string=? (manifest-entry-output e1)
  310. (manifest-entry-output e2))))
  311. (define transform
  312. (options->transformation opts))
  313. (define* (package->manifest-entry* package #:optional (output "out"))
  314. (package->manifest-entry (transform package) output))
  315. (define (packages->outputs packages mode)
  316. (match packages
  317. ((? package? package)
  318. (if (eq? mode 'ad-hoc-package)
  319. (list (package->manifest-entry* package))
  320. (package-environment-inputs package)))
  321. (((? package? package) (? string? output))
  322. (if (eq? mode 'ad-hoc-package)
  323. (list (package->manifest-entry* package output))
  324. (package-environment-inputs package)))
  325. ((lst ...)
  326. (append-map (cut packages->outputs <> mode) lst))))
  327. (manifest
  328. (delete-duplicates
  329. (append-map (match-lambda
  330. (('package 'ad-hoc-package (? string? spec))
  331. (let-values (((package output)
  332. (specification->package+output spec)))
  333. (list (package->manifest-entry* package output))))
  334. (('package 'package (? string? spec))
  335. (package-environment-inputs
  336. (transform (specification->package+output spec))))
  337. (('expression mode str)
  338. ;; Add all the outputs of the package STR evaluates to.
  339. (packages->outputs (read/eval str) mode))
  340. (('load mode file)
  341. ;; Add all the outputs of the package defined in FILE.
  342. (let ((module (make-user-module '())))
  343. (packages->outputs (load* file module) mode)))
  344. (('manifest . file)
  345. (let ((module (make-user-module '((guix profiles) (gnu)))))
  346. (manifest-entries (load* file module))))
  347. (_ '()))
  348. opts)
  349. manifest-entry=?)))
  350. (define (manifest->derivation manifest system bootstrap?)
  351. "Return the derivation for a profile of MANIFEST.
  352. BOOTSTRAP? specifies whether to use the bootstrap Guile to build the profile."
  353. (profile-derivation manifest
  354. #:system system
  355. ;; Packages can have conflicting inputs, or explicit
  356. ;; inputs that conflict with implicit inputs (e.g., gcc,
  357. ;; gzip, etc.). Thus, do not error out when we
  358. ;; encounter collision.
  359. #:allow-collisions? #t
  360. #:hooks (if bootstrap?
  361. '()
  362. %default-profile-hooks)
  363. #:locales? (not bootstrap?)))
  364. (define requisites* (store-lift requisites))
  365. (define (inputs->requisites inputs)
  366. "Convert INPUTS, a list of input tuples or store path strings, into a set of
  367. requisite store items i.e. the union closure of all the inputs."
  368. (define (input->requisites input)
  369. (requisites*
  370. (match input
  371. ((drv output)
  372. (list (derivation->output-path drv output)))
  373. ((drv)
  374. (list (derivation->output-path drv)))
  375. ((? direct-store-path? path)
  376. (list path)))))
  377. (mlet %store-monad ((reqs (mapm %store-monad
  378. input->requisites inputs)))
  379. (return (delete-duplicates (concatenate reqs)))))
  380. (define (status->exit-code status)
  381. "Compute the exit code made from STATUS, a value as returned by 'waitpid',
  382. and suitable for 'exit'."
  383. ;; See <bits/waitstatus.h>.
  384. (or (status:exit-val status)
  385. (logior #x80 (status:term-sig status))))
  386. (define exit/status (compose exit status->exit-code))
  387. (define primitive-exit/status (compose primitive-exit status->exit-code))
  388. (define* (launch-environment command profile manifest
  389. #:key pure? (white-list '()))
  390. "Run COMMAND in a new environment containing INPUTS, using the native search
  391. paths defined by the list PATHS. When PURE?, pre-existing environment
  392. variables are cleared before setting the new ones, except those matching the
  393. regexps in WHITE-LIST."
  394. ;; Properly handle SIGINT, so pressing C-c in an interactive terminal
  395. ;; application works.
  396. (sigaction SIGINT SIG_DFL)
  397. (create-environment profile manifest
  398. #:pure? pure? #:white-list white-list)
  399. (match command
  400. ((program . args)
  401. (apply execlp program program args))))
  402. (define* (launch-environment/fork command profile manifest
  403. #:key pure? (white-list '()))
  404. "Run COMMAND in a new process with an environment containing PROFILE, with
  405. the search paths specified by MANIFEST. When PURE?, pre-existing environment
  406. variables are cleared before setting the new ones, except those matching the
  407. regexps in WHITE-LIST."
  408. (match (primitive-fork)
  409. (0 (launch-environment command profile manifest
  410. #:pure? pure?
  411. #:white-list white-list))
  412. (pid (match (waitpid pid)
  413. ((_ . status) status)))))
  414. (define* (launch-environment/container #:key command bash user user-mappings
  415. profile manifest link-profile? network?
  416. map-cwd? (white-list '()))
  417. "Run COMMAND within a container that features the software in PROFILE.
  418. Environment variables are set according to the search paths of MANIFEST.
  419. The global shell is BASH, a file name for a GNU Bash binary in the
  420. store. When NETWORK?, access to the host system network is permitted.
  421. USER-MAPPINGS, a list of file system mappings, contains the user-specified
  422. host file systems to mount inside the container. If USER is not #f, each
  423. target of USER-MAPPINGS will be re-written relative to '/home/USER', and USER
  424. will be used for the passwd entry. LINK-PROFILE? creates a symbolic link from
  425. ~/.guix-profile to the environment profile.
  426. Preserve environment variables whose name matches the one of the regexps in
  427. WHILE-LIST."
  428. (define (optional-mapping->fs mapping)
  429. (and (file-exists? (file-system-mapping-source mapping))
  430. (file-system-mapping->bind-mount mapping)))
  431. (mlet %store-monad ((reqs (inputs->requisites
  432. (list (direct-store-path bash) profile))))
  433. (return
  434. (let* ((cwd (getcwd))
  435. (home (getenv "HOME"))
  436. (uid (if user 1000 (getuid)))
  437. (gid (if user 1000 (getgid)))
  438. (passwd (let ((pwd (getpwuid (getuid))))
  439. (password-entry
  440. (name (or user (passwd:name pwd)))
  441. (real-name (if user
  442. ""
  443. (passwd:gecos pwd)))
  444. (uid uid) (gid gid) (shell bash)
  445. (directory (if user
  446. (string-append "/home/" user)
  447. (passwd:dir pwd))))))
  448. (groups (list (group-entry (name "users") (gid gid))
  449. (group-entry (gid 65534) ;the overflow GID
  450. (name "overflow"))))
  451. (home-dir (password-entry-directory passwd))
  452. (logname (password-entry-name passwd))
  453. (environ (filter (match-lambda
  454. ((variable . value)
  455. (find (cut regexp-exec <> variable)
  456. white-list)))
  457. (get-environment-variables)))
  458. ;; Bind-mount all requisite store items, user-specified mappings,
  459. ;; /bin/sh, the current working directory, and possibly networking
  460. ;; configuration files within the container.
  461. (mappings
  462. (append
  463. (override-user-mappings
  464. user home
  465. (append user-mappings
  466. ;; Share current working directory, unless asked not to.
  467. (if map-cwd?
  468. (list (file-system-mapping
  469. (source cwd)
  470. (target cwd)
  471. (writable? #t)))
  472. '())))
  473. ;; Mappings for the union closure of all inputs.
  474. (map (lambda (dir)
  475. (file-system-mapping
  476. (source dir)
  477. (target dir)
  478. (writable? #f)))
  479. reqs)))
  480. (file-systems (append %container-file-systems
  481. (if network?
  482. (filter-map optional-mapping->fs
  483. %network-file-mappings)
  484. '())
  485. (map file-system-mapping->bind-mount
  486. mappings))))
  487. (exit/status
  488. (call-with-container file-systems
  489. (lambda ()
  490. ;; Setup global shell.
  491. (mkdir-p "/bin")
  492. (symlink bash "/bin/sh")
  493. ;; Set a reasonable default PS1.
  494. (setenv "PS1" "\\u@\\h \\w [env]\\$ ")
  495. ;; Setup directory for temporary files.
  496. (mkdir-p "/tmp")
  497. (for-each (lambda (var)
  498. (setenv var "/tmp"))
  499. ;; The same variables as in Nix's 'build.cc'.
  500. '("TMPDIR" "TEMPDIR" "TMP" "TEMP"))
  501. ;; Some programs expect USER and/or LOGNAME to be set.
  502. (setenv "LOGNAME" logname)
  503. (setenv "USER" logname)
  504. ;; Create a dummy home directory.
  505. (mkdir-p home-dir)
  506. (setenv "HOME" home-dir)
  507. ;; If requested, link $GUIX_ENVIRONMENT to $HOME/.guix-profile;
  508. ;; this allows programs expecting that path to continue working as
  509. ;; expected within a container.
  510. (when link-profile? (link-environment profile home-dir))
  511. ;; Create a dummy /etc/passwd to satisfy applications that demand
  512. ;; to read it, such as 'git clone' over SSH, a valid use-case when
  513. ;; sharing the host's network namespace.
  514. (mkdir-p "/etc")
  515. (write-passwd (list passwd))
  516. (write-group groups)
  517. (unless network?
  518. ;; When isolated from the network, provide a minimal /etc/hosts
  519. ;; to resolve "localhost".
  520. (call-with-output-file "/etc/hosts"
  521. (lambda (port)
  522. (display "127.0.0.1 localhost\n" port)))
  523. ;; Allow local AF_INET communications.
  524. (set-network-interface-up "lo"))
  525. ;; For convenience, start in the user's current working
  526. ;; directory or, if unmapped, the home directory.
  527. (chdir (if map-cwd?
  528. (override-user-dir user home cwd)
  529. home-dir))
  530. ;; Set environment variables that match WHITE-LIST.
  531. (for-each (match-lambda
  532. ((variable . value)
  533. (setenv variable value)))
  534. environ)
  535. (primitive-exit/status
  536. ;; A container's environment is already purified, so no need to
  537. ;; request it be purified again.
  538. (launch-environment command
  539. (if link-profile?
  540. (string-append home-dir "/.guix-profile")
  541. profile)
  542. manifest #:pure? #f)))
  543. #:guest-uid uid
  544. #:guest-gid gid
  545. #:namespaces (if network?
  546. (delq 'net %namespaces) ; share host network
  547. %namespaces)))))))
  548. (define (user-override-home user)
  549. "Return home directory for override user USER."
  550. (string-append "/home/" user))
  551. (define (override-user-mappings user home mappings)
  552. "If a username USER is provided, rewrite each HOME prefix in file system
  553. mappings MAPPINGS to a home directory determined by 'override-user-dir';
  554. otherwise, return MAPPINGS."
  555. (if (not user)
  556. mappings
  557. (map (lambda (mapping)
  558. (let ((target (file-system-mapping-target mapping)))
  559. (if (string-prefix? home target)
  560. (file-system-mapping
  561. (source (file-system-mapping-source mapping))
  562. (target (override-user-dir user home target))
  563. (writable? (file-system-mapping-writable? mapping)))
  564. mapping)))
  565. mappings)))
  566. (define (override-user-dir user home dir)
  567. "If username USER is provided, overwrite string prefix HOME in DIR with a
  568. directory determined by 'user-override-home'; otherwise, return DIR."
  569. (if (and user (string-prefix? home dir))
  570. (string-append (user-override-home user)
  571. (substring dir (string-length home)))
  572. dir))
  573. (define (link-environment profile home-dir)
  574. "Create a symbolic link from HOME-DIR/.guix-profile to PROFILE."
  575. (let ((profile-dir (string-append home-dir "/.guix-profile")))
  576. (catch 'system-error
  577. (lambda ()
  578. (symlink profile profile-dir))
  579. (lambda args
  580. (if (= EEXIST (system-error-errno args))
  581. (leave (G_ "cannot link profile: '~a' already exists within container~%")
  582. profile-dir)
  583. (apply throw args))))))
  584. (define (environment-bash container? bootstrap? system)
  585. "Return a monadic value in the store monad for the version of GNU Bash
  586. needed in the environment for SYSTEM, if any. If CONTAINER? is #f, return #f.
  587. If CONTAINER? and BOOTSTRAP?, return the store path for the bootstrap Bash.
  588. Otherwise, return the derivation for the Bash package."
  589. (with-monad %store-monad
  590. (cond
  591. ((and container? (not bootstrap?))
  592. (package->derivation bash))
  593. ;; Use the bootstrap Bash instead.
  594. ((and container? bootstrap?)
  595. (lower-object (bootstrap-executable "bash" system)))
  596. (else
  597. (return #f)))))
  598. (define (parse-args args)
  599. "Parse the list of command line arguments ARGS."
  600. (define (handle-argument arg result)
  601. (alist-cons 'package (tag-package-arg result arg) result))
  602. ;; The '--' token is used to separate the command to run from the rest of
  603. ;; the operands.
  604. (let-values (((args command) (break (cut string=? "--" <>) args)))
  605. (let ((opts (parse-command-line args %options (list %default-options)
  606. #:argument-handler handle-argument)))
  607. (match command
  608. (() opts)
  609. (("--") opts)
  610. (("--" command ...) (alist-cons 'exec command opts))))))
  611. (define (assert-container-features)
  612. "Check if containers can be created and exit with an informative error
  613. message if any test fails."
  614. (unless (user-namespace-supported?)
  615. (report-error (G_ "cannot create container: user namespaces unavailable\n"))
  616. (leave (G_ "is your kernel version < 3.10?\n")))
  617. (unless (unprivileged-user-namespace-supported?)
  618. (report-error (G_ "cannot create container: unprivileged user cannot create user namespaces\n"))
  619. (leave (G_ "please set /proc/sys/kernel/unprivileged_userns_clone to \"1\"\n")))
  620. (unless (setgroups-supported?)
  621. (report-error (G_ "cannot create container: /proc/self/setgroups does not exist\n"))
  622. (leave (G_ "is your kernel version < 3.19?\n"))))
  623. (define (register-gc-root target root)
  624. "Make ROOT an indirect root to TARGET. This is procedure is idempotent."
  625. (let* ((root (if (string-prefix? "/" root)
  626. root
  627. (string-append (canonicalize-path (dirname root))
  628. "/" (basename root)))))
  629. (catch 'system-error
  630. (lambda ()
  631. (symlink target root)
  632. ((store-lift add-indirect-root) root))
  633. (lambda args
  634. (if (and (= EEXIST (system-error-errno args))
  635. (equal? (false-if-exception (readlink root)) target))
  636. (with-monad %store-monad
  637. (return #t))
  638. (apply throw args))))))
  639. ;;;
  640. ;;; Entry point.
  641. ;;;
  642. (define-command (guix-environment . args)
  643. (category development)
  644. (synopsis "spawn one-off software environments")
  645. (with-error-handling
  646. (let* ((opts (parse-args args))
  647. (pure? (assoc-ref opts 'pure))
  648. (container? (assoc-ref opts 'container?))
  649. (link-prof? (assoc-ref opts 'link-profile?))
  650. (network? (assoc-ref opts 'network?))
  651. (no-cwd? (assoc-ref opts 'no-cwd?))
  652. (user (assoc-ref opts 'user))
  653. (bootstrap? (assoc-ref opts 'bootstrap?))
  654. (system (assoc-ref opts 'system))
  655. (profile (assoc-ref opts 'profile))
  656. (command (or (assoc-ref opts 'exec)
  657. ;; Spawn a shell if the user didn't specify
  658. ;; anything in particular.
  659. (if container?
  660. ;; The user's shell is likely not available
  661. ;; within the container.
  662. '("/bin/sh")
  663. (list %default-shell))))
  664. (mappings (pick-all opts 'file-system-mapping))
  665. (white-list (pick-all opts 'inherit-regexp)))
  666. (when container? (assert-container-features))
  667. (when (and (not container?) link-prof?)
  668. (leave (G_ "'--link-profile' cannot be used without '--container'~%")))
  669. (when (and (not container?) user)
  670. (leave (G_ "'--user' cannot be used without '--container'~%")))
  671. (when (and (not container?) no-cwd?)
  672. (leave (G_ "--no-cwd cannot be used without --container~%")))
  673. (with-store store
  674. (with-build-handler (build-notifier #:use-substitutes?
  675. (assoc-ref opts 'substitutes?)
  676. #:verbosity
  677. (assoc-ref opts 'verbosity)
  678. #:dry-run?
  679. (assoc-ref opts 'dry-run?))
  680. (with-status-verbosity (assoc-ref opts 'verbosity)
  681. (define manifest-from-opts
  682. (options/resolve-packages store opts))
  683. (define manifest
  684. (if profile
  685. (profile-manifest profile)
  686. manifest-from-opts))
  687. (when (and profile
  688. (> (length (manifest-entries manifest-from-opts)) 0))
  689. (leave (G_ "'--profile' cannot be used with package options~%")))
  690. (set-build-options-from-command-line store opts)
  691. ;; Use the bootstrap Guile when requested.
  692. (parameterize ((%graft? (assoc-ref opts 'graft?))
  693. (%guile-for-build
  694. (package-derivation
  695. store
  696. (if bootstrap?
  697. %bootstrap-guile
  698. (default-guile)))))
  699. (run-with-store store
  700. ;; Containers need a Bourne shell at /bin/sh.
  701. (mlet* %store-monad ((bash (environment-bash container?
  702. bootstrap?
  703. system))
  704. (prof-drv (manifest->derivation
  705. manifest system bootstrap?))
  706. (profile -> (if profile
  707. (readlink* profile)
  708. (derivation->output-path prof-drv)))
  709. (gc-root -> (assoc-ref opts 'gc-root)))
  710. ;; First build the inputs. This is necessary even for
  711. ;; --search-paths. Additionally, we might need to build bash for
  712. ;; a container.
  713. (mbegin %store-monad
  714. (built-derivations (if (derivation? bash)
  715. (list prof-drv bash)
  716. (list prof-drv)))
  717. (mwhen gc-root
  718. (register-gc-root profile gc-root))
  719. (cond
  720. ((assoc-ref opts 'search-paths)
  721. (show-search-paths profile manifest #:pure? pure?)
  722. (return #t))
  723. (container?
  724. (let ((bash-binary
  725. (if bootstrap?
  726. (derivation->output-path bash)
  727. (string-append (derivation->output-path bash)
  728. "/bin/sh"))))
  729. (launch-environment/container #:command command
  730. #:bash bash-binary
  731. #:user user
  732. #:user-mappings mappings
  733. #:profile profile
  734. #:manifest manifest
  735. #:white-list white-list
  736. #:link-profile? link-prof?
  737. #:network? network?
  738. #:map-cwd? (not no-cwd?))))
  739. (else
  740. (return
  741. (exit/status
  742. (launch-environment/fork command profile manifest
  743. #:white-list white-list
  744. #:pure? pure?)))))))))))))))