├── .editorconfig ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── config.yml │ ├── feature.md │ └── task.md ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── SUPPORT.md └── workflows │ └── build.yml ├── .gitignore ├── .version ├── COPYING ├── bors.toml ├── changelog.org ├── conf ├── eris.conf.example └── eris.service.in ├── default.nix ├── demo ├── .gitignore ├── cloudflare-ips.txt ├── configuration.nix ├── default.nix └── readme.org ├── eris.8.pod.in ├── eris.pl ├── module.nix ├── nix ├── bootstrap.nix ├── nixpkgs.json ├── overlays.nix ├── overlays │ └── README.md └── update.sh ├── readme.org ├── release.nix ├── t ├── cache.sk ├── t00-simple.nix ├── t01-auth.nix ├── t02-signing.nix └── t03-varnish.nix └── test.nix /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{pl,json,sh,nix,yml}] 4 | indent_style = space 5 | indent_size = 2 6 | 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS file 2 | # 3 | # For documentation on this file, see https://help.github.com/articles/about-codeowners/ 4 | # Mentioned users will get code review requests. 5 | 6 | # Primary repository owner 7 | / @thoughtpolice 8 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | **Note**: contributing implies licensing those contributions under the terms of 4 | [COPYING](../COPYING). 5 | 6 | ## Opening issues 7 | 8 | * Make sure you have a [GitHub account](https://github.com/signup/free) 9 | * [Submit an issue](https://github.com/thoughtpolice/eris/issues) - assuming one does not already exist. 10 | * Clearly describe the issue including steps to reproduce when it is a bug. 11 | * Include the git repository information, any changes, Nix version, etc. 12 | 13 | ## Submitting changes 14 | 15 | * Always run tests. This can be done using the `check` target. 16 | * Keep changes logically separated: one-commit-per-problem. 17 | * Try to keep the code style the same where-ever you're writing patches; some 18 | things may differ. If some code is inconsistent, feel free to clean it up 19 | (in a separate commit first, then make your actual change). 20 | * Try not to add lots of "superfluous commits" for typos, trivial compile 21 | errors, etc. 22 | * The repository **MUST BUILD** with every single commit you author, and the 23 | tests **MUST PASS**, even if something you're implementing or changing is 24 | incomplete. This allows very useful tools like `git bisect` to work over time. 25 | 26 | Regarding point #4 (and #5): for multi-commit requests, your code may get 27 | squashed into the smallest possible logical changes and commiting with author 28 | attribution in some cases. In general, try to keep the history clean of things 29 | like "fix typo" and "obvious build break fix", and this won't normally be 30 | necessary. Patches with these kinds of changes in the same series will 31 | generally be rejected, but ask if you're unsure. 32 | 33 | ### Writing good commit messages 34 | 35 | In addition to writing properly formatted commit messages, it's important to 36 | include relevant information so other developers can later understand *why* a 37 | change was made. While this information usually can be found by digging code, 38 | mailing list/Discourse archives, pull request discussions or upstream changes, 39 | it may require a lot of work. 40 | 41 | * The first line of a commit message **SHOULD BE** 73 columns max. 42 | * Always reference the issue you're working on in the bug tracker in your 43 | commit message, and if it fixes the issue, close it using the relevant 44 | syntax. 45 | * Try to describe the relevant change in the following way: 46 | 47 | ``` 48 | (component): (20,000 foot overview) 49 | 50 | (Motivation, reason for change) 51 | ``` 52 | 53 | For consistency, there **SHOULD NOT** be a period at the end of the commit 54 | message's summary line (the first line of the commit message). 55 | 56 | ### Notes on sign-offs and attributions, etc. 57 | 58 | When you commit, **please use -s to add a Signed-off-by line**. `Signed-off-by` 59 | is interpreted as a very weak statement of ownership, much like Git itself: by 60 | adding it, you make clear that the contributed code abides by the project 61 | license, and you are rightfully contributing it yourself or on behalf of 62 | someone. You should always do this. 63 | 64 | This means that if the patch you submit was authored by someone else -- perhaps 65 | a coworker for example that you submit it from or you revive a patch that 66 | someone forgot about a long time ago and resubmit it -- you should also include 67 | their name in the details if possible. 68 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ thoughtpolice ] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug that exists with Eris 4 | labels: 'A-bug' 5 | 6 | --- 7 | 8 | ## Issue description 9 | 10 | Describe the bug as clearly as possible. 11 | 12 | ### Steps to reproduce 13 | 14 | Provide steps to reproduce the behavior, including configuration of the server 15 | and how to trigger the behavior. 16 | 17 | ## Technical details 18 | 19 | Please run: 20 | 21 | ```bash 22 | printf ' - eris revision: ' && echo $(git rev-parse HEAD) && nix run nixpkgs.nix-info -c nix-info -m 23 | ``` 24 | 25 | from the root of the project directory, and paste the results here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea to improve Eris 4 | labels: 'A-enhancement' 5 | 6 | --- 7 | 8 | ## Proposed solution 9 | 10 | A clear and concise description of what you want to happen. 11 | 12 | ## Alternatives considered 13 | 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | ## Additional notes 17 | 18 | Add any other context about the feature request here. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: A general task (management, project related, etc) that needs to be done 4 | labels: 'A-task' 5 | 6 | --- 7 | 8 | ## What needs to be done 9 | 10 | A clear and concise description of what needs to happen. 11 | 12 | ## Alternatives considered 13 | 14 | A clear and concise description of any alternative solutions that were considered. 15 | 16 | ## Additional notes 17 | 18 | Add any other context about the feature request here. 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ###### Motivation for this change 2 | 3 | Describe the motivation for this change, why it was written a particular way, 4 | alternative options that you considered, and generally as much context as 5 | possible. 6 | 7 | **NOTE**: Do **NOT** submit security-related patches as public pull requests. 8 | To submit security-relevant fixes or patches, a security advisory should be 9 | created. See [SECURITY.md](https://github.com/thoughtpolice/eris/blob/master/.github/SECURITY.md) 10 | as well as [SUPPORT.md](https://github.com/thoughtpolice/eris/blob/master/.github/SUPPORT.md) 11 | for more information. 12 | 13 | ###### Related issue(s) 14 | 15 | Include any related issues here, and how this change might fix, impact, or 16 | relate to them in general. 17 | 18 | ###### Things done 19 | 20 | 21 | 22 | - [ ] Compiled all jobs with `nix build -f release.nix` 23 | - [ ] Ran all tests with `nix build -f release.nix test` 24 | - [ ] Assured whether relevant documentation is up to date 25 | - [ ] Fits [CONTRIBUTING.md](https://github.com/thoughtpolice/eris/blob/master/.github/CONTRIBUTING.md). 26 | 27 | --- 28 | 29 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting security issues 2 | 3 | If you discover or would like to report a security issue with Eris, please 4 | reach out to me using the information available in the [Support 5 | information](https://github.com/thoughtpolice/eris/blob/master/.github/SUPPORT.md) 6 | document, and include your GitHub account name. A [security 7 | advisory](https://help.github.com/en/articles/about-maintainer-security-advisories) 8 | will be created with you included. 9 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Getting support and help 2 | 3 | If you need support or help for bugs in Eris, please feel free to reach out to 4 | @thoughtpolice here on GitHub by filing an issue in this repository, on IRC 5 | (`irc.freenode.net`, channel `#nixos`), or by email (see my commit messages for 6 | the details). Replies cannot always be guaranteed in a timely manner, 7 | unfortunately. 8 | 9 | It's possible a request you make is technically infeasible or requires work on 10 | other components. If you're unsure of this, feel free to file an issue anyway 11 | and something can be discussed. 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - staging 7 | - trying 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | name: Build and Test Job 13 | timeout-minutes: 360 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | nixpkgs: [ lockfile, nixos-19.09, nixos-unstable ] 19 | 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Install Nix 27 | uses: cachix/install-nix-action@v13 28 | 29 | - name: nix-build (for ${{ matrix.nixpkgs }}) 30 | env: 31 | NIXPKGS_CHANNEL: ${{ matrix.nixpkgs }} 32 | run: nix-build --no-link release.nix --arg nixpkgs "channel:$NIXPKGS_CHANNEL" 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /eris.conf 2 | *.sk 3 | *.pk 4 | 5 | /result* 6 | /*.pid 7 | *.tmp 8 | *~ 9 | *# 10 | -------------------------------------------------------------------------------- /.version: -------------------------------------------------------------------------------- 1 | 0.1unstable 2 | Developer Edition 3 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ "Build and Test Job%" ] 2 | block_labels = [ "S-wip", "S-nomerge" ] 3 | pr_status = [ "DCO" ] 4 | 5 | delete_merged_branches = true 6 | timeout_sec = 7200 # two hours 7 | 8 | ## [NOTE] (aseipp): these can't be enabled because you can't self-review or 9 | ## self-approve things you're a codeowner of, and bors does not like that... 10 | 11 | # required_approvals = 1 12 | # use_codeowners = true 13 | 14 | ## [NOTE] (aseipp): squash merge would be nice, but unfortunately it does 15 | ## nuke any GPG signatures you might have (somewhat annoying), but it also 16 | ## leaves the signed-off-by lines as well, which is what I really hate; in 17 | ## theory bors should be able to do this all on its own properly, but... 18 | ## sigh... 19 | 20 | use_squash_merge = false 21 | -------------------------------------------------------------------------------- /changelog.org: -------------------------------------------------------------------------------- 1 | * Version 0.1: Developer Edition 2 | 3 | Initial public release with basic trappings. Features may be unstable. Only 4 | available from the source repository. 5 | -------------------------------------------------------------------------------- /conf/eris.conf.example: -------------------------------------------------------------------------------- 1 | # eris.conf.example: a full, authoritative example of the Eris configuration 2 | # file. If some relevant configuration value is available but not described 3 | # here, that should be considered a bug. 4 | # 5 | # The Eris configuration file is actually a Perl program that is run inside a 6 | # sandbox, giving it a more expressive configuration format than you might be 7 | # used to. 8 | 9 | # To start off with, every configuration file is a top-level value called a Perl 10 | # "hash". You can think of this like an object in JSON, a dictionary in Python 11 | # or any kind of map from keys to values. 12 | # 13 | # Perl hash values take the form '{ key1 => value1, key2 => value2, ... }', 14 | # where the values are any Perl values (including other hash values), and keys 15 | # are bare keywords (i.e. they are not quoted in any way). 16 | { 17 | 18 | # User authentication: a list of names in the format 'username:password', 19 | # which specify the list of users who are allowed to log in. Any clients to 20 | # the HTTP server must provide one of these username/password combinations 21 | # using HTTP Basic Authentication headers. Note that the password is provided 22 | # in cleartext, not in any hashed format. 23 | # 24 | # If no users are provided, then HTTP basic authentication is not required, 25 | # and any HTTP clients may download objects from the cache. 26 | # 27 | # In the following example, we enable two users. 28 | # 29 | # The default value for this variable is '[]', i.e. no authentication is required 30 | # to query or use the binary cache. 31 | users => [ 32 | 'austin:rules', 33 | 'david:rocks', 34 | ], 35 | 36 | # Signing configuration: a specified URL that identifies the authority who 37 | # signs packages, as well as the paths to the private keyfile. Note these 38 | # options for the keys are *always* filepaths: you cannot specify them inline 39 | # in the configuration file. 40 | # 41 | # The public key does not need to be specified, and is calculated 42 | # automatically from the secret key. 43 | # 44 | # The intention of the '-1' in the URL parameter is to signify key-rollover; 45 | # e.g. cache.example.com-1's key may be rotated in a year, resulting in a new 46 | # key identified by cache.example.com-2. 47 | signing => { 48 | host => 'cache.example.com-1', 49 | private => '/etc/eris/cache.sk', 50 | }, 51 | 52 | # Server listening configuration: a list of multiple addresses to bind to, as 53 | # well as their configuration. 54 | # 55 | # In the following example, we listen on three addresses, one using HTTP, 56 | # another using HTTPS, and a third over a unix socket located at 57 | # /run/eris/eris.sock -- the default HTTPS handler uses a set of keys that are 58 | # specified in the URL string. 59 | # 60 | # These examples *probably* cover just about everything you need, but the most 61 | # authoritative reference for possible options is the documentation for the 62 | # Mojo::Server::Daemon module: 63 | # 64 | # https://mojolicious.org/perldoc/Mojo/Server/Daemon#listen 65 | # 66 | # The default connection string will cause Eris to listen on port 8080, on 67 | # loopback only, e.g. the default connection string can be specified with 68 | # "http://127.0.0.1:8080" 69 | listen => [ 70 | 'http://[::]:8080', 71 | 'https://[::]:8443?cert=/etc/eris/eris.crt&key=/etc/eris/eris.key', 72 | 'http+unix://%2Frun%2Feris%2Feris.sock' 73 | ], 74 | 75 | # An upstream HTTP cache to use for proxying, or on a cache miss. If this is 76 | # configured, and Eris is asked to fetch an object from the store, and it 77 | # isn't present, then this upstream will be contacted and asked instead. This 78 | # effectively allows Eris to serve your private artifacts in the store, and 79 | # serve upstream objects when needed. 80 | # 81 | # If the given URL has the query string parameter 'always=1' attached to it, 82 | # then this HTTP cache will **ALWAYS** be used for all requests. This 83 | # effectively makes Eris a reverse proxy for some other cache server. The 84 | # default setting is 'always=0'. 85 | # 86 | # Note that this mode effectively *is* an HTTP proxy; while Nix will respect 87 | # 302s, we do not issue them, and instead actually proxy the request directly 88 | # through to the client. This means that Eris will not sign requests for NARs 89 | # served by the upstream cache. You must have all the necessary trusted keys 90 | # you need configured. 91 | # 92 | # This setting is intended to be used for deployments where you only want a 93 | # single binary cache endpoint to serve all user requests (for example, to do 94 | # egress traffic control, or relax the need for private machines to have 95 | # external IP addresses for contacting cache.nixos.org -- a single server 96 | # running Eris is the only machine that needs egress IP availability.) 97 | upstream => 'https://cache.nixos.org?always=0', 98 | 99 | # Proxy mode: if enabled, inform the HTTP server that we are, presumably, 100 | # behind another server that will set headers like X-Forwarded-For, etc. In 101 | # the event you are using a caching system like Fastly, CloudFlare, etc, or a 102 | # frontend HTTP proxy such as Nginx, you should enable this. 103 | # 104 | # The default value is '0', meaning no proxy is in front of us. 105 | proxy => 1, 106 | 107 | # Status page: if enabled, enable the Mojolicious status page, available at /mojo-status, 108 | # which will give some basic overview of the current server status. This page is subject 109 | # to the HTTP basic authentication critera specified by the 'user' configuration value. 110 | # 111 | # The default value is '0', meaning the server status is not available. 112 | status => 1, 113 | 114 | # The number of worker sub-processes to create, each of which handles requests. 115 | # You will probably not need to change this configuration value. 116 | # 117 | # The default value is 4. 118 | workers => 4, 119 | 120 | # The maximum number of accepted connections each worker process is allowed to 121 | # handle concurrently, before halting new incoming connections. 122 | # 123 | # The default value is 1000. 124 | clients => 1000, 125 | 126 | # Enable a nice HTML index page for the server. 127 | # 128 | # The default value is 1, i.e. enabled. 129 | index_page => 1, 130 | 131 | # Priority of this binary cache, relative to all others. Smaller numbers 132 | # (from 1 to 100) have higher priority. (cache.nixos.org has a priority of 133 | # 40.) 134 | # 135 | # The default value is 30. 136 | priority => 30, 137 | } 138 | -------------------------------------------------------------------------------- /conf/eris.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=eris binary cache server 3 | Documentation=man:eris(8) https://thoughtpolice.github.io/eris 4 | 5 | After=network.target 6 | 7 | [Service] 8 | Type=forking 9 | PIDFile=/tmp/eris.pid 10 | 11 | DynamicUser=true 12 | IPAccounting=true 13 | 14 | ProtectHome=yes 15 | ProtectSystem=full 16 | PrivateTmp=yes 17 | 18 | Restart=always 19 | RestartSec=5s 20 | ExecStart=@NIXOUT@/bin/eris 21 | ExecReload=@NIXOUT@/bin/eris 22 | KillMode=process 23 | 24 | [Install] 25 | WantedBy=multi-user.target 26 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { repo ? builtins.fetchGit ./. 2 | , versionFile ? ./.version 3 | , officialRelease ? false 4 | 5 | , nixpkgs ? null 6 | , config ? {} 7 | , system ? builtins.currentSystem 8 | }: 9 | 10 | with builtins; 11 | 12 | let 13 | bootstrap = import ./nix/bootstrap.nix { 14 | inherit nixpkgs config system; 15 | inherit repo officialRelease versionFile; 16 | }; 17 | in 18 | 19 | with bootstrap.pkgs; 20 | 21 | stdenv.mkDerivation rec { 22 | pname = "eris"; 23 | inherit (bootstrap) version relname; 24 | 25 | src = lib.cleanSource ./.; 26 | 27 | buildInputs = 28 | [ perl nix nix.perl-bindings glibcLocales 29 | ] ++ (with perlPackages; 30 | [ Mojolicious MojoliciousPluginStatus IOSocketSSL 31 | DBI DBDSQLite 32 | ]); 33 | 34 | outputs = [ "out" "man" ]; 35 | 36 | unpackPhase = ":"; 37 | installPhase = with perlPackages; '' 38 | mkdir -p \ 39 | $out/bin $out/libexec $out/lib/systemd/system \ 40 | $man/share/man/man8/ 41 | 42 | # Install the man page 43 | substitute ${./eris.8.pod.in} ./eris.8.pod \ 44 | --subst-var-by VERSION "${version}" 45 | pod2man \ 46 | --section=8 \ 47 | --name="ERIS" \ 48 | --center="Eris User Manual" \ 49 | ./eris.8.pod > $man/share/man/man8/eris.8 50 | 51 | # Install the systemd files 52 | substitute ${./conf/eris.service.in} $out/lib/systemd/system/eris.service \ 53 | --subst-var-by NIXOUT "$out" 54 | 55 | # Strip the nix-shell shebang lines out of the main script 56 | grep -v '#!.*nix-shell' ${./eris.pl} > $out/libexec/eris.pl 57 | 58 | # Set up accurate version information, xz utils 59 | substituteInPlace $out/libexec/eris.pl \ 60 | --replace '"0xERISVERSION"' '"${version}"' \ 61 | --replace '"0xERISRELNAME"' '"${relname}"' \ 62 | --replace '"xz"' '"${xz.bin}/bin/xz"' \ 63 | --replace '"bzip2"' '"${bzip2.bin}/bin/bzip2"' 64 | 65 | # Create the binary and set permissions 66 | touch $out/bin/eris 67 | chmod +x $out/bin/eris 68 | 69 | # Wrapper that properly sets PERL5LIB 70 | cat > $out/bin/eris < [ 'http://[::]:80', 'https://[::]:443' ], 59 | proxy => 1, 60 | 61 | signing => { 62 | host => 'cache.z0ne.pw-1', 63 | private => '/etc/eris/sign.sk', 64 | }, 65 | } 66 | ''; 67 | in { enable = true; 68 | configFile = "${erisConfig}"; 69 | ipAddressAllow = cloudflareIPs; 70 | ipAddressDeny = "any"; 71 | }; 72 | 73 | # -- El fin 74 | 75 | # This value determines the NixOS release with which your system is to be 76 | # compatible, in order to avoid breaking some software such as database 77 | # servers. You should change this only after NixOS release notes say you 78 | # should. 79 | system.stateVersion = "18.09"; # Did you read the comment? 80 | } 81 | -------------------------------------------------------------------------------- /demo/default.nix: -------------------------------------------------------------------------------- 1 | { config ? {} 2 | }: 3 | 4 | let 5 | nixpkgs = import ../nix/nixpkgs.nix { inherit config; }; 6 | 7 | in import "${nixpkgs.path}/nixos" { 8 | configuration = import ./configuration.nix; 9 | system = "x86_64-linux"; 10 | } 11 | -------------------------------------------------------------------------------- /demo/readme.org: -------------------------------------------------------------------------------- 1 | * Demo: Eris with CloudFlare and Packet.net 2 | 3 | How to deploy your own binary cache, using [[https://www.cloudflare.com][CloudFlare]], [[https://www.packet.net][Packet.net]], and Eris. 4 | 5 | ** Table of Contents :TOC_3_gh: 6 | - [[#demo-eris-with-cloudflare-and-packetnet][Demo: Eris with CloudFlare and Packet.net]] 7 | - [[#the-basic-idea][The basic idea]] 8 | - [[#getting-started][Getting started]] 9 | - [[#create-a-new-packetnet-instance][Create a new Packet.net instance]] 10 | - [[#configure-your-cloudflare-dns-settings][Configure your CloudFlare DNS settings]] 11 | - [[#important-side-note-disable-all-cloudflare-caching-via-page-rules][Important side note: disable all CloudFlare caching via Page Rules]] 12 | - [[#create-a-set-of-keys-for-signatures][Create a set of keys for signatures]] 13 | - [[#sync-your-packetnet-nixos-hardware-configuration][Sync your Packet.net NixOS hardware configuration]] 14 | - [[#build-and-copy-the-nixos-closure][Build and copy the NixOS closure]] 15 | - [[#deployswitch-the-configuration][Deploy/switch the configuration]] 16 | - [[#optional-step-reboot][Optional step: reboot]] 17 | - [[#ensure-the-eris-service-is-running][Ensure the Eris service is running]] 18 | - [[#test-by-copying-files-to-a-local-store][Test by copying files to a local store]] 19 | - [[#done-whats-next][Done. What's next?]] 20 | - [[#add-the-caches-public-key-to-your-trusted-public-keys][Add the cache's public key to your ~trusted-public-keys~]] 21 | - [[#push-objects-into-the-store-from-remote-locations][Push objects into the store from remote locations]] 22 | - [[#look-at-your-cloudflare-analytics][Look at your CloudFlare analytics]] 23 | - [[#start-enabling-private-users][Start enabling private users]] 24 | - [[#hack-the-config-file-further][Hack the config file further]] 25 | - [[#check-out-the-nixos-configuration-and-eris-module][Check out the NixOS configuration and Eris module]] 26 | - [[#try-some-more-exotic-deployments][Try some more exotic deployments]] 27 | - [[#hack-the-codebase-itself][Hack the codebase itself]] 28 | 29 | ** The basic idea 30 | 31 | The basic idea is pretty simple: we want to host a Nix binary cache -- maybe for 32 | ourselves, our own private project, maybe for ~$WORK~ -- and we want it to take 33 | little fuss, without being too expensive to run. Bandwidth is typically 34 | expensive at most providers, and serving tons of files over the network can 35 | actually be fairly CPU intensive for a cache that's intended to be hit 36 | frequently. Also -- it'd be nice to use NixOS, both for server administration, 37 | and since it comes with easy support for running Eris. 38 | 39 | *** Getting started 40 | 41 | ** Create a new Packet.net instance 42 | 43 | #+BEGIN_SRC bash 44 | export PACKET_IP="abc.wx.y.z" 45 | ssh "root@${PACKET_IP}" -- nixos-version 46 | #+END_SRC 47 | 48 | ** Configure your CloudFlare DNS settings 49 | 50 | Now that your instance has been started, you should configure ~A~ and ~AAAA~ 51 | records for your public IPv4/IPv6 addresses that you were assigned by Packet. 52 | 53 | *Be sure to enable CloudFlare's security and routing features by clicking on the 54 | "CloudFlare" icon!* If you don't do this, *your requests will not route through 55 | CloudFlare, and you will be charged for egress cache bandwidth.* 56 | 57 | Once you have DNS records set up, you're ready to deploy things. But you need to 58 | make one slight modification... 59 | 60 | *** Important side note: disable all CloudFlare caching via Page Rules 61 | 62 | At this point, there's another important consideration we should take note of: 63 | *turn off caching in CloudFlare for your cache site, via a Page Rule*. 64 | 65 | You might be wondering: /"Why would I want to do that? Don't I want CloudFlare 66 | to cache my Nix objects?"/ and the answer is simple: you might want that, but 67 | more importantly, you want to act like a good neighbor for other CloudFlare 68 | customers. 69 | 70 | Here's the deal: CloudFlare and Packet have entered into an agreement called the 71 | Bandwidth Alliance, meaning traffic between the two systems is actually not 72 | billed in the same way as normal egress traffic. Effectively, any egress/ingress 73 | bandwidth between Packet and CloudFlare is totally free. Considering egress 74 | bandwidth prices are very expensive on most Cloud Providers, that's an excellent 75 | thing to hear. This means that, for our binary cache, the only rates we pay are 76 | flat-fee rates, based on the hardware prices. 77 | 78 | But at the same time, CloudFlare /is not a cache for arbitrary content, and not 79 | an unlimited cache for freeloading./ At least at one point, I believe 80 | CloudFlare's EULA specifically ruled out "abusive" uses of their service such as 81 | loading large amounts of non-WWW content (images, css, html, etc) into the 82 | cache. It's obviously geared for "web" content, and Nix binary objects for a 83 | package manager are pretty clear non-web uses. And while generally they aren't 84 | stingy about free-tier users, constant abuse will (probably) be noticed, since 85 | you're eating up disproportionate resources. After all, if you're getting free 86 | bandwidth -- it would be nice to be a good neighbor. 87 | 88 | In particular, all of the cache space /you/ use on big binary objects is cache 89 | space that could better go to other customers for their web content, especially 90 | customers in their free or lower-paid tiers. Of course, CloudFlare's bandwidth 91 | *is* free, but we're talking about their caches. Do you /really/ want to abuse 92 | their cache, too, after you're already leveraging their network for free egress? 93 | 94 | I view this as a pretty reasonable tradeoff: free egress bandwidth, and we get 95 | CloudFlare's features like DDOS protection and endpoint security, but we turn 96 | the cache off, and let the requests directly hit our servers. The least you can 97 | do is pay off your free egress by using some CPU cycles -- especially since 98 | those cycles have flat-rate costs. 99 | 100 | A good page rule might look like this, assuming your cache is intended to be 101 | available at ~http[s]://cache.z0ne.pw/~: 102 | 103 | #+BEGIN_SRC 104 | *cache.z0ne.pw/* 105 | #+END_SRC 106 | 107 | Then, for the settings on this particular route, just be sure to enable *Caching 108 | Level: Bypass*. This applies to HTTP and HTTPS endpoints on all objects served 109 | by Eris, and completely disables caching for all of them. 110 | 111 | Now your server is protected (origin IP obscured, DDOS protected, automatic SSL) 112 | -- but you're also acting like a good neighbor who won't hurt other, more 113 | legitimate web-caching uses. Win-win! 114 | 115 | ** Create a set of keys for signatures 116 | 117 | By default, Nix requires packages that it downloads from trusted caches to be 118 | signed by a trusted, cryptographic key. We'll quickly generate these keys for 119 | the demo. 120 | 121 | First, ~ssh root@${PACKET_IP}~, and then: 122 | 123 | #+BEGIN_SRC bash 124 | mkdir /etc/eris 125 | nix-store --generate-binary-cache-key "${DOMAIN_NAME}-1" /etc/eris/sign.sk /etc/eris/sign.pk 126 | chown -R root:adm /etc/eris/ 127 | chmod 0640 /etc/eris/sign.pk 128 | exit 129 | #+END_SRC 130 | 131 | The ~eris~ systemd service is dynamically made part of the ~adm~ group, so we 132 | modify the key files to be owned by that group, and chmod ~0640~ to allow owning 133 | user and group to read the private key. 134 | 135 | ** Sync your Packet.net NixOS hardware configuration 136 | 137 | When you create a NixOS instance using Packet.net, a large amount of 138 | prepopulated information about the instance is already provided. In true NixOS 139 | form, these settings are provided as a set of modules, by default available 140 | under the ~/etc/nixos/packet/~ directory, on your instance. Your module simply 141 | needs to include ~/etc/nixos/packet.nix~ in order to use them. 142 | 143 | For the purposes of this document, I'll be building the NixOS closure image for 144 | my Packet machine locally, then copying it to the remote machine and deploying 145 | it. 146 | 147 | Sync your configuration like so: 148 | 149 | #+BEGIN_SRC bash 150 | cd ./demo/ 151 | rsync -e ssh -rv root@"${PACKET_IP}":/etc/nixos/ packet 152 | #+END_SRC 153 | 154 | This will create a new ~./demo/packet/~ directory, containing the instance 155 | configuration data. We won't be modifying this, we'll just-reuse it. 156 | 157 | Our ~configuration.nix~ has a line similar to the following: 158 | 159 | #+BEGIN_SRC nix 160 | { 161 | imports = [ ./packet/packet.nix ]; 162 | # ... more configuration ... 163 | } 164 | #+END_SRC 165 | 166 | With these files in the proper place, we can build our Packet.net image locally, 167 | and push it to the remote server. All of the prepopulated hardware information 168 | (disks, hostname, etc) will be filled out automatically. 169 | 170 | ** Build and copy the NixOS closure 171 | 172 | This will build and copy the entire NixOS closure into the remote machine, using 173 | the correct hardware settings for the instance in question: 174 | 175 | #+BEGIN_SRC bash 176 | export Q=$(nix-build -QA system) 177 | time nix copy --to "ssh://root@${PACKET_IP}" $Q 178 | #+END_SRC 179 | 180 | ** Deploy/switch the configuration 181 | 182 | Now, simply switch the configuration. You can do this remotely. 183 | 184 | #+BEGIN_SRC bash 185 | ssh "root@${PACKET_IP}" -- "$Q"/bin/switch-to-configuration switch 186 | #+END_SRC 187 | 188 | *** Optional step: reboot 189 | 190 | At this point, you can also reboot to ensure you have a clean startup (since 191 | doing upgrades across major NixOS versions can cause some glitches). 192 | 193 | #+BEGIN_SRC bash 194 | ssh "root@${PACKET_IP}" -- reboot 195 | #+END_SRC 196 | 197 | ** Ensure the Eris service is running 198 | 199 | #+BEGIN_SRC bash 200 | ssh "root@${PACKET_IP}" -- systemctl status eris-git 201 | #+END_SRC 202 | 203 | You should see the ~systemctl status~ output show you that ~eris.git~ is active 204 | and running. The ~journalctl~ logs will print out the startup information (in my 205 | example, for a cache server named ~cache.z0ne.pw~): 206 | 207 | #+BEGIN_SRC text 208 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] Eris: version X.YpreN_abcdef, mode = production (mojo 8.02) 209 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] libev: mode 4 (epoll), nix: ver 2.1.3, store: /nix/store 210 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] config: using file '/nix/store/ri9imndpl9bq4rf65wgsg9132gm1z1fj-eris.conf' 211 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] signing: user-specified, hostname = cache.z0ne.pw-1 212 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] public key: cache.z0ne.pw-1:XvOZOPoECSkRGR2VaSQoE2zlqt5qRS+9Y7bAYIzA+1s= 213 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] Listening at "http://[::]:80" 214 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: [i] Listening at "https://[::]:443" 215 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: Server available at http://[::]:80 216 | Oct 16 21:48:01 cache.z0ne.pw eris[12197]: Server available at https://[::]:443 217 | Oct 16 21:48:01 cache.z0ne.pw systemd[1]: Started eris binary cache server. 218 | #+END_SRC 219 | 220 | ** Test by copying files to a local store 221 | 222 | Now, you can test it by just copying an entire closure from the store somewhere 223 | locally. This will ensure you download the correct packages /and/ all their 224 | dependencies into a new location, so you can test the throughput/bandwidth of 225 | the system. 226 | 227 | As an example, we can just re-download the system closure we installed in the 228 | last step: 229 | 230 | #+BEGIN_SRC bash 231 | rm -rf test-store; 232 | nix copy --from "https://${DOMAIN_NAME}" --to file://$(pwd)/test-store $Q 233 | #+END_SRC 234 | 235 | This should succeed in copying all the ~.nar~ files for your system closure 236 | directly into the ~./test-store~ directory. Feel free to delete this -- it'll 237 | take up quite a bit of space -- since it's just a demonstration of everything 238 | working. 239 | 240 | ** Done. What's next? 241 | 242 | You now have your own Nix binary cache! And it has fast, sustainable bandwidth, 243 | DDoS protection, and more. There are some other things you can do now: 244 | 245 | *** Add the cache's public key to your ~trusted-public-keys~ 246 | 247 | Grab the public key: 248 | 249 | #+BEGIN_SRC bash 250 | curl https://${DOMAIN_NAME}/v1/public-key 251 | #+END_SRC 252 | 253 | Add this key to your ~nix.conf~ file under ~trusted-public-keys~, or use the 254 | ~--option trusted-public-keys~ flag to set it on demand for individual commands. 255 | Set this up properly, and you'll permanently have a cache you can call your own! 256 | 257 | *** Push objects into the store from remote locations 258 | 259 | Now that your server is up and running, you can just start tossin' stuff in 260 | there! Use SSH access to copy whatever paths in you want out of your store: 261 | 262 | #+BEGIN_SRC bash 263 | nix copy --to "ssh://root@${PACKET_IP}" /nix/store/... 264 | #+END_SRC 265 | 266 | Afterwords, you'll be able to download it later. Or, maybe you'll leave it there 267 | forever and never even think about it again. Who knows!? 268 | 269 | *** Look at your CloudFlare analytics 270 | 271 | When you checkout your CloudFlare dashboard, you'll be able to see how much 272 | bandwidth you're pushing through the system. Even though your requests aren't 273 | cached, they do pass through CloudFlare's network, and thus are shown in 274 | analytic reports. Watch the numbers get higher and higher as time goes on, [[https://tvtropes.org/pmwiki/pmwiki.php/Main/SlouchOfVillainy][while 275 | you kick back in your comically-sized gigantic chair.]] 276 | 277 | *** Start enabling private users 278 | 279 | At this point, you probably have a boatload of treasure you'd like to keep safe. 280 | Turn on the ~users~ setting in the Eris configuration file (specified in 281 | ~configuration.nix~), and keep sea-fairing pirates at bay. 282 | 283 | *** Hack the config file further 284 | 285 | The configuration file in this example is kept in the Nix store. But it might be 286 | better to keep it somewhere like ~/etc/eris/eris.conf~, so you can update it 287 | yourself more easily. 288 | 289 | You can also spice it up -- maybe use ~$ENV{...}~ in your configuration file to 290 | read settings out of the system environment variables. Combine it with 291 | ~EnvironmentFile~ in ~systemd~ and who knows what could happen! 292 | 293 | Do you have what it takes to refactor the code? (Of course you do.) 294 | 295 | *** Check out the NixOS configuration and Eris module 296 | 297 | Be sure to read the NixOS configuration in ~configuration.nix~, as well as the 298 | Eris module (in ~../module.nix~), since they have some extra boondoggles. For 299 | example, it makes the build configuration more minimal, enables some newer 300 | features like TCP BBR, sets up custom NTP servers, and it also uses IP 301 | whitelist/blacklists to *only* allow CloudFlare to talk to the Eris HTTP server 302 | (so your egress bandwidth is *only* used by CloudFlare). Try adding a million 303 | more unnecessary features! 304 | 305 | *** Try some more exotic deployments 306 | 307 | As an alternative, hack the NixOS module to try and add some new features. For 308 | example, instead of making the server publicly available on the internet and 309 | using IP whitelisting to only allow CloudFlare IPs to talk to the HTTP endpoint, 310 | you could also try configuring CloudFlare's [[https://support.cloudflare.com/hc/en-us/articles/204899617-Authenticated-Origin-Pulls][Authenticated Origin Pulls]] to keep 311 | things safe. Or combine them. You could alternatively deploy the whole system 312 | using [[https://developers.cloudflare.com/argo-tunnel/][Argo Tunnel]], which will completely remove the need for Eris to listen on 313 | the internet at all, as well as any end-to-end certificate management. 314 | 315 | *** Hack the codebase itself 316 | 317 | Want to implement something more daring? Everything is broken? Try writing a 318 | patch or submitting an issue and we'll figure it out. Eris is written in Perl by 319 | an amateur, so there are probably trillions of bugs you can fix. 320 | 321 | -------------------------------------------------------------------------------- /eris.8.pod.in: -------------------------------------------------------------------------------- 1 | =encoding utf8 2 | 3 | =head1 NAME 4 | 5 | eris -- an easy to use binary cache for the Nix package manager 6 | 7 | =head1 SYNOPSIS 8 | 9 | B > 10 | 11 | =head1 DESCRIPTION 12 | 13 | B is a easy to use "binary cache" for the Nix package manager: it exports 14 | your Nix store over an HTTP link, allowing you to download objects located on 15 | other machines. 16 | 17 | =head1 BASIC USAGE AND CONFIGURATION 18 | 19 | When Eris starts up, it reads a configuration file, and begins executing in one 20 | of two modes, controlled by command line options: daemon mode, the default if 21 | no arguments are provided, or foreground mode, if the B<--foreground> command 22 | line option is given. 23 | 24 | Daemon mode forks a process and writes its PID to the CWD. This process can 25 | later be stopped by using the B<--stop> command line option in the same CWD. 26 | 27 | In either mode, a configuration file located in the CWD named B will 28 | be read and applied. Its options and format are described below. 29 | 30 | =head1 COMMAND LINE OPTIONS 31 | 32 | Eris uses the L server included with Mojolicious for deployment, 33 | and by default exports two main command line options to control Hypnotoad 34 | startup. 35 | 36 | =over 37 | 38 | =item B<-f>, B<--foreground> 39 | 40 | Start the server in the foreground, as opposed to forking a daemon process (the 41 | default mode). 42 | 43 | =item B<-s>, B<--stop> 44 | 45 | Stop the server (previously started in daemon mode, i.e. with no command line 46 | parameters) gracefully. 47 | 48 | =back 49 | 50 | =head1 CONFIGURATION 51 | 52 | Lorem ipsum... 53 | 54 | =head1 SEE ALSO 55 | 56 | L 57 | 58 | =head1 AUTHORS 59 | 60 | Eris was originally authored, and is maintained by, L. 62 | 63 | =head1 COPYRIGHT 64 | 65 | Copyright (C) 2018-2019 Austin Seipp. License GPLv3: L. This is free software: you are 67 | free to change and redistribute it. There is NO WARRANTY, to the extent 68 | permitted by law. 69 | 70 | =head1 COLOPHON 71 | 72 | This page is part of release @VERSION@ of the Eris project. 73 | -------------------------------------------------------------------------------- /eris.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -i hypnotoad -p perl nix nix.perl-bindings glibcLocales perlPackages.Mojolicious perlPackages.MojoliciousPluginStatus perlPackages.IOSocketSSL perlPackages.DBI perlPackages.DBDSQLite 3 | 4 | # Eris: simple, flexible nix binary cache server 5 | # Copyright (C) 2018-2019 Austin Seipp 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | package Eris; 21 | 22 | ## ----------------------------------------------------------------------------- 23 | 24 | use File::Basename qw(basename); 25 | use Scalar::Util qw(looks_like_number); 26 | use Cwd qw(getcwd); 27 | use MIME::Base64; 28 | use List::Util; 29 | 30 | use Mojolicious::Lite -signatures; 31 | use Mojo::IOLoop; 32 | 33 | use Nix::Config; 34 | use Nix::Manifest; 35 | use Nix::Store; 36 | use Nix::Utils qw(readFile); 37 | 38 | ## ----------------------------------------------------------------------------- 39 | ## -- Basics 40 | 41 | # Note: replaced by the build script for nix builds 42 | our $VERSION = "0xERISVERSION"; 43 | our $RELNAME = "0xERISRELNAME"; 44 | our $XZEXE = "xz"; 45 | our $BZ2EXE = "bzip2"; 46 | 47 | my $eris_conf_file = $ENV{ERIS_CONFIG}; 48 | $eris_conf_file ||= getcwd . '/eris.conf'; 49 | 50 | # Configuration values may be set through the Perl config file 51 | plugin 'Config' => { 52 | file => $eris_conf_file, 53 | 54 | default => { 55 | # By default, access is unrestricted 56 | users => [], 57 | 58 | # By default, no signing keys are generated or used 59 | signing => 'none', 60 | 61 | # Default access is localhost only 62 | listen => [ 'http://127.0.0.1:8080' ], 63 | 64 | # Assumed to not be behind a proxy 65 | proxy => 0, 66 | 67 | # Defaults for workers and clients 68 | workers => 4, 69 | clients => 1000, 70 | 71 | # No status page by default 72 | status => 0, 73 | 74 | # Index page enabled by default 75 | index_page => 1, 76 | 77 | # Default cache priority: higher than cache.nixos.org 78 | priority => 30, 79 | 80 | # No default upstream. 81 | upstream => '', 82 | }, 83 | }; 84 | 85 | ## ----------------------------------------------------------------------------- 86 | ## -- Sanity checks 87 | 88 | # It seems to be extremely important that HOME is set properly before any 89 | # communication with the Nix daemon; this is because it uses a temporary 90 | # location under $HOME/.cache or $XDG_CACHE_DIR for temporary storage in some 91 | # operations, but it throws an exception /earlier than that/ if $HOME isn't set 92 | # (regardless of whether it will be used or not), making the server throw 93 | # unhandled 500 errors when functions like queryPathFromHashPart are called. In 94 | # a strange twist of fate however, this also seems to be racy somehow -- I was 95 | # able to successfully use the daemon *sometimes* if $HOME was unset, but not 96 | # other times. But when you run with multiple workers, this basically leads to 97 | # non-deterministic failure for clients (some worker processes may open 98 | # successful connections, and others may not.) This probably doesn't impact 99 | # nix-serve since it has a UID assigned in Nixpkgs, while Eris tries to use 100 | # DynamicUser=true in systemd 101 | # 102 | # In the end, just die if we don't have $HOME set, since there's nothing we can 103 | # do to stop this. 104 | die "HOME is not set (nix-store requires this); immediately exiting!" 105 | unless defined($ENV{HOME}); 106 | 107 | ## ----------------------------------------------------------------------------- 108 | ## -- Custom Mojo Initialization 109 | 110 | # Ensure Hypnotoad writes the .pid file into the CWD of the running app by 111 | # default; otherwise, it tries to write it next to the script, but that fails 112 | # because the script might be inside /nix/store. However, systemd also needs 113 | # to track the right PID file for termination, so we allow it to be overridden 114 | # in the environment (see module.nix) 115 | my $pid_file = $ENV{ERIS_PID_FILE}; 116 | $pid_file ||= getcwd . "/eris.pid"; 117 | 118 | app->config(hypnotoad => { 119 | pid_file => $pid_file, 120 | 121 | # Make sure Hypnotoad listens on the specified addresses 122 | listen => app->config->{listen}, 123 | 124 | # Configure proxy-mode for X-Forwarded-For, etc, if asked 125 | proxy => app->config->{proxy}, 126 | 127 | # Workers, clients 128 | workers => app->config->{workers}, 129 | clients => app->config->{clients}, 130 | }); 131 | 132 | # Add some custom content types we can use with 'render' 133 | app->types->type(narinfo => 'text/x-nix-narinfo'); 134 | 135 | ## ----------------------------------------------------------------------------- 136 | ## -- Global startup logic 137 | 138 | # Output a nice startup message 139 | my $mode = app->mode; 140 | my $ev_mode = $ENV{LIBEV_FLAGS} || 1; 141 | 142 | # The libev backend is chosen at startup time, long before we can control 143 | # it here. Best to just tell the user what they chose. 144 | # 145 | # LIBEV_FLAGS is a mix of the bitwise flags from libev's API; in particular, 146 | # there are 4 options: 147 | # 148 | # - 1 (0b0001) select(2) everything 149 | # - 2 (0b0010) poll(2) everything 150 | # - 4 (0b0100) epoll(2) linux 151 | # - 8 (0b1000) kqueue(2) mac/bsd 152 | # 153 | # the default we set in the Nix package is 12, which is a mix of epoll and 154 | # kqueue. these asynchronous interfaces are vital for real usage, so we set them 155 | # by default. 156 | my @ev_backends = (); 157 | for ($ev_mode) { 158 | if ($_ & 0b1) { push @ev_backends, "select" } 159 | if ($_ & 0b10) { push @ev_backends, "poll" } 160 | if ($_ & 0b100) { push @ev_backends, "epoll" } 161 | if ($_ & 0b1000) { push @ev_backends, "kqueue" } 162 | } 163 | my $libev_info = join "/", @ev_backends; 164 | 165 | # Basics: version, mode settings, nix configuration 166 | app->log->info( 167 | "Eris $Eris::VERSION ($Eris::RELNAME), ". 168 | "mode = $mode (mojo $Mojolicious::VERSION)" 169 | ); 170 | app->log->info( 171 | "libev: mode $ev_mode ($libev_info), ". 172 | "nix: ver $Nix::Config::version, ". 173 | "local store: $Nix::Config::storeDir" 174 | ); 175 | 176 | # Config info 177 | my $conf_info = (-e "$eris_conf_file") 178 | ? "using file '$eris_conf_file'" 179 | : "no config file, using default settings"; 180 | app->log->info("config: " . $conf_info); 181 | 182 | # Proxy, status page, index page info 183 | app->log->info( 184 | "priority: " . app->config->{priority} . ", " . 185 | "status page: " . (app->config->{status} == 1 ? "yes" : "no") . ", " . 186 | "index page: " . (app->config->{index_page} == 1 ? "yes" : "no") . ", " . 187 | "proxy headers: " . (app->config->{proxy} == 1 ? "yes" : "no") 188 | ); 189 | 190 | # User info 191 | { 192 | my $users = app->config->{users}; 193 | my $numusers = scalar @$users; 194 | my $authinfo = $numusers != 0 195 | ? "enabled, $numusers users" 196 | : "none (publicly available)"; 197 | 198 | app->log->info("authentication: " . $authinfo); 199 | } 200 | 201 | # -- 202 | # -- Signing logic 203 | # -- 204 | 205 | my ($sign_host, $sign_pk, $sign_sk) = (undef, undef, undef); 206 | 207 | ## Case 1: user-specified keys 208 | if (ref(app->config->{signing}) eq 'HASH') { 209 | $sign_sk = readFile app->config->{signing}->{private}; 210 | chomp $sign_sk; # readFile doesn't do this itself 211 | 212 | $sign_host = app->config->{signing}->{host} // (split(/:/, $sign_sk))[0]; 213 | my $sign_sk64 = +(split /:/, $sign_sk)[-1]; 214 | my $sign_skno64 = decode_base64($sign_sk64); 215 | 216 | if (length($sign_skno64) != 64) { 217 | app->log->error("invalid signing key provided! signing disabled"); 218 | $sign_host = undef; 219 | $sign_sk = undef; 220 | } else { 221 | # An ed25519 secret key contains the public key as well. It's the botton 32 222 | # bytes of the whole 64 byte key. Compute that. 223 | my $sign_pk64 = encode_base64(substr($sign_skno64, 32)); 224 | chomp $sign_pk64; # beware encode_base64() newline! 225 | 226 | # Attach the user-configured signing hostname to this key, regardless of what 227 | # was in the file in the first place. `nix-store --generate-binary-cache-key` 228 | # will write a user-chosen name at creation time, but the user here would 229 | # (rightfully) expect the served hostname to match what they put in the 230 | # config file. 231 | $sign_sk = $sign_host . ":" . $sign_sk64; 232 | $sign_pk = $sign_host . ":" . $sign_pk64; 233 | 234 | app->log->info("signing: enabled, key = $sign_host:$sign_pk64"); 235 | } 236 | } else { 237 | app->log->info("signing: no signatures enabled") 238 | } 239 | 240 | # -- 241 | # -- upstream/proxy information 242 | # -- 243 | 244 | # Upstream information 245 | my $upstream = Mojo::URL->new(app->config->{upstream}); 246 | my $resign_upstream_narinfos = 0; 247 | my $resigning_unsafe = 0; 248 | my $resign_pubkey = undef; 249 | my $resign_keyname = undef; 250 | my $resign_key64 = undef; 251 | my $always_use_upstream = 0; 252 | 253 | # ensure the upstream host has a valid nix-cache-info 254 | my $upstream_host_valid = 0; 255 | if (defined($upstream->host)) { 256 | app->log->info("upstream configured, attempting nix-cache-info ping..."); 257 | 258 | my $ua = new Mojo::UserAgent->new; 259 | $ua->transactor->name("Eris/$Eris::VERSION"); 260 | my $url = Mojo::URL->new('nix-cache-info')->to_abs($upstream); 261 | my $body = $ua->get($url)->result->body; 262 | 263 | my ($upstreamStoreDir) = $body =~ /StoreDir: (.*)/; 264 | if ($upstreamStoreDir eq $Nix::Config::storeDir) { 265 | app->log->info("upstream: ok, StoreDir=$upstreamStoreDir"); 266 | $upstream_host_valid = 1; 267 | } else { 268 | app->log->error("FAIL: upstream has StoreDir=$upstreamStoreDir, incompatible with $Nix::Config::storeDir!"); 269 | app->log->error("marking upstream as invalid"); 270 | } 271 | } 272 | 273 | # if the upstream is ok, then figure out the upstream/resigning logic 274 | if ($upstream_host_valid) { 275 | $always_use_upstream = 1 if $upstream->query->param('always'); 276 | $resign_upstream_narinfos = 1 if $upstream->query->param('resign'); 277 | 278 | # disable resigning in specific cases 279 | if (!defined($sign_sk) && $resign_upstream_narinfos) { 280 | app->log->warn("resigning configured for '".$upstream->host."', but no signing key set! disabling"); 281 | $resign_upstream_narinfos = 0; 282 | } 283 | 284 | $resign_pubkey = $upstream->query->param('public_key'); 285 | if ($resign_upstream_narinfos) { 286 | if (!$resign_pubkey) { 287 | if (!$upstream->query->param('resign_unsafe')) { 288 | app->log->warn("resigning configured for '".$upstream->host."', but no public key set!"); 289 | app->log->warn("this is HIGHLY UNSAFE, so disabling. set 'resign_unsafe=1' to bypass!"); 290 | $resign_upstream_narinfos = 0; 291 | } else { 292 | app->log->warn("resigning configured for '".$upstream->host."', but no public key set, and unsafe mode enabled!"); 293 | app->log->warn("THIS IS HIGHLY UNSAFE! YOU SHOULD CONFIGURE AN UPSTREAM PUBLIC KEY"); 294 | $resigning_unsafe = 1; 295 | } 296 | } else { 297 | # We use the key name later on to do a lookup 298 | ($resign_keyname, $resign_key64) = +(split /:/, $resign_pubkey); 299 | } 300 | } 301 | 302 | # unset the parameters 303 | $upstream->query(always => undef); 304 | $upstream->query(resign => undef); 305 | $upstream->query(resign_unsafe => undef); 306 | $upstream->query(public_key => undef); 307 | 308 | # dump debugging info 309 | if ($always_use_upstream) { 310 | app->log->info("upstream: yes, always using " . $upstream . " (proxy mode)"); 311 | } else { 312 | app->log->info("upstream: yes, using " . $upstream . " on miss"); 313 | } 314 | 315 | if ($resign_upstream_narinfos) { 316 | app->log->info("upstream: re-signing narinfos with key '".$sign_host."'"); 317 | unless ($resigning_unsafe) { 318 | app->log->info("upstream: validating with public key '".$resign_pubkey."'"); 319 | } 320 | } 321 | 322 | } else { 323 | app->log->info("upstream: no, only serving given cache"); 324 | } 325 | 326 | ## ----------------------------------------------------------------------------- 327 | ## -- Warnings, further info 328 | 329 | if ($ev_mode == 1) { 330 | # this is the only case where select(2) would be the only option 331 | app->log->warn("NOTE: using select(2) backend only! This will not scale well"); 332 | app->log->warn("NOTE: try setting the environment variable LIBEV_FLAGS=15"); 333 | app->log->warn("NOTE: see 'man eris' for more info"); 334 | } 335 | 336 | ## ----------------------------------------------------------------------------- 337 | ## -- Global authentication logic 338 | 339 | under sub { 340 | my $c = shift; 341 | my $users = app->config->{users}; 342 | my $info = $c->req->url->to_abs->userinfo; 343 | 344 | # No authentication configured, so all attempts succeed 345 | return 1 if !@$users; 346 | 347 | # Attempted authentication 348 | if (defined($info)) { 349 | # Succeed if the user information in the HTTP header matches the list 350 | # of users specified in the config 351 | return 1 if List::Util::any { $_ eq $info } @$users; 352 | } 353 | 354 | # Otherwise (e.g. auth was wrong, no auth provided), throw up the http 355 | # authentication banner 356 | $c->res->headers->www_authenticate('Basic'); 357 | $c->render(text => "Authentication required.\n", status => 401); 358 | return undef; 359 | }; 360 | 361 | ## ----------------------------------------------------------------------------- 362 | ## -- Eris API routes: not used by Nix 363 | 364 | # The following group defines all of the 'v1' API endpoints 365 | group { 366 | under '/v1'; 367 | 368 | # Version handler; probably useful one day! 369 | get '/version' => { text => "$Eris::VERSION\n" }; 370 | 371 | # Public key handler; Nix won't use this, but it's useful for clients if they 372 | # quickly want to automatically fetch/import keys 373 | get '/public-key' => sub ($c) { 374 | return $c->render(format => 'txt', text => "No public key configured\n", status => 404) 375 | unless $sign_pk; 376 | 377 | $c->render(format => 'txt', text => "$sign_pk\n"); 378 | }; 379 | }; 380 | 381 | ## ----------------------------------------------------------------------------- 382 | ## -- Eris API routes: basic cache info 383 | 384 | die "Priority setting must be a number!" 385 | unless looks_like_number(app->config->{priority}); 386 | 387 | # Cache info handler 388 | get '/nix-cache-info' => { 389 | text => join "\n", ( 390 | "StoreDir: $Nix::Config::storeDir", 391 | "WantMassQuery: 1", 392 | "Priority: " . app->config->{priority}, 393 | "", 394 | ), 395 | }; 396 | 397 | ## ----------------------------------------------------------------------------- 398 | ## -- Fetching upstream objects 399 | 400 | helper parse_narinfo => sub ($c, $body) { 401 | my $hash = {}; 402 | 403 | $hash->{References} = [ ]; 404 | $hash->{Sig} = [ ]; 405 | 406 | while ($body =~ /(.*): (.*)/g) { 407 | if ($1 eq 'References') { 408 | push @{ $hash->{References} }, (split /\s/, $2); 409 | } 410 | elsif ($1 eq 'Sig') { 411 | push @{ $hash->{Sig} }, $2; 412 | } 413 | elsif ($1 eq 'NarSize' || $1 eq 'FileSize') { 414 | # contextualize narinfo value as an int 415 | $hash->{"$1"} = ($2 + 0); 416 | } 417 | else { 418 | $hash->{"$1"} = $2; 419 | } 420 | } 421 | 422 | # we need to do two things: first, fingerprintPath expects an array ref 423 | # instead of a direct array for the references, so we need to wrap the whole 424 | # thing with [ ... ] and put it in a scalar. second, narinfos must serve 425 | # 'References:' entries WITHOUT the store path attached, but fingerprintPath 426 | # REQUIRES the store path is prefixed. so: split into an array, add the 427 | # store path prefix, and return a ref. 428 | $hash->{References} = [ map { "$Nix::Config::storeDir/$_" } @{ $hash->{References} } ]; 429 | 430 | return $hash; 431 | }; 432 | 433 | my $our_user = new Mojo::UserAgent->new; 434 | $our_user->transactor->name("Eris/$Eris::VERSION"); 435 | $our_user->max_response_size(0); # infinite download size for upstreams 436 | 437 | # Helper routine that fetches objects from an upstream server, if it's 438 | # configured. This automatically figures out the right path to serve. If 439 | # resigning for upstream caches is enabled, then we enter an alternative path 440 | # for .narinfo files that appends our own signature to the response. 441 | helper fetch_upstream => sub ($c, $ctype, $info=undef) { 442 | my $path = $c->req->url; 443 | my $prefix = defined($info) ? "$info: " : ''; 444 | app->log->debug($prefix.'fetching upstream object '.$path); 445 | 446 | my $upstream_url = Mojo::URL->new($path); 447 | $upstream_url->query(''); 448 | $upstream_url = $upstream_url->to_abs($upstream); 449 | 450 | my $tx = $our_user->build_tx(GET => $upstream_url => { 451 | Accept => $ctype, 452 | }); 453 | 454 | # if we're not serving narinfos, then just proxy the object 455 | unless ($c->stash('format') eq 'narinfo') { 456 | $tx->res->content->once(body => sub ($stx) { 457 | $c->res->headers->content_type('application/x-nix-archive'); 458 | $c->res->headers->content_length($tx->res->headers->content_length); 459 | $c->write; 460 | 461 | $tx->res->content->unsubscribe('read')->on(read => sub ($s, $bytes) { 462 | $c->write($bytes); 463 | }); 464 | }); 465 | 466 | return $our_user->start($tx); 467 | } 468 | 469 | # otherwise, fetch the whole narinfo 470 | app->log->debug("fetching upstream narinfo"); 471 | return $our_user->start_p($tx)->then(sub ($mtx) { 472 | # exit immediately for non-200 resps 473 | if ($mtx->result->code != 200) { 474 | app->log->debug("non-200 return for narinfo, no resign"); 475 | return $c->render( 476 | format => 'txt', 477 | text => $mtx->result->text, 478 | status => $mtx->result->code, 479 | ); 480 | } 481 | 482 | my $body = $mtx->result->body; 483 | chomp $body; 484 | 485 | my $narinfo = $c->parse_narinfo($body); 486 | my $signature = undef; 487 | 488 | if ($resign_upstream_narinfos) { 489 | app->log->debug("attempting to resign with key for '$sign_host'"); 490 | 491 | # NB: references can be empty, so don't bail if they are. but everything 492 | # else is mandatory 493 | if (!defined($narinfo->{StorePath}) || 494 | !defined($narinfo->{NarHash}) || 495 | !defined($narinfo->{NarSize})) { 496 | app->log->error("could not parse narinfo for valid signature!"); 497 | return $c->render(format => 'txt', text => '404', status => 404); 498 | } 499 | 500 | my $fp = fingerprintPath( 501 | $narinfo->{StorePath}, 502 | $narinfo->{NarHash}, 503 | $narinfo->{NarSize}, 504 | $narinfo->{References} 505 | ); 506 | 507 | unless ($resigning_unsafe) { 508 | # check upstream fingerprint validity 509 | my $sig64 = (grep /$resign_keyname:/, @{ $narinfo->{Sig} })[0]; 510 | $sig64 = +(split /:/, $sig64)[-1]; 511 | 512 | if (!$sig64) { 513 | app->log->error("could not find signature for $resign_keyname on upstream narinfo!"); 514 | return $c->render(format => 'txt', text => '404', status => 404); 515 | } 516 | 517 | app->log->debug("attempting to validate signature '$resign_keyname:$sig64' for '$path'..."); 518 | if (!checkSignature(decode_base64($resign_key64), decode_base64($sig64), $fp)) { 519 | app->log->error("invalid signature '$resign_keyname:$sig64' for '$path'!"); 520 | return $c->render(format => 'txt', text => '404', status => 404); 521 | } 522 | 523 | app->log->debug("valid signature for $resign_keyname on upstream path $path"); 524 | } 525 | 526 | # sign, finish 527 | $signature = signString($sign_sk, $fp); 528 | } 529 | 530 | # Add server push header, like in the local case. 531 | $c->res->headers->header('Nix-Link' => "/$narinfo->{URL}"); 532 | 533 | # Finally, render it in the form requested. 534 | if ($c->param('json')) { 535 | push @{ $narinfo->{Sig} }, $signature 536 | if defined($signature); 537 | 538 | return $c->render(json => $narinfo); 539 | } else { 540 | $body .= "\nSig: $signature" 541 | if defined($signature); 542 | 543 | # always add a newline, signature or not. see issue #15 where 544 | # this bug broke the nix client 545 | $body .= "\n"; 546 | 547 | $c->res->headers->content_length(length($body)); 548 | return $c->render(format => 'narinfo', text => $body); 549 | } 550 | }); 551 | }; 552 | 553 | # Helper routine that handles misses for local objects, if an upstream is 554 | # defined for the current configuration. If not, a 404 is returned. 555 | helper handle_miss => sub ($c, $ctype) { 556 | return $c->render(format => 'txt', text => "404", status => 404) 557 | if (!defined($upstream->host)); 558 | 559 | return $c->fetch_upstream($ctype, 'local miss'); 560 | }; 561 | 562 | # Helper routine that simply pulls out the hash parameter from the query and 563 | # returns the path of that object in the store; accessible via $c->nixhash; 564 | helper nixhash => sub ($c) { 565 | my $hash = $c->param('hash'); 566 | 567 | if (length $hash != 32) { 568 | # If the hash we get asked about isn't 32 bytes (160 bits) then we need to 569 | # go ahead and treat it as invalid, possibly to be forwarded to an upstream 570 | # server. Why? Because the local-store.cc implementation inside Nix will 571 | # treat this hash as invalid if it isn't this exact length. The reason this 572 | # would be the case is because an upstream .narinfo, served e.g. by 573 | # cache.nixos.org, may use a hash value that is bigger than 32 bytes. The 574 | # actual length of the hash doesn't matter in general for substituter logic 575 | # to work, because it just follows the URLs in the narinfo -- it's just that 576 | # queryPathFromHashPart actually requires this for local queries in its API. 577 | return ($hash, undef); 578 | } 579 | 580 | my $storePath = queryPathFromHashPart($hash); 581 | return ($hash, $storePath); 582 | }; 583 | 584 | ## ----------------------------------------------------------------------------- 585 | ## -- .narinfo handler 586 | 587 | helper format_narinfo_txt => sub ($c, $hash, $storePath) { 588 | my ($drv, $narhash, $time, $size, $refs) = queryPathInfo($storePath, 1); 589 | 590 | my @res = ( 591 | "StorePath: $storePath", 592 | "URL: nar/$hash.nar", 593 | "Compression: none", 594 | "NarHash: $narhash", 595 | "NarSize: $size", 596 | ); 597 | 598 | # Add headers for Server Push experiments. This header is non-standard, but 599 | # is only meant to be consumed by Nix or other Nix-aware software -- we don't 600 | # want it to be triggered in a browser if someone just visits a .narinfo 601 | # file. One of the primary motivators here is that it's useful for 602 | # cache.nixos.org: Fastly configurations that want to just pull the preload 603 | # URL out of a header without parsing the 'Link:' header manually. The 604 | # intent is that Fastly's `h2.push()` function can be used directly on the 605 | # content included here. 606 | $c->res->headers->header('Nix-Link' => "/nar/$hash.nar"); 607 | 608 | # Needed paths that this NAR references 609 | push(@res, "References: " . join(" ", map { basename $_ } @$refs)) 610 | if scalar @$refs > 0; 611 | 612 | # Derivation and system information 613 | if (defined $drv) { 614 | push(@res, "Deriver: " . basename $drv); 615 | 616 | # Add system information, if the .drv exists for this .nar 617 | if (isValidPath($drv)) { 618 | my $drvpath = derivationFromPath($drv); 619 | push(@res, "System: $drvpath->{platform}"); 620 | } 621 | } 622 | 623 | # Include a signature, if configured 624 | if (defined $sign_sk) { 625 | my $fp = fingerprintPath($storePath, $narhash, $size, $refs); 626 | my $sig = signString($sign_sk, $fp); 627 | push @res, "Sig: $sig"; 628 | } 629 | 630 | push @res, ""; # extra newline, so CURL/etc look nice 631 | my $narinfo = join "\n", @res; 632 | 633 | return $narinfo; 634 | }; 635 | 636 | helper render_narinfo_json => sub ($c, $hash, $storePath) { 637 | my ($drv, $narhash, $time, $size, $refs) = queryPathInfo($storePath, 1); 638 | 639 | my $obj = { 640 | StorePath => $storePath, 641 | URL => "nar/$hash.nar", 642 | Compression => "none", 643 | NarHash => $narhash, 644 | NarSize => $size, 645 | }; 646 | 647 | # Needed paths that this NAR references 648 | $obj->{References} = [ map { basename $_ } @$refs ] 649 | if scalar @$refs > 0; 650 | 651 | # Derivation and system information 652 | if (defined $drv) { 653 | $obj->{Deriver} = basename $drv; 654 | 655 | # Add system information, if the .drv exists for this .nar 656 | if (isValidPath($drv)) { 657 | my $drvpath = derivationFromPath($drv); 658 | $obj->{System} = $drvpath->{platform}; 659 | } 660 | } 661 | 662 | # Include a signature, if configured 663 | if (defined $sign_sk) { 664 | my $fp = fingerprintPath($storePath, $narhash, $size, $refs); 665 | my $sig = signString($sign_sk, $fp); 666 | $obj->{Sig} = [ $sig ]; 667 | } 668 | 669 | return $c->render(json => $obj); 670 | }; 671 | 672 | # Main .narinfo handler logic. 673 | get '/:hash' => [ format => [ 'narinfo' ] ] => sub ($c) { 674 | # Always fetch upstream results if asked. Otherwise, query the store, and 675 | # optionally pass thru to an upstream if that fails. 676 | return $c->fetch_upstream('text/x-nix-narinfo') if $always_use_upstream; 677 | my ($hash, $storePath) = $c->nixhash; 678 | return $c->handle_miss('text/x-nix-narinfo') unless $storePath; 679 | 680 | # query 681 | 682 | if ($c->param('json')) { 683 | app->log->debug("path query: $storePath (json = YES)"); 684 | return $c->render_narinfo_json($hash, $storePath); 685 | } else { 686 | app->log->debug("path query: $storePath (json = NO)"); 687 | 688 | my $narinfo = $c->format_narinfo_txt($hash, $storePath); 689 | $c->res->headers->content_length(length($narinfo)); 690 | return $c->render(format => 'narinfo', text => $narinfo); 691 | } 692 | }; 693 | 694 | ## ----------------------------------------------------------------------------- 695 | ## -- Handlers for .nar objects, both compressed and uncompressed 696 | 697 | group { 698 | # All .nar requests happen under the /nar handler URL 699 | under '/nar'; 700 | 701 | # This is the primary helper function for the NAR endpoint, which can stream 702 | # for multiple different endpoints, though for now it's fairly single-purpose. 703 | helper stream_nar => sub ($c) { 704 | # Always fetch upstream results if asked. Otherwise, query the store, and 705 | # optionally pass thru to an upstream if that fails. 706 | return $c->fetch_upstream('application/x-nix-nar') if $always_use_upstream; 707 | my ($hash, $storePath) = $c->nixhash; 708 | return $c->handle_miss('application/x-nix-nar') unless $storePath; 709 | 710 | # Set the Content-Length for tools like curl, etc 711 | my ($drv, $narhash, $time, $size, $refs) = queryPathInfo($storePath, 1); 712 | $c->res->headers->content_length($size); 713 | 714 | # And content-type, too 715 | $c->res->headers->content_type('application/x-nix-archive'); 716 | 717 | # get the right command. we don't advertise .nar.xz or .nar.bz2 ourselves, but 718 | # the upstream narinfos might, and for compatibility we offer them here. 719 | my $dump_cmd = "nix-store --dump '$storePath'"; 720 | if ($c->stash('format') eq 'nar.xz') { 721 | $dump_cmd .= "|$XZEXE --fast"; 722 | } elsif ($c->stash('format') eq 'nar.bz2') { 723 | $dump_cmd .= "|$BZ2EXE --fast"; 724 | } 725 | 726 | app->log->debug("streaming ".$c->stash('format').": $storePath"); 727 | 728 | # dump and optionally exit on 503, so error codes can be distinguished 729 | my $pid = open my $fh, '-|', $dump_cmd; 730 | return $c->render(format => 'txt', text => 'nix-store --dump failed', status => 503) 731 | unless defined($pid); 732 | 733 | my $stream = Mojo::IOLoop::Stream->new($fh); 734 | my $sid = Mojo::IOLoop->stream($stream); 735 | $c->timing->begin('nar_stream'); 736 | 737 | $stream->on(read => sub ($s, $bytes) { 738 | return $c->write($bytes); 739 | }); 740 | 741 | # record timing info when the stream is finished and close the connection 742 | $stream->on(close => sub { 743 | my $elapsed = $c->timing->elapsed('nar_stream'); 744 | my $rps = $c->timing->rps($elapsed); 745 | app->log->debug("NAR streaming ($size bytes) took ${elapsed}s (~ $rps/s)"); 746 | 747 | $c->timing->server_timing('nar_stream', $hash, $elapsed); 748 | return $c->finish; 749 | }); 750 | 751 | # the pid and handle will be removed by the GC later 752 | $c->on(finish => sub { 753 | return Mojo::IOLoop->remove($sid); 754 | }); 755 | }; 756 | 757 | # Primary route handler for /nar/:hash.nar requests 758 | get '/:hash' => [ format => [ 'nar', 'nar.xz', 'nar.bz2' ] ] => sub ($c) { 759 | return $c->stream_nar(); 760 | }; 761 | }; 762 | 763 | ## ----------------------------------------------------------------------------- 764 | ## -- Index page handler 765 | 766 | get '/version.js' => sub ($c) { 767 | $c->stash(priority => app->config->{priority}); 768 | $c->render(template => 'version', format => 'js'); 769 | } if app->config->{index_page} == 1; 770 | 771 | get '/' => sub ($c) { 772 | $c->stash(sign_pk => $sign_pk); 773 | $c->render(template => 'index'); 774 | } if app->config->{index_page} == 1; 775 | 776 | ## ----------------------------------------------------------------------------- 777 | ## -- El fin 778 | 779 | # Extra: Mojolicious Server Status UX, available at /mojo-status 780 | plugin 'Status' if app->config->{status} == 1; 781 | 782 | # Go-go gadget Mojo! 783 | app->start; 784 | 785 | # Local Variables: 786 | # mode: perl 787 | # fill-column: 80 788 | # indent-tabs-mode: nil 789 | # buffer-file-coding-system: utf-8-unix 790 | # End: 791 | 792 | __DATA__ 793 | @@ index.html.ep 794 | % my $url = url_for; 795 | 796 | 797 | 798 | 799 | 800 | 801 | Nix binary cache (Eris <%= $Eris::VERSION %>) 802 | 804 | 810 | 811 | 812 | 813 | 814 |
815 |
816 |
817 |

