user.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com>
  3. ;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2019 Tobias Geerinckx-Rice <me@tobias.gr>
  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 (gnu installer newt user)
  21. #:use-module (gnu installer user)
  22. #:use-module (gnu installer newt page)
  23. #:use-module (gnu installer newt utils)
  24. #:use-module (gnu installer utils)
  25. #:use-module (guix i18n)
  26. #:use-module (newt)
  27. #:use-module (ice-9 match)
  28. #:use-module (ice-9 receive)
  29. #:use-module (srfi srfi-1)
  30. #:use-module (srfi srfi-26)
  31. #:use-module (srfi srfi-34)
  32. #:use-module (srfi srfi-35)
  33. #:export (run-user-page))
  34. (define* (run-user-add-page #:key (name "") (real-name "")
  35. (home-directory ""))
  36. "Run a form to enter the user name, home directory, and password. Use NAME,
  37. REAL-NAME, and HOME-DIRECTORY as the initial values in the form."
  38. (define (pad-label label)
  39. (string-pad-right label 25))
  40. (define (root-account? name)
  41. (string=? name "root"))
  42. (let* ((label-name
  43. (make-label -1 -1 (pad-label (G_ "Name"))))
  44. (label-real-name
  45. (make-label -1 -1 (pad-label (G_ "Real name"))))
  46. (label-home-directory
  47. (make-label -1 -1 (pad-label (G_ "Home directory"))))
  48. (label-password
  49. (make-label -1 -1 (pad-label (G_ "Password"))))
  50. (entry-width 35)
  51. (entry-name (make-entry -1 -1 entry-width
  52. #:initial-value name))
  53. (entry-real-name (make-entry -1 -1 entry-width
  54. #:initial-value real-name))
  55. (entry-home-directory (make-entry -1 -1 entry-width
  56. #:initial-value home-directory))
  57. (password-visible-cb
  58. (make-checkbox -1 -1 (G_ "Show") #\space "x "))
  59. (entry-password (make-entry -1 -1 entry-width
  60. #:flags (logior FLAG-PASSWORD
  61. FLAG-SCROLL)))
  62. (entry-grid (make-grid 3 5))
  63. (button-grid (make-grid 1 1))
  64. (ok-button (make-button -1 -1 (G_ "OK")))
  65. (grid (make-grid 1 2))
  66. (title (G_ "User creation"))
  67. (set-entry-grid-field
  68. (cut set-grid-field entry-grid <> <> GRID-ELEMENT-COMPONENT <>))
  69. (form (make-form)))
  70. (set-entry-grid-field 0 0 label-name)
  71. (set-entry-grid-field 1 0 entry-name)
  72. (set-entry-grid-field 0 1 label-real-name)
  73. (set-entry-grid-field 1 1 entry-real-name)
  74. (set-entry-grid-field 0 2 label-home-directory)
  75. (set-entry-grid-field 1 2 entry-home-directory)
  76. (set-entry-grid-field 0 3 label-password)
  77. (set-entry-grid-field 1 3 entry-password)
  78. (set-grid-field entry-grid
  79. 2 3
  80. GRID-ELEMENT-COMPONENT
  81. password-visible-cb
  82. #:pad-left 1)
  83. (set-grid-field button-grid 0 0 GRID-ELEMENT-COMPONENT ok-button)
  84. (add-component-callback
  85. entry-name
  86. (lambda ()
  87. (set-entry-text entry-home-directory
  88. (string-append "/home/" (entry-value entry-name)))
  89. (when (string-null? (entry-value entry-real-name))
  90. (set-entry-text entry-real-name
  91. (string-titlecase (entry-value entry-name))))))
  92. (add-component-callback
  93. password-visible-cb
  94. (lambda ()
  95. (set-entry-flags entry-password
  96. FLAG-PASSWORD
  97. FLAG-ROLE-TOGGLE)))
  98. (add-components-to-form form
  99. label-name label-real-name
  100. label-home-directory label-password
  101. entry-name entry-real-name
  102. entry-home-directory entry-password
  103. password-visible-cb
  104. ok-button)
  105. (make-wrapped-grid-window (vertically-stacked-grid
  106. GRID-ELEMENT-SUBGRID entry-grid
  107. GRID-ELEMENT-SUBGRID button-grid)
  108. title)
  109. (let ((error-empty-field-page
  110. (lambda ()
  111. (run-error-page (G_ "Empty inputs are not allowed.")
  112. (G_ "Empty input"))))
  113. (error-root-page
  114. (lambda ()
  115. (run-error-page (G_ "Root account is automatically created.")
  116. (G_ "Root account")))))
  117. (receive (exit-reason argument)
  118. (run-form form)
  119. (dynamic-wind
  120. (const #t)
  121. (lambda ()
  122. (when (eq? exit-reason 'exit-component)
  123. (cond
  124. ((components=? argument ok-button)
  125. (let ((name (entry-value entry-name))
  126. (real-name (entry-value entry-real-name))
  127. (home-directory (entry-value entry-home-directory))
  128. (password (entry-value entry-password)))
  129. (cond
  130. ;; Empty field.
  131. ((or (string=? name "")
  132. (string=? home-directory ""))
  133. (error-empty-field-page)
  134. (run-user-add-page))
  135. ;; Reject root account.
  136. ((root-account? name)
  137. (error-root-page)
  138. (run-user-add-page))
  139. (else
  140. (let ((password (confirm-password password)))
  141. (if password
  142. (user
  143. (name name)
  144. (real-name real-name)
  145. (home-directory home-directory)
  146. (password (make-secret password)))
  147. (run-user-add-page #:name name
  148. #:real-name real-name
  149. #:home-directory
  150. home-directory))))))))))
  151. (lambda ()
  152. (destroy-form-and-pop form)))))))
  153. (define* (confirm-password password #:optional (try-again (const #f)))
  154. "Ask the user to confirm PASSWORD, a possibly empty string. Call TRY-AGAIN,
  155. a thunk, if the confirmation doesn't match PASSWORD, and return its result."
  156. (define confirmation
  157. (run-input-page (G_ "Please confirm the password.")
  158. (G_ "Password confirmation required")
  159. #:allow-empty-input? #t
  160. #:input-visibility-checkbox? #t))
  161. (if (string=? password confirmation)
  162. password
  163. (begin
  164. (run-error-page
  165. (G_ "Password mismatch, please try again.")
  166. (G_ "Password error"))
  167. (try-again))))
  168. (define (run-root-password-page)
  169. (define password
  170. ;; TRANSLATORS: Leave "root" untranslated: it refers to the name of the
  171. ;; system administrator account.
  172. (run-input-page (G_ "Please choose a password for the system \
  173. administrator (\"root\").")
  174. (G_ "System administrator password")
  175. #:input-visibility-checkbox? #t))
  176. (confirm-password password run-root-password-page))
  177. (define (run-user-page)
  178. (define (run users)
  179. (let* ((listbox (make-listbox
  180. -1 -1 10
  181. (logior FLAG-SCROLL FLAG-BORDER)))
  182. (info-textbox
  183. (make-reflowed-textbox
  184. -1 -1
  185. (G_ "Please add at least one user to system\
  186. using the 'Add' button.")
  187. 40 #:flags FLAG-BORDER))
  188. (add-button (make-compact-button -1 -1 (G_ "Add")))
  189. (del-button (make-compact-button -1 -1 (G_ "Delete")))
  190. (listbox-button-grid
  191. (apply
  192. vertically-stacked-grid
  193. GRID-ELEMENT-COMPONENT add-button
  194. `(,@(if (null? users)
  195. '()
  196. (list GRID-ELEMENT-COMPONENT del-button)))))
  197. (ok-button (make-button -1 -1 (G_ "OK")))
  198. (exit-button (make-button -1 -1 (G_ "Exit")))
  199. (title (G_ "User creation"))
  200. (grid
  201. (vertically-stacked-grid
  202. GRID-ELEMENT-COMPONENT info-textbox
  203. GRID-ELEMENT-SUBGRID (horizontal-stacked-grid
  204. GRID-ELEMENT-COMPONENT listbox
  205. GRID-ELEMENT-SUBGRID listbox-button-grid)
  206. GRID-ELEMENT-SUBGRID (horizontal-stacked-grid
  207. GRID-ELEMENT-COMPONENT ok-button
  208. GRID-ELEMENT-COMPONENT exit-button)))
  209. (sorted-users (sort users (lambda (a b)
  210. (string<= (user-name a)
  211. (user-name b)))))
  212. (listbox-elements
  213. (map
  214. (lambda (user)
  215. `((key . ,(append-entry-to-listbox listbox
  216. (user-name user)))
  217. (user . ,user)))
  218. sorted-users))
  219. (form (make-form)))
  220. (add-form-to-grid grid form #t)
  221. (make-wrapped-grid-window grid title)
  222. (if (null? users)
  223. (set-current-component form add-button)
  224. (set-current-component form ok-button))
  225. (receive (exit-reason argument)
  226. (run-form-with-clients form '(add-users))
  227. (dynamic-wind
  228. (const #t)
  229. (lambda ()
  230. (match exit-reason
  231. ('exit-component
  232. (cond
  233. ((components=? argument add-button)
  234. (run (cons (run-user-add-page) users)))
  235. ((components=? argument del-button)
  236. (let* ((current-user-key (current-listbox-entry listbox))
  237. (users
  238. (map (cut assoc-ref <> 'user)
  239. (remove (lambda (element)
  240. (equal? (assoc-ref element 'key)
  241. current-user-key))
  242. listbox-elements))))
  243. (run users)))
  244. ((components=? argument ok-button)
  245. (when (null? users)
  246. (run-error-page (G_ "Please create at least one user.")
  247. (G_ "No user"))
  248. (run users))
  249. (reverse users))
  250. ((components=? argument exit-button)
  251. (abort-to-prompt 'installer-step 'abort))))
  252. ('exit-fd-ready
  253. ;; Read the complete user list at once.
  254. (match argument
  255. ((('user ('name names) ('real-name real-names)
  256. ('home-directory homes) ('password passwords))
  257. ..1)
  258. (map (lambda (name real-name home password)
  259. (user (name name) (real-name real-name)
  260. (home-directory home)
  261. (password (make-secret password))))
  262. names real-names homes passwords))))))
  263. (lambda ()
  264. (destroy-form-and-pop form))))))
  265. ;; Add a "root" user simply to convey the root password.
  266. (cons (user (name "root")
  267. (home-directory "/root")
  268. (password (make-secret (run-root-password-page))))
  269. (run '())))