publish.scm 55 KB

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