unofficial mirror of guix-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [PATCH 0/2] Add 'guix publish' command
@ 2015-03-17 14:57 David Thompson
  2015-03-17 15:00 ` [PATCH 1/2] store: Add query-path-info operation David Thompson
                   ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: David Thompson @ 2015-03-17 14:57 UTC (permalink / raw)
  To: guix-devel

This patch set has been a long time coming.  Thanks to Ludo for helping
me solve the last blocking issue this morning.

This new utility allows anyone to publish their /gnu/store directory
over HTTP, exposing a Hydra-compatible API.

To use it, simply run `guix publish` on one of your machines, then tell
your other machines to use that one to fetch substitutes:

    guix archive --authorize < your-server-key.pub
    guix-daemon --substitute-urls=your-server:8080

Someday, Guix will support multiple substitution servers, which will
make this feature a lot more useful.  Being able to fetch substitutes
from hydra.gnu.org *and* from other Guix users that you trust will be
awesome. :)

TIA for the code review.

-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 1/2] store: Add query-path-info operation.
  2015-03-17 14:57 [PATCH 0/2] Add 'guix publish' command David Thompson
@ 2015-03-17 15:00 ` David Thompson
  2015-03-18  8:55   ` Ludovic Courtès
  2015-03-17 15:01 ` [PATCH 2/2] scripts: Add 'publish' command David Thompson
  2015-03-17 15:20 ` [PATCH 0/2] Add 'guix publish' command David Thompson
  2 siblings, 1 reply; 14+ messages in thread
From: David Thompson @ 2015-03-17 15:00 UTC (permalink / raw)
  To: guix-devel

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: 0001-store-Add-query-path-info-operation.patch --]
[-- Type: text/x-diff, Size: 2997 bytes --]

From e72bd43190bd561f7d96810a93f3b30f5f741343 Mon Sep 17 00:00:00 2001
From: David Thompson <dthompson2@worcester.edu>
Date: Tue, 17 Mar 2015 10:19:36 -0400
Subject: [PATCH 1/2] store: Add query-path-info operation.

* guix/store.scm (<path-info>): New record type.
  (read-path-info): New procedure.
  (read-arg): Add 'path-info' syntax.
  (query-path-info): New variable.
---
 guix/store.scm | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/guix/store.scm b/guix/store.scm
index a3f3cbf..8e1a180 100644
--- a/guix/store.scm
+++ b/guix/store.scm
@@ -60,6 +60,7 @@
             valid-path?
             query-path-hash
             hash-part->path
+            query-path-info
             add-text-to-store
             add-to-store
             build-things
@@ -79,6 +80,13 @@
             substitutable-paths
             substitutable-path-info
 
+            path-info?
+            path-info-deriver
+            path-info-hash
+            path-info-refs
+            path-info-reg-time
+            path-info-nar-size
+
             references
             requisites
             referrers
@@ -212,6 +220,24 @@
                 (cons (substitutable path deriver refs dl-size nar-size)
                       result))))))
 
+;; Information about a store path.
+(define-record-type <path-info>
+  (path-info deriver hash refs reg-time nar-size)
+  path-info?
+  (deriver path-info-deriver)
+  (hash path-info-hash)
+  (refs path-info-refs)
+  (reg-time path-info-reg-time)
+  (nar-size path-info-nar-size))
+
+(define (read-path-info p)
+  (let ((deriver  (read-store-path p))
+        (hash     (base16-string->bytevector (read-string p)))
+        (refs     (read-store-path-list p))
+        (reg-time (read-int p))
+        (nar-size (read-long-long p)))
+    (path-info deriver hash refs reg-time nar-size)))
+
 (define-syntax write-arg
   (syntax-rules (integer boolean file string string-list string-pairs
                  store-path store-path-list base16)
@@ -236,7 +262,7 @@
 
 (define-syntax read-arg
   (syntax-rules (integer boolean string store-path store-path-list
-                 substitutable-path-list base16)
+                 substitutable-path-list path-info base16)
     ((_ integer p)
      (read-int p))
     ((_ boolean p)
@@ -249,6 +275,8 @@
      (read-store-path-list p))
     ((_ substitutable-path-list p)
      (read-substitutable-path-list p))
+    ((_ path-info p)
+     (read-path-info p))
     ((_ base16 p)
      (base16-string->bytevector (read-string p)))))
 
@@ -532,6 +560,10 @@ string).  Raise an error if no such path exists."
      ;; /HASH.narinfo.
      (query-path-from-hash-part server hash-part))))
 
+(define-operation (query-path-info (store-path path))
+  "Return the derivation store path for PATH."
+  path-info)
+
 (define add-text-to-store
   ;; A memoizing version of `add-to-store', to avoid repeated RPCs with
   ;; the very same arguments during a given session.
-- 
2.1.4


[-- Attachment #2: Type: text/plain, Size: 136 bytes --]


-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-17 14:57 [PATCH 0/2] Add 'guix publish' command David Thompson
  2015-03-17 15:00 ` [PATCH 1/2] store: Add query-path-info operation David Thompson
@ 2015-03-17 15:01 ` David Thompson
  2015-03-18 10:27   ` Ludovic Courtès
  2015-03-17 15:20 ` [PATCH 0/2] Add 'guix publish' command David Thompson
  2 siblings, 1 reply; 14+ messages in thread
From: David Thompson @ 2015-03-17 15:01 UTC (permalink / raw)
  To: guix-devel

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: 0002-scripts-Add-publish-command.patch --]
[-- Type: text/x-diff, Size: 11032 bytes --]

From 7200a8ca892308a03a92e737b493244154bab358 Mon Sep 17 00:00:00 2001
From: David Thompson <dthompson2@worcester.edu>
Date: Tue, 17 Mar 2015 10:21:31 -0400
Subject: [PATCH 2/2] scripts: Add 'publish' command.

* guix/scripts/publish.scm: New file.
* Makefile.am (MODULES): Add it.
* doc/guix.texi ("Invoking guix publish"): New node.
---
 Makefile.am              |   1 +
 doc/guix.texi            |  41 +++++++++
 guix/scripts/publish.scm | 210 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 252 insertions(+)
 create mode 100644 guix/scripts/publish.scm

diff --git a/Makefile.am b/Makefile.am
index 882ab8e..703cbc1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -104,6 +104,7 @@ MODULES =					\
   guix/scripts/import/gnu.scm			\
   guix/scripts/import/nix.scm			\
   guix/scripts/environment.scm			\
+  guix/scripts/publish.scm			\
   guix.scm					\
   $(GNU_SYSTEM_MODULES)
 
diff --git a/doc/guix.texi b/doc/guix.texi
index b605c5b..71d70ed 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -120,6 +120,7 @@ Utilities
 * Invoking guix refresh::       Updating package definitions.
 * Invoking guix lint::          Finding errors in package definitions.
 * Invoking guix environment::   Setting up development environments.
+* Invoking guix publish::       Sharing substitutes.
 
 GNU Distribution
 
@@ -2712,6 +2713,7 @@ programming interface of Guix in a convenient way.
 * Invoking guix refresh::       Updating package definitions.
 * Invoking guix lint::          Finding errors in package definitions.
 * Invoking guix environment::   Setting up development environments.
+* Invoking guix publish::       Sharing substitutes.
 @end menu
 
 @node Invoking guix build
@@ -3374,6 +3376,45 @@ environment.
 It also supports all of the common build options that @command{guix
 build} supports (@pxref{Invoking guix build, common build options}).
 
