├── README.md
├── cache
└── nix-cache-info
├── get_build_hashes.sh
├── get_build_outputs.sh
├── get_builds.sh
├── run.sh
└── update.sh
/README.md:
--------------------------------------------------------------------------------
1 | # Requirements:
2 |
3 | * jq to parse berlin's API responses (in $PATH)
4 | * curl to download things
5 | * ipfs to do ipfs things (in $PATH)
6 | * a running ipfs daemon that is online
7 |
8 | # How to run:
9 |
10 | Change to the directory containing guix-ipfs-cache and run
11 | bash run.sh
12 |
13 | # Rationale
14 |
15 | TL;DR: ipfs allows sharing of whole directory structures under persistent names (ipns), even if the directory structure changes. Since guix package --substitute-url="http://localhost:8080/some/path/to/repository/of/packages"
expects a certain directory structure we can try and share this directory structure via ipfs/ipns. Click this link for an example:
16 |
17 | http://ipfs.io/ipns/QmPMJYhxbeaSYXzNLMRbvvJknpYcJG9DcG8h2kJJmukd9i
18 |
19 | (This is running via the gateway to ipfs provided by the ipfs people). Once a user has a running ipfs daemon locally they can use this:
20 |
21 | http://localhost:8080/ipns/QmPMJYhxbeaSYXzNLMRbvvJknpYcJG9DcG8h2kJJmukd9i
22 |
23 | as substitute-url. Read on below for some more details if interested.
24 |
25 | ## ipfs daemon as "local" guix substitute-mirror
26 |
27 | The ipfs daemon provides a local http-proxy to the ipfs network that allows retrieving files by their CID (content-id), a unique identifier for the respective file that derived from its contents. The default port of this proxy is TCP/8080. An url to retrieve a file from the daemon would e.g. look like this:
28 |
29 | http://localhost:8080/ipfs/QmbiJmTexTp1YBv3s7eKXDTzKGaKMTLGuns2xscmhdhadu
30 |
31 | These can be retrieved by any HTTP-Client:
32 |
33 |
34 | $ curl http://localhost:8080/ipfs/QmbiJmTexTp1YBv3s7eKXDTzKGaKMTLGuns2xscmhdhadu 35 | StorePath: /gnu/store/y857mykfnc9vd1sc2lqz5g35l111fp71-opensmtpd-test 36 | URL: nar/gzip/y857mykfnc9vd1sc2lqz5g35l111fp71-opensmtpd-test 37 | Compression: gzip 38 | NarHash: sha256:136lm3fxh4kbsgz32157cnmyypgynkkmrqqb03biym1nm6cdwyv7 39 | NarSize: 2600 40 | References: 2sznibwwvp6x0ha2j6n8s1z1brf8ra4q-opensmtpd-test-builder 41 | FileSize: 894 42 | System: x86_64-linux 43 | Deriver: 8aski1xwcn6lj112yc9ylgp2y3c7sj83-opensmtpd-test.drv 44 | Signature: 1;berlin.guixsd.org;KHNpZ25hdHVyZSAKIChkYXRhIAogIChmbGFncyByZmM2OTc5KQogIChoYXNoIHNoYTI1NiAjQTdCQzIwRjU0QzQ1NEYyRUE1QjQ1NTU1QzExN0VDOEYzMjA1RjlEQkM4N0ZDMzc4M0M1NjBCREM5REZCQUI3QyMpCiAgKQogKHNpZy12YWwgCiAgKGVjZHNhIAogICAociAjQzhGRjZGMTMyNUFENzRBRjhDRDVDOTEzREE2NThFMjM5QzJCOEQ1QjZGNzM5REM1ODdDMTVERDVCODA3RkQjKQogICAocyAjMENCMTA3NUI5NDI5MUNEODVFMDI5NDQ4N0YzMTk1QUI5RTI5ODE5ODBFQzg2RDc1NDZDMDYyRjQ1NTNBRjEzQiMpCiAgICkKICApCiAocHVibGljLWtleSAKICAoZWNjIAogICAoY3VydmUgRWQyNTUxOSkKICAgKHEgIzhEMTU2RjI5NUQyNEIwRDlBODZGQTU3NDFBODQwRkYyRDI0RjYwRjdCNkM0MTM0ODE0QUQ1NTYyNTk3MUIzOTQjKQogICApCiAgKQogKQo= 45 |46 | 47 | Individual files can be added to the ipfs-network with the
ipfs add
. There are other mechanisms, but we'll stick to this high level view for now.
48 |
49 | In addition to providing files, ipfs supports directories (also identified by CIDs). These directories are implemented as Merkle-DAGs -- a form of cryptographic data structure that is a directed acyclic graph (DAG).
50 |
51 | 52 | $ mkdir test 53 | $ touch test/foo 54 | $ ipfs add -r test/ 55 | added QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH test/foo 56 | added QmWLU2zkdpnTSzKEu1RLN43mFjayCJzXrnE8nP1KT2htNd test 57 | 0 B / ? [----------------------------------------------------------------------------------------------------=] 0.00% 58 |59 | 60 | One nice feature is that the ipfs daemon proxy supports lookup of files relative to a directory by clear names: 61 | 62 |
63 | $ curl http://localhost:8080/ipfs/QmWLU2zkdpnTSzKEu1RLN43mFjayCJzXrnE8nP1KT2htNd/foo 64 | $ 65 |66 | 67 | In this case the file was empty. To illustrate one important point about directories in ipfs, let's change the file's content: 68 | 69 |
70 | $ echo "Hello, world!" > test/foo 71 | $ ipfs add -r test/ 72 | added QmeeLUVdiSTTKQqhWqsffYDtNvvvcTfJdotkNyi1KDEJtQ test/foo 73 | added QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 test 74 | 14 B / 14 B [=================================================================================================] 100.00% 75 |76 | 77 | The attentive reader will have noticed that the resulting CID of the
test
directory has changed. This will happen whenever a file's content changed or the structure of the directory changed (e.g. by adding new or removing existing files from the directory).
78 |
79 | 80 | $ curl http://localhost:8080/ipfs/QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1/foo 81 | Hello, world! 82 |83 | 84 | At this point we can imagine how a local ipfs daemon can act as substitute-url for guix: We simply publish a directory structure on ipfs that resembles the structure the
guix package
expects, namely:
85 |
86 | * A bunch of narinfo
files directly in the root of the directory
87 | * A nix-cache-control text file in the root of the directory
88 | * A bunch of nar
files in a nar/gzip
subdirectory.
89 |
90 | An example of this would be https://ipfs.io/ipfs/QmR6y77ijvafrZanwxw63Q2QHkMBK4PbhTxSgTC9m3Un3o
(ipfs.io runs a gateway similar to the local ipfs daemon, so please click on this link to browse the directory).
91 |
92 | A user could, in principle, now use an url like this in a call to guix package
.
93 |
94 | 95 | guix package --substitute-url="http://localhost:8080/ipfs/QmR6y77ijvafrZanwxw63Q2QHkMBK4PbhTxSgTC9m3Un3o https://mirror.hydra.gnu.org" -i emacs 96 |97 | 98 | The attentive reader will have noticed one weakness of this approach: Everytime the root-directory of this repository changes the resulting URL will change as well. This would imply that users would have to constantly update the URL they use in the
substitute-url
parameter to guix package
. To remedy situations like this ipfs has implemented a name system called ipns.
99 |
100 | ipfs allows to publish ipns names that are mutable. The name is the hash of a public part of a public/private key pair, and per default ipfs name publish
uses the ipfs node's key.
101 |
102 | 103 | $ ipfs name publish QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 104 | Published to QmaeGpMRsHmeVaQFRnwtuZYdSdVgbc3Y64aDFs1ya8Frnb: /ipfs/QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 105 |106 | 107 | This basically completes the necessary components to provide a stable substitute-url for guix that can have changing contents. Checkout https://ipfs.io/ipns/QmPMJYhxbeaSYXzNLMRbvvJknpYcJG9DcG8h2kJJmukd9i which is the address that this repository's code running on a server produces. Note how the first part of the url changed from
ipfs
to ipns
(note: like said above the ipns name is specific to the particular ipfs node publishing that name):
108 |
109 |
110 | guix package --substitute-url="http://localhost:8080/ipns/QmPMJYhxbeaSYXzNLMRbvvJknpYcJG9DcG8h2kJJmukd9i https://mirror.hydra.gnu.org" -i emacs
111 |
112 |
113 | # Fetching substitutes from berlin.guixsd.org and publishing them via ipfs
114 |
115 | The code in this repository contains a bunch of shell scripts (with no error handling whatsoever - so beware, it's just a proof of concept) that perform these steps:
116 |
117 | * Query http://berlin.guixsd.org for newly finished builds
118 | * Fetch narinfos into cache/
119 | * Fetch the respective nar into cache/nar/gzip
120 | * build up directory structure that guix package
expects
121 | * publish them all to ipfs
122 | * update the ipns name to point to the latest version
123 |
124 | with binaries retrieved from berlin.guixsd.org's API that informs about finished builds.
125 |
126 | # An optimization for size, speed and profit
127 |
128 | Once a directory reaches a certain size, adding it recursively with ipfs add
becomes slow as a dog (disk IO alone being a bottleneck). Also just keeping all files on disk is using up precious disk space while the promise of ipfs is that the swarm can take over hosting "responsibility". Luckily there is a way to add entries to a directory object directly without having to rerun ipfs add -r
over and over again.
129 |
130 | Here's an example. Let's add a directory structure under ipfs:
131 |
132 | 133 | ipfs add -r test/ 134 | added QmeeLUVdiSTTKQqhWqsffYDtNvvvcTfJdotkNyi1KDEJtQ test/foo 135 | added QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 test 136 | 14 B / 14 B [=================================================================================================] 100.00% 137 |138 | 139 | and publish it under an ipns name: 140 | 141 |
142 | $ ipfs name publish QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 143 | Published to QmaeGpMRsHmeVaQFRnwtuZYdSdVgbc3Y64aDFs1ya8Frnb: /ipfs/QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 144 |145 | 146 | If we now want to, at some later point, add to the directory published under this name we have two options: 147 | 148 | * Remember the resulting CID of last directory state (in this case
QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1
)
149 | * Look it up first from its published name
150 |
151 | The second option could be done like:
152 |
153 | 154 | $ ipfs name resolve QmaeGpMRsHmeVaQFRnwtuZYdSdVgbc3Y64aDFs1ya8Frnb 155 | /ipfs/QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 156 |157 | 158 | This gives us back the CID of the previous state of the directory. Now to add onto that we can perform these steps for each newly added file: 159 | 160 | Add the file to ipfs: 161 | 162 |
163 | $ ipfs add bar 164 | added QmTEzo7FYzUCd5aq7bGKoMLfsbbsebpfARRZd4Znejb25R bar 165 | 4 B / 4 B [===================================================================================================] 100.00% 166 |167 | 168 | ...modify the directory object from above: 169 | 170 |
171 | $ ipfs object patch add-link /ipfs/QmT429U7M2Civz8qmkA6uZ5tvLJ9njQAvzsX6BvWodJ3i1 bar QmTEzo7FYzUCd5aq7bGKoMLfsbbsebpfARRZd4Znejb25R 172 | QmcD4o2EK1qS8H4dR3usWmiZqKCGwSugUgkRaXgtcqEEaF 173 |174 | 175 | resulting in a new CID for the modified directory: 176 | 177 |
178 | $ ipfs ls QmcD4o2EK1qS8H4dR3usWmiZqKCGwSugUgkRaXgtcqEEaF 179 | QmTEzo7FYzUCd5aq7bGKoMLfsbbsebpfARRZd4Znejb25R 12 bar 180 | QmeeLUVdiSTTKQqhWqsffYDtNvvvcTfJdotkNyi1KDEJtQ 22 foo 181 |182 | 183 | which we can then republish: 184 | 185 |
186 | $ ipfs name publish QmcD4o2EK1qS8H4dR3usWmiZqKCGwSugUgkRaXgtcqEEaF 187 | Published to QmaeGpMRsHmeVaQFRnwtuZYdSdVgbc3Y64aDFs1ya8Frnb: /ipfs/QmcD4o2EK1qS8H4dR3usWmiZqKCGwSugUgkRaXgtcqEEaF 188 |189 | 190 | And voila: 191 | 192 |
193 | $ ipfs ls /ipns/QmaeGpMRsHmeVaQFRnwtuZYdSdVgbc3Y64aDFs1ya8Frnb 194 | QmTEzo7FYzUCd5aq7bGKoMLfsbbsebpfARRZd4Znejb25R 12 bar 195 | QmeeLUVdiSTTKQqhWqsffYDtNvvvcTfJdotkNyi1KDEJtQ 22 foo 196 |197 | 198 | The implementation in the provided scripts is left as excercise to the interested reader for now :) 199 | 200 | -------------------------------------------------------------------------------- /cache/nix-cache-info: -------------------------------------------------------------------------------- 1 | StoreDir: /gnu/store 2 | WantMassQuery: 0 3 | Priority: 100 4 | -------------------------------------------------------------------------------- /get_build_hashes.sh: -------------------------------------------------------------------------------- 1 | curl https://berlin.guixsd.org/api/latestbuilds?nr=30 | jq .[].buildoutputs.out.path | cut -d / -f 4 | cut -d "-" -f 1 2 | -------------------------------------------------------------------------------- /get_build_outputs.sh: -------------------------------------------------------------------------------- 1 | curl https://berlin.guixsd.org/api/latestbuilds?nr=10 | jq .[].buildoutputs.out.path 2 | -------------------------------------------------------------------------------- /get_builds.sh: -------------------------------------------------------------------------------- 1 | curl https://berlin.guixsd.org/api/latestbuilds?nr=10 | jq . 2 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | while true; do 2 | sleep 300 3 | bash update.sh 4 | done 5 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | echo "#### CACHE UPDATE STARTED" 2 | mkdir -p cache/nar/gzip 3 | 4 | echo "#### FINISHED JOBS" 5 | bash get_build_hashes.sh > current_hashes.txt; 6 | for n in `cat current_hashes.txt`; do 7 | narinfo="$n.narinfo" 8 | if [ ! -f "./cache/$narinfo" ]; then 9 | echo get: "$narinfo"; 10 | wget https://berlin.guixsd.org/"$n".narinfo -P ./cache/ 11 | sleep 1 12 | fi 13 | done 14 | 15 | echo "#### GETTING NARINFOS" 16 | rm -f current_nars.txt 17 | for n in cache/*.narinfo; do 18 | cat "$n" | grep ^URL | cut -d / -f 3 >> current_nars.txt 19 | done 20 | 21 | 22 | echo "#### GETTING NARS" 23 | for n in `cat current_nars.txt`; do 24 | if [ ! -f "./cache/nar/gzip/$n" ]; then 25 | echo get: "$n" 26 | wget https://berlin.guixsd.org/nar/gzip/"$n" -P ./cache/nar/gzip/ 27 | fi 28 | done 29 | 30 | echo "#### UPDATING IPFS" 31 | ipfs add -r cache > ipfs_log.txt 32 | 33 | echo "#### UPDATING IPNS" 34 | cache_cid=`tail -n 1 ipfs_log.txt | cut -d ' ' -f 2` 35 | ipfs name publish "$cache_cid" 36 | echo "#### DONE." 37 | --------------------------------------------------------------------------------