From: self <self@awful.systems> Date: Mon, 26 Jun 2023 03:31:04 +0000 (-0700) Subject: Initial commit X-Git-Url: http://these/git/%22https:/nerdica.net/%7B%60%24%7BwebArchiveUrl%7D/save/static/git-favicon.png?a=commitdiff_plain;h=c34b578dfebfd5557d6b74a1627af3f8f2e4ebb1;p=awful.systems.git Initial commit --- c34b578dfebfd5557d6b74a1627af3f8f2e4ebb1 diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..425fd49 --- /dev/null +++ b/.envrc @@ -0,0 +1,6 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8=" +fi +use flake + +source_env_if_exists .envrc.private diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77567c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.idea +*.log +tmp/ + +result/ +.direnv/ +.envrc.private diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..f900e95 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,9 @@ +keys: + - &admin_self age1ykfwuq666clqzxk4vjyjhtk29h7s3ztcu4ewfwgrq9kaxrmeapdqw0ec85 + - &host_these age1qwdxl2jdwu2feee4ttlhr06682026gftt9n6cw9n6yxjsr2vzy7se389re +creation_rules: + - path_regex: secrets/[^/]+.yaml$ + key_groups: + - age: + - *admin_self + - *host_these diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6586b4e --- /dev/null +++ b/flake.lock @@ -0,0 +1,128 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1687171271, + "narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1687466461, + "narHash": "sha256-oupXI7g7RPzlpGUfAu1xG4KBK53GrZH8/xeKgKDB4+Q=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ecb441f22067ba1d6312f4932a7c64efa8d19a7b", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.05", + "type": "indirect" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1687031877, + "narHash": "sha256-yMFcVeI+kZ6KD2QBrFPNsvBrLq2Gt//D0baHByMrjFY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e2e2059d19668dab1744301b8b0e821e3aae9c99", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1686979235, + "narHash": "sha256-gBlBtk+KrezFkfMrZw6uwTuA7YWtbFciiS14mEoTCo0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7cc30fd5372ddafb3373c318507d9932bd74aafe", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pkgs-unstable": { + "locked": { + "lastModified": 1687502512, + "narHash": "sha256-dBL/01TayOSZYxtY4cMXuNCBk8UMLoqRZA+94xiFpJA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3ae20aa58a6c0d1ca95c9b11f59a2d12eebc511f", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "pkgs-unstable": "pkgs-unstable", + "sops-nix": "sops-nix" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": "nixpkgs_2", + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1687398569, + "narHash": "sha256-e/umuIKFcFtZtWeX369Hbdt9r+GQ48moDmlTcyHWL28=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "2ff6973350682f8d16371f8c071a304b8067f192", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2dc8760 --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + description = "The deployment flake for the awful.systems cluster"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-23.05"; + flake-utils.url = "github:numtide/flake-utils"; + pkgs-unstable.url = "nixpkgs/nixos-unstable"; + sops-nix.url = "github:Mic92/sops-nix"; + }; + + outputs = { self, nixpkgs, flake-utils, sops-nix, ... }@attrs: + { + nixosConfigurations = { + these = nixpkgs.lib.nixosSystem { + specialArgs = attrs; + modules = + [ ./hosts/these/configuration.nix sops-nix.nixosModules.sops ]; + }; + }; + } // flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + deploy = name: target: + pkgs.writeShellScriptBin "deploy-${name}" '' + nixos-rebuild switch --fast --flake .#${name} \ + --target-host ${target} \ + $@ + ''; + go = name: target: + pkgs.writeShellScriptBin "go-${name}" '' + ssh ${target} $@ + ''; + init-secrets = pkgs.writeShellScriptBin "init-secrets" '' + mkdir -p ~/.config/sops/age + cp $1 /tmp/init-secrets-key && + ${pkgs.openssh}/bin/ssh-keygen -p -N "" -f /tmp/init-secrets-key && + ${pkgs.ssh-to-age}/bin/ssh-to-age -private-key -i /tmp/init-secrets-key > ~/.config/sops/age/keys.txt + rm /tmp/init-secrets-key + echo Your age public key is: + ${pkgs.age}/bin/age-keygen -y ~/.config/sops/age/keys.txt + ''; + in { + devShells.default = pkgs.mkShell { + buildInputs = [ + pkgs.sops + pkgs.ssh-to-age + pkgs.age + (deploy "these" "root@these.awful.systems") + (go "these" "root@these.awful.systems") + init-secrets + pkgs.bashInteractive + ]; + }; + }); +} diff --git a/git/default.nix b/git/default.nix new file mode 100644 index 0000000..92667a5 --- /dev/null +++ b/git/default.nix @@ -0,0 +1,36 @@ +{ config, lib, pkgs, ... }: + +let + new-repo = pkgs.writeShellScriptBin "new-repo" '' +mkdir -p ${config.users.extraUsers.git.home}/repos/''${1}.git +${pkgs.git}/bin/git init --bare ${config.users.extraUsers.git.home}/repos/''${1}.git/ +chown -R git:git ${config.users.extraUsers.git.home}/repos +''; + in +{ + users.extraUsers.git = { + uid = 402; + isSystemUser = true; + home = "/home/git"; + createHome = true; + group = "git"; + homeMode = "770"; + shell = "${pkgs.git}/bin/git-shell"; + }; + + users.extraGroups.git = { gid = 402; }; + + services.nginx.gitweb = { + enable = true; + location = "/git"; + group = "git"; + virtualHost = "awful.systems"; + }; + + services.gitweb = { + gitwebTheme = true; + projectroot = "/home/git/repos"; + }; + + environment.systemPackages = [ new-repo ]; +} diff --git a/hardware/hetzner-cloud.nix b/hardware/hetzner-cloud.nix new file mode 100644 index 0000000..e4c3bd3 --- /dev/null +++ b/hardware/hetzner-cloud.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ (modulesPath + "/profiles/qemu-guest.nix") ./shared.nix ]; + + boot.initrd.availableKernelModules = + [ "ata_piix" "virtio_pci" "xhci_pci" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + boot.loader.grub.enable = true; + boot.loader.grub.devices = [ "/dev/sda" ]; + + fileSystems."/" = { device = "/dev/sda1"; }; + + swapDevices = [ ]; + + time.timeZone = "America/Los_Angeles"; + + networking.useDHCP = false; + networking.interfaces.ens3.useDHCP = true; # public IP + networking.interfaces.ens10.useDHCP = true; # first internal network + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/hardware/shared.nix b/hardware/shared.nix new file mode 100644 index 0000000..06f498d --- /dev/null +++ b/hardware/shared.nix @@ -0,0 +1,21 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ../secrets/keys + ]; + # Initial empty root password for easy login: + users.users.root.initialHashedPassword = ""; + services.openssh.settings.PermitRootLogin = "prohibit-password"; + + services.openssh.enable = true; + + nix.gc = { + automatic = true; + options = "--delete-older-than 5d"; + }; + + environment.systemPackages = [ pkgs.ssh-to-age ]; + + system.stateVersion = "22.05"; +} diff --git a/hosts/these/configuration.nix b/hosts/these/configuration.nix new file mode 100644 index 0000000..d9de0c9 --- /dev/null +++ b/hosts/these/configuration.nix @@ -0,0 +1,12 @@ +{ pkgs, ... }: +{ + imports = [ + ../../hardware/hetzner-cloud.nix + ../../secrets + ../../pass + ../../lemmy + ../../git + ]; + + networking.hostName = "these"; +} diff --git a/lemmy/default.nix b/lemmy/default.nix new file mode 100644 index 0000000..c89e030 --- /dev/null +++ b/lemmy/default.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, pkgs-unstable, ... }: + +{ + imports = [ ./prod.nix ]; + + services.lemmy-prod = { + enable = true; + server.package = pkgs.callPackage ./server.nix { Security = null; }; + ui.package = pkgs.callPackage ./ui.nix { }; + nginx.enable = true; + database.createLocally = true; + + settings = { + hostname = "awful.systems"; + setup = { + admin_username = "self"; + admin_email = "self@awful.systems"; + site_name = "awful.systems"; + }; + }; + }; + + sops.secrets."lemmy/initial_admin_password" = { }; + + sops.templates.lemmy-prod.content = builtins.toJSON + (config.services.lemmy-prod.settings // { + setup = config.services.lemmy-prod.settings.setup // { + admin_password = config.sops.placeholder."lemmy/initial_admin_password"; + }; + }); + + systemd.services.lemmy-prod = { + serviceConfig = { + User = "lemmy"; + Group = "lemmy"; + LoadCredential = ''lemmy-prod:${config.sops.templates.lemmy-prod.path}''; + }; + + environment = { + LEMMY_CONFIG_LOCATION = lib.mkForce "%d/lemmy-prod"; + RUST_BACKTRACE = "full"; + LEMMY_DATABASE_URL = + pkgs.lib.mkForce "postgres:///lemmy?host=/run/postgresql&user=lemmy"; + }; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; +} diff --git a/lemmy/disable-sharp.patch b/lemmy/disable-sharp.patch new file mode 100644 index 0000000..13d77a6 --- /dev/null +++ b/lemmy/disable-sharp.patch @@ -0,0 +1,96 @@ +diff --git a/package.json b/package.json +index 01f5100..54016b6 100644 +--- a/package.json ++++ b/package.json +@@ -85,7 +85,6 @@ + "sass-loader": "^13.2.2", + "serialize-javascript": "^6.0.1", + "service-worker-webpack": "^1.0.0", +- "sharp": "^0.32.1", + "tippy.js": "^6.3.7", + "toastify-js": "^1.12.0", + "tributejs": "^5.1.3", +diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx +index 1377598..452e0c8 100644 +--- a/src/server/utils/create-ssr-html.tsx ++++ b/src/server/utils/create-ssr-html.tsx +@@ -1,7 +1,6 @@ + import { Helmet } from "inferno-helmet"; + import { renderToString } from "inferno-server"; + import serialize from "serialize-javascript"; +-import sharp from "sharp"; + import { favIconPngUrl, favIconUrl } from "../../shared/config"; + import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces"; + import { buildThemeList } from "./build-themes-list"; +@@ -9,7 +8,6 @@ import { fetchIconPng } from "./fetch-icon-png"; + + const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || ""; + +-let appleTouchIcon: string | undefined = undefined; + + export async function createSsrHtml( + root: string, +@@ -21,25 +19,6 @@ export async function createSsrHtml( + (await buildThemeList())[0] + }.css" />`; + +- if (!appleTouchIcon) { +- appleTouchIcon = site?.site_view.site.icon +- ? `data:image/png;base64,${sharp( +- await fetchIconPng(site.site_view.site.icon) +- ) +- .resize(180, 180) +- .extend({ +- bottom: 20, +- top: 20, +- left: 20, +- right: 20, +- background: "#222222", +- }) +- .png() +- .toBuffer() +- .then(buf => buf.toString("base64"))}` +- : favIconPngUrl; +- } +- + const erudaStr = + process.env["LEMMY_UI_DEBUG"] === "true" + ? renderToString( +@@ -83,9 +62,7 @@ export async function createSsrHtml( + + <!-- Web app manifest --> + <link rel="manifest" href="/manifest.webmanifest" /> +- <link rel="apple-touch-icon" href=${appleTouchIcon} /> +- <link rel="apple-touch-startup-image" href=${appleTouchIcon} /> +- ++ + <!-- Styles --> + <link rel="stylesheet" type="text/css" href="/static/styles/styles.css" /> + +diff --git a/src/server/utils/generate-manifest-json.ts b/src/server/utils/generate-manifest-json.ts +index 2f9d8b8..245ad4f 100644 +--- a/src/server/utils/generate-manifest-json.ts ++++ b/src/server/utils/generate-manifest-json.ts +@@ -2,7 +2,6 @@ import { getHttpBaseExternal } from "@utils/env"; + import { readFile } from "fs/promises"; + import { GetSiteResponse } from "lemmy-js-client"; + import path from "path"; +-import sharp from "sharp"; + import { fetchIconPng } from "./fetch-icon-png"; + + const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512]; +@@ -40,14 +39,6 @@ export default async function ({ + path.join(defaultLogoPathDirectory, `icon-${size}x${size}.png`) + ).then(buf => buf.toString("base64")); + +- if (icon) { +- src = await sharp(icon) +- .resize(size, size) +- .png() +- .toBuffer() +- .then(buf => buf.toString("base64")); +- } +- + return { + sizes: `${size}x${size}`, + type: "image/png", diff --git a/lemmy/package.json b/lemmy/package.json new file mode 100644 index 0000000..45d4daf --- /dev/null +++ b/lemmy/package.json @@ -0,0 +1,138 @@ +{ + "name": "lemmy-ui", + "version": "0.18.0", + "description": "An isomorphic UI for lemmy", + "repository": "https://github.com/LemmyNet/lemmy-ui", + "license": "AGPL-3.0", + "author": "Dessalines <tyhou13@gmx.com>", + "scripts": { + "prebuild:dev": "yarn clean && node generate_translations.js", + "build:dev": "webpack --mode=development", + "prebuild:prod": "yarn clean && node generate_translations.js", + "build:prod": "webpack --mode=production", + "clean": "yarn run rimraf dist", + "dev": "yarn start", + "lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"", + "prepare": "husky install", + "start": "yarn build:dev --watch", + "themes:build": "sass src/assets/css/themes/:src/assets/css/themes", + "themes:watch": "sass --watch src/assets/css/themes/:src/assets/css/themes", + "translations:generate": "node generate_translations.js", + "translations:init": "git submodule init && yarn translations:update", + "translations:update": "git submodule update --remote --recursive" + }, + "lint-staged": { + "*.{ts,tsx,js}": [ + "prettier --write", + "eslint --fix" + ], + "*.{css, scss}": [ + "prettier --write" + ], + "package.json": [ + "sortpack" + ] + }, + "dependencies": { + "@babel/plugin-proposal-decorators": "^7.21.0", + "@babel/plugin-transform-runtime": "^7.21.4", + "@babel/plugin-transform-typescript": "^7.21.3", + "@babel/preset-env": "7.21.5", + "@babel/preset-typescript": "^7.21.5", + "@babel/runtime": "^7.21.5", + "@emoji-mart/data": "^1.1.0", + "autosize": "^6.0.1", + "babel-loader": "^9.1.2", + "babel-plugin-inferno": "^6.6.0", + "bootstrap": "^5.2.3", + "check-password-strength": "^2.0.7", + "classnames": "^2.3.1", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^11.0.0", + "cross-fetch": "^3.1.5", + "css-loader": "^6.7.3", + "emoji-mart": "^5.4.0", + "emoji-short-name": "^2.0.0", + "express": "~4.18.2", + "history": "^5.3.0", + "html-to-text": "^9.0.5", + "i18next": "^22.4.15", + "inferno": "^8.1.1", + "inferno-create-element": "^8.1.1", + "inferno-helmet": "^5.2.1", + "inferno-hydrate": "^8.1.1", + "inferno-i18next-dess": "0.0.2", + "inferno-router": "^8.1.1", + "inferno-server": "^8.1.1", + "isomorphic-cookie": "^1.2.4", + "jwt-decode": "^3.1.2", + "lemmy-js-client": "0.18.0-rc.2", + "lodash": "^4.17.21", + "markdown-it": "^13.0.1", + "markdown-it-container": "^3.0.0", + "markdown-it-emoji": "^2.0.2", + "markdown-it-footnote": "^3.0.3", + "markdown-it-html5-embed": "^1.0.0", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "mini-css-extract-plugin": "^2.7.5", + "moment": "^2.29.4", + "register-service-worker": "^1.7.2", + "run-node-webpack-plugin": "^1.3.0", + "sanitize-html": "^2.10.0", + "sass": "^1.62.1", + "sass-loader": "^13.2.2", + "serialize-javascript": "^6.0.1", + "service-worker-webpack": "^1.0.0", + "sharp": "^0.32.1", + "tippy.js": "^6.3.7", + "toastify-js": "^1.12.0", + "tributejs": "^5.1.3", + "webpack": "5.82.1", + "webpack-cli": "^5.1.1", + "webpack-node-externals": "^3.0.0" + }, + "devDependencies": { + "@babel/core": "^7.21.8", + "@types/autosize": "^4.0.0", + "@types/bootstrap": "^5.2.6", + "@types/express": "^4.17.17", + "@types/html-to-text": "^9.0.0", + "@types/markdown-it": "^12.2.3", + "@types/markdown-it-container": "^2.0.5", + "@types/node": "^20.1.2", + "@types/sanitize-html": "^2.9.0", + "@types/serialize-javascript": "^5.0.1", + "@types/toastify-js": "^1.11.1", + "@typescript-eslint/eslint-plugin": "^5.59.5", + "@typescript-eslint/parser": "^5.59.5", + "eslint": "^8.40.0", + "eslint-plugin-inferno": "^7.32.2", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^4.2.1", + "husky": "^8.0.3", + "import-sort-style-module": "^6.0.0", + "lint-staged": "^13.2.2", + "prettier": "^2.8.8", + "prettier-plugin-import-sort": "^0.0.7", + "prettier-plugin-organize-imports": "^3.2.2", + "prettier-plugin-packagejson": "^2.4.3", + "rimraf": "^5.0.0", + "sortpack": "^2.3.4", + "style-loader": "^3.3.2", + "terser": "^5.17.3", + "typescript": "^5.0.4", + "webpack-dev-server": "4.15.0" + }, + "packageManager": "yarn@1.22.19", + "engines": { + "node": ">=8.9.0" + }, + "engineStrict": true, + "importSort": { + ".js, .jsx, .ts, .tsx": { + "style": "module", + "parser": "typescript" + } + } +} diff --git a/lemmy/pin.json b/lemmy/pin.json new file mode 100644 index 0000000..8522dbb --- /dev/null +++ b/lemmy/pin.json @@ -0,0 +1,7 @@ +{ + "version": "0.18.0", + "serverSha256": "sha256-KzEelj2/+wfp570Vw1+FoqiYZd1PxELTdopGSeel97E=", + "serverCargoSha256": "sha256-p1ZytuaXouKFkKjsEsaNjndoioTSVVM2pf72qE8/qyM=", + "uiSha256": "sha256-pB6uEL9gDwvsi+FbooKBhTCJ+Qmc6Vl2bBTMiL1hUJI=", + "uiYarnDepsSha256": "sha256-NtluS6Cr39L9nGwNA17c7xsM5xoJraS02a7sp7r9KPI=" +} diff --git a/lemmy/prod.nix b/lemmy/prod.nix new file mode 100644 index 0000000..a2be39f --- /dev/null +++ b/lemmy/prod.nix @@ -0,0 +1,252 @@ +{ lib, pkgs, config, ... }: +with lib; +let + cfg = config.services.lemmy-prod; + settingsFormat = pkgs.formats.json { }; +in +{ + imports = [ + (mkRemovedOptionModule [ "services" "lemmy-prod" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.") + ]; + + options.services.lemmy-prod = { + + enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust"); + + server = { + package = mkPackageOptionMD pkgs "lemmy-server" {}; + }; + + ui = { + package = mkPackageOptionMD pkgs "lemmy-ui" {}; + + port = mkOption { + type = types.port; + default = 1234; + description = lib.mdDoc "Port where lemmy-ui should listen for incoming requests."; + }; + }; + + caddy.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy"); + nginx.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the nginx reverse proxy"); + + database = { + createLocally = mkEnableOption (lib.mdDoc "creation of database on the instance"); + + uri = mkOption { + type = with types; nullOr str; + default = null; + description = lib.mdDoc "The connection URI to use. Takes priority over the configuration file if set."; + }; + }; + + settings = mkOption { + default = { }; + description = lib.mdDoc "Lemmy configuration"; + + type = types.submodule { + freeformType = settingsFormat.type; + + options.hostname = mkOption { + type = types.str; + default = null; + description = lib.mdDoc "The domain name of your instance (eg 'lemmy.ml')."; + }; + + options.port = mkOption { + type = types.port; + default = 8536; + description = lib.mdDoc "Port where lemmy should listen for incoming requests."; + }; + + options.captcha = { + enabled = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Enable Captcha."; + }; + difficulty = mkOption { + type = types.enum [ "easy" "medium" "hard" ]; + default = "medium"; + description = lib.mdDoc "The difficultly of the captcha to solve."; + }; + }; + }; + }; + + }; + + config = + lib.mkIf cfg.enable { + services.lemmy-prod.settings = (mapAttrs (name: mkDefault) + { + bind = "127.0.0.1"; + tls_enabled = true; + pictrs_url = with config.services.pict-rs; "http://${address}:${toString port}"; + actor_name_max_length = 20; + + rate_limit.message = 180; + rate_limit.message_per_second = 60; + rate_limit.post = 6; + rate_limit.post_per_second = 600; + rate_limit.register = 3; + rate_limit.register_per_second = 3600; + rate_limit.image = 6; + rate_limit.image_per_second = 3600; + } // { + database = mapAttrs (name: mkDefault) { + user = "lemmy"; + host = "/run/postgresql"; + port = 5432; + database = "lemmy"; + pool_size = 5; + }; + }); + + services.postgresql = mkIf cfg.database.createLocally { + enable = true; + ensureDatabases = [ cfg.settings.database.database ]; + ensureUsers = [{ + name = cfg.settings.database.user; + ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES"; + }]; + }; + + services.pict-rs.enable = true; + + services.caddy = mkIf cfg.caddy.enable { + enable = mkDefault true; + virtualHosts."${cfg.settings.hostname}" = { + extraConfig = '' + handle_path /static/* { + root * ${cfg.ui.package}/dist + file_server + } + @for_backend { + path /api/* /pictrs/* /feeds/* /nodeinfo/* + } + handle @for_backend { + reverse_proxy 127.0.0.1:${toString cfg.settings.port} + } + @post { + method POST + } + handle @post { + reverse_proxy 127.0.0.1:${toString cfg.settings.port} + } + @jsonld { + header Accept "application/activity+json" + header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + } + handle @jsonld { + reverse_proxy 127.0.0.1:${toString cfg.settings.port} + } + handle { + reverse_proxy 127.0.0.1:${toString cfg.ui.port} + } + ''; + }; + }; + + services.nginx = mkIf cfg.nginx.enable { + enable = mkDefault true; + virtualHosts."${cfg.settings.hostname}".locations = let + ui = "http://127.0.0.1:${toString cfg.ui.port}"; + backend = "http://127.0.0.1:${toString cfg.settings.port}"; + in { + "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = { + # backend requests + proxyPass = backend; + proxyWebsockets = true; + recommendedProxySettings = true; + }; + "/" = { + # mixed frontend and backend requests, based on the request headers + proxyPass = "$proxpass"; + recommendedProxySettings = true; + extraConfig = '' + set $proxpass "${ui}"; + if ($http_accept = "application/activity+json") { + set $proxpass "${backend}"; + } + if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") { + set $proxpass "${backend}"; + } + if ($request_method = POST) { + set $proxpass "${backend}"; + } + + # Cuts off the trailing slash on URLs to make them valid + rewrite ^(.+)/+$ $1 permanent; + ''; + }; + }; + }; + + assertions = [ + { + assertion = cfg.database.createLocally -> cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql"; + message = "if you want to create the database locally, you need to use a local database"; + } + { + assertion = (!(hasAttrByPath ["federation"] cfg.settings)) && (!(hasAttrByPath ["federation" "enabled"] cfg.settings)); + message = "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect"; + } + ]; + + systemd.services.lemmy-prod = { + description = "Lemmy server (production)"; + + environment = { + LEMMY_CONFIG_LOCATION = "${settingsFormat.generate "config.hjson" cfg.settings}"; + LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri; + }; + + documentation = [ + "https://join-lemmy.org/docs/en/admins/from_scratch.html" + "https://join-lemmy.org/docs/en/" + ]; + + wantedBy = [ "multi-user.target" ]; + + after = [ "pict-rs.service" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ]; + + requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ]; + + serviceConfig = { + DynamicUser = true; + RuntimeDirectory = "lemmy"; + ExecStart = "${cfg.server.package}/bin/lemmy_server"; + }; + }; + + systemd.services.lemmy-ui-prod = { + description = "Lemmy UI (production)"; + + environment = { + LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}"; + LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}"; + LEMMY_EXTERNAL_HOST = cfg.settings.hostname; + LEMMY_HTTPS = "false"; + }; + + documentation = [ + "https://join-lemmy.org/docs/en/admins/from_scratch.html" + "https://join-lemmy.org/docs/en/" + ]; + + wantedBy = [ "multi-user.target" ]; + + after = [ "lemmy-prod.service" ]; + + requires = [ "lemmy-prod.service" ]; + + serviceConfig = { + DynamicUser = true; + WorkingDirectory = "${cfg.ui.package}"; + ExecStart = "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js"; + }; + }; + }; + +} diff --git a/lemmy/server.nix b/lemmy/server.nix new file mode 100644 index 0000000..a76ce53 --- /dev/null +++ b/lemmy/server.nix @@ -0,0 +1,58 @@ +{ lib +, stdenv +, rustPlatform +, fetchFromGitHub +, openssl +, postgresql +, libiconv +, Security +, protobuf +, rustfmt +, nixosTests +}: +let + pinData = lib.importJSON ./pin.json; + version = pinData.version; +in +rustPlatform.buildRustPackage rec { + inherit version; + pname = "lemmy-server"; + + src = fetchFromGitHub { + owner = "LemmyNet"; + repo = "lemmy"; + rev = version; + sha256 = pinData.serverSha256; + fetchSubmodules = true; + }; + + cargoSha256 = pinData.serverCargoSha256; + + buildInputs = [ postgresql ] + ++ lib.optionals stdenv.isDarwin [ libiconv Security ]; + + # Using OPENSSL_NO_VENDOR is not an option on darwin + # As of version 0.10.35 rust-openssl looks for openssl on darwin + # with a hardcoded path to /usr/lib/libssl.x.x.x.dylib + # https://github.com/sfackler/rust-openssl/blob/master/openssl-sys/build/find_normal.rs#L115 + OPENSSL_LIB_DIR = "${lib.getLib openssl}/lib"; + OPENSSL_INCLUDE_DIR = "${openssl.dev}/include"; + + PROTOC = "${protobuf}/bin/protoc"; + PROTOC_INCLUDE = "${protobuf}/include"; + nativeBuildInputs = [ protobuf rustfmt ]; + + passthru.updateScript = ./update.sh; + passthru.tests.lemmy-server = nixosTests.lemmy; + + doCheck = false; + doInstallCheck = false; + + meta = with lib; { + description = "ð Building a federated alternative to reddit in rust"; + homepage = "https://join-lemmy.org/"; + license = licenses.agpl3Only; + maintainers = with maintainers; [ happysalada billewanick ]; + mainProgram = "lemmy_server"; + }; +} diff --git a/lemmy/sharp.nix b/lemmy/sharp.nix new file mode 100644 index 0000000..353890b --- /dev/null +++ b/lemmy/sharp.nix @@ -0,0 +1,19 @@ +{ lib, buildNpmPackage, fetchFromGitHub }: + +buildNpmPackage rec { + pname = "sharp"; + version = "0.32.1"; + + src = fetchFromGitHub { + owner = "lovell"; + repo = "sharp"; + rev = "v${version}"; + hash = "sha256-bpReMsKXK7/fivLsk6QyHTBX9LL2WbxXdDBrTV5luWo="; + }; + + npmDepsHash = "sha256-p1ZytuaXouKFkKjsEsaNjndo1oTSVVM2pf12qE8/q1M="; + + npmPackFlags = [ "--ignore-scripts" ]; + + NODE_OPTIONS = "--openssl-legacy-provider"; +} diff --git a/lemmy/ui.nix b/lemmy/ui.nix new file mode 100644 index 0000000..db5d6dd --- /dev/null +++ b/lemmy/ui.nix @@ -0,0 +1,73 @@ +{ lib, mkYarnPackage, libsass, nodejs, python3, pkg-config +, fetchFromGitHub, fetchYarnDeps, nixosTests }: + +let + pinData = lib.importJSON ./pin.json; + + pkgConfig = { + node-sass = { + nativeBuildInputs = [ pkg-config ]; + buildInputs = [ libsass python3 ]; + postInstall = '' + LIBSASS_EXT=auto yarn --offline run build + rm build/config.gypi + ''; + }; + }; + + name = "lemmy-ui"; + version = pinData.version; + + src = fetchFromGitHub { + owner = "LemmyNet"; + repo = name; + rev = version; + fetchSubmodules = true; + sha256 = pinData.uiSha256; + }; + + patches = [ ./disable-sharp.patch ]; +in mkYarnPackage { + + inherit src pkgConfig name version patches; + + extraBuildInputs = [ libsass ]; + + packageJSON = ./package.json; + offlineCache = fetchYarnDeps { + yarnLock = src + "/yarn.lock"; + sha256 = pinData.uiYarnDepsSha256; + }; + + yarnPreBuild = '' + export npm_config_nodedir=${nodejs} + ''; + + buildPhase = '' + # Yarn writes cache directories etc to $HOME. + export HOME=$PWD/yarn_home + + ln -sf $PWD/node_modules $PWD/deps/lemmy-ui/ + + yarn --offline build:prod + ''; + + preInstall = '' + mkdir $out + cp -R ./deps/lemmy-ui/dist $out + cp -R ./node_modules $out + ''; + + distPhase = "true"; + + passthru.updateScript = ./update.sh; + passthru.tests.lemmy-ui = nixosTests.lemmy; + + meta = with lib; { + description = "Building a federated alternative to reddit in rust"; + homepage = "https://join-lemmy.org/"; + license = licenses.agpl3Only; + maintainers = with maintainers; [ happysalada billewanick ]; + inherit (nodejs.meta) platforms; + }; +} diff --git a/lemmy/update.sh b/lemmy/update.sh new file mode 100755 index 0000000..61dd05a --- /dev/null +++ b/lemmy/update.sh @@ -0,0 +1,51 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i oil -p oil jq sd nix-prefetch-github ripgrep moreutils + +# TODO set to `verbose` or `extdebug` once implemented in oil +shopt --set xtrace +# we need failures inside of command subs to get the correct dependency sha256 +shopt --unset inherit_errexit + +const directory = $(dirname $0 | xargs realpath) +const owner = "LemmyNet" +const ui_repo = "lemmy-ui" +const server_repo = "lemmy" +const latest_rev = $(curl -q https://api.github.com/repos/${owner}/${server_repo}/releases/latest | \ + jq -r '.tag_name') +const latest_version = $(echo $latest_rev) +const current_version = $(jq -r '.version' $directory/pin.json) +echo "latest version: $latest_version, current version: $current_version" +if ("$latest_version" === "$current_version") { + echo "lemmy is already up-to-date" + return 0 +} else { + # for some strange reason, hydra fails on reading upstream package.json directly + const source = "https://raw.githubusercontent.com/$owner/$ui_repo/$latest_version" + const package_json = $(curl -qf $source/package.json) + echo $package_json > $directory/package.json + + const server_tarball_meta = $(nix-prefetch-github $owner $server_repo --rev $latest_rev --fetch-submodules) + const server_tarball_hash = "sha256-$(echo $server_tarball_meta | jq -r '.sha256')" + const ui_tarball_meta = $(nix-prefetch-github $owner $ui_repo --rev $latest_rev --fetch-submodules) + const ui_tarball_hash = "sha256-$(echo $ui_tarball_meta | jq -r '.sha256')" + + jq ".version = \"$latest_version\" | \ + .\"serverSha256\" = \"$server_tarball_hash\" | \ + .\"uiSha256\" = \"$ui_tarball_hash\" | \ + .\"serverCargoSha256\" = \"\" | \ + .\"uiYarnDepsSha256\" = \"\"" $directory/pin.json | sponge $directory/pin.json + + const new_cargo_sha256 = $(nix-build $directory/../../../.. -A lemmy-server 2>&1 | \ + tail -n 2 | \ + head -n 1 | \ + sd '\s+got:\s+' '') + + const new_offline_cache_sha256 = $(nix-build $directory/../../../.. -A lemmy-ui 2>&1 | \ + tail -n 2 | \ + head -n 1 | \ + sd '\s+got:\s+' '') + + jq ".\"serverCargoSha256\" = \"$new_cargo_sha256\" | \ + .\"uiYarnDepsSha256\" = \"$new_offline_cache_sha256\"" \ + $directory/pin.json | sponge $directory/pin.json +} diff --git a/pass/default.nix b/pass/default.nix new file mode 100644 index 0000000..42d41ac --- /dev/null +++ b/pass/default.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: + +{ + users.extraUsers."_pass" = { + uid = 401; + isSystemUser = true; + home = "/home/_pass"; + createHome = true; + group = "_pass"; + shell = "${pkgs.git}/bin/git-shell"; + openssh.authorizedKeys.keyFiles = [ ./pass_id_ed25519.pub ]; + }; + + users.extraGroups."_pass" = { gid = 401; }; +} diff --git a/pass/pass_id_ed25519.pub b/pass/pass_id_ed25519.pub new file mode 100644 index 0000000..7e7d862 --- /dev/null +++ b/pass/pass_id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILpWiqcXbhUo5YxOUApw6v7BJBrf3WLVge9JpTJUGOsm j@severance diff --git a/secrets/default.nix b/secrets/default.nix new file mode 100644 index 0000000..937dcad --- /dev/null +++ b/secrets/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: + +{ + sops = { + age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; + defaultSopsFile = ./secrets.yaml; + }; +} diff --git a/secrets/keys/default.nix b/secrets/keys/default.nix new file mode 100644 index 0000000..e8d6c33 --- /dev/null +++ b/secrets/keys/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: + +{ + users.users = { + root.openssh.authorizedKeys.keyFiles = [ ./self_id_ed25519.pub ]; + git.openssh.authorizedKeys.keyFiles = [ ./self_id_ed25519.pub ]; + }; +} diff --git a/secrets/keys/self_id_ed25519.pub b/secrets/keys/self_id_ed25519.pub new file mode 100644 index 0000000..066018b --- /dev/null +++ b/secrets/keys/self_id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDL3Q90hpvleou7rhE/MRqzcUP8sV2Itkms3Ynlaf5YS self@awful.systems diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml new file mode 100644 index 0000000..e96c897 --- /dev/null +++ b/secrets/secrets.yaml @@ -0,0 +1,31 @@ +lemmy: + initial_admin_password: ENC[AES256_GCM,data:fPDeGjYK1PK7dEp5JFbH2Q==,iv:PPol1OK6s54WGg4gmO9ss/u9+QM6YcIKTug7/8V442Y=,tag:R9Wv+9gGocgmFYwDUfPUlQ==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1ykfwuq666clqzxk4vjyjhtk29h7s3ztcu4ewfwgrq9kaxrmeapdqw0ec85 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLYnNxSFMwUFY5ZmtYSk1k + ZVFLcVNQYXZYbFl0VDhBam1aeGtVTzUrdkhBCjFlTHpQU2ZiRjVhU2dTRmZJZVZL + Z094ZEFzTEFyYlA0eU9GSXFCYWdRckEKLS0tIFpjdHlBMlFYLzZzTVdoekE5eXdU + L3RYL0ZRKzdOMjJLVnJUTVlHaDBEUUkKyvlJ3mcJZ3U9iWIL4YLJDEtUCkz2Kmh2 + 2SF8Tz0gshOL8xBXeaoleXN2sHvnC5PqePvzu6Q8hs8iX81WxY+Nyw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1qwdxl2jdwu2feee4ttlhr06682026gftt9n6cw9n6yxjsr2vzy7se389re + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhenJvaCszWHpoeUdJWWtY + bTFSY2kxU0dCL2JvMXJQWHdpMGdaT0J4Z0hFCkJUNHVSd1A3MytJb1p3aVE2T0JF + MkgzaDluVXdJV2ZJb2pITVBGTFlNOUkKLS0tIFdCU0V4MFh1elN0ZWEzVS9OcVNI + NlNKT3g5dWlZckM0MTVwNVAzajU0YkEKyY98VzxcSz9NqaBsKV89Wegr+d0ZuzJH + Yt5R1uCjeBHBNW3++qVRf2koWouPpMYa69eDrlRUkL0SkJXVC4QzqQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2023-06-25T23:58:12Z" + mac: ENC[AES256_GCM,data:5XuxyFuU5W/Lsy9Wq6RP2R9VPiR2jMaHsnofzzDGEYyuQSmi8eb/rdneJGDDR7Efmj9mz9rBxLvYAV2hVSokGz9y+2/J6L4D/C1FNDkxK5zsgUR5wHOoIdoUCTHwc8/XCIh8+PjZgRZqhTja9nuTdJ8FsKpswHun4XKTI5NKbXM=,iv:qZRs3FR0jBuVfc5FL3tfZXTm7LYF/eMsqfMPQAHqS/Q=,tag:gEnq3o0qY+Tmg+MarZssIg==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.3