+@node Invoking guix publish
+@section Invoking @command{guix publish}
+
+The purpose of @command{guix publish} is to expose a Hydra-compatible
+HTTP API for sharing substitutes from the local store.
+
+The general syntax is:
+
+@example
+guix publish @var{options}@dots{}
+@end example
+
+Running @command{guix publish} without any additional arguments will
+spawn an HTTP server on port 8080:
+
+@example
+guix publish
+@end example
+
+Once a publishing server has been authorized (@pxref{Invoking guix archive}),
+the Guix daemon may use it to download substitutes:
+
+@example
+guix-daemon --substitute-urls=example.org:8080
+@end example
+
+The following options are available:
+
+@table @code
+@item --port=@var{port}
+@itemx -p @var{port}
+Listen for HTTP requests on @var{port}.
+
+@item --repl[=@var{port}]
+@itemx -r [@var{port}]
+Spawn a Guile REPL server (@pxref{REPL Servers,,, guile, GNU Guile
+Reference Manual}) on @var{port} (37146 by default).  Useful for developers.
+@end table
+
 @c *********************************************************************
 @node GNU Distribution
 @chapter GNU Distribution
diff --git a/guix/scripts/publish.scm b/guix/scripts/publish.scm
new file mode 100644
index 0000000..6be5ca6
--- /dev/null
+++ b/guix/scripts/publish.scm
@@ -0,0 +1,210 @@
+(define-module (guix scripts publish)
+  #:use-module ((system repl server) #:prefix repl:)
+  #:use-module (ice-9 binary-ports)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:use-module (rnrs io ports)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-2)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (web http)
+  #:use-module (web request)
+  #:use-module (web response)
+  #:use-module (web server)
+  #:use-module (web uri)
+  #:use-module (guix base32)
+  #:use-module (guix base64)
+  #:use-module (guix config)
+  #:use-module (guix derivations)
+  #:use-module (guix hash)
+  #:use-module (guix pki)
+  #:use-module (guix pk-crypto)
+  #:use-module (guix store)
+  #:use-module (guix serialization)
+  #:use-module (guix ui)
+  #:export (guix-publish))
+
+(define (show-help)
+  (display (_ "Usage: guix publish [OPTION]...
+Publish the store directory over HTTP.\n"))
+  (display (_ "
+  -p, --port=PORT        listen on PORT"))
+  (display (_ "
+  -r, --repl[=PORT]      spawn REPL server on PORT"))
+  (newline)
+  (display (_ "
+  -h, --help             display this help and exit"))
+  (display (_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  (list (option '(#\h "help") #f #f
+                (lambda _
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda _
+                  (show-version-and-exit "guix publish")))
+        (option '(#\p "port") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'port (string->number* arg) result)))
+        (option '(#\r "repl") #f #t
+                (lambda (opt name arg result)
+                  ;; If port unspecified, use default Guile REPL port.
+                  (let ((port (and arg (string->number* arg))))
+                    (alist-cons 'repl (or port 37146) result))))))
+
+(define %default-options
+  '((port . 8080)
+    (repl . #f)))
+
+(define %store (open-connection))
+
+(define %private-key
+  (call-with-input-file %private-key-file
+    (compose string->canonical-sexp
+             get-string-all)))
+
+(define %public-key
+  (call-with-input-file %public-key-file
+    (compose string->canonical-sexp
+             get-string-all)))
+
+(define %nix-cache-info
+  `(("StoreDir" . ,%store-directory)
+    ("WantMassQuery" . 0)
+    ("Priority" . 100)))
+
+(define (load-derivation file-name)
+  "Read the derivation located at FILE-NAME."
+  (with-input-from-file file-name
+    (lambda ()
+      (read-derivation (current-input-port)))))
+
+(define (false-if-empty-string str)
+  (and (not (string-null? str)) str))
+
+(define (sign-string s)
+  "Sign the hash of the string S with the daemon's key."
+  (let ((hash (bytevector->hash-data (sha256 (string->utf8 s)))))
+    (signature-sexp hash %private-key %public-key)))
+
+(define base64-encode-string
+  (compose base64-encode string->utf8))
+
+(define (narinfo-string store-path path-info derivation deriver key)
+  (let* ((url (string-append "nar/" (basename store-path)))
+         (nar-hash (bytevector->base32-string
+                    (path-info-hash path-info)))
+         (nar-size (path-info-nar-size path-info))
+         (references (string-join (map basename (path-info-refs path-info))
+                                  " "))
+         (system (derivation-system derivation))
+         (deriver (basename deriver))
+         (info (format #f
+                       "StorePath: ~a
+URL: ~a
+Compression: none
+NarHash: sha256:~a
+NarSize: ~d
+References: ~a
+System: ~a
+Deriver: ~a~%"
+                       store-path url nar-hash nar-size references
+                       system deriver))
+         (signature (base64-encode-string
+                     (canonical-sexp->string (sign-string info)))))
+    (format #f "~aSignature: 1;~a;~a~%" info (gethostname) signature)))
+
+(define (not-found request)
+  "Render 404 response for REQUEST."
+  (values (build-response #:code 404)
+          (string-append "Resource not found: "
+                         (uri-path (request-uri request)))))
+
+(define (render-nix-cache-info)
+  "Render server information."
+  (values '((content-type . (text/plain)))
+          (lambda (port)
+            (for-each (match-lambda
+                       ((key . value)
+                        (format port "~a: ~a~%" key value)))
+                      %nix-cache-info))))
+
+(define (render-narinfo request hash)
+  "Render metadata for the store path corresponding to HASH."
+  (apply values
+         (or (and-let* ((store-path (false-if-empty-string
+                                     (hash-part->path %store hash)))
+                        (path-info (query-path-info %store store-path))
+                        (deriver (let ((d (path-info-deriver path-info)))
+                                   (if (string-null? d) store-path d)))
+                        (drv (and (file-exists? deriver)
+                                  (load-derivation deriver)))
+                        (info (narinfo-string store-path path-info drv
+                                              deriver %private-key)))
+               (list '((content-type . (application/x-nix-narinfo)))
+                     (lambda (port)
+                       (display info port))))
+             (call-with-values (lambda () (not-found request)) list))))
+
+(define (render-nar request store-item)
+  "Render archive of the store path corresponding to STORE-ITEM."
+  (let ((store-path (string-append %store-directory "/" store-item)))
+    (values '((content-type . (application/x-nix-archive
+                               (charset . "ISO-8859-1"))))
+            (lambda (port)
+              (write-file store-path port)))))
+
+(define extract-narinfo-hash
+  (let ((regexp (make-regexp "^([a-df-np-sv-z0-9]{32}).narinfo$")))
+    (lambda (str)
+      "Return the hash within the narinfo resource string STR, or false if STR
+is invalid."
+      (and=> (regexp-exec regexp str)
+             (cut match:substring <> 1)))))
+
+(define (get-request? request)
+  "Return #t if REQUEST uses the GET method."
+  (eq? (request-method request) 'GET))
+
+(define (request-path-components request)
+  "Split the URI path of REQUEST into a list of component strings.  For
+example: \"/foo/bar\" yields '(\"foo\" \"bar\")."
+  (split-and-decode-uri-path (uri-path (request-uri request))))
+
+(define (request-handler request body)
+  (format #t "~a ~a~%"
+          (request-method request)
+          (uri-path (request-uri request)))
+  (if (get-request? request) ; reject POST, PUT, etc.
+      (match (request-path-components request)
+        (("nix-cache-info")                          ; /nix-cache-info
+         (render-nix-cache-info))
+        (((= extract-narinfo-hash (? string? hash))) ; /<hash>.narinfo
+         (render-narinfo request hash))
+        (("nar" store-path)                          ; /nar/<hash-part>
+         (render-nar request store-path))
+        (_ (not-found request)))
+      (not-found request)))
+
+(define (run-publish-server port)
+  (run-server (lambda args (apply request-handler args))
+              'http
+              `(#:addr ,INADDR_ANY
+                #:port ,port)))
+
+(define (guix-publish . args)
+  (with-error-handling
+    (let* ((opts (parse-command-line args %options (list %default-options)))
+           (port (assoc-ref opts 'port))
+           (repl-port (assoc-ref opts 'repl)))
+      (format #t "Publishing store on port ~d~%" port)
+      (when repl-port
+        (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port)))
+      (run-publish-server (assoc-ref opts 'port)))))
-- 
2.1.4


[-- Attachment #2: Type: text/plain, Size: 136 bytes --]


-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/2] Add 'guix publish' command
  2015-03-17 14:57 [PATCH 0/2] Add 'guix publish' command David Thompson
  2015-03-17 15:00 ` [PATCH 1/2] store: Add query-path-info operation David Thompson
  2015-03-17 15:01 ` [PATCH 2/2] scripts: Add 'publish' command David Thompson
@ 2015-03-17 15:20 ` David Thompson
  2 siblings, 0 replies; 14+ messages in thread
From: David Thompson @ 2015-03-17 15:20 UTC (permalink / raw)
  To: guix-devel

David Thompson <dthompson2@worcester.edu> writes:

> This patch set has been a long time coming.  Thanks to Ludo for helping
> me solve the last blocking issue this morning.
>
> This new utility allows anyone to publish their /gnu/store directory
> over HTTP, exposing a Hydra-compatible API.
>
> To use it, simply run `guix publish` on one of your machines, then tell
> your other machines to use that one to fetch substitutes:
>
>     guix archive --authorize < your-server-key.pub
>     guix-daemon --substitute-urls=your-server:8080

Oh, I forgot to mention a limitation: 'guix publish' currently does not
use compress archives, so it uses a lot more bandwidth than Hydra does
currently.  In the future, it will be changed to use bz2 compression
like Hydra.

-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/2] store: Add query-path-info operation.
  2015-03-17 15:00 ` [PATCH 1/2] store: Add query-path-info operation David Thompson
@ 2015-03-18  8:55   ` Ludovic Courtès
  2015-03-27 16:56     ` David Thompson
  0 siblings, 1 reply; 14+ messages in thread
From: Ludovic Courtès @ 2015-03-18  8:55 UTC (permalink / raw)
  To: David Thompson; +Cc: guix-devel

David Thompson <dthompson2@worcester.edu> skribis:

> From e72bd43190bd561f7d96810a93f3b30f5f741343 Mon Sep 17 00:00:00 2001
> From: David Thompson <dthompson2@worcester.edu>
> Date: Tue, 17 Mar 2015 10:19:36 -0400
> Subject: [PATCH 1/2] store: Add query-path-info operation.
>
> * guix/store.scm (<path-info>): New record type.
>   (read-path-info): New procedure.
>   (read-arg): Add 'path-info' syntax.
>   (query-path-info): New variable.

[...]

> +  (reg-time path-info-reg-time)

Please change the procedure name to ‘path-info-registration-time’.

> +(define-operation (query-path-info (store-path path))
> +  "Return the derivation store path for PATH."
> +  path-info)

Invalid docstring.

Could you add a test in tests/store.scm?  It could add a file with
‘add-text-to-store’, with a non-empty reference list, and check its
references and hash, for instance.

Thanks!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-17 15:01 ` [PATCH 2/2] scripts: Add 'publish' command David Thompson
@ 2015-03-18 10:27   ` Ludovic Courtès
  2015-03-27 16:58     ` David Thompson
  0 siblings, 1 reply; 14+ messages in thread
From: Ludovic Courtès @ 2015-03-18 10:27 UTC (permalink / raw)
  To: David Thompson; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 1370 bytes --]

David Thompson <dthompson2@worcester.edu> skribis:

> From 7200a8ca892308a03a92e737b493244154bab358 Mon Sep 17 00:00:00 2001
> From: David Thompson <dthompson2@worcester.edu>
> Date: Tue, 17 Mar 2015 10:21:31 -0400
> Subject: [PATCH 2/2] scripts: Add 'publish' command.
>
> * guix/scripts/publish.scm: New file.
> * Makefile.am (MODULES): Add it.
> * doc/guix.texi ("Invoking guix publish"): New node.

Yaaay!

> +@node Invoking guix publish
> +@section Invoking @command{guix publish}
> +
> +The purpose of @command{guix publish} is to expose a Hydra-compatible
> +HTTP API for sharing substitutes from the local store.

s/API/interface/ maybe

I think we should first describe the functionality (what it means to
share the store over HTTP), and only then mention Hydra-compatibility
(which is not something users really care about.)

There should be a word about signing, with an xref to ‘guix archive’ I
think.  Perhaps later we could add an option to choose the signing key.

> +@example
> +guix-daemon --substitute-urls=example.org:8080

It should have “http://”.

Eventually™ it will be possible to specify substitute URLs from the
client; whether to actually use them with still be decided based on the
keys the sysadmin authorized.  Preliminary patch that adds
‘--substitute-urls’ to ‘guix build’ et al.:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-patch, Size: 3429 bytes --]

--- a/guix/store.scm
+++ b/guix/store.scm
@@ -484,11 +512,11 @@ encoding conversion errors."
     (when (>= (nix-server-minor-version server) 10)
       (send (boolean use-substitutes?)))
     (when (>= (nix-server-minor-version server) 12)
-      (let ((pairs (if timeout
-                       `(("build-timeout" . ,(number->string timeout))
-                         ,@binary-caches)
-                       binary-caches)))
-        (send (string-pairs pairs))))
+      (let ((pairs `(,@(if timeout
+                           `(("build-timeout" . ,(number->string timeout)))
+                           '())
+                     ("substitute-urls" . ,(string-join substitute-urls)))))
+        (send (string-pairs (pk 'pairs pairs)))))
     (let loop ((done? (process-stderr server)))
       (or done? (process-stderr server)))))
 
diff --git a/guix/scripts/build.scm b/guix/scripts/build.scm
index 370c2a3..df38b5e 100644
--- a/guix/scripts/build.scm
+++ b/guix/scripts/build.scm
@@ -110,6 +110,9 @@ options handled by 'set-build-options-from-command-line', and listed in
   (display (_ "
       --no-substitutes   build instead of resorting to pre-built substitutes"))
   (display (_ "
+      --substitute-urls=URLS
+                         fetch substitute from URLS if they are authorized"))
+  (display (_ "
       --no-build-hook    do not attempt to offload builds via the build hook"))
   (display (_ "
       --max-silent-time=SECONDS
@@ -133,6 +136,8 @@ options handled by 'set-build-options-from-command-line', and listed in
                      #:max-build-jobs (or (assoc-ref opts 'max-jobs) 1)
                      #:fallback? (assoc-ref opts 'fallback?)
                      #:use-substitutes? (assoc-ref opts 'substitutes?)
+                     #:substitute-urls (or (assoc-ref opts 'substitute-urls)
+                                           '())
                      #:use-build-hook? (assoc-ref opts 'build-hook?)
                      #:max-silent-time (assoc-ref opts 'max-silent-time)
                      #:timeout (assoc-ref opts 'timeout)
@@ -166,6 +171,13 @@ options handled by 'set-build-options-from-command-line', and listed in
                          (alist-cons 'substitutes? #f
                                      (alist-delete 'substitutes? result))
                          rest)))
+        (option '("substitute-urls") #t #f
+                (lambda (opt name arg result . rest)
+                  (apply values
+                         (alist-cons 'substitute-urls
+                                     (string-tokenize arg)
+                                     (alist-delete 'substitute-urls result))
+                         rest)))
         (option '("no-build-hook") #f #f
                 (lambda (opt name arg result . rest)
                   (apply values

diff --git a/guix/scripts/substitute-binary.scm b/guix/scripts/substitute-binary.scm
index 903564c..1d45753 100755
--- a/guix/scripts/substitute-binary.scm
+++ b/guix/scripts/substitute-binary.scm
@@ -631,7 +631,10 @@ found."
   (assoc-ref (daemon-options) option))
 
 (define %cache-url
-  (match (and=> (find-daemon-option "substitute-urls")
+  (match (and=> (string-append
+                 (find-daemon-option "untrusted-substitute-urls") ;client
+                 " "
+                 (find-daemon-option "substitute-urls"))          ;admin
                 string-tokenize)
     ((url)
      url)

[-- Attachment #3: Type: text/plain, Size: 3438 bytes --]


I won’t commit it yet because at this point the substituter caches only
from one server, so users could populate
/var/guix/substitute-binary/cache and lead other users to use the
substituter that they chose (or to use nothing if the substituter server
in question returned 404 for all the narinfos.)

That should be easily fixed though (for the interested reader? ;-)).

> --- /dev/null
> +++ b/guix/scripts/publish.scm

Make sure to add the license header.

> +  (display (_ "Usage: guix publish [OPTION]...
> +Publish the store directory over HTTP.\n"))

Maybe “Publish ~a over HTTP” with (%store-directory) would be more
immediately obvious (and translations would be accurate ;-)).

> +(define (load-derivation file-name)
> +  "Read the derivation located at FILE-NAME."
> +  (with-input-from-file file-name
> +    (lambda ()
> +      (read-derivation (current-input-port)))))

(call-with-input-file file read-derivation)

> +(define (sign-string s)
> +  "Sign the hash of the string S with the daemon's key."
> +  (let ((hash (bytevector->hash-data (sha256 (string->utf8 s)))))
> +    (signature-sexp hash %private-key %public-key)))

I had to change it to:

  (define (sign-string s)
    "Sign the hash of the string S with the daemon's key."
    (let ((hash (bytevector->hash-data (sha256 (string->utf8 s))
                                       #:key-type (key-type %public-key))))
      (signature-sexp hash %private-key %public-key)))

Otherwise, ‘bytevector->hash-data’ will assume you have an ECC key and
‘sign’ will raise an exception if you happen to have an RSA key, for
instance.

Maybe ‘signed-string’ would be a more appropriate name since it’s a pure
function.

> +(define (narinfo-string store-path path-info derivation deriver key)

Docstring please.  I would suggest using keyword arguments for arguments
above position 2.

Aren’t ‘derivation’ and ‘deriver’ redundant with ‘path-info’?

> +  (let* ((url (string-append "nar/" (basename store-path)))
> +         (nar-hash (bytevector->base32-string
> +                    (path-info-hash path-info)))
> +         (nar-size (path-info-nar-size path-info))
> +         (references (string-join (map basename (path-info-refs path-info))
> +                                  " "))
> +         (system (derivation-system derivation))
> +         (deriver (basename deriver))
> +         (info (format #f

Please align the RHS and maybe use single-word identifiers.  (I hate it
when I look this fussy.)

> +    (values '((content-type . (application/x-nix-archive
> +                               (charset . "ISO-8859-1"))))

Please add a comment saying that choosing ISO-8859-1 is crucial since
otherwise HTTP clients will interpret the byte stream as UTF-8 and
arbitrarily change invalid byte sequences.  We don’t want anyone to feel
that pain again.  ;-)

> +      (format #t "Publishing store on port ~d~%" port)

Lowercase and use (_ "publishing ..."), and add the file to
po/guix/POTFILES.in.

Now, it would be good to add a bunch of tests.  :-)

Perhaps one way to do it would be to write them in Scheme, and invoke
‘guix-publish’ in a thread, similar to the HTTP tests in
tests/lint.scm.  From there we could check .narinfo and .nar URLs.
WDYT?

Thanks for working on it in spite of the numerous issues you
encountered!

Ludo’.

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/2] store: Add query-path-info operation.
  2015-03-18  8:55   ` Ludovic Courtès
@ 2015-03-27 16:56     ` David Thompson
  2015-03-27 21:30       ` Ludovic Courtès
  0 siblings, 1 reply; 14+ messages in thread
From: David Thompson @ 2015-03-27 16:56 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 1012 bytes --]

Ludovic Courtès <ludo@gnu.org> writes:

> David Thompson <dthompson2@worcester.edu> skribis:
>
>> From e72bd43190bd561f7d96810a93f3b30f5f741343 Mon Sep 17 00:00:00 2001
>> From: David Thompson <dthompson2@worcester.edu>
>> Date: Tue, 17 Mar 2015 10:19:36 -0400
>> Subject: [PATCH 1/2] store: Add query-path-info operation.
>>
>> * guix/store.scm (<path-info>): New record type.
>>   (read-path-info): New procedure.
>>   (read-arg): Add 'path-info' syntax.
>>   (query-path-info): New variable.
>
> [...]
>
>> +  (reg-time path-info-reg-time)
>
> Please change the procedure name to ‘path-info-registration-time’.
>
>> +(define-operation (query-path-info (store-path path))
>> +  "Return the derivation store path for PATH."
>> +  path-info)
>
> Invalid docstring.
>
> Could you add a test in tests/store.scm?  It could add a file with
> ‘add-text-to-store’, with a non-empty reference list, and check its
> references and hash, for instance.

Done.

New patch below.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-store-Add-query-path-info-operation.patch --]
[-- Type: text/x-diff, Size: 3873 bytes --]

From d86678e29c951ae4983cea92074e8f04c3e49f50 Mon Sep 17 00:00:00 2001
From: David Thompson <dthompson2@worcester.edu>
Date: Tue, 17 Mar 2015 10:19:36 -0400
Subject: [PATCH 1/2] store: Add query-path-info operation.

* guix/store.scm (<path-info>): New record type.
  (read-path-info): New procedure.
  (read-arg): Add 'path-info' syntax.
  (query-path-info): New variable.
* tests/store.scm ("query-path-info"): New test.
---
 guix/store.scm  | 34 +++++++++++++++++++++++++++++++++-
 tests/store.scm | 10 ++++++++++
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/guix/store.scm b/guix/store.scm
index 3d6b069..10b9062 100644
--- a/guix/store.scm
+++ b/guix/store.scm
@@ -60,6 +60,7 @@
             valid-path?
             query-path-hash
             hash-part->path
+            query-path-info
             add-text-to-store
             add-to-store
             build-things
@@ -79,6 +80,13 @@
             substitutable-paths
             substitutable-path-info
 
+            path-info?
+            path-info-deriver
+            path-info-hash
+            path-info-references
+            path-info-registration-time
+            path-info-nar-size
+
             references
             requisites
             referrers
@@ -212,6 +220,24 @@
                 (cons (substitutable path deriver refs dl-size nar-size)
                       result))))))
 
+;; Information about a store path.
+(define-record-type <path-info>
+  (path-info deriver hash references registration-time nar-size)
+  path-info?
+  (deriver path-info-deriver)
+  (hash path-info-hash)
+  (references path-info-references)
+  (registration-time path-info-registration-time)
+  (nar-size path-info-nar-size))
+
+(define (read-path-info p)
+  (let ((deriver  (read-store-path p))
+        (hash     (base16-string->bytevector (read-string p)))
+        (refs     (read-store-path-list p))
+        (registration-time (read-int p))
+        (nar-size (read-long-long p)))
+    (path-info deriver hash refs registration-time nar-size)))
+
 (define-syntax write-arg
   (syntax-rules (integer boolean file string string-list string-pairs
                  store-path store-path-list base16)
@@ -236,7 +262,7 @@
 
 (define-syntax read-arg
   (syntax-rules (integer boolean string store-path store-path-list
-                 substitutable-path-list base16)
+                 substitutable-path-list path-info base16)
     ((_ integer p)
      (read-int p))
     ((_ boolean p)
@@ -249,6 +275,8 @@
      (read-store-path-list p))
     ((_ substitutable-path-list p)
      (read-substitutable-path-list p))
+    ((_ path-info p)
+     (read-path-info p))
     ((_ base16 p)
      (base16-string->bytevector (read-string p)))))
 
@@ -541,6 +569,10 @@ string).  Raise an error if no such path exists."
      ;; /HASH.narinfo.
      (query-path-from-hash-part server hash-part))))
 
+(define-operation (query-path-info (store-path path))
+  "Return the info (hash, references, etc.) for PATH."
+  path-info)
+
 (define add-text-to-store
   ;; A memoizing version of `add-to-store', to avoid repeated RPCs with
   ;; the very same arguments during a given session.
diff --git a/tests/store.scm b/tests/store.scm
index f778c20..eeceed4 100644
--- a/tests/store.scm
+++ b/tests/store.scm
@@ -606,6 +606,16 @@
          (file (add %store "foo" "Lowered.")))
     (call-with-input-file file get-string-all)))
 
+(test-assert "query-path-info"
+  (let* ((ref (add-text-to-store %store "ref" "foo"))
+         (item (add-text-to-store %store "item" "bar" (list ref)))
+         (info (query-path-info %store item)))
+    (and (equal? (path-info-references info) (list ref))
+         (equal? (path-info-hash info)
+                 (sha256
+                  (string->utf8
+                   (call-with-output-string (cut write-file item <>))))))))
+
 (test-end "store")
 
 \f
-- 
2.1.4


[-- Attachment #3: Type: text/plain, Size: 136 bytes --]


-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-18 10:27   ` Ludovic Courtès
@ 2015-03-27 16:58     ` David Thompson
  2015-03-27 22:41       ` Ludovic Courtès
  0 siblings, 1 reply; 14+ messages in thread
From: David Thompson @ 2015-03-27 16:58 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel

[-- Attachment #1: Type: text/plain, Size: 549 bytes --]

Ludovic Courtès <ludo@gnu.org> writes:

> [...]

Fixed everything you mentioned.

> Now, it would be good to add a bunch of tests.  :-)
>
> Perhaps one way to do it would be to write them in Scheme, and invoke
> ‘guix-publish’ in a thread, similar to the HTTP tests in
> tests/lint.scm.  From there we could check .narinfo and .nar URLs.
> WDYT?

Good idea.  That's what I ended up doing.

> Thanks for working on it in spite of the numerous issues you
> encountered!

And thanks for the thorough review!  New patch below:


[-- Attachment #2: 0002-scripts-Add-publish-command.patch --]
[-- Type: text/x-diff, Size: 18012 bytes --]

From a40d47dc64571aade0c92b4bdf3c56f6870842cc Mon Sep 17 00:00:00 2001
From: David Thompson <dthompson2@worcester.edu>
Date: Tue, 17 Mar 2015 10:21:31 -0400
Subject: [PATCH 2/2] scripts: Add 'publish' command.

* guix/scripts/publish.scm: New file.
* po/guix/POTFILES.in: Add it.
* tests/publish.scm: New file.
* Makefile.am (MODULES): Add script module.
  (SCM_TESTS): Add test module.
* doc/guix.texi ("Invoking guix publish"): New node.
---
 Makefile.am              |   4 +-
 doc/guix.texi            |  51 ++++++++++-
 guix/scripts/publish.scm | 233 +++++++++++++++++++++++++++++++++++++++++++++++
 po/guix/POTFILES.in      |   1 +
 tests/publish.scm        | 114 +++++++++++++++++++++++
 5 files changed, 401 insertions(+), 2 deletions(-)
 create mode 100644 guix/scripts/publish.scm
 create mode 100644 tests/publish.scm

diff --git a/Makefile.am b/Makefile.am
index 4a1f8d0..4ff7e65 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -104,6 +104,7 @@ MODULES =					\
   guix/scripts/import/gnu.scm			\
   guix/scripts/import/nix.scm			\
   guix/scripts/environment.scm			\
+  guix/scripts/publish.scm			\
   guix.scm					\
   $(GNU_SYSTEM_MODULES)
 
@@ -178,7 +179,8 @@ SCM_TESTS =					\
   tests/union.scm				\
   tests/profiles.scm				\
   tests/syscalls.scm				\
-  tests/lint.scm
+  tests/lint.scm				\
+  tests/publish.scm
 
 if HAVE_GUILE_JSON
 
diff --git a/doc/guix.texi b/doc/guix.texi
index 3c72e65..1b81217 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -121,6 +121,7 @@ Utilities
 * Invoking guix refresh::       Updating package definitions.
 * Invoking guix lint::          Finding errors in package definitions.
 * Invoking guix environment::   Setting up development environments.
+* Invoking guix publish::       Sharing substitutes.
 
 GNU Distribution
 
@@ -2511,7 +2512,7 @@ To illustrate the idea, here is an example of a gexp:
   #~(begin
       (mkdir #$output)
       (chdir #$output)
-      (symlink (string-append #$coreutils "/bin/ls") 
+      (symlink (string-append #$coreutils "/bin/ls")
                "list-files")))
 @end example
 
@@ -2746,6 +2747,7 @@ programming interface of Guix in a convenient way.
 * Invoking guix refresh::       Updating package definitions.
 * Invoking guix lint::          Finding errors in package definitions.
 * Invoking guix environment::   Setting up development environments.
+* Invoking guix publish::       Sharing substitutes.
 @end menu
 
 @node Invoking guix build
@@ -3408,6 +3410,53 @@ environment.
 It also supports all of the common build options that @command{guix
 build} supports (@pxref{Invoking guix build, common build options}).
 
+@node Invoking guix publish
+@section Invoking @command{guix publish}
+
+The purpose of @command{guix publish} is to enable users to easily share
+their store with others.  When @command{guix publish} runs, it spawns an
+HTTP server which allows anyone with network access to obtain
+substitutes from it.  This means that any machine running Guix can also
+act as if it were a build farm, since the HTTP interface is
+Hydra-compatible.  For security, each substitute is signed with the
+system's signing key (@pxref{Invoking guix archive}).
+
+@command{guix publish} is a tool for system administrators, so only the
+root user may invoke it.
+
+The general syntax is:
+
+@example
+guix publish @var{options}@dots{}
+@end example
+
+Running @command{guix publish} without any additional arguments will
+spawn an HTTP server on port 8080:
+
+@example
+guix publish
+@end example
+
+Once a publishing server has been authorized (@pxref{Invoking guix archive}),
+the Guix daemon may use it to download substitutes:
+
+@example
+guix-daemon --substitute-urls=http://example.org:8080
+@end example
+
+The following options are available:
+
+@table @code
+@item --port=@var{port}
+@itemx -p @var{port}
+Listen for HTTP requests on @var{port}.
+
+@item --repl[=@var{port}]
+@itemx -r [@var{port}]
+Spawn a Guile REPL server (@pxref{REPL Servers,,, guile, GNU Guile
+Reference Manual}) on @var{port} (37146 by default).
+@end table
+
 @c *********************************************************************
 @node GNU Distribution
 @chapter GNU Distribution
diff --git a/guix/scripts/publish.scm b/guix/scripts/publish.scm
new file mode 100644
index 0000000..1339e3b
--- /dev/null
+++ b/guix/scripts/publish.scm
@@ -0,0 +1,233 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 David Thompson <davet@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts publish)
+  #:use-module ((system repl server) #:prefix repl:)
+  #:use-module (ice-9 binary-ports)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:use-module (rnrs io ports)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-2)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (web http)
+  #:use-module (web request)
+  #:use-module (web response)
+  #:use-module (web server)
+  #:use-module (web uri)
+  #:use-module (guix base32)
+  #:use-module (guix base64)
+  #:use-module (guix config)
+  #:use-module (guix derivations)
+  #:use-module (guix hash)
+  #:use-module (guix pki)
+  #:use-module (guix pk-crypto)
+  #:use-module (guix store)
+  #:use-module (guix serialization)
+  #:use-module (guix ui)
+  #:export (guix-publish))
+
+(define (show-help)
+  (format #t (_ "Usage: guix publish [OPTION]...
+Publish ~a over HTTP.\n") %store-directory)
+  (display (_ "
+  -p, --port=PORT        listen on PORT"))
+  (display (_ "
+  -r, --repl[=PORT]      spawn REPL server on PORT"))
+  (newline)
+  (display (_ "
+  -h, --help             display this help and exit"))
+  (display (_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  (list (option '(#\h "help") #f #f
+                (lambda _
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda _
+                  (show-version-and-exit "guix publish")))
+        (option '(#\p "port") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'port (string->number* arg) result)))
+        (option '(#\r "repl") #f #t
+                (lambda (opt name arg result)
+                  ;; If port unspecified, use default Guile REPL port.
+                  (let ((port (and arg (string->number* arg))))
+                    (alist-cons 'repl (or port 37146) result))))))
+
+(define %default-options
+  '((port . 8080)
+    (repl . #f)))
+
+(define (read-file-sexp file)
+  (call-with-input-file file
+    (compose string->canonical-sexp
+             get-string-all)))
+
+(define %private-key
+  (read-file-sexp %private-key-file))
+
+(define %public-key
+  (read-file-sexp %public-key-file))
+
+(define %nix-cache-info
+  `(("StoreDir" . ,%store-directory)
+    ("WantMassQuery" . 0)
+    ("Priority" . 100)))
+
+(define (load-derivation file)
+  "Read the derivation from FILE."
+  (call-with-input-file file read-derivation))
+
+(define (signed-string s)
+  "Sign the hash of the string S with the daemon's key."
+  (let ((hash (bytevector->hash-data (sha256 (string->utf8 s))
+                                     #:key-type (key-type %public-key))))
+    (signature-sexp hash %private-key %public-key)))
+
+(define base64-encode-string
+  (compose base64-encode string->utf8))
+
+(define (narinfo-string store-path path-info key)
+  (let* ((url        (string-append "nar/" (basename store-path)))
+         (hash       (bytevector->base32-string
+                      (path-info-hash path-info)))
+         (size       (path-info-nar-size path-info))
+         (references (string-join
+                      (map basename (path-info-references path-info))
+                      " "))
+         (deriver (path-info-deriver path-info))
+         (base-info  (format #f
+                             "StorePath: ~a
+URL: ~a
+Compression: none
+NarHash: sha256:~a
+NarSize: ~d
+References: ~a~%"
+                             store-path url hash size references))
+         ;; Do not render a "Deriver" or "System" line if we are rendering
+         ;; info for a derivation.
+         (info (if (string-null? deriver)
+                   base-info
+                   (let ((drv (load-derivation deriver)))
+                     (format #f "~aSystem: ~a~%Deriver: ~a~%"
+                             base-info (derivation-system drv)
+                             (basename deriver)))))
+         (signature  (base64-encode-string
+                      (canonical-sexp->string (signed-string info)))))
+    (format #f "~aSignature: 1;~a;~a~%" info (gethostname) signature)))
+
+(define (not-found request)
+  "Render 404 response for REQUEST."
+  (values (build-response #:code 404)
+          (string-append "Resource not found: "
+                         (uri-path (request-uri request)))))
+
+(define (render-nix-cache-info)
+  "Render server information."
+  (values '((content-type . (text/plain)))
+          (lambda (port)
+            (for-each (match-lambda
+                       ((key . value)
+                        (format port "~a: ~a~%" key value)))
+                      %nix-cache-info))))
+
+(define (render-narinfo store request hash)
+  "Render metadata for the store path corresponding to HASH."
+  (let* ((store-path (hash-part->path store hash))
+         (path-info (and (not (string-null? store-path))
+                         (query-path-info store store-path))))
+    (if path-info
+        (values '((content-type . (application/x-nix-narinfo)))
+                (cut display
+                     (narinfo-string store-path path-info %private-key)
+                     <>))
+        (not-found request))))
+
+(define (render-nar request store-item)
+  "Render archive of the store path corresponding to STORE-ITEM."
+  (let ((store-path (string-append %store-directory "/" store-item)))
+    ;; The ISO-8859-1 charset *must* be used otherwise HTTP clients will
+    ;; interpret the byte stream as UTF-8 and arbitrarily change invalid byte
+    ;; sequences.
+    (if (file-exists? store-path)
+        (values '((content-type . (application/x-nix-archive
+                                   (charset . "ISO-8859-1"))))
+                (lambda (port)
+                  (write-file store-path port)))
+        (not-found request))))
+
+(define extract-narinfo-hash
+  (let ((regexp (make-regexp "^([a-df-np-sv-z0-9]{32}).narinfo$")))
+    (lambda (str)
+      "Return the hash within the narinfo resource string STR, or false if STR
+is invalid."
+      (and=> (regexp-exec regexp str)
+             (cut match:substring <> 1)))))
+
+(define (get-request? request)
+  "Return #t if REQUEST uses the GET method."
+  (eq? (request-method request) 'GET))
+
+(define (request-path-components request)
+  "Split the URI path of REQUEST into a list of component strings.  For
+example: \"/foo/bar\" yields '(\"foo\" \"bar\")."
+  (split-and-decode-uri-path (uri-path (request-uri request))))
+
+(define (make-request-handler store)
+  (lambda (request body)
+    (format #t "~a ~a~%"
+            (request-method request)
+            (uri-path (request-uri request)))
+    (if (get-request? request) ; reject POST, PUT, etc.
+        (match (request-path-components request)
+          ;; /nix-cache-info
+          (("nix-cache-info")
+           (render-nix-cache-info))
+          ;; /<hash>.narinfo
+          (((= extract-narinfo-hash (? string? hash)))
+           (render-narinfo store request hash))
+          ;; /nar/<store-item>
+          (("nar" store-item)
+           (render-nar request store-item))
+          (_ (not-found request)))
+        (not-found request))))
+
+(define (run-publish-server port store)
+  (run-server (make-request-handler store)
+              'http
+              `(#:addr ,INADDR_ANY
+                #:port ,port)))
+
+(define (guix-publish . args)
+  (with-error-handling
+    (let* ((opts (parse-command-line args %options (list %default-options)))
+           (port (assoc-ref opts 'port))
+           (repl-port (assoc-ref opts 'repl))
+           (store (open-connection)))
+      (format #t (_ "publishing ~a on port ~d~%") %store-directory port)
+      (when repl-port
+        (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port)))
+      (run-publish-server (assoc-ref opts 'port) store))))
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 619f6f9..998c611 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -13,6 +13,7 @@ guix/scripts/substitute-binary.scm
 guix/scripts/authenticate.scm
 guix/scripts/system.scm
 guix/scripts/lint.scm
+guix/scripts/publish.scm
 guix/gnu-maintenance.scm
 guix/ui.scm
 guix/http-client.scm
diff --git a/tests/publish.scm b/tests/publish.scm
new file mode 100644
index 0000000..60f57a8
--- /dev/null
+++ b/tests/publish.scm
@@ -0,0 +1,114 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 David Thompson <davet@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (test-publish)
+  #:use-module (guix scripts publish)
+  #:use-module (guix tests)
+  #:use-module (guix config)
+  #:use-module (guix utils)
+  #:use-module (guix hash)
+  #:use-module (guix store)
+  #:use-module (guix base32)
+  #:use-module (guix base64)
+  #:use-module ((guix serialization) #:select (restore-file))
+  #:use-module (guix pk-crypto)
+  #:use-module (web client)
+  #:use-module (web response)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-64)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim))
+
+(define %store
+  (open-connection-for-tests))
+
+(define %reference (add-text-to-store %store "ref" "foo"))
+
+(define %item (add-text-to-store %store "item" "bar" (list %reference)))
+
+(define (http-get-body uri)
+  (call-with-values (lambda () (http-get uri))
+    (lambda (response body) body)))
+
+(define (publish-uri route)
+  (string-append "http://localhost:6789" route))
+
+;; Run a local publishing server in a separate thread.
+(call-with-new-thread
+ (lambda ()
+   (guix-publish "--port=6789"))) ; attempt to avoid port collision
+
+;; Wait until the server is accepting connections.
+(let ((conn (socket PF_INET SOCK_STREAM 0)))
+  (let loop ()
+    (unless (false-if-exception
+             (connect conn AF_INET (inet-pton AF_INET "127.0.0.1") 6789))
+      (loop))))
+
+(test-begin "publish")
+
+(test-equal "/nix-cache-info"
+  (format #f "StoreDir: ~a\nWantMassQuery: 0\nPriority: 100\n"
+          %store-directory)
+  (http-get-body (publish-uri "/nix-cache-info")))
+
+(test-equal "/*.narinfo"
+  (let* ((info (query-path-info %store %item))
+         (unsigned-info
+          (format #f
+                  "StorePath: ~a
+URL: nar/~a
+Compression: none
+NarHash: sha256:~a
+NarSize: ~d
+References: ~a~%"
+                  %item
+                  (basename %item)
+                  (bytevector->base32-string
+                   (path-info-hash info))
+                  (path-info-nar-size info)
+                  (basename (first (path-info-references info)))))
+         (signature (base64-encode
+                     (string->utf8
+                      (canonical-sexp->string
+                       ((@@ (guix scripts publish) signed-string)
+                        unsigned-info))))))
+    (format #f "~aSignature: 1;~a;~a~%"
+            unsigned-info (gethostname) signature))
+  (utf8->string
+   (http-get-body
+    (publish-uri
+     (string-append "/" (store-path-hash-part %item) ".narinfo")))))
+
+(test-equal "/nar/*"
+  "bar"
+  (call-with-temporary-output-file
+   (lambda (temp port)
+     (let ((nar (utf8->string
+                 (http-get-body
+                  (publish-uri
+                   (string-append "/nar/" (basename %item)))))))
+       (call-with-input-string nar (cut restore-file <> temp)))
+     (call-with-input-file temp read-string))))
+
+(test-end "publish")
+
+\f
+(exit (= (test-runner-fail-count (test-runner-current)) 0))
-- 
2.1.4


[-- Attachment #3: Type: text/plain, Size: 136 bytes --]


-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/2] store: Add query-path-info operation.
  2015-03-27 16:56     ` David Thompson
@ 2015-03-27 21:30       ` Ludovic Courtès
  0 siblings, 0 replies; 14+ messages in thread
From: Ludovic Courtès @ 2015-03-27 21:30 UTC (permalink / raw)
  To: David Thompson; +Cc: guix-devel

David Thompson <dthompson2@worcester.edu> skribis:

> From d86678e29c951ae4983cea92074e8f04c3e49f50 Mon Sep 17 00:00:00 2001
> From: David Thompson <dthompson2@worcester.edu>
> Date: Tue, 17 Mar 2015 10:19:36 -0400
> Subject: [PATCH 1/2] store: Add query-path-info operation.
>
> * guix/store.scm (<path-info>): New record type.
>   (read-path-info): New procedure.
>   (read-arg): Add 'path-info' syntax.
>   (query-path-info): New variable.
> * tests/store.scm ("query-path-info"): New test.

LGTM.  Also add a copyright line in the files.

Thank you!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-27 16:58     ` David Thompson
@ 2015-03-27 22:41       ` Ludovic Courtès
  2015-03-29 17:02         ` Mark H Weaver
  2015-04-04 18:30         ` David Thompson
  0 siblings, 2 replies; 14+ messages in thread
From: Ludovic Courtès @ 2015-03-27 22:41 UTC (permalink / raw)
  To: David Thompson; +Cc: guix-devel

David Thompson <dthompson2@worcester.edu> skribis:

> From a40d47dc64571aade0c92b4bdf3c56f6870842cc Mon Sep 17 00:00:00 2001
> From: David Thompson <dthompson2@worcester.edu>
> Date: Tue, 17 Mar 2015 10:21:31 -0400
> Subject: [PATCH 2/2] scripts: Add 'publish' command.
>
> * guix/scripts/publish.scm: New file.
> * po/guix/POTFILES.in: Add it.
> * tests/publish.scm: New file.
> * Makefile.am (MODULES): Add script module.
>   (SCM_TESTS): Add test module.
> * doc/guix.texi ("Invoking guix publish"): New node.

[...]

> +@node Invoking guix publish
> +@section Invoking @command{guix publish}
> +
> +The purpose of @command{guix publish} is to enable users to easily share
> +their store with others.  When @command{guix publish} runs, it spawns an
> +HTTP server which allows anyone with network access to obtain
> +substitutes from it.  This means that any machine running Guix can also
> +act as if it were a build farm, since the HTTP interface is
> +Hydra-compatible.  For security, each substitute is signed with the
> +system's signing key (@pxref{Invoking guix archive}).

I would skip a line after “Hydra-compatible,” and make it like:

  For security, each substitute is signed, allowing recipients to check
  their authenticity and integrity (@pxref{Substitutes}).  Because
  @command{guix publish} uses the system's signing key, which is only
  readable by the system administrator, it must run as root.

> +@command{guix publish} is a tool for system administrators, so only the
> +root user may invoke it.

... so this sentence can be removed.

Note for later: it should drop privileges once the key has been read and
the port open.

> +Once a publishing server has been authorized (@pxref{Invoking guix archive}),
> +the Guix daemon may use it to download substitutes:

“the daemon may download substitutes from it:”

> +(define (read-file-sexp file)
> +  (call-with-input-file file
> +    (compose string->canonical-sexp
> +             get-string-all)))
> +
> +(define %private-key
> +  (read-file-sexp %private-key-file))
> +
> +(define %public-key
> +  (read-file-sexp %public-key-file))

Since this can throw, it should not be done at the top-level.  So it
should be wrapped it in ‘delay’ or in a thunk.

> +(define (narinfo-string store-path path-info key)

Docstring please.  :-)

> +(define (render-nar request store-item)
> +  "Render archive of the store path corresponding to STORE-ITEM."
> +  (let ((store-path (string-append %store-directory "/" store-item)))
> +    ;; The ISO-8859-1 charset *must* be used otherwise HTTP clients will
> +    ;; interpret the byte stream as UTF-8 and arbitrarily change invalid byte
> +    ;; sequences.
> +    (if (file-exists? store-path)
> +        (values '((content-type . (application/x-nix-archive
> +                                   (charset . "ISO-8859-1"))))
> +                (lambda (port)
> +                  (write-file store-path port)))
> +        (not-found request))))

This is OK for now, but I just realized that this will be blocking the
server for the duration of the whole transfer.  Someone could DoS you by
substituting TeX Live.  ;-)

We’ll need a solution but it seems that it’ll be hard to avoid threads.

Thoughts?

> +(define (guix-publish . args)
> +  (with-error-handling
> +    (let* ((opts (parse-command-line args %options (list %default-options)))

I had overlooked it but it should use plain ‘args-fold*’ instead of
‘parse-command-line’ (the latter handles $GUIX_BUILD_OPTIONS and ‘guix
publish’ doesn’t build anything.)

> +           (store (open-connection)))

Use (with-store store body ...) instead.

OK to push with these changes.

Thanks!

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-27 22:41       ` Ludovic Courtès
@ 2015-03-29 17:02         ` Mark H Weaver
  2015-03-29 17:29           ` David Thompson
  2015-04-04 18:30         ` David Thompson
  1 sibling, 1 reply; 14+ messages in thread
From: Mark H Weaver @ 2015-03-29 17:02 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel

ludo@gnu.org (Ludovic Courtès) writes:

> David Thompson <dthompson2@worcester.edu> skribis:
>
>> +(define (render-nar request store-item)
>> +  "Render archive of the store path corresponding to STORE-ITEM."
>> +  (let ((store-path (string-append %store-directory "/" store-item)))
>> +    ;; The ISO-8859-1 charset *must* be used otherwise HTTP clients will
>> +    ;; interpret the byte stream as UTF-8 and arbitrarily change invalid byte
>> +    ;; sequences.
>> +    (if (file-exists? store-path)
>> +        (values '((content-type . (application/x-nix-archive
>> +                                   (charset . "ISO-8859-1"))))
>> +                (lambda (port)
>> +                  (write-file store-path port)))
>> +        (not-found request))))
>
> This is OK for now, but I just realized that this will be blocking the
> server for the duration of the whole transfer.  Someone could DoS you by
> substituting TeX Live.  ;-)
>
> We’ll need a solution but it seems that it’ll be hard to avoid threads.
>
> Thoughts?

I haven't looked closely, but how about using subprocesses instead of
threads?

      Mark

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-29 17:02         ` Mark H Weaver
@ 2015-03-29 17:29           ` David Thompson
  2015-03-30 19:32             ` Ludovic Courtès
  0 siblings, 1 reply; 14+ messages in thread
From: David Thompson @ 2015-03-29 17:29 UTC (permalink / raw)
  To: Mark H Weaver, Ludovic Courtès; +Cc: guix-devel

Mark H Weaver <mhw@netris.org> writes:

> ludo@gnu.org (Ludovic Courtès) writes:
>
>> David Thompson <dthompson2@worcester.edu> skribis:
>>
>>> +(define (render-nar request store-item)
>>> +  "Render archive of the store path corresponding to STORE-ITEM."
>>> +  (let ((store-path (string-append %store-directory "/" store-item)))
>>> +    ;; The ISO-8859-1 charset *must* be used otherwise HTTP clients will
>>> +    ;; interpret the byte stream as UTF-8 and arbitrarily change invalid byte
>>> +    ;; sequences.
>>> +    (if (file-exists? store-path)
>>> +        (values '((content-type . (application/x-nix-archive
>>> +                                   (charset . "ISO-8859-1"))))
>>> +                (lambda (port)
>>> +                  (write-file store-path port)))
>>> +        (not-found request))))
>>
>> This is OK for now, but I just realized that this will be blocking the
>> server for the duration of the whole transfer.  Someone could DoS you by
>> substituting TeX Live.  ;-)
>>
>> We’ll need a solution but it seems that it’ll be hard to avoid threads.
>>
>> Thoughts?
>
> I haven't looked closely, but how about using subprocesses instead of
> threads?

That's along the lines of what I was thinking.  One could spawn a bunch
of 'guix publish' processes on different ports and put a load balancer
in front of them.

I think that this problem, if it is to be solved with more Scheme code,
is an issue to address in Guile core by changing the http server
implementation.

-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-29 17:29           ` David Thompson
@ 2015-03-30 19:32             ` Ludovic Courtès
  0 siblings, 0 replies; 14+ messages in thread
From: Ludovic Courtès @ 2015-03-30 19:32 UTC (permalink / raw)
  To: David Thompson; +Cc: guix-devel

David Thompson <dthompson2@worcester.edu> skribis:

> Mark H Weaver <mhw@netris.org> writes:
>
>> ludo@gnu.org (Ludovic Courtès) writes:
>>
>>> David Thompson <dthompson2@worcester.edu> skribis:
>>>
>>>> +(define (render-nar request store-item)
>>>> +  "Render archive of the store path corresponding to STORE-ITEM."
>>>> +  (let ((store-path (string-append %store-directory "/" store-item)))
>>>> +    ;; The ISO-8859-1 charset *must* be used otherwise HTTP clients will
>>>> +    ;; interpret the byte stream as UTF-8 and arbitrarily change invalid byte
>>>> +    ;; sequences.
>>>> +    (if (file-exists? store-path)
>>>> +        (values '((content-type . (application/x-nix-archive
>>>> +                                   (charset . "ISO-8859-1"))))
>>>> +                (lambda (port)
>>>> +                  (write-file store-path port)))
>>>> +        (not-found request))))
>>>
>>> This is OK for now, but I just realized that this will be blocking the
>>> server for the duration of the whole transfer.  Someone could DoS you by
>>> substituting TeX Live.  ;-)
>>>
>>> We’ll need a solution but it seems that it’ll be hard to avoid threads.
>>>
>>> Thoughts?
>>
>> I haven't looked closely, but how about using subprocesses instead of
>> threads?

Sounds good.

> That's along the lines of what I was thinking.  One could spawn a bunch
> of 'guix publish' processes on different ports and put a load balancer
> in front of them.

What about changing the ‘open’ method of the <server-impl> (as in
tests/lint.scm) so that it forks upon socket opening?  (That could also
be the moment where we’d drop privileges.)

> I think that this problem, if it is to be solved with more Scheme code,
> is an issue to address in Guile core by changing the http server
> implementation.

Once we have a sufficiently different and valuable server
implementation, we could turn it into a (web server xxx) module.  I
think there’s value in keeping the simple/simplistic (web server http)
too.

Ludo’.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/2] scripts: Add 'publish' command.
  2015-03-27 22:41       ` Ludovic Courtès
  2015-03-29 17:02         ` Mark H Weaver
@ 2015-04-04 18:30         ` David Thompson
  1 sibling, 0 replies; 14+ messages in thread
From: David Thompson @ 2015-04-04 18:30 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel

Ludovic Courtès <ludo@gnu.org> writes:

> OK to push with these changes.

Done and pushed!  Thanks for the helpful review.

We'll see about addressing the web server issues moving forward.  I'm
not feeling up to the challenge of writing a new HTTP server at the
moment.

-- 
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2015-04-04 18:30 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-17 14:57 [PATCH 0/2] Add 'guix publish' command David Thompson
2015-03-17 15:00 ` [PATCH 1/2] store: Add query-path-info operation David Thompson
2015-03-18  8:55   ` Ludovic Courtès
2015-03-27 16:56     ` David Thompson
2015-03-27 21:30       ` Ludovic Courtès
2015-03-17 15:01 ` [PATCH 2/2] scripts: Add 'publish' command David Thompson
2015-03-18 10:27   ` Ludovic Courtès
2015-03-27 16:58     ` David Thompson
2015-03-27 22:41       ` Ludovic Courtès
2015-03-29 17:02         ` Mark H Weaver
2015-03-29 17:29           ` David Thompson
2015-03-30 19:32             ` Ludovic Courtès
2015-04-04 18:30         ` David Thompson
2015-03-17 15:20 ` [PATCH 0/2] Add 'guix publish' command David Thompson

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).