init.scm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. ;;; init.scm --- Shepherd init file
  2. ;; Copyright © 2015–2017 Alex Kost
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;;
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; In the following code, 'service' may mean a service object, a service
  17. ;; name (symbol) or even (for more confusion) a base-name of a "display
  18. ;; service".
  19. ;; "Display service" is a service that should be started on a particular
  20. ;; display (X server and what should be run on it). Its name (symbol
  21. ;; for '#:provides' service slot) consists of 2 parts: base-name and
  22. ;; display; for example: 'x:0', 'xterm:1', etc.
  23. (use-modules
  24. (ice-9 popen)
  25. (ice-9 rdelim)
  26. (ice-9 regex)
  27. (srfi srfi-1)
  28. (srfi srfi-26)
  29. (al display)
  30. (al files)
  31. (al plists)
  32. (al places)
  33. (al processes)
  34. (al utils))
  35. ;; (use-modules (shepherd service) (oop goops))
  36. (define %dbus-address
  37. (format #f "unix:path=/tmp/dbus-~a" (getuid)))
  38. (define %ssh-socket #f) ; set by 'run-gpg-agent'
  39. ;;; Miscellaneous auxiliary code
  40. (define (->symbol string-or-symbol)
  41. (if (symbol? string-or-symbol)
  42. string-or-symbol
  43. (string->symbol string-or-symbol)))
  44. (define (display->vt display)
  45. "Convert DISPLAY string into a string with VT number.
  46. Use 'vt7' for display ':0', vt8 for ':1', etc."
  47. (let ((display-num (display-string->number display)))
  48. (string-append "vt" (number->string (+ 7 display-num)))))
  49. (define* (environ* #:optional display)
  50. "Return environment with some additional things.
  51. If DISPLAY is specified, add it to the environment.
  52. DISPLAY can be either a string or a procedure returning a string."
  53. (environment-excursion
  54. (lambda ()
  55. (setenv "DBUS_SESSION_BUS_ADDRESS" %dbus-address)
  56. (when %ssh-socket
  57. (setenv "SSH_AUTH_SOCK" %ssh-socket))
  58. (when display
  59. (setenv "DISPLAY"
  60. (if (string? display) display (display)))))
  61. environ))
  62. ;; Override `make-system-constructor' to make it similar to
  63. ;; `make-forkexec-constructor', i.e. make it support a list of strings
  64. ;; for `system*' procedure as a COMMAND (in original
  65. ;; `make-system-constructor', COMMAND is a "rest" argument and it is a
  66. ;; list of strings for `system' procedure).
  67. (define (run-command command)
  68. (zero? (status:exit-val (apply system* command))))
  69. (define (make-system-constructor command)
  70. (lambda _
  71. (run-command command)))
  72. (define (make-system-destructor command)
  73. (lambda _
  74. (not (run-command command))))
  75. (define* (make-system-constructor-with-env command #:key display)
  76. (lambda _
  77. (with-environment-excursion (environ* display)
  78. (run-command command))))
  79. (define* (make-forkexec-constructor-with-env command #:key display)
  80. ;; Calling 'make-forkexec-constructor' directly has a downside: it
  81. ;; calculates environment immediately (i.e., when a service is created
  82. ;; (i.e., when this config file is loaded)). It is better to delay
  83. ;; calculating environment until the service will be started. This
  84. ;; can be done with a simple lambda wrapper.
  85. (lambda args
  86. (apply (make-forkexec-constructor
  87. command
  88. #:environment-variables (environ* display))
  89. args)))
  90. (define (available-display)
  91. ;; Check only the first 3 displays. If none is used, it is very
  92. ;; unlikely that there is an available X server on a higher DISPLAY.
  93. (or (first-used-display 3)
  94. (first-unused-display)))
  95. ;;; Auxiliary code for services
  96. (define* (starter #:key (base-services '()) (default-services '())
  97. user-transformer final-transformer)
  98. "Return procedure for starting services.
  99. Services are started in a direct order; at first BASE-SERVICES and then
  100. USER-SERVICES or DEFAULT-SERVICES.
  101. The procedure returns a list of all services if they have been started
  102. successfully. Otherwise it returns #f."
  103. (define* (transform services #:optional transformer
  104. (fallback '()))
  105. (if transformer
  106. (map transformer services)
  107. fallback))
  108. (lambda user-services
  109. (let* ((user-services (transform user-services user-transformer))
  110. (services (append base-services
  111. (if (null? user-services)
  112. default-services
  113. user-services)))
  114. (services (transform services final-transformer services)))
  115. (and (every start services)
  116. services))))
  117. (define (stop-services services . _)
  118. "Stop SERVICES in a reverse order and return #f."
  119. (for-each stop (reverse services))
  120. #f)
  121. (define (make-service . args)
  122. (apply make <service> args))
  123. (define* (make-target #:key (maker make-service)
  124. (services '())
  125. (start (starter #:base-services services))
  126. (stop stop-services)
  127. #:allow-other-keys
  128. #:rest args)
  129. "Return service to start/stop a list of SERVICES.
  130. SERVICES are being started in the direct order and stopped in the
  131. reverse order."
  132. (apply maker
  133. #:start start
  134. #:stop stop
  135. args))
  136. (define (display-service-name display base-name)
  137. "Return name of a 'display service' BASE-NAME for DISPLAY."
  138. (symbol-append base-name (string->symbol display)))
  139. (define (display-services-names display base-names)
  140. "Return list of 'display services' names by BASE-NAMES and DISPLAY."
  141. (map (cut display-service-name display <>)
  142. base-names))
  143. (define (display-service-description display base-description)
  144. "Return description of a 'display service' by BASE-DESCRIPTION and DISPLAY."
  145. (format #f "~a (DISPLAY=~a)" base-description display))
  146. ;; `make-display-service' procedure uses `plist-new', because ARGS (the
  147. ;; #:rest argument) contains all keyword arguments (e.g., #:docstring),
  148. ;; that needs to be shadowed, otherwise `make-service' will be called
  149. ;; with 2 #:docstring arguments and may (and surely will) take the wrong
  150. ;; one. Illustration of the problem:
  151. ;;
  152. ;; (define* (p1 #:key str . args)
  153. ;; (values str args))
  154. ;; (define* (p2 #:key str . args)
  155. ;; (apply p1 #:str (string-append str "-bar") args))
  156. ;;
  157. ;; (p2 #:str "foo") => "foo"
  158. ;; => (#:str "foo-bar" #:str "foo")
  159. ;;
  160. ;; The same takes place for `make-display-target'.
  161. (define* (make-display-service #:key display
  162. (docstring "Unknown")
  163. (provides '())
  164. (requires '())
  165. #:allow-other-keys
  166. #:rest args)
  167. (apply make-service
  168. (plist-new args
  169. #:docstring (display-service-description display docstring)
  170. #:provides (display-services-names display provides)
  171. #:requires (display-services-names display requires))))
  172. (define* (make-display-target #:key display
  173. (services '())
  174. #:allow-other-keys
  175. #:rest args)
  176. (apply make-target
  177. (plist-new args
  178. #:maker make-display-service
  179. #:services (display-services-names display services))))
  180. (define (make-simple-display-service display . args)
  181. (apply make-display-service
  182. #:display display
  183. ;; I changed my mind: do not require 'x' because if there is
  184. ;; some running X server not managed by shepherd, I still want to be
  185. ;; able to 'start <something>:<N>' there.
  186. ;; #:requires '(x)
  187. args))
  188. (define* (make-simple-forkexec-display-service display #:key command
  189. #:allow-other-keys
  190. #:rest args)
  191. (apply make-simple-display-service display
  192. #:start (make-forkexec-constructor-with-env
  193. command
  194. #:display display)
  195. #:stop (make-kill-destructor)
  196. args))
  197. (define* (make-simple-system-display-service display #:key command
  198. #:allow-other-keys
  199. #:rest args)
  200. (apply make-simple-display-service display
  201. #:start (make-system-constructor-with-env
  202. command
  203. #:display display)
  204. args))
  205. ;;; Daemons
  206. (define dbus
  207. (make-service
  208. #:docstring "D-Bus Session Daemon"
  209. #:provides '(dbus)
  210. #:start (make-forkexec-constructor-with-env
  211. (list "dbus-daemon" "--session" "--nofork"
  212. "--address" %dbus-address)
  213. ;; Start dbus with $DISPLAY, as dbus may start services
  214. ;; (e.g., notification daemon) that need this environment.
  215. #:display available-display)
  216. #:stop (make-kill-destructor)))
  217. (define guile-daemon
  218. (make-service
  219. #:docstring "Guile Daemon"
  220. #:provides '(guile-daemon)
  221. #:start (make-forkexec-constructor-with-env
  222. '("guile-daemon")
  223. #:display available-display)
  224. #:stop (make-kill-destructor)
  225. #:actions
  226. (make-actions
  227. (lirc
  228. "Connect (or reconnect) LIRC daemon client."
  229. (make-system-constructor
  230. '("gdpipe" "(lirc-client-reconnect)"))))))
  231. (define (run-gpg-agent)
  232. "Run gpg-agent as daemon and set '%ssh-socket' according to its output.
  233. Return exit status of the gpg-agent."
  234. (let* ((pinentry (guix-user-profile-file "bin/pinentry"))
  235. (gpg-command `("gpg-agent" "--daemon"
  236. ,@(if (file-exists? pinentry)
  237. (list "--pinentry-program" pinentry)
  238. '())))
  239. (port (apply open-pipe* OPEN_READ gpg-command))
  240. (output (read-string port))
  241. (env-match (string-match "\\`SSH_AUTH_SOCK=([^;]*)" output)))
  242. (when env-match
  243. (set! %ssh-socket (match:substring env-match 1)))
  244. ;; XXX gpg-agent daemonizes too quickly, so we get a system error
  245. ;; (waitpid: No child processes). Just ignore it and return 0.
  246. (catch #t
  247. (lambda () (close-pipe port))
  248. (const 0))))
  249. (define gpg-agent
  250. (make-service
  251. #:docstring "GPG Agent"
  252. #:provides '(gpg-agent)
  253. #:start (lambda _
  254. (zero? (status:exit-val (run-gpg-agent))))
  255. #:stop (make-system-destructor
  256. '("gpg-connect-agent" "killagent" "/bye"))))
  257. (define (postgres-command . args)
  258. "Return 'pg_ctl' command to control PostgreSQL server."
  259. (cons* "pg_ctl"
  260. (string-append "--pgdata=" (home-file ".postgresql/data"))
  261. (string-append "--log=" (home-file ".postgresql/log/pg_ctl.log"))
  262. args))
  263. (define postgres
  264. (make-service
  265. #:docstring "PostgreSQL server"
  266. #:provides '(postgres postgresql)
  267. #:start (make-system-constructor (postgres-command "start"))
  268. #:stop (make-system-constructor (postgres-command "stop"))
  269. #:actions
  270. (make-actions
  271. (reload "Reload configuration files."
  272. (make-system-constructor (postgres-command "reload"))))))
  273. (define irexec
  274. (make-service
  275. #:docstring "IR Exec Daemon"
  276. #:provides '(irexec)
  277. #:start (make-forkexec-constructor
  278. '("irexec"))
  279. #:stop (make-kill-destructor)))
  280. (define emacs-daemon
  281. (make-service
  282. #:docstring "Emacs daemon"
  283. #:provides '(emacsd)
  284. #:start
  285. (make-system-constructor-with-env
  286. '("emacs" "--no-site-file" "--daemon"))
  287. #:stop
  288. (make-system-destructor
  289. '("emacsclient" "--eval" "(let (kill-emacs-hook) (kill-emacs))"))))
  290. (define daemons
  291. (list dbus gpg-agent irexec postgres guile-daemon emacs-daemon))
  292. ;;; Misc services
  293. (define daemons-target
  294. (make-target
  295. #:docstring "Daemons target.
  296. Start daemons and additional specified services."
  297. #:provides '(daemons)
  298. #:start
  299. (starter #:base-services '(dbus guile-daemon gpg-agent)
  300. #:user-transformer ->symbol)))
  301. (define eval-service
  302. (let ((module (current-module)))
  303. (make-service
  304. #:docstring "Evaluate specified scheme expressions.
  305. This service is intended to perform 'batch' starts/stops, e.g.:
  306. deco start eval \"(for-each stop daemons)\"
  307. deco start eval \"(stop 'wm:0)\" \"(start 'stumpwm:0)\"
  308. This service always fails, so that it is always ready to be started
  309. again."
  310. #:provides '(eval)
  311. #:start (lambda strings
  312. (for-each (cut eval-string <> module)
  313. strings)
  314. #f))))
  315. (define amixer-service
  316. (let ((aset (lambda args
  317. (run-command (append '("amixer" "set" "-q")
  318. args)))))
  319. (make-service
  320. #:docstring "Set sound parameters."
  321. #:provides '(amixer)
  322. #:start (lambda _
  323. (and (aset "Master" "50%")
  324. ;; (aset "PCM" "80%")
  325. (aset "Line" "10%" "mute"))))))
  326. (define misc-services
  327. (list daemons-target eval-service amixer-service))
  328. ;;; Display services
  329. (define (sudo-command . args)
  330. "Return a sudo command for running command indicated by ARGS."
  331. (cons* "sudo" "--non-interactive" "--" args))
  332. (define* (xorg-command #:key (display ":0") (vt "vt7"))
  333. (define (has-fonts.dir? directory)
  334. "Return #t if DIRECTORY exists and has 'fonts.dir' file."
  335. (file-exists? (string-append directory "/fonts.dir")))
  336. (define (subdirs directory)
  337. "Return a list of sub-directories of DIRECTORY."
  338. (if (file-exists? directory)
  339. (find-files directory ".")
  340. '()))
  341. (let* ((config-dir (config-file "X/xorg.conf"))
  342. (module-dir (let ((modules "lib/xorg/modules"))
  343. (first-existing-file
  344. (guix-system-profile-file modules)
  345. (guix-user-profile-file modules))))
  346. (font-dirs
  347. (filter has-fonts.dir?
  348. (append
  349. (list (home-file ".local/share/fonts"))
  350. (list (guix-profile-file "fonts" "share/fonts/truetype"))
  351. (subdirs (guix-profile-file "fonts" "share/fonts/X11"))
  352. (subdirs "/usr/share/fonts")))))
  353. `("Xdaemon" ,display ,vt
  354. "-nolisten" "tcp" "-logverbose" "-noreset"
  355. "-configdir" ,config-dir
  356. ,@(if (null? font-dirs)
  357. '()
  358. (list "-fp" (apply comma-separated font-dirs)))
  359. ,@(if (not module-dir)
  360. '()
  361. (list "-modulepath" module-dir)))))
  362. (define* (xorg-service #:key display vt)
  363. (make-display-service
  364. #:display display
  365. #:docstring "Xorg server"
  366. #:provides '(x)
  367. #:start (make-system-constructor
  368. (apply sudo-command (xorg-command #:display display
  369. #:vt vt)))
  370. #:stop (make-system-destructor
  371. (sudo-command "Xkill" display))))
  372. (define (xorg-service* display)
  373. (xorg-service #:display display
  374. #:vt (display->vt display)))
  375. (define (xset-service display)
  376. (make-simple-system-display-service display
  377. #:docstring "Xset"
  378. #:provides '(xset)
  379. #:command (list "xset" "r" "rate" "193" "43" "b" "off")))
  380. (define (xsetroot-service display)
  381. (make-simple-system-display-service display
  382. #:docstring "Xsetroot"
  383. #:provides '(xsetroot)
  384. #:command (list "xsetroot" "-solid" "gray25"
  385. "-xcf" (home-file ".icons/default/cursors/cell") "1")))
  386. (define (setxkbmap-service display)
  387. (make-simple-system-display-service display
  388. #:docstring "setxkbmap"
  389. #:provides '(setxkbmap)
  390. #:command '("setxkbmap" "us,ru,us" "dvorak,,")))
  391. (define (xmodmap-service display)
  392. (make-simple-system-display-service display
  393. #:docstring "Xmodmap"
  394. #:provides '(xmodmap)
  395. #:command (list "xmodmap" (config-file "X/Xmodmap"))))
  396. (define (xrdb-service display)
  397. (make-simple-system-display-service display
  398. #:docstring "Xrdb (X resource database)"
  399. #:provides '(xrdb)
  400. #:command (list "xrdb" "-merge" (config-file "X/Xresources"))))
  401. (define (unclutter-service display)
  402. (make-simple-forkexec-display-service display
  403. #:docstring "Unclutter (hide idle cursor)"
  404. #:provides '(unclutter)
  405. #:command '("unclutter" "-root" "-jitter" "5")))
  406. (define (openbox-service display)
  407. (make-simple-forkexec-display-service display
  408. #:docstring "Openbox"
  409. #:provides '(openbox wm)
  410. #:command '("openbox")
  411. #:actions
  412. (make-actions
  413. (reload
  414. "Reload configuration file."
  415. (make-system-constructor-with-env
  416. '("openbox" "--reconfigure")
  417. #:display display)))))
  418. (define (stumpwm-service display)
  419. (make-simple-forkexec-display-service display
  420. #:docstring "Stumpwm"
  421. #:provides '(stumpwm wm)
  422. #:command '("stumpwm")))
  423. (define (xterm-service display)
  424. (make-simple-forkexec-display-service display
  425. #:docstring "Xterm"
  426. #:provides '(xterm)
  427. #:command '("xterm")))
  428. (define (emacs-service display)
  429. (make-simple-forkexec-display-service display
  430. #:docstring "Emacs"
  431. #:provides '(emacs)
  432. #:command '("emacs" "--no-site-file")))
  433. (define (conkeror-service display)
  434. (make-simple-forkexec-display-service display
  435. #:docstring "Conkeror"
  436. #:provides '(conkeror)
  437. #:command '("conkeror")))
  438. (define (xsettings-target display)
  439. (make-display-target
  440. #:display display
  441. #:services '(xset xsetroot setxkbmap xmodmap xrdb)
  442. #:docstring "Xsettings target"
  443. #:provides '(xsettings)))
  444. (define (gui-target display)
  445. "Target service to start GUI session on DISPLAY."
  446. (make-display-target
  447. #:display display
  448. #:docstring "GUI target.
  449. Start X server with some settings and additional services or 'xterm' if
  450. none are specified."
  451. #:provides '(gui)
  452. #:start
  453. (starter #:base-services '(x xsettings)
  454. #:default-services '(xterm)
  455. #:user-transformer ->symbol
  456. #:final-transformer (cut display-service-name display <>))))
  457. (define (make-display-services display)
  458. "Return list of all 'display services' for DISPLAY."
  459. (map (cut <> display)
  460. (list xorg-service*
  461. xset-service
  462. xsetroot-service
  463. setxkbmap-service
  464. xmodmap-service
  465. xrdb-service
  466. unclutter-service
  467. openbox-service
  468. stumpwm-service
  469. xterm-service
  470. emacs-service
  471. conkeror-service
  472. xsettings-target
  473. gui-target)))
  474. ;;; Let's go!
  475. (apply register-services
  476. (append daemons
  477. misc-services
  478. (make-display-services ":0")
  479. (make-display-services ":1")
  480. (make-display-services ":2")))
  481. ;; Do not start services if SHEPHERD_SERVICES is 0 or empty.
  482. (let ((env (getenv "SHEPHERD_SERVICES")))
  483. (unless (and env
  484. (or (string-null? env)
  485. (string= "0" env)))
  486. (start 'daemons)
  487. (start amixer-service)))
  488. (action 'shepherd 'daemonize)
  489. ;;; init.scm ends here