818 | This service, <%= $url->to_abs %>, provides a "binary 819 | cache" for the Nix package 820 | manager. It is used to automatically speed up builds. 821 |

822 |
823 |
824 |
825 |
826 |
827 |
Usage
828 | 829 |

Using /etc/nix/nix.conf:

830 | % if (defined($sign_pk)) { 831 |
substituters = <%= $url->to_abs %>
832 | trusted-public-keys = <%= $sign_pk %>
833 | % } else { 834 |
substituters = <%= $url->to_abs %>
835 | % } 836 | 837 |

Using /etc/nixos/configuration.nix:

838 | % if (defined($sign_pk)) { 839 |
nix.binaryCaches = [ "<%= $url->to_abs %>" ];
840 | nix.binaryCachePublicKeys = [ "<%= $sign_pk %>" ];
841 | % } else { 842 |
nix.binaryCaches = [ "<%= $url->to_abs %>" ];
843 | % } 844 | 845 |

Using nix, nix-build, etc:

846 | % if (defined($sign_pk)) { 847 |
nix build \\
848 | --option substituters '<%= $url->to_abs %>' \\
849 | --option trusted-public-keys '<%= $sign_pk %>' \\
850 | ...
851 | % } else { 852 |
nix build --substituters '<%= $url->to_abs %>' ...
853 | % } 854 |
855 |
856 |
857 |
858 |
859 | Powered by Eris. π 863 | % if (app->config->{status} == 1) { 864 | View server status. 866 | % } 867 |
868 |
869 |
870 | 871 | 872 | 873 | 874 | @@ version.js.ep 875 | let versionInfo = "<%= $Eris::VERSION %>. Cache priority: <%= $priority %>"; 876 | let versionDisplayed = false; 877 | 878 | function showVersionClick() { 879 | if (!versionDisplayed) { 880 | versionDisplayed = true; 881 | document.getElementById('versioninfo') 882 | .insertAdjacentHTML('afterbegin', versionInfo) 883 | } 884 | } 885 | -------------------------------------------------------------------------------- /module.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | 3 | with builtins; 4 | with lib; 5 | 6 | let 7 | cfg = config.services.eris-git; 8 | 9 | eris = import ./. { nixpkgs = pkgs.path; }; 10 | in 11 | { 12 | options = { 13 | services.eris-git = { 14 | enable = mkEnableOption "Eris: the simple, flexible Nix binary cache"; 15 | 16 | configFile = mkOption { 17 | type = types.nullOr types.str; 18 | default = null; 19 | description = "The path to the Eris configuration file."; 20 | }; 21 | 22 | debug = mkOption { 23 | type = types.bool; 24 | default = false; 25 | description = "Enable request/response debugging for the server"; 26 | }; 27 | 28 | ipAddressAllow = mkOption { 29 | type = types.nullOr types.str; 30 | default = null; 31 | description = "List of IP addresses to allow to the listening ports"; 32 | }; 33 | 34 | ipAddressDeny = mkOption { 35 | type = types.nullOr types.str; 36 | default = null; 37 | description = "List of IP addresses to deny to the listening ports"; 38 | }; 39 | }; 40 | }; 41 | 42 | config = mkIf cfg.enable { 43 | environment.systemPackages = [ eris ]; 44 | 45 | systemd.services.eris-git = { 46 | description = "eris binary cache server"; 47 | documentation = [ "man:eris(8)" "https://thoughtpolice.github.io/eris" ]; 48 | 49 | requires = [ "nix-daemon.socket" ]; 50 | after = [ "network.target" ]; 51 | wantedBy = [ "multi-user.target" ]; 52 | 53 | path = [ config.nix.package.out ]; 54 | environment.NIX_REMOTE = "daemon"; 55 | environment.ERIS_PID_FILE = "/run/eris/eris.pid"; 56 | environment.ERIS_CONFIG = mkIf (cfg.configFile != null) cfg.configFile; 57 | environment.LIBEV_FLAGS = "4"; # go ahead and mandate epoll(2) 58 | 59 | environment.MOJO_LOG_SHORT = mkIf (!cfg.debug) "1"; 60 | environment.MOJO_MODE = mkIf cfg.debug "development"; 61 | 62 | # Note: it's important to set this for nix-store, because it wants to use 63 | # $HOME in order to use a temporary cache dir. bizarre failures will occur 64 | # otherwise 65 | environment.HOME = "/run/eris"; 66 | 67 | serviceConfig = { 68 | ExecStart = "${eris}/bin/eris"; 69 | Type="forking"; 70 | Restart = "always"; 71 | RestartSec = "5s"; 72 | 73 | DynamicUser = true; 74 | ProtectHome = "yes"; 75 | 76 | ConfigurationDirectory = "eris"; 77 | RuntimeDirectory = "eris"; 78 | PIDFile = "/run/eris/eris.pid"; 79 | KillMode = "process"; 80 | 81 | SupplementaryGroups="adm"; 82 | TemporaryFileSystem="/etc:ro"; 83 | BindReadOnlyPaths="/etc/eris"; 84 | 85 | IPAccounting = true; 86 | IPAddressAllow = mkIf (cfg.ipAddressAllow != null) cfg.ipAddressAllow; 87 | IPAddressDeny = mkIf (cfg.ipAddressDeny != null) cfg.ipAddressDeny; 88 | LimitNOFILE = 65536; 89 | 90 | # TODO FIXME: This should really be replaced with socket activation... 91 | AmbientCapabilities="cap_net_bind_service"; 92 | }; 93 | }; 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /nix/bootstrap.nix: -------------------------------------------------------------------------------- 1 | { 2 | # Source code of the package. 3 | repo 4 | 5 | # The nixpkgs import specification, given by a user.. Can come in one of many 6 | # forms: 7 | , nixpkgs ? null 8 | 9 | # The file containing version information (as well as the release name) for 10 | # this package. The format of the version file is exactly two lines: 11 | # 12 | # 13 | # 14 | # 15 | # For instance, a file with the contents: 16 | # 17 | # 1.0 18 | # Golden Master 19 | # 20 | # is all that's needed to describe what the build version. 21 | , versionFile ? ./.version 22 | 23 | # The JSON file containing the 'locked' set of packages to use when importing 24 | # nixpkgs and running a build. The lockfile specifies an unambiguous nixpkgs 25 | # snapshot, but is only used when the `nixpkgs` parameter is set to `null`. 26 | # (This allows you to maintain/manipulate/update the .json file instead of 27 | # the nix file, if you like.) 28 | , lockFile ? ./nixpkgs.json 29 | 30 | # Whether or not this build represents an official 'release build' for your 31 | # users to then consume. The default 'false' implies this is an unstable 32 | # build from a repository. When officialRelease = false, then extra version 33 | # information about the build (such as the Git commit revision) will be 34 | # included in the version field. If true, the version information will 35 | # exactly match the string given in `versionFile`. 36 | , officialRelease ? false 37 | 38 | # The nixpkgs configuration. This can be used to apply things like global 39 | # package overrides to all builds. 40 | , config ? {} 41 | 42 | # The system to build on. Defaults to the current system double (e.g. 43 | # 'x86_64-darwin'). Setting this explicitly is only useful if you're 44 | # interested in doing cross compilation or remote building. 45 | , system ? builtins.currentSystem 46 | 47 | # The minimum Nix version that a user must have in order to build this 48 | # project. 49 | , minimumNixVersion ? "2.3" 50 | }: 51 | 52 | with builtins; 53 | 54 | let 55 | isHttp = x: substring 0 5 (toString x) == "http:" || substring 0 6 (toString x) == "https:"; 56 | isChannel = x: substring 0 8 (toString x) == "channel:"; 57 | 58 | # import logic 59 | pkgSource = 60 | # Default case: a specified package set, located in nixpkgs.json, next to this file. 61 | if (nixpkgs == null || nixpkgs == "channel:lockfile") then ( 62 | if pathExists lockFile == false 63 | then throw "Error: you specified 'nixpkgs = null', implying you have a lock file (located in ${toString lockFile}), but it doesn't exist!" 64 | else 65 | let lockfile = fromJSON (readFile lockFile); 66 | in fetchTarball { inherit (lockfile) url sha256; } 67 | ) 68 | 69 | # Attrs case: a package set that's specified inline 70 | else if isAttrs nixpkgs then (fetchTarball { inherit (nixpkgs) url sha256; }) 71 | 72 | # Path case: the user gave an absolute path to some Nixpkgs checkout or whatnot. We can just return it. 73 | else if isPath nixpkgs then nixpkgs 74 | 75 | # HTTP/channel case: an attempted fetch from some HTTP(S) URL. Use fetchTarball without a sha256. 76 | else if (isHttp nixpkgs || isChannel nixpkgs) then (fetchTarball { url = nixpkgs; }) 77 | 78 | # Otherwise, this is invalid 79 | else throw "Invalid nixpkgs configuration for '${toString nixpkgs}'! (try a path, http URL, or 'null')"; 80 | 81 | overlays = 82 | let 83 | files = builtins.filter (f: 84 | let ext = builtins.substring (builtins.stringLength f - 4) 4 f; 85 | in ext == ".nix" 86 | ) (builtins.attrNames (builtins.readDir ./overlays)); 87 | in builtins.map (x: import (./. + "/overlays/${x}")) files; 88 | 89 | pkgs = import pkgSource { inherit config system overlays; }; 90 | 91 | versionInfo = pkgs.lib.splitString "\n" (pkgs.lib.fileContents versionFile); 92 | basever = builtins.elemAt versionInfo 0; 93 | vsuffix = pkgs.lib.optionalString (!officialRelease) 94 | "+${toString repo.revCount}-g${repo.shortRev}"; 95 | 96 | relname = builtins.elemAt versionInfo 1; 97 | version = "${basever}${vsuffix}"; 98 | in 99 | 100 | # Finally, check the nix version before importing the actual component we want. 101 | if !((compareVersions nixVersion minimumNixVersion) >= 0) 102 | then throw ("Invalid Nix version '" + nixVersion + "'; at least " + minimumNixVersion + " is required!") 103 | else { inherit pkgs relname version; } 104 | -------------------------------------------------------------------------------- /nix/nixpkgs.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/nixos/nixpkgs-channels/archive/cc1ae9f21b9e0ce998e706a3de1bad0b5259f22d.tar.gz", 3 | "rev": "cc1ae9f21b9e0ce998e706a3de1bad0b5259f22d", 4 | "sha256": "0zjafww05h50ncapw51b5qxgbv9prjyag0j22jnfc3kcs5xr4ap0" 5 | } 6 | -------------------------------------------------------------------------------- /nix/overlays.nix: -------------------------------------------------------------------------------- 1 | # This should be a list of filepaths, each file containing a Nix overlay 2 | # expression. Overlays are applied in exactly the order specified in this list, 3 | # as ordering is relevant. 4 | # 5 | # You DO NOT need to call 'import' or any other such thing; just listing the 6 | # filepaths here is enough for them to get picked up. Some examples are 7 | # included. 8 | 9 | [ 10 | # ./overlays/liburing.nix 11 | # ./overlays/mimalloc.nix 12 | ] 13 | -------------------------------------------------------------------------------- /nix/overlays/README.md: -------------------------------------------------------------------------------- 1 | This directory should contain a list of files that apply overlays to the chosen 2 | version of `nixpkgs` you're using. For example, you might have a `liburing.nix` 3 | file containing an overlay for that one package, or a set of packages being 4 | overlaid in a single file. There should be one package, or one coherent "set" 5 | of packages overlaid in each file. 6 | 7 | Overlay ordering is critical, and is controled by the file `../overlays.mix`, 8 | relative to this directory. 9 | -------------------------------------------------------------------------------- /nix/update.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -i bash -p jq curl 3 | 4 | ## Utility to automatically update nixpkgs.json quickly and easily from either 5 | ## the Nixpkgs upstream or a custom fork. Runs interactively using `nix-shell` 6 | ## for zero-install footprint. 7 | set -e 8 | 9 | API="https://api.github.com/repos" 10 | ORG=${ORG:-"nixos"} 11 | REPO=${REPO:-"nixpkgs-channels"} 12 | BRANCH=${BRANCH:-"nixpkgs-unstable"} 13 | URL="https://github.com/${ORG}/${REPO}" 14 | 15 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 16 | [ ! -e "$SCRIPT_DIR/nixpkgs.json" ] && \ 17 | >&2 echo "ERROR: nixpkgs.json must be located next to this update script!" && \ 18 | exit 1 19 | 20 | if [[ "x$1" == "x" ]]; then 21 | echo -n "No revision, so grabbing latest upstream Nixpkgs master commit... " 22 | REV=$(curl -s "${API}/nixos/${REPO}/commits/${BRANCH}" | jq -r '.sha') 23 | echo "OK, got ${REV:0:6}" 24 | else 25 | if [[ "x$2" == "x" ]]; then 26 | REV="$1" 27 | echo "Custom revision (but no repo) provided, using ${URL}" 28 | else 29 | REV="$2" 30 | URL="$1" 31 | echo "Custom revision in upstream ${URL} will be used" 32 | fi 33 | fi 34 | 35 | DOWNLOAD="$URL/archive/$REV.tar.gz" 36 | echo "Updating to nixpkgs revision ${REV:0:6} from $URL" 37 | SHA256=$(nix-prefetch-url --unpack "$DOWNLOAD") 38 | 39 | cat > "$SCRIPT_DIR/nixpkgs.json" < 'value', # strings 245 | option2 => 1, # integers 246 | option3 => [ 1, 2 ], # arrays 247 | option4 => { # hashes ("objects") 248 | param1 => 'value1', 249 | param2 => 'value2', 250 | }, 251 | option5 => $ENV{VALUE} || "default", # read '$VALUE' from the environment 252 | } 253 | #+END_SRC 254 | 255 | Comments start with ~#~, and trailing commas are allowed in all positions, just 256 | as regular Perl code allows. 257 | 258 | The last example of ~option5~ shows how to use the Perl-based nature to your 259 | advantage, by instead reading a value out of the environment at startup time, 260 | with a default option provided. By utilizing this, you can get a lot of 261 | flexibility out of the configuration file format with pretty minimal fuss. 262 | 263 | *** Listening ports and addresses 264 | 265 | Listening ports and addresses for the HTTP server are configured through the 266 | ~listen~ option in ~eris.conf~. This parameter takes a list of strings, 267 | specified as URLs, which specify the connection information, somewhat like an 268 | ODBC/JDBC connection string. The configuration is best expressed by some 269 | examples: 270 | 271 | #+BEGIN_SRC perl 272 | { 273 | listen => [ 274 | 'http://*:3000', # listen on all IPv4 interfaces, on port 3000 275 | 'http://[::]:3000', # same, but on all IPv4 and IPv6 interfaces 276 | 'http://[::1]:3000', # IPv6 only 277 | 278 | 'http://*:3000?reuse=1', # enable SO_REUSEPORT 279 | 'https://*:4000', # listen on HTTPS, as well. uses built-in testing certs 280 | 281 | # specify a custom certificate and keyfile 282 | 'https://*:3000?cert=/x/server.crt&key=/y/server.key', 283 | 284 | # listen on a (percent-encoded) unix socket path, e.g. for frontend proxies 285 | # this listens in /tmp/eris.sock 286 | 'http+unix://%2Ftmp%2Feris.sock', 287 | ] 288 | } 289 | #+END_SRC 290 | 291 | *** Signing support 292 | 293 | Packages are signed "on the fly" when served by the cache. You can configure 294 | signing in one of three modes: 295 | 296 | 1. No signing (the default mode). 297 | 298 | 2. Hard-coded keys, generated/procured ahead of time. 299 | 300 | These three behaviors are controlled using the ~signing~ option in ~eris.conf~. 301 | 302 | **** 'No signature'-mode 303 | 304 | The default mode is to not use signatures at all, which can be specified using 305 | the ~none~ setting: 306 | 307 | #+BEGIN_SRC perl 308 | { 309 | signing => 'none', 310 | } 311 | #+END_SRC 312 | 313 | **** Using pre-generated keys 314 | 315 | Pre-generated keys are also easy; rather than a freeform string, you simply use 316 | an options hash to specify the hostname, and the files containing the 317 | public and private keys. 318 | 319 | Assuming you generate a set of keys using ~nix-store --generate-binary-cache-key 320 | cache.example.com-1 /etc/nix/cache.sk /etc/nix/cache.pk~, you can configure Eris 321 | with: 322 | 323 | #+BEGIN_SRC perl 324 | { 325 | signing => { 326 | host => 'cache.example.com-1', 327 | private => '/etc/nix/cache.sk', 328 | }, 329 | } 330 | #+END_SRC 331 | 332 | The host attribute can be omitted when the private key is in the form of ~host:key~. 333 | 334 | *** Support for private users via HTTP authentication 335 | 336 | You can add support for basic HTTP authentication via the ~users~ field in 337 | ~eris.conf~, which contains a list of ~user:password~ strings. 338 | 339 | #+BEGIN_SRC perl 340 | { 341 | users => [ 342 | 'austin:rules', 343 | 'david:rocks' 344 | ], 345 | } 346 | #+END_SRC 347 | 348 | Given the above configuration, you can test the endpoint with ~curl~: 349 | 350 | #+BEGIN_SRC bash 351 | # this works 352 | curl -u austin:rules http://eris/nix-cache-info 353 | 354 | # this fails 355 | curl -u david:rules http://eris/nix-cache-info 356 | 357 | # and so does this 358 | curl http://eris/nix-cache-info 359 | #+END_SRC 360 | 361 | Once this configuration is in place, clients can authenticate with the server 362 | using a standard cURL ~.netrc~ configuration file. This file takes the following 363 | form: 364 | 365 | #+BEGIN_SRC 366 | machine 367 | login 368 | password 369 | ... 370 | #+END_SRC 371 | 372 | Entries may be repeated to provide multiple logins for different caches. 373 | 374 | Now, you can use the option ~--option netrc-file /path/to/netrc~ with any of 375 | your ~nix~ commands in order to authenticate properly, e.g. 376 | 377 | #+BEGIN_SRC bash 378 | nix --option netrc-file /path/to/netrc copy --from http://.../ /nix/store/... 379 | #+END_SRC 380 | 381 | *NOTE*: The path must be absolute. 382 | 383 | Check out [[https://ec.haxx.se/usingcurl-netrc.html][the cURL manual page for ~.netrc~ files]], and the [[https://nixos.org/nix/manual/#name-11][nix.conf manual]] 384 | (particularly the ~netrc-file~ option) for more information. 385 | 386 | *** TLS support 387 | 388 | TLS support is controlled by the ~listen~ parameter in ~eris.conf~, as shown 389 | earlier. In particular, simply specifying an HTTPS URI in the ~listen~ 390 | configuration will use a built-in set of testing certificates, distributed with 391 | Mojolicious: 392 | 393 | #+BEGIN_SRC perl 394 | { 395 | listen => ['https://*:443'], 396 | } 397 | #+END_SRC 398 | 399 | But you almost _definitely do not want to do this_, since there's no way for 400 | clients to securely verify the certificate. Provided you do have a signed, valid 401 | certificate, specifying the key and certificate is done with the ~&cert=~ and 402 | ~&key=~ URL parameters: 403 | 404 | #+BEGIN_SRC perl 405 | { 406 | listen => [ 'https://*:443?cert=/etc/eris/ssl.crt&key=/etc/eris/ssl.key' ], 407 | } 408 | #+END_SRC 409 | 410 | *** NixOS-specific notes 411 | 412 | There are a few NixOS-specific things to note, enforced primarily by the NixOS 413 | module and systemd, which users might want to be aware of: 414 | 415 | 1. *Eris has no visible /tmp dir*. Do not try to include or write files 416 | here; they will never be visible by any other service, due to 417 | ~PrivateTemp=true~ being specified for systemd. 418 | 419 | 2. *Eris has no assigned user*. The module uses systemd's ~DynamicUser=true~ 420 | directive, so UIDs are assigned dynamically to the service. (This could 421 | be changed in the future but requires some upstream NixOS coordination 422 | for reserving UIDs.) 423 | 424 | 3. *Eris is part of the ~adm~ group*. The intention is that members of the 425 | ~adm~ group will be able to do things like rotate signing keys, located 426 | under ~/etc/eris~; these actions don't require full admin privileges, but 427 | ~eris~ will want to read the results. 428 | 429 | 3. *Eris can only read ~/etc/eris~ and almost nothing else. It cannot write 430 | there*. We use an array of systemd's filesystem namespace features to 431 | essentially allow the path ~/etc/eris~ to be bind-mounted inside the 432 | service. 433 | 434 | This means that even though ~eris~ is part of the ~adm~ group, it cannot 435 | read almost anything else in ~/etc~ anyway. 436 | 437 | Due to this combination of features, if you would like to keep your keys, 438 | etc in a safe, read-only place, it's suggested to put them in ~/etc/eris~ 439 | and mark them as read-only files with strict visibility permission. 440 | 441 | ** Checking Mojolicious server status 442 | 443 | Eris uses the [[https://metacpan.org/pod/Mojolicious::Plugin::Status][Mojolicious::Plugin::Status]] module in order to provide some basic 444 | information about the running machine. The server status can be found by viewing 445 | ~http://localhost:8080/mojo-status~, which will show you the server uptime, 446 | currently connected clients, and more, formatted as a nice, live HTML page. 447 | 448 | You must enable the status plugin by setting the configuration value ~status => 449 | 1~ in ~eris.conf~ 450 | 451 | ** Full configuration file reference 452 | 453 | Check out [[https://github.com/thoughtpolice/eris/blob/master/conf/eris.conf.example][./conf/eris.conf.example]] in this repository for the full 454 | configuration file reference, along with some examples. 455 | 456 | * Deployment 457 | 458 | There are several options for running the cache server, but the following three 459 | outline the most typical scenarios. 460 | 461 | ** Running Eris standalone 462 | 463 | As you saw above, you can easily install Eris into the Nix environment of your 464 | user account, making it trivial and easy to quickly export your Nix store. (You 465 | can even run it directly from the source code repository, too. See [[Hacking]] for 466 | more.) 467 | 468 | In the original example above, we executed the standalone ~eris~ program in 469 | /foreground mode/, using the ~-f~ flag. By default, ~eris~ executes in daemon 470 | mode: it forks a process, writes a ~.pid~ file, and then detaches from the host 471 | shell. 472 | 473 | This means if you simply log into a machine and run ~eris~, it will immediately 474 | fork and start running. When you log out, it will stay running. That's all you 475 | have to do! In order to stop the running daemon, just execute ~eris -s~, which 476 | will kill the prior worker processes, using the ~.pid~ file. 477 | 478 | And, of course, if you'd like to keep it running while in foreground mode, be 479 | sure to run it behind something like ~tmux~ or ~screen~! 480 | 481 | ** Running Eris as a NixOS service 482 | 483 | Eris comes with a NixOS-compatible service module, allowing you to quickly and 484 | easily serve your Nix store on any machine you're running. We saw how to do this 485 | earlier, but to recap, after importing, just add the following lines to your 486 | configuration: 487 | 488 | #+BEGIN_SRC nix 489 | { config, pkgs, lib, ... }: 490 | 491 | { 492 | # ... 493 | services.eris-git.enable = true; 494 | } 495 | #+END_SRC 496 | 497 | Like above, this defaults to only serving the HTTP cache on ~localhost~ for 498 | security reasons, so you'll need to tweak the configuration to expose it on your 499 | LAN/WAN address. 500 | 501 | Check ~module.nix~ for information on the configuration options. 502 | 503 | ** Running Eris as a service on any Linux distribution 504 | 505 | Eris can also be deployed on non-NixOS machines, which is often convenient for 506 | users and many deployment situations where NixOS isn't available. 507 | 508 | The easiest way to do this is to first log in as the ~root~ user on your Linux 509 | machine with Nix installed. For Nix-on-Linux, the root user controls the 510 | default set of system profiles and channels, so we'll want to install it 511 | there. 512 | 513 | #+BEGIN_SRC bash 514 | $ whoami 515 | root 516 | $ nix run nixpkgs.git -c git clone https://github.com/thoughtpolice/eris.git 517 | $ cd eris/ 518 | $ nix-env -i $(nix-build --no-link -Q release.nix -A eris) 519 | #+END_SRC 520 | 521 | ~eris~ is now installed for the ~root~ user. This installs the ~eris~ outputs 522 | into the default profile, which includes an ~eris.service~ file for systemd. 523 | By installing it into the root user, we can give it a stable path. 524 | 525 | Now, you can link this file into the default systemd search path, enable it, 526 | and start it. 527 | 528 | #+BEGIN_SRC bash 529 | $ systemctl link /nix/var/nix/profiles/system/sw/lib/systemd/system/eris.service 530 | $ systemctl enable eris 531 | $ systemctl start eris 532 | #+END_SRC 533 | 534 | Whenever you want to upgrade ~eris~, just install a new version of the package 535 | into the ~root~ users account (e.g. by running ~git pull~ and re-performing the 536 | installation.) ~systemd~ will still follow the same stable symbolic link name to 537 | the updated filesystem paths. 538 | 539 | Likewise, there is also a stable path to the ~eris~ binary installed in the 540 | default profile, located at: 541 | 542 | #+BEGIN_SRC bash 543 | /nix/var/nix/profiles/system/sw/bin/eris 544 | #+END_SRC 545 | 546 | Note that, because this ~eris.service~ file is inside ~/nix/store~, it is 547 | read-only. You are advised to carefully examine the service file and see if it 548 | meets your needs. If it doesn't, which is possible, simply copying it to 549 | ~/etc/system/systemd/~ on your system and following the same commands above will 550 | give you a version you can edit. 551 | 552 | * HTTP API 553 | 554 | There are only a couple HTTP endpoints that Nix actually relies on in order to 555 | download files from an HTTP server. But Eris exposes a few more, too. 556 | 557 | ** Basic Nix HTTP API 558 | 559 | There are three primary endpoints a Nix-compliant HTTP cache must implement: 560 | 561 | 1. ~/nix-cache-info~ -- information about the cache server, including where 562 | the Nix store is located. 563 | 564 | 2. ~/:hash.narinfo~ -- the narinfo endpoint. A ~GET~ request against this 565 | server endpoint will give back information about the resulting object named 566 | ~:hash~ in the store, including its path, if it exists. If the object cannot 567 | be found in the store, a 404 error code is returned. 568 | 569 | 3. ~/nar/:hash.nar~ -- the download endpoint. A ~GET~ request against 570 | this endpoint will download the ~.nar~ file for the given store object, 571 | identified by ~:hash~. 572 | 573 | ** Eris HTTP API (v1) 574 | 575 | The prior endpoints give you enough to query Nix packages from the store, but 576 | Eris also exposes a few extra endpoints, which are probably more useful for 577 | end-users, or scripting tools. 578 | 579 | - ~/v1/public-key~ -- the Ed25519 public key, which all served objects will be 580 | signed by, This would be useful in scripting environments to identify what 581 | key the server will sign with. If a server is not configured to sign 582 | downloaded objects, a 404 error code is returned. 583 | 584 | - ~/v1/version~ -- the version of ~Eris~, in traditional Nix format, including 585 | pre-release/git information if applicable. This endpoint is always available 586 | and will never return a non-200 error code, outside of "catastrophic" 587 | situations (network/disk/ghosts attacking you). 588 | 589 | * Demo: build a private cache with [[https://www.cloudflare.com][CloudFlare]] and [[https://www.packet.net][Packet.net]] 590 | 591 | A demonstration of a full-fledged deployment on top of [[https://www.packet.net][Packet.net]] using 592 | [[https://www.cloudflare.com][CloudFlare]] as a frontend firewall, cache, and DNS service is provided. Thanks to 593 | the [[https://www.cloudflare.com/bandwidth-alliance/][Bandwidth Alliance]], egress between Packet and CloudFlare is free, so the 594 | only costs you pay for the cache server are for the physical hardware. 595 | 596 | See [[./demo/readme.org][the ~./demo/~ directory]] for more information. 597 | 598 | * FAQ 599 | 600 | ** Why write this? 601 | 602 | A few reasons: 603 | 604 | 1. I wanted something more configurable than [[https://github.com/edolstra/nix-serve][nix-serve]], which is a bit 605 | barebones and doesn't include necessary features like authentication. 606 | 2. I wanted something /less heavyweight/ and obscure than [[https://nixos.org/hydra/][Hydra]], which I've 607 | had many painful experiences with. 608 | 3. It was a good reason to learn to use [[https://mojolicious.org][Mojolicious]], which is awesome. 609 | 610 | ** What's with the name? 611 | 612 | Eris is the daughter of [[https://en.wikipedia.org/wiki/Eris_(mythology)][Nyx]] in Greek mythology. 613 | 614 | ** Does Eris handle cache /uploads/? 615 | 616 | No. It's assumed you will use some mechanism such as ~nix copy --to ssh://...~ 617 | in order to securely copy store objects to the remote server that runs Eris. 618 | They will then become available in the cache. 619 | 620 | ** What about alternative systems like Cachix? 621 | 622 | [[https://cachix.org][Cachix]] is a new service for the NixOS community that offers simple, easy-to-use 623 | hosting for Nix binary caches. You might be wondering if you should use Cachix 624 | or Eris for your project. 625 | 626 | Here's my simple guideline as the author of Eris: *you probably want to use 627 | Cachix if at all possible*. If you're doing open source work it's also freely 628 | available, which is especially attractive, but paid, closed-source caches should 629 | be available soon. 630 | 631 | The reasons for this are a bit obvious but it's essentially worth repeating 632 | here: you probably don't want to run and maintain your own binary cache server. 633 | NixOS is wonderful but even then, it is a constant maintenance overhead of 634 | tuning, deployment, upgrades, and security. 635 | 636 | On top of that, Eris doesn't really care about or involve itself in the /other/ 637 | half required of a full caching system: uploads, as previously mentioned. Cachix 638 | does 'first-class' authenticated uploads, i.e. it is a feature. Using SSH is 639 | fine, and keeps Eris simple, but involves secondary authorization/policy 640 | management at your own expense. (It's possible this might change one day, but 641 | it's unlikely any time in the near-future.) 642 | 643 | ** Austin, you wrote this in /Perl/? 644 | 645 | A lot of people know me (Austin Seipp, the primary author) as a Haskell 646 | programmer. But even outside of that, Perl doesn't ever seem vogue these days 647 | for new projects (a truly damning image, coming from an industry that's mostly 648 | fashion-driven), which might leave some to wonder. So this is a quick way of 649 | saying: I know you're thinking "Why would you choose Perl", and the answer may 650 | surprise you. 651 | 652 | The short of it is: because I like Perl, and it was a chance to learn how to use 653 | Mojolicious (which I can now say I like quite a lot). That is basically all it 654 | comes down to. From this point of view I consider Eris a complete success: it 655 | has been relatively painfree to develop (thanks to Mojo) and I believe its 656 | future evolution will work out well, and remain clean, and easy to understand, 657 | over time. 658 | 659 | * Hacking 660 | 661 | If you want to work on the source code, here are a few tips and tricks. 662 | 663 | ** Running Eris in-place 664 | 665 | The easiest way to get started with Eris is to just run it right out of this 666 | repository by executing the ~eris.pl~ script: 667 | 668 | #+BEGIN_SRC bash 669 | $ git clone https://github.com/thoughtpolice/eris.git 670 | $ cd eris 671 | $ MOJO_MODE=development ./eris.pl -f 672 | #+END_SRC 673 | 674 | This uses ~nix-shell~'s support for shebang lines in order to immediately run 675 | the underlying Perl script with no fuss. You can just hack on ~eris.pl~ in place 676 | and restart as you like. 677 | 678 | ~MOJO_MODE=development~ sets up development mode for the HTTP Route handlers, 679 | which makes debugging errors and faults much easier. 680 | 681 | If you want to test the whole build process and run the resulting executable 682 | from the Nix derivation, you can do that with ~nix-build~: 683 | 684 | #+BEGIN_SRC bash 685 | export MOJO_MODE=development 686 | $(nix-build -Q --no-out-link release.nix -A eris)/bin/eris -f 687 | #+END_SRC 688 | 689 | ** Running the tests 690 | 691 | Running the tests can be done using ~nix build~ quite easily: 692 | 693 | #+BEGIN_SRC bash 694 | $ nix build -f release.nix test 695 | #+END_SRC 696 | 697 | This actually runs the complete set of tests that exist under the ~./t/~ 698 | directory. Each file contains its own NixOS-based test which is collected 699 | into a full attrset, based on the filename (~test.nix~ is very short, so feel 700 | free to read it yourself). 701 | 702 | ** Protip: Running Eris behind Ngrok 703 | 704 | [[https://ngrok.io][ngrok]] is an online service that exposes public URLs for local webservers and is 705 | useful for testing integration. It comes with a free tier. However, it can also 706 | be used to quickly expose Eris to remote machines. The free tier only allows 40 707 | connections per minute, however, so it's only useful for light testing. 708 | 709 | The ~ngrok~ binary is available in Nixpkgs; you can install and authenticate 710 | with the http://ngrok.io service as follows, then launch an HTTP tunnel: 711 | 712 | #+BEGIN_SRC bash 713 | $ nix-env -iA nixpkgs.ngrok 714 | $ ngrok authtoken ... 715 | $ ngrok http 8080 716 | #+END_SRC 717 | 718 | Now, you're free to use the randomly generated ~ngrok.io~ domain as a temporary 719 | binary cache. 720 | 721 | Note that if you do this, you probably want to enable Hypnotoad's ~proxy~ 722 | setting so that the server will correctly recognize ~X-Forwarded-For~ headers 723 | and user IPs properly. Add something like this to your ~eris.conf~: 724 | 725 | #+BEGIN_SRC perl 726 | { 727 | proxy => 1, 728 | } 729 | #+END_SRC 730 | 731 | * TODOs 732 | 733 | These are basically in the order I wish to tackle them. 734 | 735 | ** Dynamic routes 736 | 737 | It would be interesting to explore 'dynamic routes' for caches, e.g. different 738 | caches located at different HTTP endpoints with different authentication 739 | mechanisms, or backends. 740 | 741 | ** Let's Encrypt integration 742 | 743 | For those of us out there who trust nobody, it would be nice if the Hypnotoad 744 | server could auto-start itself with a set of TLS certificates. 745 | 746 | * Authors 747 | 748 | See [[https://raw.githubusercontent.com/thoughtpolice/eris/master/AUTHORS.txt][AUTHORS.txt]] for the list of contributors to the project. 749 | 750 | * License 751 | 752 | *GPLv3 or later*. See [[https://raw.githubusercontent.com/thoughtpolice/eris/master/COPYING][COPYING]] for precise terms of copyright and redistribution. 753 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | { repo ? builtins.fetchGit ./. 2 | , versionFile ? ./.version 3 | , officialRelease ? false 4 | 5 | , nixpkgs ? null 6 | , config ? {} 7 | , system ? builtins.currentSystem 8 | }: 9 | 10 | let 11 | bootstrap = import ./nix/bootstrap.nix { 12 | inherit nixpkgs config system; 13 | inherit repo officialRelease versionFile; 14 | }; 15 | in 16 | 17 | let 18 | pkgs = bootstrap.pkgs; 19 | 20 | jobs = rec { 21 | eris = import ./. { nixpkgs = pkgs.path; inherit repo officialRelease; }; 22 | test = import ./test.nix { nixpkgs = pkgs; }; 23 | 24 | docker = with pkgs; 25 | let 26 | # needed for container/host resolution 27 | nsswitch-conf = writeTextFile { 28 | name = "nsswitch.conf"; 29 | text = "hosts: dns files"; 30 | destination = "/etc/nsswitch.conf"; 31 | }; 32 | in dockerTools.buildLayeredImage { 33 | name = "eris"; 34 | tag = eris.version; 35 | 36 | contents = [ eris nsswitch-conf iana-etc cacert tzdata busybox ]; 37 | 38 | config = { 39 | Entrypoint = [ "/bin/eris" ]; 40 | Cmd = [ "--help" ]; 41 | Env = [ "ERIS_CONFIG=/etc/eris.conf" ]; 42 | }; 43 | }; 44 | }; 45 | in jobs 46 | -------------------------------------------------------------------------------- /t/cache.sk: -------------------------------------------------------------------------------- 1 | eris-1:mm5+sdthAOKsA88QLEUB4kpDk2H0ge0TBdFDevAyq8VoabHPej+ZYhZY9fSSsda+yq+WWGPSKYNLjTPL5Bq9Aw== -------------------------------------------------------------------------------- /t/t00-simple.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | testPkg = pkgs.writeShellScriptBin "simple00-test" '' 5 | echo hello world 6 | ''; 7 | 8 | configFile = pkgs.writeText "eris.conf" '' 9 | { 10 | listen => ['http://[::]:8080'], 11 | } 12 | ''; 13 | in 14 | { 15 | nodes = { 16 | eris = { config, pkgs, ... }: 17 | { imports = [ ../module.nix ]; 18 | 19 | services.eris-git = { 20 | enable = true; 21 | configFile = "${configFile}"; 22 | }; 23 | 24 | networking.firewall.allowedTCPPorts = [ 8080 ]; 25 | environment.systemPackages = [ testPkg ]; 26 | }; 27 | 28 | client01 = { config, pkgs, ... }: 29 | { imports = []; 30 | 31 | nix.requireSignedBinaryCaches = false; 32 | nix.binaryCaches = [ "http://eris:8080" ]; 33 | }; 34 | }; 35 | 36 | testScript = '' 37 | startAll; 38 | $eris->waitForOpenPort(8080); 39 | 40 | $client01->succeed("curl -f http://eris:8080/v1/version"); 41 | $client01->succeed("curl -f http://eris:8080/nix-cache-info"); 42 | $client01->fail("curl -f http://eris:8080/v1/public-key"); 43 | 44 | $client01->waitUntilSucceeds("nix copy --from http://eris:8080/ ${testPkg}"); 45 | $client01->succeed("${testPkg}/bin/simple00-test"); 46 | ''; 47 | } 48 | -------------------------------------------------------------------------------- /t/t01-auth.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | testPkg = pkgs.writeShellScriptBin "simple00-test" '' 5 | echo hello world 6 | ''; 7 | 8 | configFile = pkgs.writeText "eris.conf" '' 9 | { 10 | users => [ 'austin:rules' ], 11 | listen => ['http://[::]:8080'], 12 | } 13 | ''; 14 | 15 | netrcFile = pkgs.writeText "netrc" '' 16 | machine eris 17 | login austin 18 | password rules 19 | ''; 20 | in 21 | { 22 | nodes = { 23 | eris = { config, pkgs, ... }: 24 | { imports = [ ../module.nix ]; 25 | 26 | services.eris-git = { 27 | enable = true; 28 | configFile = "${configFile}"; 29 | }; 30 | 31 | networking.firewall.allowedTCPPorts = [ 8080 ]; 32 | environment.systemPackages = [ testPkg ]; 33 | }; 34 | 35 | client01 = { config, pkgs, ... }: 36 | { imports = []; 37 | 38 | nix.requireSignedBinaryCaches = false; 39 | nix.binaryCaches = [ "http://eris:8080" ]; 40 | }; 41 | }; 42 | 43 | testScript = '' 44 | startAll; 45 | $eris->waitForOpenPort(8080); 46 | 47 | $client01->fail("curl -f http://eris:8080/v1/version"); 48 | $client01->succeed("curl -f -u austin:rules http://eris:8080/v1/version"); 49 | 50 | $client01->waitUntilSucceeds("nix --option netrc-file ${netrcFile} copy --from http://eris:8080/ ${testPkg}"); 51 | $client01->succeed("${testPkg}/bin/simple00-test"); 52 | ''; 53 | } 54 | -------------------------------------------------------------------------------- /t/t02-signing.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | testPkg = pkgs.postgresql; 5 | 6 | configFile = pkgs.writeText "eris.conf" '' 7 | { 8 | listen => ['http://[::]:80'], 9 | signing => { 10 | host => 'eris-1', 11 | private => '${./cache.sk}', 12 | }, 13 | } 14 | ''; 15 | 16 | copyScript = pkgs.writeShellScriptBin "copy-test" '' 17 | set -e 18 | 19 | PUBKEY=$(curl -f -s http://eris/v1/public-key) 20 | nix copy \ 21 | --option trusted-public-keys "$PUBKEY" \ 22 | --from http://eris \ 23 | --to /root/test-store \ 24 | "$@" 25 | ''; 26 | in 27 | { 28 | nodes = { 29 | eris = { config, pkgs, ... }: 30 | { imports = [ ../module.nix ]; 31 | 32 | services.eris-git = { 33 | enable = true; 34 | configFile = "${configFile}"; 35 | }; 36 | 37 | networking.firewall.allowedTCPPorts = [ 80 ]; 38 | environment.systemPackages = [ testPkg ]; 39 | }; 40 | 41 | client01 = { config, pkgs, ... }: 42 | { imports = []; 43 | 44 | nix.binaryCaches = [ "http://eris" ]; 45 | environment.systemPackages = [ copyScript ]; 46 | }; 47 | }; 48 | 49 | testScript = '' 50 | startAll; 51 | $eris->waitForOpenPort(80); 52 | 53 | $client01->succeed("curl -f http://eris/v1/version"); 54 | $client01->succeed("curl -f http://eris/v1/public-key"); 55 | 56 | $client01->waitUntilSucceeds("${copyScript}/bin/copy-test ${testPkg}"); 57 | $client01->succeed("nix run --store /root/test-store ${testPkg} -c psql --version") 58 | ''; 59 | } 60 | -------------------------------------------------------------------------------- /t/t03-varnish.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | testPkg = pkgs.writeShellScriptBin "varnish-test" '' 5 | echo hello world 6 | ''; 7 | 8 | configFile = pkgs.writeText "eris.conf" '' 9 | { 10 | listen => ['http://[::1]:5000'], 11 | } 12 | ''; 13 | in 14 | { 15 | nodes = { 16 | eris = { config, pkgs, ... }: 17 | { imports = [ ../module.nix ]; 18 | 19 | services.eris-git = { 20 | enable = true; 21 | configFile = "${configFile}"; 22 | }; 23 | 24 | services.varnish = { 25 | enable = true; 26 | http_address = "0.0.0.0:80"; 27 | config = '' 28 | vcl 4.0; 29 | 30 | backend eris { 31 | .host = "::1"; 32 | .port = "5000"; 33 | } 34 | ''; 35 | }; 36 | 37 | networking.firewall.allowedTCPPorts = [ 80 ]; 38 | environment.systemPackages = [ testPkg ]; 39 | }; 40 | 41 | client01 = { config, pkgs, ... }: 42 | { imports = []; 43 | 44 | nix.requireSignedBinaryCaches = false; 45 | nix.binaryCaches = [ "http://eris" ]; 46 | }; 47 | }; 48 | 49 | testScript = '' 50 | startAll; 51 | $eris->waitForOpenPort(80); 52 | $eris->waitForOpenPort(5000); 53 | 54 | $client01->succeed("curl -f http://eris/v1/version"); 55 | $client01->succeed("curl -f http://eris/nix-cache-info"); 56 | $client01->fail("curl -f http://eris/v1/public-key"); 57 | 58 | $client01->waitUntilSucceeds("nix copy --from http://eris/ ${testPkg}"); 59 | $client01->succeed("${testPkg}/bin/varnish-test"); 60 | ''; 61 | } 62 | -------------------------------------------------------------------------------- /test.nix: -------------------------------------------------------------------------------- 1 | { nixpkgs }: 2 | 3 | with builtins; 4 | 5 | let 6 | system = "x86_64-linux"; 7 | nixosPath = nixpkgs.path + "/nixos"; 8 | makeTest = import (nixosPath + "/tests/make-test.nix"); 9 | 10 | toTest = file: _: { 11 | name = replaceStrings [ ".nix" ] [ "" ] file; 12 | value = makeTest (import (./. + "/t/${file}")) {}; 13 | }; 14 | 15 | tests = nixpkgs.lib.mapAttrs' toTest (builtins.readDir ./t); 16 | in tests 17 | --------------------------------------------------------------------------------