From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1.migadu.com ([2001:41d0:1008:1e59::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms8.migadu.com with LMTPS id wC9VAdZCjWUepgAAkFu2QA (envelope-from ) for ; Thu, 28 Dec 2023 10:41:42 +0100 Received: from aspmx1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1.migadu.com with LMTPS id MKPMN9VCjWVmKgEA62LTzQ (envelope-from ) for ; Thu, 28 Dec 2023 10:41:42 +0100 X-Envelope-To: larch@yhetil.org Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=posteo.net header.s=2017 header.b=TRfjUmnF; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org"; dmarc=fail reason="SPF not aligned (strict)" header.from=posteo.net (policy=none) ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1703756501; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:resent-cc: resent-from:resent-sender:resent-message-id:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=9rHm8LOqFik3y3biVCsQAP8r8HzMSMk3wxKoK9scAfU=; b=DlcT03lLeX/vQsXVsAWruUNIRZtWHfJB39il6Qw+dFnGtbEnyTLFqTDdENWrOqz+otPVgX /tNKco/IwMsnbvBkz4W+OQNJBSH5RCJ79MuN9Mhfxtj4jN5J/7NKHzvxTAtTPm6C1pGTgu rm8aqWQiMYwdHeZz+poCzNxi9WJeiP18qPsrGWdd4DlLJJTxQDOlr9YXqj9hesTUMN83Q4 JQXCdWs5630J3eTY1ifigsVZcPkHeOadxhOaOOxKK5p2lgr/IXS4VP/TdzO8d6tWkHm3er glvuHl9phlT2Q3RtyR4onhZeeExnxuzZ7uj4W/SteIq6DsmbIAoB4OPKHr4YZg== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1703756501; a=rsa-sha256; cv=none; b=IW4f9kTgg0QwrR5PQ1wmJjBmErUzPFci9REumY2OQJ+mcybID9hXKXoLXxe6Yzq5Ml0aOo QDWal1T0/+VeSUuTzxsA0Kgk0aFw7gwp61Cd97W8UUD8XNgV2xu2ETwa38CwS/PlhabzyT 28irE1BqjuVrxAa+IDVDJuFqMrO2hxNLpmIUz3isv5K3v6mLGJ2xWKqhSyCS+TUMAjmCuE DVU7dEFukSV4LjZDqZCt3rcnAjjYF27iDVchZzQd80Fd0CHWaCqhb/t+MRB54mEI8vK7P2 NRBerx506FVr1ELyEmbVwQ+BWGOkEy95Wg1jiSf90XQac/r1/AYuhojrm++vYg== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=posteo.net header.s=2017 header.b=TRfjUmnF; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org"; dmarc=fail reason="SPF not aligned (strict)" header.from=posteo.net (policy=none) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 5E9DA502B3 for ; Thu, 28 Dec 2023 10:41:41 +0100 (CET) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rImt1-0007dt-OX; Thu, 28 Dec 2023 04:41:07 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rImt0-0007ce-2Y for guix-patches@gnu.org; Thu, 28 Dec 2023 04:41:06 -0500 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rImsz-00082r-Pb for guix-patches@gnu.org; Thu, 28 Dec 2023 04:41:05 -0500 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1rImsy-0005ls-TH for guix-patches@gnu.org; Thu, 28 Dec 2023 04:41:04 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#52555] [PATCH v4 0/7] Decentralized substitute distribution with ERIS References: <20211216161724.547-1-pukkamustard@posteo.net> In-Reply-To: <20211216161724.547-1-pukkamustard@posteo.net> Resent-From: pukkamustard Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Thu, 28 Dec 2023 09:41:04 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 52555 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 52555@debbugs.gnu.org Cc: pukkamustard , ludo@gnu.org, maximedevos@telenet.be Received: via spool by 52555-submit@debbugs.gnu.org id=B52555.170375645422100 (code B ref 52555); Thu, 28 Dec 2023 09:41:04 +0000 Received: (at 52555) by debbugs.gnu.org; 28 Dec 2023 09:40:54 +0000 Received: from localhost ([127.0.0.1]:38528 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rImsk-0005kE-CF for submit@debbugs.gnu.org; Thu, 28 Dec 2023 04:40:53 -0500 Received: from mout02.posteo.de ([185.67.36.66]:53141) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rImsQ-0005iG-CH for 52555@debbugs.gnu.org; Thu, 28 Dec 2023 04:40:37 -0500 Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id 8EE8D240104 for <52555@debbugs.gnu.org>; Thu, 28 Dec 2023 10:40:25 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1703756425; bh=SdKZpWRq0990wrVl+i3HkaH+qdISYGvIUGvP/VX4tr8=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version: Content-Transfer-Encoding:From; b=TRfjUmnFHFBvOWr8DMEgxnu3Xf3Zik/qR0izLy6qazYsWkv/x/arR6zmuLeOFuhlM GAYpgpLf4xiJB1Jt715kerCV0PuScX8nNT3/t9v2xTKjFyE7Xq71t69X+R7cf85rtj L75I1JgFQ3EQZefgYedTuZqooa3O6Yp2XQx80+smyZ2vkAaR/l7jfdWil++94ClRXj wuMImmjmO+dBfAwu/kjCQJy7omK3VEQsn/d+rPPJeBWH91+3euBO39TE/43Wc/JklQ kTn12A1VOvjxZMu8FAnMBmoIG68+xgB7XHodLUjlkdiJ22+mq+/otR/K7VkSAJ/Mkt YBaD+9D/M8HFA== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4T13Qc20jmz6tyW; Thu, 28 Dec 2023 10:40:24 +0100 (CET) From: pukkamustard Date: Thu, 28 Dec 2023 09:40:03 +0000 Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: guix-patches-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US X-Migadu-Spam-Score: -3.83 X-Spam-Score: -3.83 X-Migadu-Queue-Id: 5E9DA502B3 X-Migadu-Scanner: mx11.migadu.com X-TUID: ocSo+f3wqY7m Dear Guix, This is the V4 of a proposal towards decentralized subsitute distirbution using the ERIS encoding. The initial proposal was submitted in December 2021, V2 and V3 a year later in 2022. All is still very much work-in-progress, but I'm happy to submit it as is to keep up with good old traditions. Thank you also for all the valuable comments and feedback via mail and live at Guix Days last year. Sorry for being so slow to react. The general idea of the proposal is to use ERIS (http://purl.org/eris) for substitute distribution. This allows substitutes to be shared over various protocols such as IPFS, GNUnet, NNCP, HTTP, CoAP, a USB stick or some Spritely-esque fun. ERIS itself defines an encoding of content into uniformly sized, encrypted and content-addressed blocks. The original content can be decoded with a short read capability that can be encoded as an URN and access to the blocks that make up the content. Blocks can be shared over different protocols with low security requirements on the transport layer itself. Read capabilities need to be shared in a secure manner. This series does following: - Adds an `ERIS` field holding the ERIS read capability of a substitute to the Narinfo as published by `guix publish` - Allows the daemon/substitute script to decode content using the ERIS URN instead of fetching a substitute via HTTP. ## Changes to previous version ### Fibers When encoding and decoding it is important for performance to be able to fetch multiple blocks concurrently (from potentially multiple peers). For this we use Guile fibers. Fiberization is currently very crude as there seem to be weird interactions with the parallization strategies currently used by `guix publish` and `guix substitute`. Help here is extremely welcome. ### ERIS-FS In previous versions we encoded the Nar-file of the susbstitute. In this version we use a specialized encoding of file-system trees for ERIS: ERIS-FS (https://eris.codeberg.page/eer/eris-fs.xml). ERIS-FS allows de-duplication of files and much easier parlallized decoding of substitutes. The ideas are similar to the custom encoding defined for substitutes over IPFS (https://issues.guix.gnu.org/33899). One major consequence is that the SHA256 sum of the Nar file as published in the Narinfo is no longer used to verify integrity of downloaded susbtitute. Instead we must ensure that the `ERIS` field in the Narinfo is trusted, `equivalent-narinfo?` and such have been changed towards this. Thanks to Maxime for pointing out this subtlety! ### External Block Store Daemon Ludovic Courtès writes: > Also, the store directory should be /var/cache/guix/publish/eris by > default IMO. This comment caused a lot of pondering and hacking! :) The problem seems to be that two processes need to be able to access the block store securely: `guix publish` when publishing substitutes and the Guix daemon when fetching substitutes (blocks need to be stored when fetching for de-duplication). `guix publish` is usually run as a non-privileged user, whereas the Guix daemon runs with much higher privileges. The block store needs to be secure in the sense that manipulating blocks should be prevented by less privileged processes. I'm afraid the only solution I came up with is that the block store is not a Unix directory or database, but a daemon that listens on a Unix Socket. Enter Kapla (https://codeberg.org/eris/kapla). Kapla is a Guile program that stores and transports blocks. The Guix daemon (via substitute scripts) and the `guix publish` server talk to Kapla over a Unix socket. Kapla stores blocks in a database (SQlite) and connects with multiple peers and/or services such as IPFS/GNUnet to get blocks. I was fighting such an architecture for a long time. I would have preferred if the Guix daemon and `guix publish` simply use a library for everything instead of relying on another external service. However, there seem to be some advantages: - Managing peers seem to be quite complex and stateful. Maybe better if this is externalized from Guix proper. - The block storage and transport mechanisms can be used to share any files (try `kapla encode my-file` and `kapla decode ...`). - We can experiment with more transports quicker in a service external to Guix and include dependencies that might not make sense to have in Guix proper. The protocol that is used to talk to the external block store is CoAP over Unix Sockets or TCP. Kapla is not the only software that speaks this protocol. There is also a Golang block store that can be used (https://codeberg.org/eris/eris-go). ### CoAP for block transport Currently the protocol over which blocks are transported over networks is the Constrained Application Protocol (CoAP) (https://eris.codeberg.page/eer/coap.xml). CoAP is well suited as it allows multiple asynchronous in-flight requests (unlike HTTP 1.1). This is important for parallizing block retrieval. It also allows bi-directional requests, making it attractive for more peer-to-peer systems where some peers might not have a public IP. As it is designed for constrained environment it is quite simple and the implementation we use is in pure Guile. More block transports can be added by implementing them in Kapla (e.g. IPFS or GNUnet) or using something completely different than Kapla that speaks CoAP over Unix Sockets (such as eris-go that also can use NNCP for block transport). ### Comments to V3 I hope to have addressed all comments to the V3 (Thanks Ludo!). This included things like organizing commits differently, using (ice-9 match) and how to handle multiple return values. ## Testing Testing is a bit complicated. We need to find a better and more systematic way of doing so. Suggestions are very welcome! Manually this is how to do it: 1. Authorize local substitutes We will be running a local substitute server so we need to add the local signing key to the list of authorized keys. In the system configurations: ``` (modify-services %base-services (guix-service-type config => (guix-configuration (inherit config) (authorized-keys (cons* ;; allow substitutes from ourselves for testing purposes (local-file "/etc/signing-key.pub") %default-authorized-guix-keys))))) ``` 2. Configure the local Guix checkout #+BEGIN_SRC shell ./bootstrap && ./configure --localstatedir=/var --sysconfdir=/etc && make #+END_SRC The ~--sysconfdir~ is required so that guix will use the ACL in ~/etc/guix/acl~. 3. Start a Kapla daemon: ``` ./pre-inst-env guix build kapla -- kaplad -d ``` The `-d` option enables debug output. Kapla will listen for connection on a Unix socket. The path will be output, e.g.: ``` 2023-12-23 15:10:23 (INFO): Listening on Unix socket /run/user/1000/eris.sock ``` 3. Build some package that we will ``` $ ./pre-inst-env guix build libchop --no-grafts /gnu/store/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 ``` 4. Start a local publish server ``` sudo -E ./pre-inst-env guix publish --public-key=/etc/guix/signing-key.pub --private-key=/etc/guix/signing-key.sec --cache=/tmp/guix-publish-cache/ --eris=coap+unix:///run/user/1000/eris.sock ``` I need to run it with sudo in order to be able to use the proper signing keys. The `--eris=STORE_URL` option defines where to store blocks. `--eris` can also be provided without a URL which will cause the ERIS read capability to be computed but blocks won't be stored anywhere. See "Proposed Deployment" on when this might make sense. 5. Get the narinfo from the publish server ``` $ curl http://localhost:8080/60m6qih391rq95ck64am8ir64z0sv0zr.narinfo StorePath: /gnu/store/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 NarHash: sha256:1i2hhzw81qfsba0d1b09ax13694imgjrpay0122gqll85dx7k7ml NarSize: 1021984 ERIS: urn:eris:BIARSURIJ3WYLEINE6W5ZF7LKTIHL42AE367TQ355ORW5UZVSTQGT5H5T2OLKF7XICML3VHLTLMDWXLUCQVKHRKNVREV3GMVX3J5RMT4GU References: 1i0iz5rgixyva0zy4bmaasjil2683xrn-mit-krb5-1.20 2w976k6g70gkfih9wwhalqsni209vcqz-gdbm-1.23 4p1l5bdxxbyyqc3wh0d07jv9rp1pdcy7-guile-2.0.14 60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 620h3panf2lss42ns625rlay522g2hza-tdb-1.4.7 8y0pwifz8a3d7zbdfzsawa1amf4afx1s-libgcrypt-1.10.1 930nwsiysdvy2x5zv1sf6v7ym75z8ayk-gcc-11.3.0-lib gsjczqir1wbz8p770zndrpw4rnppmxi3-glibc-2.35 m9wi9hcrf7f9dm4ri32vw1jrbh1csywi-libgpg-error-1.45 pl09vk5g3cl8fxfln2hjk996pyahqk8m-bzip2-1.0.8 r5saysi65chivbv3y65nzsisx8rypp76-libtirpc-1.3.1 rib9g2ig1xf3kclyl076w28parmncg4k-bash-minimal-5.1.16 slzq3zqwj75lbrg4ly51hfhbv2vhryv5-zlib-1.2.13 z4likj1rnshd81hr0vwvwhdxpfwa1rz7-lzo-2.10 Deriver: zcly5fm6rsqis9csk33i7zfqjidjzy1a-libchop-0.5.2.drv Signature: 1;strawberry;KHNpZ25hdHVyZSAKIChkYXRhIAogIChmbGFncyByZmM2OTc5KQogIChoYXNoIHNoYTI1NiAjRjFEQTI3RkJDQzA4RTI2MzZDQkY3NEE5NkMwNjQ5QzZBQ0U3QjEzNDRGNzJFNEM1OUMwMzIyRUIzMjFFMUY4RCMpCiAgKQogKHNpZy12YWwgCiAgKGVjZHNhIAogICAociAjMDQwQzYwNzBBOTlBQjI1NEFDNkMwOTdGREIxMUREMkZBNEExNTFGNzYyOTBBRUNENzdCQUMzQ0M0REVERkI0RCMpCiAgIChzICMwQzZGMEFGOTgzODg1NDE5NzAwNzI0NEE5NDU2RTYyMDAxNEE2NUI5NDgxODc4QTlFNjJDOTA2RDQ3NTVGM0YyIykKICAgKQogICkKIChwdWJsaWMta2V5IAogIChlY2MgCiAgIChjdXJ2ZSBFZDI1NTE5KQogICAocSAjMDRDMkY4ODk1QTU0NDNGNTlCODk2NDEwMEI1MDY0NzU4RjQ1N0YzMENEREE1MTQyQzE0MDc0NjExNTA1NTc5MCMpCiAgICkKICApCiApCg== URL: nar/gzip/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 Compression: gzip FileSize: 345576 ``` Note the new `ERIS` field. You might have to get the narinfo twice for all the fields to appear. The ERIS read capability URN (urn:eris:BIARSURIJ...) contains enough information to decode the substitute. If you look at the kaplad log output you will see that some blocks were stored. You could now run: ``` kapla decode urn:eris:BIARSURIJ3WYLEINE6W5ZF7LKTIHL42AE367TQ355ORW5UZVSTQGT5H5T2OLKF7XICML3VHLTLMDWXLUCQVKHRKNVREV3GMVX3J5RMT4GU out ``` to decode the substitute to the folder out. The Guix daemon can do the same when getting substitutes. 6. Remove the libchop store item: ``` guix gc -D /gnu/store/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 ``` 7. Start the guix daemon from the checkout: ``` sudo -E ./pre-inst-env guix-daemon --build-users-group=guixbuild --eris-store-url=coap+unix:///run/user/1001/eris.sock --substitute-urls=http://localhost:8080/ ``` Note that the `--eris-store-url` argument points to the same store as what we used for the publish server - the blocks come go to and come from the same place. We need to use the local publish server for getting the narinfo with the `ERIS` field. 8. Get the substitute: ``` ./pre-inst-env guix build libchop --no-grafts substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'.substitute: updating substitutes from 'http://localhost:8080/'... 100.0% 0.3 MB will be downloaded: /gnu/store/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 substituting /gnu/store/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2... Downloading urn:eris:BIARSURIJ3WYLEINE6W5ZF7LKTIHL42AE367TQ355ORW5UZVSTQGT5H5T2OLKF7XICML3VHLTLMDWXLUCQVKHRKNVREV3GMVX3J5RMT4GU... /gnu/store/60m6qih391rq95ck64am8ir64z0sv0zr-libchop-0.5.2 ``` Substitute was fetched using ERIS! The daemon automatically tries to use ERIS when the `--eris-store-url` is passed and the narinfo has a signed ERIS field. If not it will fetch substitutes using HTTP. On failures while using ERIS it will also revert back to getting substitutes via HTTP. 9. Restart your system guix daemon ``` sudo herd restart guix-daemon ``` 10. Figure out a better way to test this. ## Proposed Deployment As an initial deployment it would be nice if the official Guix substitute servers include the ERIS read capability in the Narinfo withouth making blocks available. This only requires a bit of CPU for computing the ERIS read capability. No additional disk space or bandwidth is required by the official substitute servers. Users can get the signed ERIS read capability from the official Guix susbtitute servers but can fetch blocks from anywhere while still being sure to get the right substitutes. This would allow community members to make blocks available independently and experiment with various transports while not changing the trust-model when fetching substitutes. ## TODOs - [ ] Better fiberization of `guix publish` and `guix/scripts/substitute.scm` - [ ] Debug issue where blocks of large substitutes are not properly stored (possibly related to hackey fiberization). - [ ] Add Guix tests - [ ] Add documentation. - [ ] Write a RFC along the proposed Request-For-Comment process (https://issues.guix.gnu.org/66844) - [ ] Allow encoding of blocks without running `guix publish` (maybe an `eris` format for `guix pack`). - [ ] Dependencies (guile-eris and guile-coap) contain some fixes that need to be properly released. - [ ] Implement CoAP peer connections and maybe IPFS transport in Kapla. - [ ] Release initial version of Kapla. - [ ] Update service definitions for `guix-publish`, `guix-daemon` with an option to enable decentralized substitute stuff and add service definition for `kapla`. ## Hic Sunt Dracones Everything is still quite fragile. The fiberization of `guix substitute` and `guix publish` is hackey. Kapla is still very limited and unreleased. But I hope to have been able to capture the current state of things and paint a picture of the plan. Thanks for making it so far and for your comments, questions and hackings. Greetings, pukkamustard pukkamustard (7): narinfo: Add ERIS field. gnu: Add guile-coap. gnu: guile-eris: Update to 1.2.0-dev. publish: Add ERIS URN to narinfo. eris: Connect with an ERIS Store over CoAP+Unix. substitute: Decode substitutes using ERIS. gnu: Add kapla. Makefile.am | 1 + configure.ac | 5 + gnu/packages/guile-xyz.scm | 144 ++++++++++++++++++++++------ gnu/packages/package-management.scm | 1 + guix/eris.scm | 137 ++++++++++++++++++++++++++ guix/narinfo.scm | 17 +++- guix/scripts/publish.scm | 58 ++++++++--- guix/scripts/substitute.scm | 76 +++++++++++---- nix/nix-daemon/guix-daemon.cc | 5 + 9 files changed, 380 insertions(+), 64 deletions(-) create mode 100644 guix/eris.scm base-commit: 4a1b3830a8ff6b05ad9a2b27c8a2cdbd00a45787 -- 2.41.0