publish.scm 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015 David Thompson <davet@gnu.org>
  3. ;;; Copyright © 2020 by Amar M. Singh <nly@disroot.org>
  4. ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  5. ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  6. ;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (guix scripts publish)
  23. #:use-module ((system repl server) #:prefix repl:)
  24. #:use-module (ice-9 binary-ports)
  25. #:use-module (ice-9 format)
  26. #:use-module (ice-9 match)
  27. #:use-module (ice-9 poll)
  28. #:use-module (ice-9 regex)
  29. #:use-module (ice-9 rdelim)
  30. #:use-module (ice-9 threads)
  31. #:use-module (rnrs bytevectors)
  32. #:use-module (srfi srfi-1)
  33. #:use-module (srfi srfi-2)
  34. #:use-module (srfi srfi-9)
  35. #:use-module (srfi srfi-9 gnu)
  36. #:use-module (srfi srfi-11)
  37. #:use-module (srfi srfi-19)
  38. #:use-module (srfi srfi-26)
  39. #:use-module (srfi srfi-34)
  40. #:use-module (srfi srfi-37)
  41. #:use-module (web http)
  42. #:use-module (web request)
  43. #:use-module (web response)
  44. #:use-module (web server)
  45. #:use-module (web uri)
  46. #:autoload (sxml simple) (sxml->xml)
  47. #:autoload (guix avahi) (avahi-publish-service-thread)
  48. #:use-module (guix base32)
  49. #:use-module (guix base64)
  50. #:use-module (guix config)
  51. #:use-module (guix derivations)
  52. #:use-module (gcrypt hash)
  53. #:use-module (guix pki)
  54. #:use-module (gcrypt pk-crypto)
  55. #:use-module (guix workers)
  56. #:use-module (guix store)
  57. #:use-module ((guix serialization) #:select (write-file))
  58. #:use-module (zlib)
  59. #:autoload (lzlib) (call-with-lzip-output-port
  60. make-lzip-output-port)
  61. #:autoload (zstd) (call-with-zstd-output-port
  62. make-zstd-output-port)
  63. #:use-module (guix cache)
  64. #:use-module (guix ui)
  65. #:use-module (guix scripts)
  66. #:use-module ((guix utils)
  67. #:select (with-atomic-file-output compressed-file?))
  68. #:use-module ((guix build utils)
  69. #:select (dump-port mkdir-p find-files))
  70. #:use-module ((guix build syscalls) #:select (set-thread-name))
  71. #:export (%default-gzip-compression
  72. %public-key
  73. %private-key
  74. signed-string
  75. open-server-socket
  76. publish-service-type
  77. run-publish-server
  78. guix-publish))
  79. (define (show-help)
  80. (format #t (G_ "Usage: guix publish [OPTION]...
  81. Publish ~a over HTTP.\n") %store-directory)
  82. (display (G_ "
  83. -p, --port=PORT listen on PORT"))
  84. (display (G_ "
  85. --listen=HOST listen on the network interface for HOST"))
  86. (display (G_ "
  87. -u, --user=USER change privileges to USER as soon as possible"))
  88. (display (G_ "
  89. -a, --advertise advertise on the local network"))
  90. (display (G_ "
  91. -C, --compression[=METHOD:LEVEL]
  92. compress archives with METHOD at LEVEL"))
  93. (display (G_ "
  94. -c, --cache=DIRECTORY cache published items to DIRECTORY"))
  95. (display (G_ "
  96. --cache-bypass-threshold=SIZE
  97. serve store items below SIZE even when not cached"))
  98. (display (G_ "
  99. --workers=N use N workers to bake items"))
  100. (display (G_ "
  101. --ttl=TTL announce narinfos can be cached for TTL seconds"))
  102. (display (G_ "
  103. --negative-ttl=TTL announce missing narinfos can be cached for TTL seconds"))
  104. (display (G_ "
  105. --nar-path=PATH use PATH as the prefix for nar URLs"))
  106. (display (G_ "
  107. --public-key=FILE use FILE as the public key for signatures"))
  108. (display (G_ "
  109. --private-key=FILE use FILE as the private key for signatures"))
  110. (display (G_ "
  111. -r, --repl[=PORT] spawn REPL server on PORT"))
  112. (newline)
  113. (display (G_ "
  114. -h, --help display this help and exit"))
  115. (display (G_ "
  116. -V, --version display version information and exit"))
  117. (newline)
  118. (show-bug-report-information))
  119. (define (getaddrinfo* host)
  120. "Like 'getaddrinfo', but properly report errors."
  121. (catch 'getaddrinfo-error
  122. (lambda ()
  123. (getaddrinfo host))
  124. (lambda (key error)
  125. (leave (G_ "lookup of host '~a' failed: ~a~%")
  126. host (gai-strerror error)))))
  127. ;; Nar compression parameters.
  128. (define-record-type <compression>
  129. (compression type level)
  130. compression?
  131. (type compression-type)
  132. (level compression-level))
  133. (define %no-compression
  134. (compression 'none 0))
  135. (define %default-gzip-compression
  136. ;; Since we compress on the fly, default to fast compression.
  137. (compression 'gzip 3))
  138. (define (default-compression type)
  139. (compression type 3))
  140. (define (actual-compressions item requested)
  141. "Return the actual compressions used for ITEM, which may be %NO-COMPRESSION
  142. if ITEM is already compressed."
  143. (if (compressed-file? item)
  144. (list %no-compression)
  145. requested))
  146. (define (low-compression c)
  147. "Return <compression> of the same type as C, but optimized for low CPU
  148. usage."
  149. (compression (compression-type c)
  150. (min (compression-level c) 2)))
  151. (define %options
  152. (list (option '(#\h "help") #f #f
  153. (lambda _
  154. (show-help)
  155. (exit 0)))
  156. (option '(#\V "version") #f #f
  157. (lambda _
  158. (show-version-and-exit "guix publish")))
  159. (option '(#\a "advertise") #f #f
  160. (lambda (opt name arg result)
  161. (alist-cons 'advertise? #t result)))
  162. (option '(#\u "user") #t #f
  163. (lambda (opt name arg result)
  164. (alist-cons 'user arg result)))
  165. (option '(#\p "port") #t #f
  166. (lambda (opt name arg result)
  167. (alist-cons 'port (string->number* arg) result)))
  168. (option '("listen") #t #f
  169. (lambda (opt name arg result)
  170. (match (getaddrinfo* arg)
  171. ((info _ ...)
  172. (alist-cons 'address (addrinfo:addr info)
  173. result))
  174. (()
  175. (leave (G_ "lookup of host '~a' returned nothing")
  176. name)))))
  177. (option '(#\C "compression") #f #t
  178. (lambda (opt name arg result)
  179. (let* ((colon (string-index arg #\:))
  180. (type (cond
  181. (colon (string-take arg colon))
  182. ((string->number arg) "gzip")
  183. (else arg)))
  184. (level (if colon
  185. (string->number*
  186. (string-drop arg (+ 1 colon)))
  187. (or (string->number arg) 3))))
  188. (match level
  189. (0
  190. (alist-cons 'compression %no-compression result))
  191. (level
  192. (match (string->compression-type type)
  193. ((? symbol? type)
  194. (alist-cons 'compression
  195. (compression type level)
  196. result))
  197. (_
  198. (warning (G_ "~a: unsupported compression type~%")
  199. type)
  200. result)))))))
  201. (option '(#\c "cache") #t #f
  202. (lambda (opt name arg result)
  203. (alist-cons 'cache arg result)))
  204. (option '("cache-bypass-threshold") #t #f
  205. (lambda (opt name arg result)
  206. (alist-cons 'cache-bypass-threshold (size->number arg)
  207. result)))
  208. (option '("workers") #t #f
  209. (lambda (opt name arg result)
  210. (alist-cons 'workers (string->number* arg)
  211. result)))
  212. (option '("ttl") #t #f
  213. (lambda (opt name arg result)
  214. (let ((duration (string->duration arg)))
  215. (unless duration
  216. (leave (G_ "~a: invalid duration~%") arg))
  217. (alist-cons 'narinfo-ttl (time-second duration)
  218. result))))
  219. (option '("negative-ttl") #t #f
  220. (lambda (opt name arg result)
  221. (let ((duration (string->duration arg)))
  222. (unless duration
  223. (leave (G_ "~a: invalid duration~%") arg))
  224. (alist-cons 'narinfo-negative-ttl (time-second duration)
  225. result))))
  226. (option '("nar-path") #t #f
  227. (lambda (opt name arg result)
  228. (alist-cons 'nar-path arg result)))
  229. (option '("public-key") #t #f
  230. (lambda (opt name arg result)
  231. (alist-cons 'public-key-file arg result)))
  232. (option '("private-key" "secret-key") #t #f
  233. (lambda (opt name arg result)
  234. (alist-cons 'private-key-file arg result)))
  235. (option '(#\r "repl") #f #t
  236. (lambda (opt name arg result)
  237. ;; If port unspecified, use default Guile REPL port.
  238. (let ((port (and arg (string->number* arg))))
  239. (alist-cons 'repl (or port 37146) result))))))
  240. (define %default-options
  241. `((port . 8080)
  242. ;; By default, serve nars under "/nar".
  243. (nar-path . "nar")
  244. (public-key-file . ,%public-key-file)
  245. (private-key-file . ,%private-key-file)
  246. ;; Default number of workers when caching is enabled.
  247. (workers . ,(current-processor-count))
  248. (address . ,(make-socket-address AF_INET INADDR_ANY 0))
  249. (repl . #f)))
  250. ;; The key pair used to sign narinfos.
  251. (define %private-key
  252. (make-parameter #f))
  253. (define %public-key
  254. (make-parameter #f))
  255. (define %nix-cache-info
  256. `(("StoreDir" . ,%store-directory)
  257. ("WantMassQuery" . 0)
  258. ("Priority" . 100)))
  259. ;;; A common buffer size value used for the TCP socket SO_SNDBUF option and
  260. ;;; the gzip compressor buffer size.
  261. (define %default-buffer-size
  262. (* 208 1024))
  263. (define %default-socket-options
  264. ;; List of options passed to 'setsockopt' when transmitting files.
  265. (list (list SO_SNDBUF %default-buffer-size)))
  266. (define* (configure-socket socket #:key (level SOL_SOCKET)
  267. (options %default-socket-options))
  268. "Apply multiple option tuples in OPTIONS to SOCKET, using LEVEL."
  269. (for-each (cut apply setsockopt socket level <>)
  270. options))
  271. (define (signed-string s)
  272. "Sign the hash of the string S with the daemon's key. Return a canonical
  273. sexp for the signature."
  274. (let* ((public-key (%public-key))
  275. (hash (bytevector->hash-data (sha256 (string->utf8 s))
  276. #:key-type (key-type public-key))))
  277. (signature-sexp hash (%private-key) public-key)))
  278. (define base64-encode-string
  279. (compose base64-encode string->utf8))
  280. (define* (store-item->recutils store-item
  281. #:key
  282. (nar-path "nar")
  283. (compression %no-compression)
  284. file-size)
  285. "Return the 'Compression' and 'URL' fields of the narinfo for STORE-ITEM,
  286. with COMPRESSION, starting at NAR-PATH."
  287. (let ((url (encode-and-join-uri-path
  288. `(,@(split-and-decode-uri-path nar-path)
  289. ,@(match compression
  290. (($ <compression> 'none)
  291. '())
  292. (($ <compression> type)
  293. (list (symbol->string type))))
  294. ,(basename store-item)))))
  295. (format #f "URL: ~a~%Compression: ~a~%~@[FileSize: ~a~%~]"
  296. url (compression-type compression) file-size)))
  297. (define* (narinfo-string store store-path key
  298. #:key (compressions (list %no-compression))
  299. (nar-path "nar") (file-sizes '()))
  300. "Generate a narinfo key/value string for STORE-PATH; an exception is raised
  301. if STORE-PATH is invalid. Produce a URL that corresponds to COMPRESSION. The
  302. narinfo is signed with KEY. NAR-PATH specifies the prefix for nar URLs.
  303. Optionally, FILE-SIZES is a list of compression/integer pairs, where the
  304. integer is size in bytes of the compressed NAR; it informs the client of how
  305. much needs to be downloaded."
  306. (let* ((path-info (query-path-info store store-path))
  307. (compressions (actual-compressions store-path compressions))
  308. (hash (bytevector->nix-base32-string
  309. (path-info-hash path-info)))
  310. (size (path-info-nar-size path-info))
  311. (file-sizes `((,%no-compression . ,size) ,@file-sizes))
  312. (references (string-join
  313. (map basename (path-info-references path-info))
  314. " "))
  315. (deriver (path-info-deriver path-info))
  316. (base-info (format #f
  317. "\
  318. StorePath: ~a
  319. ~{~a~}\
  320. NarHash: sha256:~a
  321. NarSize: ~d
  322. References: ~a~%"
  323. store-path
  324. (map (lambda (compression)
  325. (let ((size (assoc-ref file-sizes
  326. compression)))
  327. (store-item->recutils store-path
  328. #:file-size size
  329. #:nar-path nar-path
  330. #:compression
  331. compression)))
  332. compressions)
  333. hash size references))
  334. ;; Do not render a "Deriver" or "System" line if we are rendering
  335. ;; info for a derivation.
  336. (info (if (not deriver)
  337. base-info
  338. (catch 'system-error
  339. (lambda ()
  340. (let ((drv (read-derivation-from-file deriver)))
  341. (format #f "~aSystem: ~a~%Deriver: ~a~%"
  342. base-info (derivation-system drv)
  343. (basename deriver))))
  344. (lambda args
  345. ;; DERIVER might be missing, but that's fine:
  346. ;; it's only used for <substitutable> where it's
  347. ;; optional. 'System' is currently unused.
  348. (if (= ENOENT (system-error-errno args))
  349. base-info
  350. (apply throw args))))))
  351. (signature (base64-encode-string
  352. (canonical-sexp->string (signed-string info)))))
  353. (format #f "~aSignature: 1;~a;~a~%" info (gethostname) signature)))
  354. (define* (not-found request
  355. #:key (phrase "Resource not found")
  356. ttl)
  357. "Render 404 response for REQUEST."
  358. (values (build-response #:code 404
  359. #:headers (if ttl
  360. `((cache-control (max-age . ,ttl)))
  361. '()))
  362. (string-append phrase ": "
  363. (uri-path (request-uri request)))))
  364. (define (render-nix-cache-info)
  365. "Render server information."
  366. (values '((content-type . (text/plain)))
  367. (lambda (port)
  368. (for-each (match-lambda
  369. ((key . value)
  370. (format port "~a: ~a~%" key value)))
  371. %nix-cache-info))))
  372. (define* (render-narinfo store request hash
  373. #:key ttl (compressions (list %no-compression))
  374. (nar-path "nar") negative-ttl)
  375. "Render metadata for the store path corresponding to HASH. If TTL is true,
  376. advertise it as the maximum validity period (in seconds) via the
  377. 'Cache-Control' header. This allows 'guix substitute' to cache it for an
  378. appropriate duration. NAR-PATH specifies the prefix for nar URLs."
  379. (let ((store-path (hash-part->path store hash)))
  380. (if (string-null? store-path)
  381. (not-found request #:phrase "" #:ttl negative-ttl)
  382. (values `((content-type . (application/x-nix-narinfo))
  383. ,@(if ttl
  384. `((cache-control (max-age . ,ttl)))
  385. '()))
  386. (cut display
  387. (narinfo-string store store-path (%private-key)
  388. #:nar-path nar-path
  389. #:compressions compressions)
  390. <>)))))
  391. (define* (nar-cache-file directory item
  392. #:key (compression %no-compression))
  393. (string-append directory "/"
  394. (symbol->string (compression-type compression))
  395. "/" (basename item) ".nar"))
  396. (define* (narinfo-cache-file directory item
  397. #:key (compression %no-compression))
  398. (string-append directory "/"
  399. (symbol->string (compression-type compression))
  400. "/" (basename item)
  401. ".narinfo"))
  402. (define (hash-part-mapping-cache-file directory hash)
  403. (string-append directory "/hashes/" hash))
  404. (define run-single-baker
  405. (let ((baking (make-weak-value-hash-table))
  406. (mutex (make-mutex)))
  407. (lambda (item thunk)
  408. "Run THUNK, which is supposed to bake ITEM, but make sure only one
  409. thread is baking ITEM at a given time."
  410. (define selected?
  411. (with-mutex mutex
  412. (and (not (hash-ref baking item))
  413. (begin
  414. (hash-set! baking item (current-thread))
  415. #t))))
  416. (when selected?
  417. (dynamic-wind
  418. (const #t)
  419. thunk
  420. (lambda ()
  421. (with-mutex mutex
  422. (hash-remove! baking item))))))))
  423. (define-syntax-rule (single-baker item exp ...)
  424. "Bake ITEM by evaluating EXP, but make sure there's only one baker for ITEM
  425. at a time."
  426. (run-single-baker item (lambda () exp ...)))
  427. (define (narinfo-files cache)
  428. "Return the list of .narinfo files under CACHE."
  429. (if (file-is-directory? cache)
  430. (find-files cache
  431. (lambda (file stat)
  432. (string-suffix? ".narinfo" file)))
  433. '()))
  434. (define (nar-expiration-time ttl)
  435. "Return the narinfo expiration time (in seconds since the Epoch). The
  436. expiration time is +inf.0 when passed an item that is still in the store; in
  437. other cases, it is the last-access time of the item plus TTL.
  438. This policy allows us to keep cached nars that correspond to valid store
  439. items. Failing that, we could eventually have to recompute them and return
  440. 404 in the meantime."
  441. (let ((expiration-time (file-expiration-time ttl)))
  442. (lambda (file)
  443. (let ((item (string-append (%store-prefix) "/"
  444. (basename file ".narinfo"))))
  445. ;; Note: We don't need to use 'valid-path?' here because FILE would
  446. ;; not exist if ITEM were not valid in the first place.
  447. (if (file-exists? item)
  448. +inf.0
  449. (expiration-time file))))))
  450. (define (hash-part->path* store hash cache)
  451. "Like 'hash-part->path' but cache results under CACHE. This ensures we can
  452. still map HASH to the corresponding store file name, even if said store item
  453. vanished from the store in the meantime."
  454. (let ((cached (hash-part-mapping-cache-file cache hash)))
  455. (catch 'system-error
  456. (lambda ()
  457. (call-with-input-file cached read-string))
  458. (lambda args
  459. (if (= ENOENT (system-error-errno args))
  460. (match (hash-part->path store hash)
  461. ("" "")
  462. (result
  463. (mkdir-p (dirname cached))
  464. (call-with-output-file (string-append cached ".tmp")
  465. (lambda (port)
  466. (display result port)))
  467. (rename-file (string-append cached ".tmp") cached)
  468. result))
  469. (apply throw args))))))
  470. (define cache-bypass-threshold
  471. ;; Maximum size of a store item that may be served by the '/cached' handlers
  472. ;; below even when not in cache.
  473. (make-parameter (* 10 (expt 2 20))))
  474. (define (bypass-cache? store item)
  475. "Return true if we allow ITEM to be downloaded before it is cached. ITEM is
  476. interpreted as the basename of a store item."
  477. (guard (c ((store-error? c) #f))
  478. (< (path-info-nar-size (query-path-info store item))
  479. (cache-bypass-threshold))))
  480. (define* (render-narinfo/cached store request hash
  481. #:key ttl (compressions (list %no-compression))
  482. (nar-path "nar") negative-ttl
  483. cache pool)
  484. "Respond to the narinfo request for REQUEST. If the narinfo is available in
  485. CACHE, then send it; otherwise, return 404 and \"bake\" that nar and narinfo
  486. requested using POOL."
  487. (define (delete-entry narinfo)
  488. ;; Delete NARINFO and the corresponding nar from CACHE.
  489. (let* ((nar (string-append (string-drop-right narinfo
  490. (string-length ".narinfo"))
  491. ".nar"))
  492. (base (basename narinfo ".narinfo"))
  493. (hash (string-take base (string-index base #\-)))
  494. (mapping (hash-part-mapping-cache-file cache hash)))
  495. (delete-file* narinfo)
  496. (delete-file* nar)
  497. (delete-file* mapping)))
  498. (let* ((item (hash-part->path* store hash cache))
  499. (compressions (actual-compressions item compressions))
  500. (cached (and (not (string-null? item))
  501. (narinfo-cache-file cache item
  502. #:compression
  503. (first compressions)))))
  504. (cond ((string-null? item)
  505. (not-found request #:ttl negative-ttl))
  506. ((file-exists? cached)
  507. ;; Narinfo is in cache, send it.
  508. (values `((content-type . (application/x-nix-narinfo))
  509. ,@(if ttl
  510. `((cache-control (max-age . ,ttl)))
  511. '()))
  512. (lambda (port)
  513. (display (call-with-input-file cached
  514. read-string)
  515. port))))
  516. ((and (file-exists? item) ;cheaper than the 'valid-path?' RPC
  517. (valid-path? store item))
  518. ;; Nothing in cache: bake the narinfo and nar in the background and
  519. ;; return 404.
  520. (eventually pool
  521. (single-baker item
  522. ;; Check whether CACHED has been produced in the meantime.
  523. (unless (file-exists? cached)
  524. ;; (format #t "baking ~s~%" item)
  525. (bake-narinfo+nar cache item
  526. #:ttl ttl
  527. #:compressions compressions
  528. #:nar-path nar-path)))
  529. (when ttl
  530. (single-baker 'cache-cleanup
  531. (maybe-remove-expired-cache-entries cache
  532. narinfo-files
  533. #:entry-expiration
  534. (nar-expiration-time ttl)
  535. #:delete-entry delete-entry
  536. #:cleanup-period ttl))))
  537. ;; If ITEM passes 'bypass-cache?', render a temporary narinfo right
  538. ;; away, with a short TTL. The narinfo is temporary because it
  539. ;; lacks 'FileSize', for instance, which the cached narinfo will
  540. ;; have. Chances are that the nar will be baked by the time the
  541. ;; client asks for it.
  542. (if (bypass-cache? store item)
  543. (render-narinfo store request hash
  544. #:ttl 300 ;temporary
  545. #:nar-path nar-path
  546. #:compressions compressions)
  547. (not-found request
  548. #:phrase "We're baking it"
  549. #:ttl 300))) ;should be available within 5m
  550. (else
  551. (not-found request #:phrase "" #:ttl negative-ttl)))))
  552. (define (compress-nar cache item compression)
  553. "Save in directory CACHE the nar for ITEM compressed with COMPRESSION."
  554. (define nar
  555. (nar-cache-file cache item #:compression compression))
  556. (define (write-compressed-file call-with-compressed-output-port)
  557. ;; Note: the file port gets closed along with the compressed port.
  558. (call-with-compressed-output-port (open-output-file (string-append nar ".tmp"))
  559. (lambda (port)
  560. (write-file item port))
  561. #:level (compression-level compression))
  562. (rename-file (string-append nar ".tmp") nar))
  563. (mkdir-p (dirname nar))
  564. (match (compression-type compression)
  565. ('gzip
  566. (write-compressed-file call-with-gzip-output-port))
  567. ('lzip
  568. (write-compressed-file call-with-lzip-output-port))
  569. ('zstd
  570. (write-compressed-file call-with-zstd-output-port))
  571. ('none
  572. ;; Cache nars even when compression is disabled so that we can
  573. ;; guarantee the TTL (see <https://bugs.gnu.org/28664>.)
  574. (with-atomic-file-output nar
  575. (lambda (port)
  576. (write-file item port)
  577. ;; Make the file world-readable, contrary to what
  578. ;; 'with-atomic-file-output' does.
  579. (chmod port (logand #o644 (lognot (umask)))))))))
  580. (define* (bake-narinfo+nar cache item
  581. #:key ttl (compressions (list %no-compression))
  582. (nar-path "/nar"))
  583. "Write the narinfo and nar for ITEM to CACHE."
  584. (define (compressed-nar-size compression)
  585. (let* ((nar (nar-cache-file cache item #:compression compression))
  586. (stat (stat nar #f)))
  587. (and stat
  588. (cons compression (stat:size stat)))))
  589. (let ((compression (actual-compressions item compressions)))
  590. (for-each (cut compress-nar cache item <>) compressions)
  591. (match compressions
  592. ((main others ...)
  593. (let ((narinfo (narinfo-cache-file cache item
  594. #:compression main)))
  595. (with-atomic-file-output narinfo
  596. (lambda (port)
  597. ;; Open a new connection to the store. We cannot reuse the main
  598. ;; thread's connection to the store since we would end up sending
  599. ;; stuff concurrently on the same channel.
  600. (with-store store
  601. (let ((sizes (filter-map compressed-nar-size compression)))
  602. (display (narinfo-string store item
  603. (%private-key)
  604. #:nar-path nar-path
  605. #:compressions compressions
  606. #:file-sizes sizes)
  607. port)))
  608. ;; Make the cached narinfo world-readable, contrary to what
  609. ;; 'with-atomic-file-output' does, so that other users can rsync
  610. ;; the whole cache.
  611. (chmod port (logand #o644 (lognot (umask))))))
  612. ;; Make narinfo files for OTHERS hard links to NARINFO such that the
  613. ;; atime-based cache eviction considers either all the nars or none
  614. ;; of them as candidates.
  615. (for-each (lambda (other)
  616. (let ((other (narinfo-cache-file cache item
  617. #:compression other)))
  618. (link narinfo other)))
  619. others))))))
  620. ;; XXX: Declare the 'X-Nar-Compression' HTTP header, which is in fact for
  621. ;; internal consumption: it allows us to pass the compression info to
  622. ;; 'http-write', as part of the workaround to <http://bugs.gnu.org/21093>.
  623. (declare-header! "X-Nar-Compression"
  624. (lambda (str)
  625. (match (call-with-input-string str read)
  626. (('compression type level)
  627. (compression type level))))
  628. compression?
  629. (lambda (compression port)
  630. (match compression
  631. (($ <compression> type level)
  632. (write `(compression ,type ,level) port)))))
  633. (define* (render-nar store request store-item
  634. #:key (compression %no-compression))
  635. "Render archive of the store path corresponding to STORE-ITEM."
  636. (let ((store-path (string-append %store-directory "/" store-item)))
  637. ;; The ISO-8859-1 charset *must* be used otherwise HTTP clients will
  638. ;; interpret the byte stream as UTF-8 and arbitrarily change invalid byte
  639. ;; sequences.
  640. (if (valid-path? store store-path)
  641. (values `((content-type . (application/x-nix-archive
  642. (charset . "ISO-8859-1")))
  643. (x-nar-compression . ,compression))
  644. ;; XXX: We're not returning the actual contents, deferring
  645. ;; instead to 'http-write'. This is a hack to work around
  646. ;; <http://bugs.gnu.org/21093>.
  647. store-path)
  648. (not-found request))))
  649. (define* (render-nar/cached store cache request store-item
  650. #:key ttl (compression %no-compression))
  651. "Respond to REQUEST with a nar for STORE-ITEM. If the nar is in CACHE,
  652. return it; otherwise, return 404. When TTL is true, use it as the
  653. 'Cache-Control' expiration time."
  654. (let ((cached (nar-cache-file cache store-item
  655. #:compression compression)))
  656. (cond ((file-exists? cached)
  657. (values `((content-type . (application/octet-stream
  658. (charset . "ISO-8859-1")))
  659. ,@(if ttl
  660. `((cache-control (max-age . ,ttl)))
  661. '())
  662. ;; XXX: We're not returning the actual contents, deferring
  663. ;; instead to 'http-write'. This is a hack to work around
  664. ;; <http://bugs.gnu.org/21093>.
  665. (x-raw-file . ,cached))
  666. #f))
  667. ((let* ((hash (and=> (string-index store-item #\-)
  668. (cut string-take store-item <>)))
  669. (item (and hash
  670. (guard (c ((store-error? c) #f))
  671. (hash-part->path store hash)))))
  672. (and item (not (string-null? item))
  673. (bypass-cache? store item)))
  674. ;; Render STORE-ITEM live. We reach this because STORE-ITEM is
  675. ;; being baked but clients are already asking for it. Thus, we're
  676. ;; duplicating work, but doing so allows us to reduce delays.
  677. (render-nar store request store-item
  678. #:compression (low-compression compression)))
  679. (else
  680. (not-found request)))))
  681. (define (render-content-addressed-file store request
  682. name algo hash)
  683. "Return the content of the result of the fixed-output derivation NAME that
  684. has the given HASH of type ALGO."
  685. ;; TODO: Support other hash algorithms.
  686. (if (and (eq? algo 'sha256) (= 32 (bytevector-length hash)))
  687. (let ((item (fixed-output-path name hash
  688. #:hash-algo algo
  689. #:recursive? #f)))
  690. (if (valid-path? store item)
  691. (values `((content-type . (application/octet-stream
  692. (charset . "ISO-8859-1")))
  693. ;; XXX: We're not returning the actual contents,
  694. ;; deferring instead to 'http-write'. This is a hack to
  695. ;; work around <http://bugs.gnu.org/21093>.
  696. (x-raw-file . ,item))
  697. #f)
  698. (not-found request)))
  699. (not-found request)))
  700. (define (render-log-file store request name)
  701. "Render the log file for NAME, the base name of a store item. Don't attempt
  702. to compress or decompress the log file; just return it as-is."
  703. (define (response-headers file)
  704. ;; XXX: We're not returning the actual contents, deferring instead to
  705. ;; 'http-write'. This is a hack to work around
  706. ;; <http://bugs.gnu.org/21093>.
  707. (cond ((string-suffix? ".gz" file)
  708. `((content-type . (text/plain (charset . "UTF-8")))
  709. (content-encoding . (gzip))
  710. (x-raw-file . ,file)))
  711. ((string-suffix? ".bz2" file)
  712. `((content-type . (application/x-bzip2
  713. (charset . "ISO-8859-1")))
  714. (x-raw-file . ,file)))
  715. (else ;uncompressed
  716. `((content-type . (text/plain (charset . "UTF-8")))
  717. (x-raw-file . ,file)))))
  718. (let ((log (log-file store
  719. (string-append (%store-prefix) "/" name))))
  720. (if log
  721. (values (response-headers log) log)
  722. (not-found request))))
  723. (define (render-signing-key)
  724. "Render signing key."
  725. (let ((file %public-key-file))
  726. (values `((content-type . (text/plain (charset . "UTF-8")))
  727. (x-raw-file . ,file))
  728. file)))
  729. (define (render-home-page request)
  730. "Render the home page."
  731. (values `((content-type . (text/html (charset . "UTF-8"))))
  732. (call-with-output-string
  733. (lambda (port)
  734. (sxml->xml '(html
  735. (head (title "GNU Guix Substitute Server"))
  736. (body
  737. (h1 "GNU Guix Substitute Server")
  738. (p "Hi, "
  739. (a (@ (href
  740. "https://guix.gnu.org/manual/en/html_node/Invoking-guix-publish.html"))
  741. (tt "guix publish"))
  742. " speaking. Welcome!")
  743. (p "Here is the "
  744. (a (@ (href
  745. "signing-key.pub"))
  746. (tt "signing key"))
  747. " for this server. Knock yourselves out!")))
  748. port)))))
  749. (define (extract-narinfo-hash str)
  750. "Return the hash within the narinfo resource string STR, or false if STR
  751. is invalid."
  752. (and (string-suffix? ".narinfo" str)
  753. (let ((base (string-drop-right str 8)))
  754. (and (string-every %nix-base32-charset base)
  755. base))))
  756. (define (get-request? request)
  757. "Return #t if REQUEST uses the GET method."
  758. (eq? (request-method request) 'GET))
  759. (define (request-path-components request)
  760. "Split the URI path of REQUEST into a list of component strings. For
  761. example: \"/foo/bar\" yields '(\"foo\" \"bar\")."
  762. (split-and-decode-uri-path (uri-path (request-uri request))))
  763. ;;;
  764. ;;; Server.
  765. ;;;
  766. (define %http-write
  767. (@@ (web server http) http-write))
  768. (define (strip-headers response)
  769. "Return RESPONSE's headers minus 'Content-Length' and our internal headers."
  770. (fold alist-delete
  771. (response-headers response)
  772. '(content-length x-raw-file x-nar-compression)))
  773. (define (sans-content-length response)
  774. "Return RESPONSE without its 'content-length' header."
  775. (set-field response (response-headers)
  776. (strip-headers response)))
  777. (define (with-content-length response length)
  778. "Return RESPONSE with a 'content-length' header set to LENGTH."
  779. (set-field response (response-headers)
  780. (alist-cons 'content-length length
  781. (strip-headers response))))
  782. (define-syntax-rule (swallow-EPIPE exp ...)
  783. "Swallow EPIPE errors raised by EXP..."
  784. (catch 'system-error
  785. (lambda ()
  786. exp ...)
  787. (lambda args
  788. (if (= EPIPE (system-error-errno args))
  789. (values)
  790. (apply throw args)))))
  791. (define-syntax-rule (swallow-zlib-error exp ...)
  792. "Swallow 'zlib-error' exceptions raised by EXP..."
  793. (catch 'zlib-error
  794. (lambda ()
  795. exp ...)
  796. (const #f)))
  797. (define (nar-compressed-port port compression)
  798. "Return a port on which to write the body of the response of a /nar request,
  799. according to COMPRESSION."
  800. (match compression
  801. (($ <compression> 'gzip level)
  802. ;; Note: We cannot used chunked encoding here because
  803. ;; 'make-gzip-output-port' wants a file port.
  804. (make-gzip-output-port port
  805. #:level level
  806. #:buffer-size %default-buffer-size))
  807. (($ <compression> 'lzip level)
  808. (make-lzip-output-port port
  809. #:level level))
  810. (($ <compression> 'zstd level)
  811. (make-zstd-output-port port
  812. #:level level))
  813. (($ <compression> 'none)
  814. port)
  815. (#f
  816. port)))
  817. (define (http-write server client response body)
  818. "Write RESPONSE and BODY to CLIENT, possibly in a separate thread to avoid
  819. blocking."
  820. ;; XXX: The default Guile web server implementation supports the keep-alive
  821. ;; mechanism. However, as we run our own modified version of the http-write
  822. ;; procedure, we need to access a few server implementation details to keep
  823. ;; it functional.
  824. (define *error-events*
  825. (logior POLLHUP POLLERR))
  826. (define *read-events*
  827. POLLIN)
  828. (define *events*
  829. (logior *error-events* *read-events*))
  830. ;; Access the server poll set variable.
  831. (define http-poll-set
  832. (@@ (web server http) http-poll-set))
  833. ;; Copied from (web server http).
  834. (define (keep-alive? response)
  835. (let ((v (response-version response)))
  836. (and (or (< (response-code response) 400)
  837. (= (response-code response) 404))
  838. (case (car v)
  839. ((1)
  840. (case (cdr v)
  841. ((1) (not (memq 'close (response-connection response))))
  842. ((0) (memq 'keep-alive (response-connection response)))))
  843. (else #f)))))
  844. (define (keep-alive port)
  845. "Add the given PORT the server poll set."
  846. (force-output port)
  847. (poll-set-add! (http-poll-set server) port *events*))
  848. (define compression
  849. (assoc-ref (response-headers response) 'x-nar-compression))
  850. (match (response-content-type response)
  851. (('application/x-nix-archive . _)
  852. ;; When compressing the NAR on the go, we cannot announce its size
  853. ;; beforehand to the client. Hence, the keep-alive mechanism cannot work
  854. ;; here.
  855. (let ((keep-alive? (and (eq? (compression-type compression) 'none)
  856. (keep-alive? response))))
  857. ;; Add the client to the server poll set, so that we can receive
  858. ;; further requests without closing the connection.
  859. (when keep-alive?
  860. (keep-alive client))
  861. ;; Sending the the whole archive can take time so do it in a separate
  862. ;; thread so that the main thread can keep working in the meantime.
  863. (call-with-new-thread
  864. (lambda ()
  865. (set-thread-name "publish nar")
  866. (let* ((response (write-response (sans-content-length response)
  867. client))
  868. (port (begin
  869. (force-output client)
  870. (configure-socket client)
  871. ;; Duplicate the response port, so that it is
  872. ;; not automatically closed when closing the
  873. ;; returned port. This is needed for the
  874. ;; keep-alive mechanism.
  875. (nar-compressed-port
  876. (duplicate-port
  877. (response-port response) "w+0b")
  878. compression))))
  879. ;; XXX: Given our ugly workaround for <http://bugs.gnu.org/21093>
  880. ;; in 'render-nar', BODY here is just the file name of the store
  881. ;; item. We call 'write-file' from here because we know that's
  882. ;; the only way to avoid building the whole nar in memory, which
  883. ;; could quickly become a real problem. As a bonus, we even do
  884. ;; sendfile(2) directly from the store files to the socket.
  885. (swallow-zlib-error
  886. (swallow-EPIPE
  887. (write-file (utf8->string body) port)))
  888. (swallow-zlib-error
  889. (close-port port)
  890. (unless keep-alive?
  891. (close-port client)))
  892. (values))))))
  893. (_
  894. (match (assoc-ref (response-headers response) 'x-raw-file)
  895. ((? string? file)
  896. (when (keep-alive? response)
  897. (keep-alive client))
  898. ;; Send a raw file in a separate thread.
  899. (call-with-new-thread
  900. (lambda ()
  901. (set-thread-name "publish file")
  902. (catch 'system-error
  903. (lambda ()
  904. (call-with-input-file file
  905. (lambda (input)
  906. (let* ((size (stat:size (stat input)))
  907. (response (write-response
  908. (with-content-length response size)
  909. client))
  910. (output (response-port response)))
  911. (configure-socket client)
  912. (if (file-port? output)
  913. (sendfile output input size)
  914. (dump-port input output))
  915. (unless (keep-alive? response)
  916. (close-port output))
  917. (values)))))
  918. (lambda args
  919. ;; If the file was GC'd behind our back, that's fine. Likewise
  920. ;; if the client closes the connection.
  921. (unless (memv (system-error-errno args)
  922. (list ENOENT EPIPE ECONNRESET))
  923. (apply throw args))
  924. (values))))))
  925. (#f
  926. ;; Handle other responses sequentially.
  927. (%http-write server client response body))))))
  928. (define-server-impl concurrent-http-server
  929. ;; A variant of Guile's built-in HTTP server that offloads possibly long
  930. ;; responses to a different thread.
  931. (@@ (web server http) http-open)
  932. (@@ (web server http) http-read)
  933. http-write
  934. (@@ (web server http) http-close))
  935. (define (string->compression-type string)
  936. "Return a symbol denoting the compression method expressed by STRING; return
  937. #f if STRING doesn't match any supported method."
  938. (match string
  939. ("gzip" 'gzip)
  940. ("lzip" 'lzip)
  941. ("zstd" 'zstd)
  942. (_ #f)))
  943. (define (effective-compression requested-type compressions)
  944. "Given the REQUESTED-TYPE for compression and the set of chosen COMPRESSION
  945. methods, return the applicable compression."
  946. (or (find (match-lambda
  947. (($ <compression> type)
  948. (and (eq? type requested-type)
  949. compression)))
  950. compressions)
  951. (default-compression requested-type)))
  952. (define (preserve-connection-headers request response)
  953. "Add REQUEST's 'connection' header, if any, to HEADERS, a list of response
  954. headers."
  955. (if (pair? response)
  956. (let ((connection
  957. (assq 'connection (request-headers request))))
  958. (append response
  959. (if connection
  960. (list connection)
  961. '())))
  962. response))
  963. (define* (make-request-handler store
  964. #:key
  965. cache pool
  966. narinfo-ttl narinfo-negative-ttl
  967. (nar-path "nar")
  968. (compressions (list %no-compression)))
  969. (define compression-type?
  970. string->compression-type)
  971. (define nar-path?
  972. (let ((expected (split-and-decode-uri-path nar-path)))
  973. (cut equal? expected <>)))
  974. (define (handle request body)
  975. (format #t "~a ~a~%"
  976. (request-method request)
  977. (uri-path (request-uri request)))
  978. (if (get-request? request) ;reject POST, PUT, etc.
  979. (match (request-path-components request)
  980. ;; /nix-cache-info
  981. (("nix-cache-info")
  982. (render-nix-cache-info))
  983. ;; /
  984. ((or () ("index.html"))
  985. (render-home-page request))
  986. ;; guix signing-key
  987. (("signing-key.pub")
  988. (render-signing-key))
  989. ;; /<hash>.narinfo
  990. (((= extract-narinfo-hash (? string? hash)))
  991. (if cache
  992. (render-narinfo/cached store request hash
  993. #:cache cache
  994. #:pool pool
  995. #:ttl narinfo-ttl
  996. #:negative-ttl narinfo-negative-ttl
  997. #:nar-path nar-path
  998. #:compressions compressions)
  999. (render-narinfo store request hash
  1000. #:ttl narinfo-ttl
  1001. #:negative-ttl narinfo-negative-ttl
  1002. #:nar-path nar-path
  1003. #:compressions compressions)))
  1004. ;; /nar/file/NAME/sha256/HASH
  1005. (("file" name "sha256" hash)
  1006. (guard (c ((invalid-base32-character? c)
  1007. (not-found request)))
  1008. (let ((hash (nix-base32-string->bytevector hash)))
  1009. (render-content-addressed-file store request
  1010. name 'sha256 hash))))
  1011. ;; /log/OUTPUT
  1012. (("log" name)
  1013. (render-log-file store request name))
  1014. ;; Use different URLs depending on the compression type. This
  1015. ;; guarantees that /nar URLs remain valid even when 'guix publish'
  1016. ;; is restarted with different compression parameters.
  1017. ;; /nar/gzip/<store-item>
  1018. ((components ... (? compression-type? type) store-item)
  1019. (if (nar-path? components)
  1020. (let* ((compression-type (string->compression-type type))
  1021. (compression (effective-compression compression-type
  1022. compressions)))
  1023. (if cache
  1024. (render-nar/cached store cache request store-item
  1025. #:ttl narinfo-ttl
  1026. #:compression compression)
  1027. (render-nar store request store-item
  1028. #:compression compression)))
  1029. (not-found request)))
  1030. ;; /nar/<store-item>
  1031. ((components ... store-item)
  1032. (if (nar-path? components)
  1033. (if cache
  1034. (render-nar/cached store cache request store-item
  1035. #:ttl narinfo-ttl
  1036. #:compression %no-compression)
  1037. (render-nar store request store-item
  1038. #:compression %no-compression))
  1039. (not-found request)))
  1040. (x (not-found request)))
  1041. (not-found request)))
  1042. ;; Preserve the request's 'connection' header in the response, so that the
  1043. ;; server can close the connection if this is requested by the client.
  1044. (lambda (request body)
  1045. (let-values (((response response-body)
  1046. (handle request body)))
  1047. (values (preserve-connection-headers request response)
  1048. response-body))))
  1049. (define (service-name)
  1050. "Return the Avahi service name of the server."
  1051. (string-append "guix-publish-" (gethostname)))
  1052. (define publish-service-type
  1053. ;; Return the Avahi service type of the server.
  1054. "_guix_publish._tcp")
  1055. (define* (run-publish-server socket store
  1056. #:key
  1057. advertise? port
  1058. (compressions (list %no-compression))
  1059. (nar-path "nar") narinfo-ttl narinfo-negative-ttl
  1060. cache pool)
  1061. (when advertise?
  1062. (let ((name (service-name)))
  1063. ;; XXX: Use a callback from Guile-Avahi here, as Avahi can pick a
  1064. ;; different name to avoid name clashes.
  1065. (info (G_ "Advertising ~a~%.") name)
  1066. (avahi-publish-service-thread name
  1067. #:type publish-service-type
  1068. #:port port)))
  1069. (run-server (make-request-handler store
  1070. #:cache cache
  1071. #:pool pool
  1072. #:nar-path nar-path
  1073. #:narinfo-ttl narinfo-ttl
  1074. #:narinfo-negative-ttl narinfo-negative-ttl
  1075. #:compressions compressions)
  1076. concurrent-http-server
  1077. `(#:socket ,socket)))
  1078. (define (open-server-socket address)
  1079. "Return a TCP socket bound to ADDRESS, a socket address."
  1080. (let ((sock (socket (sockaddr:fam address) SOCK_STREAM 0)))
  1081. (configure-socket sock #:options (cons (list SO_REUSEADDR 1)
  1082. %default-socket-options))
  1083. (bind sock address)
  1084. sock))
  1085. (define (gather-user-privileges user)
  1086. "Switch to the identity of USER, a user name."
  1087. (catch 'misc-error
  1088. (lambda ()
  1089. (let ((user (getpw user)))
  1090. (setgroups #())
  1091. (setgid (passwd:gid user))
  1092. (setuid (passwd:uid user))))
  1093. (lambda (key proc message args . rest)
  1094. (leave (G_ "user '~a' not found: ~a~%")
  1095. user (apply format #f message args)))))
  1096. ;;;
  1097. ;;; Entry point.
  1098. ;;;
  1099. (define-command (guix-publish . args)
  1100. (category packaging)
  1101. (synopsis "publish build results over HTTP")
  1102. (with-error-handling
  1103. (let* ((opts (parse-command-line args %options (list %default-options)
  1104. #:build-options? #f
  1105. #:argument-handler
  1106. (lambda (arg result)
  1107. (leave (G_ "~A: extraneous argument~%") arg))))
  1108. (advertise? (assoc-ref opts 'advertise?))
  1109. (user (assoc-ref opts 'user))
  1110. (port (assoc-ref opts 'port))
  1111. (ttl (assoc-ref opts 'narinfo-ttl))
  1112. (negative-ttl (assoc-ref opts 'narinfo-negative-ttl))
  1113. (compressions (match (filter-map (match-lambda
  1114. (('compression . compression)
  1115. compression)
  1116. (_ #f))
  1117. opts)
  1118. (()
  1119. ;; Default to fast & low compression.
  1120. (list %default-gzip-compression))
  1121. (lst (reverse lst))))
  1122. (address (let ((addr (assoc-ref opts 'address)))
  1123. (make-socket-address (sockaddr:fam addr)
  1124. (sockaddr:addr addr)
  1125. port)))
  1126. (socket (open-server-socket address))
  1127. (nar-path (assoc-ref opts 'nar-path))
  1128. (repl-port (assoc-ref opts 'repl))
  1129. (cache (assoc-ref opts 'cache))
  1130. (workers (assoc-ref opts 'workers))
  1131. ;; Read the key right away so that (1) we fail early on if we can't
  1132. ;; access them, and (2) we can then drop privileges.
  1133. (public-key (read-file-sexp (assoc-ref opts 'public-key-file)))
  1134. (private-key (read-file-sexp (assoc-ref opts 'private-key-file))))
  1135. (when user
  1136. ;; Now that we've read the key material and opened the socket, we can
  1137. ;; drop privileges.
  1138. (gather-user-privileges user))
  1139. (when (zero? (getuid))
  1140. (warning (G_ "server running as root; \
  1141. consider using the '--user' option!~%")))
  1142. (parameterize ((%public-key public-key)
  1143. (%private-key private-key)
  1144. (cache-bypass-threshold
  1145. (or (assoc-ref opts 'cache-bypass-threshold)
  1146. (cache-bypass-threshold))))
  1147. (info (G_ "publishing ~a on ~a, port ~d~%")
  1148. %store-directory
  1149. (inet-ntop (sockaddr:fam address) (sockaddr:addr address))
  1150. (sockaddr:port address))
  1151. (for-each (lambda (compression)
  1152. (info (G_ "using '~a' compression method, level ~a~%")
  1153. (compression-type compression)
  1154. (compression-level compression)))
  1155. compressions)
  1156. (when repl-port
  1157. (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port)))
  1158. ;; Set the name of the main thread.
  1159. (set-thread-name "guix publish")
  1160. (with-store store
  1161. (run-publish-server socket store
  1162. #:advertise? advertise?
  1163. #:port port
  1164. #:cache cache
  1165. #:pool (and cache (make-pool workers
  1166. #:thread-name
  1167. "publish worker"))
  1168. #:nar-path nar-path
  1169. #:compressions compressions
  1170. #:narinfo-negative-ttl negative-ttl
  1171. #:narinfo-ttl ttl))))))
  1172. ;;; Local Variables:
  1173. ;;; eval: (put 'single-baker 'scheme-indent-function 1)
  1174. ;;; End: