commit 4bd3c54bbf306d20236000836fb052450d51ab68 Author: Y Date: Mon Sep 3 20:06:05 2018 +0200 1yr-old; first commit 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. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..3860a4a --- /dev/null +++ b/README.adoc @@ -0,0 +1,134 @@ +///// +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. +///// + += Automated configuration of a home-server +:toc: + +== Introduction + +This project contains a collection of https://docs.ansible.com/[Ansible] rules, that: + +* both automate and document the setup of a home-server, as secure as possible; +* should greatly ease the quick setup of a replacement server, in case the main one has a hardware failure (this is for a single machine, not a datacenter). + +Let it be clear, that the target is a *home*-server, not an entreprise solution, not a personal VM “in the Cloud”, but a real hardware machine plugged at home to the xDSL or fiber router, that links you to an ISP. +Besides, this project should not be used without *solid knowledge of Linux and its command-line, as well as https://git-scm.com/[Git]*! + +Oh! And I decided to let go of Debian, and use https://www.archlinux.org/[Archlinux] instead. +Someone once questionned me about such a choice for a server; here was my answer: + +[quote, Yves, https://linuxfr.org/news/pyruse-1-0-pour-remplacer-fail2ban-et-autres-scruteurs-de-journaux-sur-un-gnu-linux-moderne#comment-1729871] +_____ +In practice, Archlinux is working pretty well on a server. Before that, I was using Debian. +My experience (for a server) is such: + +Archlinux:: +* `−` There is almost always something to handle after software upgrades ⇒ I never let these upgrades run automatically unsupervised. +* `+` On the other hand, the solutions to the problems that arise are always simple, and I know that I can handle them. +* `+` Moreover, the software is always up-to-date, which makes exploring new use-cases so much easier! +* `+` Finally, it is trivial to package new software, as I did with https://yalis.fr/git/yves/pyruse/[Pyruse]; this allows me to avoid `./configure && make install` steps in my Ansible playbook. + +Debian:: +* `+` Updates usually just happen, almost unnoticed. +* `−` But when there _is_ a failure, I have to delve into Debian’s idiosyncrasies, and this is not always easy… +* `+` Security updates are done in a serious way, which compensates for the age of the packets. +* `−` But as time passes, some software becomes complicated, or even impossible in some cases, to test. + +Everything is in the personal balance that suits you. +I am perfectly comfortable with the command line, and Archlinux is better suited to my goals. +But one should not be dogmatic: other Linux distributions may be better suited to other situations. +_____ + +== What can the server do? + +Here is what is currently available (I will not repeat “automated” every time, since everything is done with Ansible): + +* a container acting as a https://en.wikipedia.org/wiki/DMZ_(computing)[DMZ], which is the only part of the server, that the Internet can reach; +* firewalls (one for the DMZ, the other for the backend server); +* as much https://freedesktop.org/wiki/Software/systemd/[systemd] as possible (almost all logs, the DMZ, network, ntp, dns…); +* systemd journals’ scrutation with automatic reporting of urgent situations, and a daily report; +* `/etc` changes followed in Git, with a separation between the upstream state (branch `master`) and the everyday state (branch `run`); +* certificate renewal using the https://letsencrypt.org/how-it-works/[ACME protocol], and certificate deployment to the locations where the software needs it; +* software upgrades; +* https://en.wikipedia.org/wiki/Dynamic_DNS[dynamic DNS] handling (remember, this is a _home_ server, where a fixed IPv4 address is not a given); +* centralized handling of users in https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol[LDAP]; +* a web portal and https://en.wikipedia.org/wiki/Single_sign-on[SSO], to reach the different web services for registered users; +* a web https://en.wikipedia.org/wiki/User_interface[UI] for handling LDAP entries and mail aliases (also in LDAP); +* mail handling, with SMTP and IMAP; +* a PostgreSQL database; +* an SSH server, hidden with https://en.wikipedia.org/wiki/Port_knocking[port-knocking]; +* a web server configured to allow additional contents for clients who port-knocked properly; +* a https://en.wikipedia.org/wiki/Blog[blog]; +* a web UI for the Git projects hosted on the server; +* a “personal cloud”, for files (https://en.wikipedia.org/wiki/WebDAV[WebDAV]), contacts (https://en.wikipedia.org/wiki/CardDAV[CardDAV]), and calendars (https://en.wikipedia.org/wiki/CalDAV[CalDAV]), all (and more!) freely synchronizable with an https://www.fairphone.com/en/[Android smartphone]; +* automatic mounting of the “personal cloud” files when logging in on the server; +* an https://en.wikipedia.org/wiki/Network_File_System[NFS] server; +* an https://en.wikipedia.org/wiki/XMPP[XMPP] server; +* a print server; +* a scan server; +* a remote-controlled media server (requires audio and video outputs); +* a https://en.wikipedia.org/wiki/Digital_Living_Network_Alliance[DLNA]/uPNP server; +* a private https://en.wikipedia.org/wiki/Pastebin[pastebin]-like service; +* a https://en.wikipedia.org/wiki/BitTorrent[BitTorrent] server; +* a https://github.com/yarrick/iodine[DNS tunnel]; +* a SSH-over-link:https://en.wikipedia.org/wiki/Transport_Layer_Security[TLS] tunnel; +* https://en.wikipedia.org/wiki/Bonjour_(software)[Bonjour]-SD (Service-Discovery); +* a web UI for bookmarks (work in progress…); +* a web XMPP client (work in progress…). + +== Current status + +This configuration has led to a working server, which has been up and running for more than a year. +However: + +* the Ansible rules written here still have rough edges; +* this is a quick (one might say hasty) publishing of the current Ansible rules, and they’re in need of way more documentation… + +This project is also a way for me to _learn_ Ansible, and I’d be happy to know if I misused Ansible somewhere `:-)` + +== Requirements + +A domain name is needed. +It is expected that DNS entries are handled by an external service, because the home-server does not do that itself. +For example, https://dns.he.net/[Hurricane Electric] can be used. + +For testing purposes, a free temporary domain may be used, for example at https://freedns.afraid.org/[Free DNS]. + +At home, the server needs to be connected to a router that has these properties: + +* allows machines on the LAN to have a fixed IP: the server, and also all terminals (PC, Android…) that shall be trusted; +* has a “DMZ mode” (ie. route all incoming Internet traffic — with possible exceptions — to a chosen IP on the LAN), or at least port-by-port NAT; +* is a gateway to the Internet for LAN machines; +* allows all incoming and outgoing traffic (most notably SMTP, which tends to be blocked by default); + +The server itself should have at least 2GB of RAM, and at least 2 CPU cores (for better multitasking). +On my https://www.udoo.org/udoo-x86/[Basic Udoo X86] (2GB RAM and 4× https://ark.intel.com/fr/products/92124/Intel-Atom-x5-E8000-Processor-2M-Cache-up-to-2_00-GHz[x5-E8000]@1.04GHz CPU), with all of the above services running, I get good performance, 60% RAM used, and an average system load of 8%, which is rather good! + +Last but not least, the machine that will run the Ansible playbook should have a version of Ansible greater than 2.2: + +* module `include_role` runs dynamically (available since version 2.4); +* modules `ini_file`, `lineinfile`, `mount`, and `replace` use the `path` parameter (available since version 2.3); +* modules `ldap_attr` and `ldap_entry` are used (available since version 2.3); +* module `lineinfile` uses the `firstmatch` parameter (available since version 2.5); +* module `user` uses the `create_home` parameter (available since version 2.5). + +Also, this machine must have a static IP address on the LAN, because only this computer will be allowed to run Ansible commands on the server, using the dedicated SSH key. + +== Usage + +First, the server must be link:bootstrap.adoc[prepared, so that Ansible can connect and run the rules]. + +Then the rules are run by launching this command at the root of the project: + +```bash +$ ansible-playbook -i production site.yml +``` + +[literal.small] +..... +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. +..... diff --git a/bootstrap.adoc b/bootstrap.adoc new file mode 100644 index 0000000..70555d6 --- /dev/null +++ b/bootstrap.adoc @@ -0,0 +1,543 @@ +///// +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. +///// + +:keymap: fr-bepo +:front-name: dmz +:front-ip: 192.168.1.254 +:back-name: home +:back-ip: 192.168.1.253 +:pc-ip: 192.168.1.252 +:net-bits: 24 +:net-gateway: 192.168.1.1 +:your-uid: me +:sys-disk: /dev/mmcblk0 +:sys-esp: /dev/mmcblk0p1 +:sys-pv: /dev/mmcblk0p2 +:sys-vg: Sys +:data-vg: Data +:appdata-lv: AppData +:userdata-lv: UserData +:bt-storage-name: p2p +:bt-storage-todo: iso.torrent +:bt-storage-doing: .iso.wip +:bt-storage-done: iso +:prosody-db: prosody +:prosody-db-user: prosody +:nextcloud-db: nextcloud +:nextcloud-db-user: nextcloud +:nextcloud-root: /usr/share/webapps/nextcloud +:nextcloud-user: cloud + += Bootstrap of the home-server +:toc: + +TIP: Modifiy this document’s header variables and it will then reflect your own preferences. +https://addons.mozilla.org/fr/firefox/addon/asciidoctorjs-live-preview/[View the result in Firefox]. + +== Purpose + +The server is entirely configured by https://docs.ansible.com/[Ansible]. +Thus, what this document is about should be entirely done with Ansible. +However, Ansible can only reach and control the server if the server has some basic software installed (namely, SSH and Python), and has its network interface correctly configured. +This is a chicken-and-egg problem, which is solved by manually bootstraping the server. + +== Archlinux standard installation + +Once the Archlinux installation media (USB in my case) is inserted and booted (in EFI mode), the https://wiki.archlinux.org/index.php/Installation_guide[official documentation] basically comes down to this (to be adapted for your actual preferences): + +Basic configuration and partioning:: +* `{sys-disk}` is the small integrated storage area, where the system gets installed. +* The “{data-vg}” LVM-VG is a (set of) storage device(s) (SATA, eSATA, or USB3) with lots of extra space (for example on `/dev/sdb`). +* Each application that manages state data gets its own mount points inside a BTRFS “{appdata-lv}” volume. +* User data is stored in a BTRFS “{userdata-lv}” volume. ++ +[subs="+attributes"] +```bash +root@archiso ~ # export LVM=/dev/mapper +root@archiso ~ # export DMZ=/mnt/var/lib/machines/{front-name} +root@archiso ~ # export APPDATA=/mnt/mnt/AppData +root@archiso ~ # export USERDATA=/mnt/mnt/UserData + +root@archiso ~ # loadkeys {keymap} +root@archiso ~ # ping -c 1 archlinux.org +… +1 packets transmitted, 1 received, 0% packet loss, time 0ms +… +root@archiso ~ # timedatectl set-ntp true + +root@archiso ~ # fdisk {sys-disk} +… +Command (m for help): g +Created a new GPT disklabel… + +Command (m for help): n +Partition number (1-128, default 1): +First sector (…): +Last sector, +sectors or +size{K,M,G,T,P} (…): +128M + +Created a new partition 1… + +Command (m for help): t +Selected partition 1 +Hex code (type L to list all codes): 1 +Changed type of partition 'Linux filesystem' to 'EFI System'. + +Command (m for help): n +Partition number (2-128, default 2): +First sector (…): +Last sector, +sectors or +size{K,M,G,T,P} (…): + +Created a new partition 2… + +Command (m for help): t +Partition number (1,2, default 2): +Hex code (type L to list all codes): 31 + +Changed type of partition 'Linux filesystem' to 'Linux LVM'. + +Command (m for help): w +The partition table has been altered. +Calling ioctl() to re-read partition table. +Syncing disks. + +root@archiso ~ # mkfs.vfat -n ESP {sys-esp} +… +root@archiso ~ # pvcreate {sys-pv} +… +root@archiso ~ # vgcreate {sys-vg} {sys-pv} +… +root@archiso ~ # lvcreate -L 5G -n Root {sys-vg} +… +root@archiso ~ # lvcreate -L 2G -n Cont {sys-vg} +… +root@archiso ~ # mkfs.ext4 $LVM/{sys-vg}-Root +… +root@archiso ~ # mkfs.btrfs --mixed --label Cont $LVM/{sys-vg}-Cont +… +root@archiso ~ # lvcreate -L 10G -n RootVar {data-vg} +… +root@archiso ~ # mkfs.ext4 $LVM/{data-vg}-RootVar +… +root@archiso ~ # lvcreate -L 1G -n ContVar {data-vg} +… +root@archiso ~ # mkfs.ext4 $LVM/{data-vg}-ContVar +… +root@archiso ~ # lvcreate -L 100G -n {appdata-lv} {data-vg} +… +root@archiso ~ # mkfs.btrfs --mixed --label {appdata-lv} $LVM/{data-vg}-{appdata-lv} +… +root@archiso ~ # lvcreate -L 700G -n {userdata-lv} {data-vg} +… +root@archiso ~ # mkfs.btrfs --mixed --label {userdata-lv} $LVM/{data-vg}-{userdata-lv} +… +root@archiso ~ # lvcreate -L 1G -n Home {data-vg} +… +root@archiso ~ # mkfs.ext4 $LVM/{data-vg}-Home +… +``` + +Host and guest mounting:: +* The hardware host holds the sensitive data, and is not reachable from the Internet. +* the guest container is the DMZ and holds directly accessible Internet services. ++ +[subs="+attributes"] +```bash +root@archiso ~ # mount $LVM/{sys-vg}-Root /mnt +root@archiso ~ # mkdir -p /mnt/{boot,home,var} $APPDATA $USERDATA +root@archiso ~ # mount LABEL=ESP /mnt/boot +root@archiso ~ # mount $LVM/{data-vg}-Home /mnt/home +root@archiso ~ # mount $LVM/{data-vg}-RootVar /mnt/var +root@archiso ~ # mount $LVM/{data-vg}-{appdata-lv} $APPDATA +root@archiso ~ # mkdir -p /mnt/var/cache/{minidlna,pacman/pkg} +root@archiso ~ # mkdir -p \ +> /mnt/var/lib/{acme,dovecot,gitea,kodi,machines,nextcloud,openldap,postgres} +root@archiso ~ # mkdir -p /mnt/var/spool/mail + +root@archiso ~ # btrfs subvolume create $APPDATA/acme.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/acme.srv +… +root@archiso ~ # btrfs subvolume create $APPDATA/ddclient.cache +… +root@archiso ~ # btrfs subvolume create $APPDATA/dovecot.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/gitea.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/kodi.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/mail.spool +… +root@archiso ~ # btrfs subvolume create $APPDATA/minidlna.cache +… +root@archiso ~ # btrfs subvolume create $APPDATA/movim.cache +… +root@archiso ~ # btrfs subvolume create $APPDATA/movim.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/nextcloud.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/nginx.log +… +root@archiso ~ # btrfs subvolume create $APPDATA/openldap.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/pacman_pkg.cache +… +root@archiso ~ # btrfs subvolume create $APPDATA/postgres.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/prosody.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/transmission.lib +… +root@archiso ~ # btrfs subvolume create $APPDATA/webapps.srv +… + +root@archiso ~ # mount \ +> -o subvol=acme.lib,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/acme +root@archiso ~ # mount \ +> -o subvol=dovecot.lib,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/dovecot +root@archiso ~ # mount \ +> -o subvol=gitea.lib,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/gitea +root@archiso ~ # mount \ +> -o subvol=kodi.lib,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/kodi +root@archiso ~ # mount \ +> -o subvol=mail.spool,compress=lzo,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/spool/mail +root@archiso ~ # mount \ +> -o subvol=minidlna.cache,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/cache/minidlna +root@archiso ~ # mount \ +> -o subvol=nextcloud.lib,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/nextcloud +root@archiso ~ # mount \ +> -o subvol=openldap.lib,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/openldap +root@archiso ~ # mount \ +> -o subvol=pacman_pkg.cache,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/cache/pacman/pkg +root@archiso ~ # mount \ +> -o subvol=postgres.lib,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} /mnt/var/lib/postgres + +root@archiso ~ # mount $LVM/{sys-vg}-Cont /mnt/var/lib/machines +root@archiso ~ # btrfs subvolume create $DMZ +… +root@archiso ~ # mkdir -p $DMZ/var +root@archiso ~ # mount $LVM/{data-vg}-ContVar $DMZ/var +root@archiso ~ # mkdir -p $DMZ/srv/{acme,webapps} +root@archiso ~ # mkdir -p $DMZ/var/cache/{ddclient,movim} +root@archiso ~ # mkdir -p $DMZ/var/lib/{prosody,transmission} +root@archiso ~ # mkdir -p $DMZ/var/log/nginx + +root@archiso ~ # mount \ +> -o subvol=acme.srv,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/srv/acme +root@archiso ~ # mount \ +> -o subvol=ddclient.cache,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/var/cache/ddclient +root@archiso ~ # mount \ +> -o subvol=movim.cache \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/var/cache/movim +root@archiso ~ # mount \ +> -o subvol=movim.lib,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/var/lib/movim +root@archiso ~ # mount \ +> -o subvol=nginx.log,compress=lzo,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/var/log/nginx +root@archiso ~ # mount \ +> -o subvol=prosody.lib,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/var/lib/prosody +root@archiso ~ # mount \ +> -o subvol=transmission.lib,nodatacow \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/var/lib/transmission +root@archiso ~ # mount \ +> -o subvol=webapps.srv,compress=lzo \ +> $LVM/{data-vg}-{appdata-lv} $DMZ/srv/webapps + +root@archiso ~ # mkdir $DMZ/var/lib/transmission/{Todo,Doing,Done} +root@archiso ~ # mount -o nodatacow $LVM/{data-vg}-{userdata-lv} $USERDATA +root@archiso ~ # mkdir -p $USERDATA/{bt-storage-name} +root@archiso ~ # for d in {bt-storage-todo} {bt-storage-doing} {bt-storage-done}; do +> btrfs subvolume create $USERDATA/{bt-storage-name}/$d +> done +… + +root@archiso ~ # mount \ +> -o subvol={bt-storage-name}/{bt-storage-todo},nodatacow \ +> $LVM/{data-vg}-{userdata-lv} $DMZ/var/lib/transmission/Todo +root@archiso ~ # mount \ +> -o subvol={bt-storage-name}/{bt-storage-doing},nodatacow \ +> $LVM/{data-vg}-{userdata-lv} $DMZ/var/lib/transmission/Doing +root@archiso ~ # mount \ +> -o subvol={bt-storage-name}/{bt-storage-done},nodatacow \ +> $LVM/{data-vg}-{userdata-lv} $DMZ/var/lib/transmission/Done +``` + +Archlinux installation:: +* When this is done, be sure to check that `/mnt/etc/fstab` perfectly matches the wanted result (the above mount points). ++ +```bash +root@archiso ~ # pacstrap /mnt base arch-install-scripts intel-ucode \ +> openssh python2 etckeeper git lvm2 btrfs-progs rsync +… +root@archiso ~ # genfstab -L /mnt >>/mnt/etc/fstab +``` + +Archlinux initial configuration:: +* The basic files for the host must roughly match the final configuration, enough to let Ansible control the right host on the right IP without error. +* The values used here *must* match those in link:group_vars/all[]. ++ +[subs="+attributes"] +```bash +root@archiso ~ # arch-chroot /mnt +[root@archiso /]# echo {back-name} >/etc/hostname +[root@archiso /]# cat >/etc/systemd/network/bridge.netdev <<-"THEEND" +> [NetDev] +> Name=wire +> Kind=bridge +> THEEND +[root@archiso /]# cat >/etc/systemd/network/bridge.network <<-"THEEND" +> [Match] +> Name=wire +> +> [Network] +> IPForward=yes +> Address={back-ip}/{net-bits} +> Gateway={net-gateway} +> THEEND +[root@archiso /]# cat >/etc/systemd/network/wired.network <<-"THEEND" +> [Match] +> Name=en* +> +> [Network] +> Bridge=wire +> THEEND +[root@archiso /]# systemctl enable systemd-networkd.service +… +[root@archiso /]# sed -i '/prohibit-password/s/.*/PermitRootLogin yes/' \ +> /etc/ssh/sshd_config +[root@archiso /]# mkdir ~root/.ssh +[root@archiso /]# chmod 700 ~root/.ssh +[root@archiso /]# scp {your-uid}@{pc-ip}:.ssh/id_ansible.pub \ +> ~root/.ssh/authorized_keys +… +[root@archiso /]# chmod 600 ~root/.ssh/authorized_keys +[root@archiso /]# systemctl enable sshd.service +… +[root@archiso /]# sed -i '/^HOOKS=/s/block filesystems/block lvm2 filesystems/' \ +> /etc/mkinitcpio.conf +[root@archiso /]# mkinitcpio -p linux +… +[root@archiso /]# passwd +… +passwd: password updated successfully +[root@archiso /]# bootctl --path=/boot install +… +[root@archiso /]# cat >/boot/loader/entries/arch.conf <<-THEEND +> title Arch Linux +> linux /vmlinuz-linux +> initrd /intel-ucode.img +> initrd /initramfs-linux.img +> options root=$LVM/{sys-vg}-Root rw +> THEEND +[root@archiso /]# cat >/boot/loader/loader.conf <<-"THEEND" +> default arch +> editor 0 +> THEEND +[root@archiso /]# printf '%s, %s\n' \ +> 'ACTION=="add", SUBSYSTEM=="usb"' \ +> 'TEST=="power/control", ATTR{power/control}="off"' \ +> >/etc/udev/rules.d/50-usb_power_save.rules +[root@archiso /]# exit +root@archiso ~ # systemctl reboot +``` + +This last command about USB and power control disables power saving for USB. +This line is only interesting if the main data drive is connected with USB. + +[IMPORTANT] +=========== +In theory, at this stage, the machine is ready to be controlled by Ansible. +However, Ansible fails at first, because for some reason, `pacstrap` in the “front” Ansible role fails to initialize the DMZ if the location already contains mount points, so: + +. I had to temporarily unmount everything under `/var/lib/machines/{front-name}`, and delete the `/var/lib/machines/{front-name}/usr` sub-diretory. +. I also temporarily commented out the whole front-half of `site.xml`, as well as the “front-run” role of the back part. +. Then I ran Ansible again. +. When the DMZ was correctly initialized, I renamed `/var/lib/machines/{front-name}/var` to `/var/lib/machines/{front-name}/var.new`. +. Then I created a new `/var/lib/machines/{front-name}/var`, inside of which I mounted all the above DMZ-specific mount points again. +. In the `/var/lib/machines/{front-name}/` directory, I ran `rsync -av var.new/ var/`. +. After that, I could remove the `/var.new` directory (see below), restore `site.yml` to its original state, and start Ansible once again. + +When I wanted to delete the DMZ’s `var.new` directory as root, I was denied the permission! +This is because `pacstrap` created the DMZ’s own `var/lib/machines` as a btrfs subvolume, which can only be deleted with the `btrfs subvolume delete var.new/lib/machines` command (`var.new` because of the renaming above). +Then removing `var.new` worked. +=========== + +== Post-installation tasks + +You may want to restore some data from a former installation. +This section contains some examples of data restoration. + +NOTE: Most values and paths here are examples, and shall be adapted. + +=== Dotclear + +[subs="+attributes"] +```bash +[root@{back-name} ~]# systemctl -M {front-name} stop haproxy.service +[root@{back-name} ~]# systemctl -M {front-name} stop nginx.service +[root@{back-name} ~]# systemctl -M {front-name} stop php-fpm.service +[root@{back-name} ~]# sudo -u postgres pg_restore -c -C -F c -d postgres \ +> '12.*'/'version' => '10.0.2.1'/" \ +> /etc/webapps/nextcloud/config/config.php +[root@{back-name} ~]# cd {nextcloud-root} +[root@{back-name} nextcloud]# sudo -u {nextcloud-user} \ +> /usr/bin/env NEXTCLOUD_CONFIG_DIR=/etc/webapps/nextcloud/config \ +> /usr/bin/php occ upgrade +… +[root@{back-name} nextcloud]# cd /etc +[root@{back-name} etc]# git reset --hard +… +[root@{back-name} etc]# etckeeper init +``` + +Migrate users to LDAP (they keep the same name):: +* connect to the database: ++ +[subs="+attributes"] +```bash +[root@{back-name} etc]# su - postgres +[postgres@{back-name} ~]$ psql +postgres=# ALTER DATABASE {nextcloud-db} OWNER TO {nextcloud-db-user}; +ALTER DATABASE +postgres=# \c {nextcloud-db} +… +{nextcloud-db}=# +``` +* browse a table (eg. `addressbooks`) to note the number associated to each user (eg. “`{your-uid}`” associated to number “`6266`”); +* migrate user `{your-uid}` (repeat for each user): the idea is to delete most data, considering that it is sync’ed somewhere and it can be restored by resynchronizing: ++ +[subs="+attributes"] +```sql +{nextcloud-db}=# delete from oc_accounts where uid='{your-uid}'; +DELETE 1 +{nextcloud-db}=# delete from oc_addressbooks where principaluri='principals/users/{your-uid}_6266'; +DELETE 1 +{nextcloud-db}=# delete from oc_calendars where principaluri='principals/users/{your-uid}_6266'; +DELETE 1 +{nextcloud-db}=# delete from oc_credentials; +DELETE 0 +{nextcloud-db}=# delete from oc_filecache where name='{your-uid}_6266'; +DELETE 1 +{nextcloud-db}=# delete from oc_jobs where argument='{"uid":"{your-uid}_6266"}'; +DELETE 1 +{nextcloud-db}=# delete from oc_mounts where user_id like '%{your-uid}_6266%'; +DELETE 1 +{nextcloud-db}=# delete from oc_preferences where userid='{your-uid}_6266'; +DELETE 10 +{nextcloud-db}=# delete from oc_storages where id='home::{your-uid}_6266'; +DELETE 1 +{nextcloud-db}=# delete from oc_users where uid='{your-uid}'; +DELETE 1 +{nextcloud-db}=# update oc_ldap_user_mapping set owncloud_name='{your-uid}' where owncloud_name='{your-uid}_6266'; +UPDATE 1 +{nextcloud-db}=# commit; +… +{nextcloud-db}=# \q +``` + +Restart Nextcloud:: ++ +[subs="+attributes"] +```bash +[root@{back-name} ~]# systemctl start uwsgi@nextcloud.socket +[root@{back-name} ~]# systemctl start nextcloud-maintenance.timer +[root@{back-name} ~]# systemctl -M {front-name} start nginx.service +[root@{back-name} ~]# systemctl -M {front-name} start haproxy.service +``` + +=== Restore emails + +I was formerly using BincIMAP, and then Courier-IMAP, and I also ran Dovecot once, on a backup server, when my main server’s power supply burnt. +As a consequence, the Maildirs were polluted with dot-files from various origins. +I decided to do a clean import, especially since I configured Dovecot in a way that makes it more performant, with the constraint that it must have exclusive access to the mail storage. + +[subs="+attributes"] +```bash +[root@{back-name} ~]# find /backup/user-Maildirs -depth \ +> \( -iname '*binc*' -o -iname '*courier*' -o -iname '*dovecot*' \) \ +> -exec rm -rf {} \; +[root@{back-name} ~]# for u in $(ls /backup/user-Maildirs); do +> chown -R $u /backup/user-Maildirs/$u +> doveadm import -s -u $u maildir:/backup/user-Maildirs/$u/Maildir/ '' ALL +> done +``` + +[literal.small] +..... +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. +..... diff --git a/group_vars/all b/group_vars/all new file mode 100644 index 0000000..28a525e --- /dev/null +++ b/group_vars/all @@ -0,0 +1,501 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# Short personal nickname that will be mostly used as part of filenames under /etc. +nickname: personal + +# Hostname and IPv4 address of the DMZ. +DMZ: dmz +DMZ_IP: 192.168.1.254 + +# Hostname and IPv4 address of the back-end server (with all the data). +SafeZone: home +SafeZone_IP: 192.168.1.253 + +# Domain names that the certificate should cover. +acme_domains: 'example.org www.example.org pubsub.example.org' + +# Public key that Ansible will use to manage the server, and IP address of the controller PC. +# The public key (`….pub` file) is generated as the result of running `ssh-keygen -t ed25519`. +ansible_authorized_key: 'ssh-ed25519 AAAA0000bbbb1111CCCC2222dddd3333EEEE4444ffff5555GGGG6666hhhh7777IIII me@my-pc' +ansible_master: 192.168.1.252 + +# System user that will build packages from AUR (https://aur.archlinux.org/). +aur_user: git + +# Just leave this with an empty-string value. +chroot: '' + +# https://wiki.archlinux.org/index.php/Keyboard_configuration_in_console +default_keymap: en + +# https://jlk.fjfi.cvut.cz/arch/manpages/man/papersize.5 +default_papersize: a4 + +# LDAP (real) user that will have admin rights in Dotclear (the blog). +dotclear_admin_user: me + +# Name of the Dotclear database in PostgreSQL. +dotclear_db: dotclear + +# PostgreSQL user who owns the Dotclear database. +dotclear_db_user: dotclear + +# Password for the PostgreSQL user who owns the Dotclear database. +dotclear_db_password: dotclear + +# Dotclear encrypts sensitive data with a master key, that is set here (random string). +dotclear_master_key: 0123456789abcdefghijklmnopqrstuvwxyz + +# Location where Dotclear is installed, which *must* end with “/dotclear” +dotclear_root: /srv/webapps/dotclear + +# The default locale (https://wiki.archlinux.org/index.php/Locale). +locales_default: 'en_US.UTF-8' + +# All installed locales on the server. +locales_enabled: 'en_US.UTF-8 fr_FR.UTF-8 fr_FR@euro' + +# Enable DNSSEC in systemd-resolved (“yes” or “no”, as a character string); experimental! +dns_sec: 'no' + +# DNS servers to use on the server, for example: +# FDN-1 (v4) FDN-2 (v4) FDN-1 (v6) FDN-2 (v6) OpenNIC-1 OpenNIC-2 Google +dns_hosts: '80.67.169.12 80.67.169.40 2001:910:800::12 2001:910:800::40 87.98.175.85 5.135.183.146 8.8.8.8' + +# Nearest NTP servers (https://www.ntppool.org/). +ntp_hosts: '0.uk.pool.ntp.org 1.uk.pool.ntp.org 2.uk.pool.ntp.org 3.uk.pool.ntp.org' + +# IP addresses that are allowed to browse DLNA/uPNP contents, even though they are not trusted. +# This is a space-separated list of networks (IP/bits). +# A typical example would be a living-room BD player or TV, which includes a DLNA client. +fw_dlna_clients: 192.168.1.53/32 + +# Number of minutes allowed between two consecutive ports of the port-knocking sequence. +fw_knock_timeout_min: 2 + +# Port-knocking sequence. A port may appear multiple times, but not next to each-other. +fw_portknock_seq: 1 22 333 4444 333 22 1 + +# The email address associated to root, for commits in the git repository that stores changes to /etc. +git_contact_email: hostmaster@example.org + +# Name of the Gitea (web UI for Git) database in PostgreSQL. +gitea_db: gitea + +# PostgreSQL user who owns the Gitea database. +gitea_db_user: gitea + +# Password for the PostgreSQL user who owns the Gitea database. +gitea_db_password: gitea + +# Disable creation of organisations in Gitea (“true” or “false”, as a character string). +gitea_disable_org_creation: 'true' + +# Disable self-registration in Gitea (“true” or “false”, as a character string). +gitea_disable_registration: 'false' + +# Enable email notifications in Gitea (“true” or “false”, as a character string). +gitea_enable_notify_email: 'true' + +# Maximum size of HTTP and PHP uploads. +http_max_upload: 10000M + +# Document-root of the HTTP server. +http_root: /srv/http + +# URL prefix of Dotclear (blog). +http_pfx_dotclear: /blog + +# URL prefix of Gitea (web UI for Git). +http_pfx_gitea: /git + +# URL prefix of LDAP-Account-Manager (web UI for LDAP). +http_pfx_lam: /account + +# URL prefix of Movim (XMPP web client). +http_pfx_movim: /social + +# URL prefix of Nextcloud (self-hosted “cloud”). +http_pfx_nextcloud: /cloud + +# URL prefix of PrivateBin (self-hosted “pastebin”). +http_pfx_privatebin: /paste + +# URL prefix of Prosody-generated URL (file uploads, BOSH, websockets…). +http_pfx_prosody: /xmpp- + +# URL prefix of SSOwat (SSO and web portal). +http_pfx_ssowat: /start + +# URL prefix of Transmission (web UI for BitTorrent). +http_pfx_transmission: /torrent + +# URL prefix of Wallabag (social sharing of bookmarks). +http_pfx_wallabag: /bookmarks + +# Subdomain-name that will serve DNS packets for Iodine (DNS tunnel). Choose it short! +iodine_domain: dt.example.org + +# Network associated with the DNS tunnel (IP address of the server on this network, “/”, bits for the network-mask). +iodine_net: '172.16.12.1/28' + +# Password of the DNS tunnel. +iodine_password: '_t_r___e@6358' + +# Location of Kodi state data (not the media contents). +kodi_data: /var/lib/kodi + +# System user that will run Kodi. +kodi_user: kodi + +# Master password, needed to change LDAP-Account-Manager settings. +lam_master_password: lam + +# Password policy for LDAP-Account-Manager (https://www.ldap-account-manager.org/static/doc/manual-onePage/#idm695). +# “-1” means “all”. +lam_checkedRulesCount: -1 +lam_passwordMinClasses: 3 +lam_passwordMinLength: 10 +lam_passwordMinLower: 0 +lam_passwordMinNumeric: 0 +lam_passwordMinSymbol: 1 +lam_passwordMinUpper: 0 +lam_passwordMustNotContain3Chars: 'true' +lam_passwordMustNotContainUser: 'true' + +# Title for LDAP-Account-Manager in the SSOwat portal. +lam_sso_title: Directory + +# Additional ACL for LDAP. +# This is typically used to give extra powers to users, for example regarding aliases management. +ldap_extra_acl: | + access to dn.subtree="ou=Aliases,dc=example,dc=org" + by dn.base="uid=me,ou=Users,dc=example,dc=org" write + by self read + by * read + +# Organization-name for this home-server LDAP directory. +ldap_o_name: 'Home' + +# Root of the LDAP directory. Usually the domain-name with commas instead of dots, and “dc=” in front of each level. +ldap_root: dc=example,dc=org + +# Password of the root user (administrator) in OpenLDAP. +ldap_rootpw: 'OE104995à6&o_zKR4' + +# Same password, as expected by OpenLDAP. +# See https://gist.github.com/rca/7217540 (python2) or https://www.openldap.org/faq/data/cache/347.html. +ldap_rootpw_sha: '{SSHA}Raa3TlvDPZTjdM44nKZQt+hDvQRvaMDC' + +# Custom system groups and memberships, declared in LDAP. +# This is the right place to declare a group in which to put all real and system users, who will be allowed to read media contents. +ldap_system_groups: '[ + {"cn": "registered", "gidNumber": 1200} + {"cn": "media", "gidNumber": 1201} + ]' +ldap_system_group_members: '[ + {"group": "media", "member": "me"}, + {"group": "media", "member": "cloud"}, + {"group": "media", "member": "kodi"} + ]' + +# Real users (ie. with a Linux account on the server) to declare in LDAP. +# Each user in the JSON list contains: +# — uidNumber: a unique user ID, which must be ≥1000; +# — gidNumber: a group ID, which should be a “gidNumber” of ldap_system_groups; +# — uid: the login name, usually short, without spaces, and all lowercase; +# — cn: the user’s firstname; +# — sn: the user’s surname; +# — password: the user’s password upon creation, in the same format as ldap_rootpw_sha (“change_me” in the example). +# These settings are only read when creating the users in LDAP. +ldap_system_users: '[ + {"uidNumber": 1000, "gidNumber": 1200, "uid": "you", "cn": "Yule-Offa", "sn": "Udel", "password": "{SSHA}393aKNBzihkeHWXalkw/vpdy3dYHoh5L"}, + {"uidNumber": 1001, "gidNumber": 1200, "uid": "me", "cn": "Mae", "sn": "Ellen", "password": "{SSHA}393aKNBzihkeHWXalkw/vpdy3dYHoh5L"} + ]' + +# Guest users (they can use the provided software, but do not have a Linux account). +# The fields are the same as above, minus the Linux UID and GID numbers. +# These settings are only read when creating the users in LDAP. +ldap_virtual_users: '[ + {"uid": "she", "cn": "Her", "sn": "…", "password": "{SSHA}393aKNBzihkeHWXalkw/vpdy3dYHoh5L"}, + {"uid": "he", "cn": "Him", "sn": "…", "password": "{SSHA}393aKNBzihkeHWXalkw/vpdy3dYHoh5L"} + ]' + +# Linux UID and GID to use for users who do not have their own. +# 65534 = nobody +ldap_virtual_user_uid: 65534 +ldap_virtual_user_gid: 65534 + +# LDAP attributes to assign to users, either Linux users or guests. +# Each entry in the list contains: +# — uid: the login name of the user to modify; +# — attr: the LDAP attribute to set; +# — value: the value to store in the chosen attribute. +# These settings are enforced at each run. Examples: +# — gecos: the full name that typically appears on the login screen; +# — http://directory.fedoraproject.org/docs/389ds/design/shadow-account-support.html. +ldap_users_attrs: '[ + {"uid": "you", "attr": "gecos", "value": "Y-O. Udel"}, + {"uid": "you", "attr": "shadowLastChange", "value": "16000"}, + {"uid": "you", "attr": "shadowMax", "value": "99999"}, + {"uid": "you", "attr": "shadowWarning", "value": "7"}, + {"uid": "me", "attr": "gecos", "value": "M. Ellen"}, + {"uid": "me", "attr": "shadowLastChange", "value": "16000"}, + {"uid": "me", "attr": "shadowMax", "value": "99999"}, + {"uid": "me", "attr": "shadowWarning", "value": "7"} + ]' + +# Login name and password of the LibreOffice OnLine web services’ administrator. +# Usefulness not clear; it doesn’t hurt to use the same values as in “nextcloud_admin_user” and “nextcloud_admin_password”… +loolwsd_admin_user: nextcloud_admin +loolwsd_admin_password: nextcloud_admin + +# LibreOffice OnLine’s description: “The maximum percentage of system memory consumed +# by all of the LibreOffice Online, after which we start cleaning up idle documents”. +loolwsd_maxmem_asdouble: '80.0' + +# Non-system mail aliases (stored in LDAP, in contrast to system aliases, which are stored in /etc/mail/aliases). +# Each entry in the list contains: +# — alias: a unique mail alias, either new or with existing associated recipients; +# — member: the login name of the user to add as a recipient for the alias. +mail_alias_memberships: '[ + {"alias": "shop", "member": "you"}, + {"alias": "throwable", "member": "me"}, + {"alias": "family", "member": "me"}, + {"alias": "family", "member": "you"} + ]' + +# DKIM selector to use (see http://yalis.fr/cms/index.php/post/2014/01/31/Why-buy-a-domain-name-Secure-mail%2E). +# See the “dmz_exim” role for the storage of the private and public keys. +mail_dkim_selector: home + +# Actual Linux user, that receives all system emails (for root, postmaster, hostmaster…). +mail_forward_root_to: me + +# IPv6 address of the ISP’s smarthost when the ISP does not handle SMTP on IPv6 (example: smtp.bbox.fr). +mail_ignore_ip: '2001:860:e2ef::f503:0:2' + +# All local mail destinations, which include managed domains, as well as host names. +mail_local_domains: 'home dmz localhost example.org *.example.org *.local' + +# The ISP’s smarthost (which listens on port 25). +mail_smtp_smarthost: smtp.bbox.fr + +# The group name for media contents (see also “ldap_system_groups”). +media_group: media + +# Custom Minidlna configuration, including the locations where it will look for media contents. +# None of the “media_dir” paths is currently allowed under /opt. +# Apart from “media_dir”, the settings already set upstream must not be overriden. +# See also “nfs_exports”, and https://sourceforge.net/p/minidlna/git/ci/master/tree/minidlna.conf (upstream). +media_minidlna_conf: | + media_dir=V,/srv/nfs/share/video + media_dir=A,/srv/nfs/share/my_CDs + media_dir=A,/srv/nfs/share/my_MP3 + media_dir=P,/srv/nfs/share/photos + root_container=B + friendly_name=HomeMedia + +# Name of the Movim database in PostgreSQL. +movim_db: movim + +# PostgreSQL user who owns the Movim database. +movim_db_user: movim + +# Password for the PostgreSQL user who owns the Movim database. +movim_db_password: movim + +# Administrator for Movim. +movim_admin_user: movim_admin + +# Password of the administrator for Movim. +movim_admin_password: movim_admin + +# Localhost port on which Movim is listening +movim_private_port: 33333 + +# Domain names to which network access from the DMZ is allowed. +# This space-separated list should contain: +# — the web address for checking the current public IP given by the ISP; +# — the web address for updating the dynamic DNS; +# — the web address for updating web applications… +net_allowed_domains: 'checkip.dns.he.net dyn.dns.he.net freedns.afraid.org download.dotclear.org dotaddict.org api.movim.eu' + +# Start Of Authority: the root domain name configured on the server. +net_soa: example.org + +# Subdomain for the XMPP multi-user chat component. +net_subdom_muc: muc + +# Subdomain for the XMPP pub-sub component. +net_subdom_pubsub: pubsub + +# Subdomain for which TLS traffic (port 443) is analysed as SSH instead of HTTP. +net_subdom_ssh: ssh + +# Local networks from which network connections are trusted. +# OpenSSH requires that the IP in front of the “/” character is the first IP of the range! +net_trusted_ranges: '192.168.1.248/28 127.0.0.0/8 ::1' + +# Administrator for Nextcloud (not necessarily an LDAP user). +nextcloud_admin_user: nextcloud_admin + +# Password of the administrator for Nextcloud. +nextcloud_admin_password: nextcloud_admin + +# Path to Nextcloud’s configuration. +nextcloud_conf: /etc/webapps/nextcloud/config + +# Path to local Nextcloud data (not the users’ files). +nextcloud_data: /var/lib/nextcloud + +# Name of the Nextcloud database in PostgreSQL. +nextcloud_db: nextcloud + +# PostgreSQL user who owns the Nextcloud database. +nextcloud_db_user: nextcloud + +# Password for the PostgreSQL user who owns the Nextcloud database. +nextcloud_db_password: nextcloud + +# Path to Nextcloud distribution data (not the users’ files). +nextcloud_root: /usr/share/webapps/nextcloud + +# System user that will run Nextcloud. +nextcloud_user: cloud + +# Local paths (on the safe side of the server) that shall be exported with NFS. +# Each entry contains: +# — name: the name of the NFS export, under /srv/nfs; +# — path: the exported local path. +nfs_exports: '[ + {"name": "share", "path": "/mnt/share"}, + {"name": "share/video", "path": "/mnt/media/video"}, + {"name": "share/my_CDs", "path": "/mnt/media/my_CDs"}, + {"name": "share/my_MP3", "path": "/mnt/media/my_MP3"}, + {"name": "share/photos", "path": "/mnt/media/photos"} + ]' + +# NFS export options (https://linux.die.net/man/5/exports). +nfs_options: 'rw,no_subtree_check,no_root_squash,no_wdelay,crossmnt' + +# Log level for nginx (http://nginx.org/en/docs/ngx_core_module.html#error_log). +nginx_loglevel: info + +# Administrator password for PostgreSQL. +pgpassword: PostgreSQL + +# Maximum number of bytes in a Privatebin paste (or image). +privatebin_bytes_limit: 10485760 + +# Enable discussions in Privatebin (“true” or “false” as a character string). +privatebin_enable_discussion: 'false' + +# Enable passwords in Privatebin (“true” or “false” as a character string). +privatebin_enable_passwords: 'false' + +# Enable uploads in Privatebin (“true” or “false” as a character string). +privatebin_enable_uploads: 'true' + +# Open discussions by default in Privatebin (“true” or “false” as a character string). +privatebin_open_discussion: 'false' + +# Delay in seconds before an opportunistic purge of old pastes is attempted while processing a request. +privatebin_purge_delay: 300 + +# Title for Privatebin in the SSOwat portal. +privatebin_sso_title: Privatebin + +# Name of the Prosody database in PostgreSQL. +prosody_db: prosody + +# PostgreSQL user who owns the Prosody database. +prosody_db_user: prosody + +# Password for the PostgreSQL user who owns the Prosody database. +prosody_db_password: prosody + +# Space-separated list of SANE drivers to keep enabled, for scanner sharing. +sane_drivers: epson2 + +# Space-separated list of pacman mirrors to use. +software_mirrors: 'archlinux.de-labrusse.fr mirror.archlinux.ikoula.com' + +# Software that will get removed if present, on next run of the playbook (JSON list). +software_to_del: '["dhcpcd"]' + +# Comma-separated list of software that pacman should not automatically upgrade. +software_to_ignore: 'linux,linux-firmware,linux-headers' + +# Environment variables that SSH may keep for remote connections. +ssh_accept_env: 'LANG LC_*' + +# Allow port-forwarding with SSH (“yes” or “no” as a character string). +ssh_allow_tcpforward: 'yes' + +# Allow binding of port-forwardings on the LAN interface with SSH (“yes” or “no” as a character string). +ssh_allow_gatewayports: 'yes' + +# Allow X11 forwarding with SSH (“yes” or “no” as a character string). +ssh_allow_x11forward: 'yes' + +# Allow SSH tunnels (“yes” or “no” as a character string). +ssh_allow_tunnel: 'yes' + +# System user that will accept SSH connections in the DMZ, as a way to get access to the safe zone. +ssh_bastion_user: gatekeeper + +# SHA-512 password of the system user who can remotely SSH to the DMZ (here: “let-me-in”). +# See https://unix.stackexchange.com/a/76337 for some help. +ssh_bastion_pwd_sha512: '$6$ZN4I.yIVUj0amxqe$5dBx1d34tNm9NMmmFV3UxZ0V2ecmOjefK5dbTW5Da/xC8M78sZbPQdegcqA3/9Wtr2fMQ0y6pxVh31Q01PrfS/' + +# Client-alive interval for the SSH daemon, in seconds. +ssh_clientalive_interval: 600 + +# Server’s timezone. +timezone: Europe/Paris + +# TLS ciphers to enable in TLS-terminating software (HAProxy, Nginx…). +# See https://wiki.mozilla.org/Security/Server_Side_TLS. +tls_ciphers: 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS' + +# HAProxy server and bind options to use (https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#5). +tls_options: 'no-sslv3 no-tls-tickets' + +# Transmission (BitTorrent) public/peer port +transmission_bt_port: 60000 + +# Transmission private RPC port (for the Web UI). +transmission_rpc_port: 50000 + +# Path to the directory where Transmission should store the downloads that are finished, on the safe side. +transmission_real_done_at: /mnt/share/p2p/iso + +# Path to the directory where Transmission should read torrent files to process, on the safe side. +transmission_real_todo_at: /mnt/share/p2p/iso.torrent + +# Name given to “transmission_real_done_at” and “transmission_real_todo_at” as NFS exports. +transmission_nfs_done_at: share/p2p/iso +transmission_nfs_todo_at: share/p2p/iso.torrent + +# Name of the Wallabag database in PostgreSQL. +wallabag_db: wallabag + +# PostgreSQL user who owns the Wallabag database. +wallabag_db_user: wallabag + +# Password for the PostgreSQL user who owns the Wallabag database. +wallabag_db_password: wallabag + +# Space-separated list of the XMPP accounts that are considered administrators of the XMPP service. +xmpp_admins: 'me@example.org' + +# Network hosts from which registration is possible (else it is forbidden). +# Registration of hosted users is automatic. +xmpp_registration_hosts: '127.0.0.1 192.168.1.254 192.168.1.253 192.168.1.252' + +# Secret value known to the XMPP upload service (HTTP), so that it is only used by the XMPP network. +xmpp_upload_secret: 'xmpp upload secret' diff --git a/group_vars/back b/group_vars/back new file mode 100644 index 0000000..83ab20c --- /dev/null +++ b/group_vars/back @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +hostname: home +ssh_allowed_users: 'root jane john' +software_to_add: '["intel-ucode"]' diff --git a/group_vars/front b/group_vars/front new file mode 100644 index 0000000..3a267df --- /dev/null +++ b/group_vars/front @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +hostname: dmz +ssh_allowed_users: 'root {{ssh_bastion_user}}' +software_to_add: '[]' diff --git a/group_vars/front_chroot b/group_vars/front_chroot new file mode 100644 index 0000000..3127b19 --- /dev/null +++ b/group_vars/front_chroot @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +hostname: dmz +chroot: "/var/lib/machines/{{DMZ}}" +ssh_allowed_users: 'root {{ssh_bastion_user}}' +software_to_add: '[]' diff --git a/production b/production new file mode 100644 index 0000000..a3c243e --- /dev/null +++ b/production @@ -0,0 +1,9 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +[back] +ansible.home + +[front] +ansible.dmz diff --git a/roles/_maintenance_start/defaults/main.yml b/roles/_maintenance_start/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/_maintenance_start/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/_maintenance_start/tasks/main.yml b/roles/_maintenance_start/tasks/main.yml new file mode 100644 index 0000000..6923c62 --- /dev/null +++ b/roles/_maintenance_start/tasks/main.yml @@ -0,0 +1,25 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: stop some services + shell: | + for u in \ + nextcloud-maintenance.{timer,service} uwsgi@nextcloud.{socket,service} \ + dehydrated.{timer,service} \ + minidlna.service \ + nfs-server.service + do + systemctl stop $u + done + when: + - (inventory_hostname in groups['back']) + +- name: stop some services + shell: | + for u in spamassassin.service spamassassin-update.{timer,service}; do + systemctl stop $u + done + when: + - (inventory_hostname in groups['front']) diff --git a/roles/_maintenance_stop/defaults/main.yml b/roles/_maintenance_stop/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/_maintenance_stop/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/_maintenance_stop/tasks/main.yml b/roles/_maintenance_stop/tasks/main.yml new file mode 100644 index 0000000..d7d855e --- /dev/null +++ b/roles/_maintenance_stop/tasks/main.yml @@ -0,0 +1,27 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: stop again services that may got started by handlers + include_role: name=_maintenance_start allow_duplicates=true + +- name: restart some services + shell: | + for u in \ + nextcloud-maintenance.timer uwsgi@nextcloud.socket \ + dehydrated.timer \ + minidlna.service \ + nfs-server.service + do + systemctl start $u + done + when: + - (inventory_hostname in groups['back']) + +- name: restart some services + shell: | + systemctl start spamassassin.service + systemctl start spamassassin-update.timer + when: + - (inventory_hostname in groups['front']) diff --git a/roles/acme_back/defaults/main.yml b/roles/acme_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/acme_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/acme_back/files/dehydrated.timer b/roles/acme_back/files/dehydrated.timer new file mode 100644 index 0000000..2c2211e --- /dev/null +++ b/roles/acme_back/files/dehydrated.timer @@ -0,0 +1,12 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +[Unit] +Description=Run Dehydrated (Let’s Encrypt client) every week + +[Timer] +OnCalendar=weekly + +[Install] +WantedBy=timers.target diff --git a/roles/acme_back/handlers/main.yml b/roles/acme_back/handlers/main.yml new file mode 100644 index 0000000..9325b2b --- /dev/null +++ b/roles/acme_back/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart dehydrated.service + systemd: + daemon_reload: true + name: dehydrated.timer + state: restarted diff --git a/roles/acme_back/meta.OK/main.yml b/roles/acme_back/meta.OK/main.yml new file mode 100644 index 0000000..a7b2107 --- /dev/null +++ b/roles/acme_back/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: postinstall + - role: front_run diff --git a/roles/acme_back/tasks/main.yml b/roles/acme_back/tasks/main.yml new file mode 100644 index 0000000..9ee8c9b --- /dev/null +++ b/roles/acme_back/tasks/main.yml @@ -0,0 +1,85 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: ACME +### ⇐ UPSTREAM BEGIN ### + +- name: install dehydrated (Let’s Encrypt) + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: '["dehydrated-git"]' + aur_user: git + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: ACME +### ⇐ UPSTREAM END ### + +- name: set Let’s Encrypt domains + copy: + content: | + {{acme_domains}} + dest: /etc/dehydrated/domains.txt + mode: 0644 + +- name: create Let’s Encrypt top directory + file: + path: /var/lib/acme + state: directory + mode: 0711 + +- name: create Let’s Encrypt accounts directory + file: + path: /var/lib/acme/accounts + state: directory + mode: 0700 + +- name: create Let’s Encrypt certs directory + file: + path: /var/lib/acme/certs + state: directory + mode: 0755 + +- name: set dehydrated settings + template: + src: templates/dehydrated.config.j2 + dest: /etc/dehydrated/config + mode: 0600 + +- name: set dehydrated hooks + template: + src: templates/hook.sh.j2 + dest: "/etc/dehydrated/{{nickname}}-hook.sh" + mode: 0700 + +- name: create dehydrated timer + copy: + src: files/dehydrated.timer + dest: /etc/systemd/system/dehydrated.timer + mode: 0644 + notify: + - restart dehydrated.service + +- name: enable dehydrated + systemd: + daemon_reload: true + name: dehydrated.timer + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: ACME +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/acme_back/templates/dehydrated.config.j2 b/roles/acme_back/templates/dehydrated.config.j2 new file mode 100644 index 0000000..c6b2263 --- /dev/null +++ b/roles/acme_back/templates/dehydrated.config.j2 @@ -0,0 +1,12 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +#CA="https://acme-staging.api.letsencrypt.org/directory" +ACCOUNTDIR=/var/lib/acme/accounts +CERTDIR=/var/lib/acme/certs +CHAINCACHE=/var/lib/acme/chains +LOCKFILE=/var/lib/acme/lock +WELLKNOWN=/var/lib/machines/{{DMZ}}/srv/acme +CONTACT_EMAIL=hostmaster@{{net_soa}} +HOOK=/etc/dehydrated/{{nickname}}-hook.sh diff --git a/roles/acme_back/templates/hook.sh.j2 b/roles/acme_back/templates/hook.sh.j2 new file mode 100644 index 0000000..41baf79 --- /dev/null +++ b/roles/acme_back/templates/hook.sh.j2 @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +RSH=/usr/local/bin/{{DMZ}} +ETC_CHANGED_{{hostname}}= +ETC_CHANGED_{{DMZ}}= + +etckeeper_hook() { + if [ -n "$ETC_CHANGED_{{hostname}}" ]; then + etc_stop_local 'ACME update' + fi + if [ -n "$ETC_CHANGED_{{DMZ}}" ]; then + $RSH "etc_stop_local 'ACME update'" + fi +} + +trap etckeeper_hook EXIT + +deploy_challenge() { + local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" + + printf '%s' "$TOKEN_VALUE" >"$WELLKNOWN/$TOKEN_FILENAME" +} + +clean_challenge() { + local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" + + rm -f "$WELLKNOWN/$TOKEN_FILENAME" +} + +# $1: force|test; $2: KEYFILE; $3: CERTFILE; $4: FULLCHAINFILE; $5: CHAINFILE; $6: TIMESTAMP +deploy_exim() { + if [ "$1" == test ] \ + && $RSH 'test -f /etc/mail/exim.pem && test -f /etc/mail/exim.crt' \ + && $RSH 'find /etc/mail/exim.{pem,crt} -mmin -$((1+($(date +%s)-${1})/60)) -printf . | grep -q ..' ${6%.*}; then + return 0 + fi + local copy='cat >$1; chown exim $1; chmod 400 $1; touch -t $(date +%Y%m%d%H%M -d @$2) $1' + $RSH "$copy" /etc/mail/exim.pem $6 <"$2" + $RSH "$copy" /etc/mail/exim.crt $6 <"$4" + systemctl -M {{DMZ}} reload exim.service + ETC_CHANGED_{{DMZ}}=1 +} + +# $1: force|test; $2: KEYFILE; $3: CERTFILE; $4: FULLCHAINFILE; $5: CHAINFILE; $6: TIMESTAMP +deploy_prosody() { + if [ "$1" == test ] \ + && $RSH 'test -f /etc/prosody/certs/{{net_soa}}.key && test -f /etc/prosody/certs/{{net_soa}}.crt' \ + && $RSH 'find /etc/prosody/certs/{{net_soa}}.{key,crt} -mmin -$((1+($(date +%s)-${1})/60)) -printf . | grep -q ..' ${6%.*}; then + return 0 + fi + local copy='cat >$1; chown prosody $1; chmod 400 $1; touch -t $(date +%Y%m%d%H%M -d @$2) $1' + $RSH "$copy" /etc/prosody/certs/{{net_soa}}.key $6 <"$2" + $RSH "$copy" /etc/prosody/certs/{{net_soa}}.crt $6 <"$4" + systemctl -M {{DMZ}} reload prosody.service + ETC_CHANGED_{{DMZ}}=1 +} + +# $1: force|test; $2: KEYFILE; $3: CERTFILE; $4: FULLCHAINFILE; $5: CHAINFILE; $6: TIMESTAMP +deploy_haproxy() { + if [ "$1" == test ] \ + && $RSH 'test -f /etc/haproxy/tls.pem' \ + && $RSH 'find /etc/haproxy/tls.pem -mmin -$((1+($(date +%s)-${1})/60)) -printf . | grep -q .' ${6%.*}; then + return 0 + fi + local copy='cat >$1; chmod 400 $1; touch -t $(date +%Y%m%d%H%M -d @$2) $1' + cat "$4" "$2" | $RSH "$copy" /etc/haproxy/tls.pem $6 + systemctl -M {{DMZ}} reload haproxy.service + ETC_CHANGED_{{DMZ}}=1 +} + +deploy_cert() { + local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" + + deploy_exim force "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$TIMESTAMP" + deploy_prosody force "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$TIMESTAMP" + deploy_haproxy force "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$TIMESTAMP" +} + +unchanged_cert() { + local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="$(find "$2" -printf '%T@')" + + deploy_exim test "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$TIMESTAMP" + deploy_prosody test "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$TIMESTAMP" + deploy_haproxy test "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$TIMESTAMP" +} + +invalid_challenge() { + local DOMAIN="${1}" RESPONSE="${2}" + + printf 'Failed ACME challenge for DOMAIN=%s: RESPONSE=%s\n' "$DOMAIN" "$RESPONSE" >&2 +} + +request_failure() { + local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" + + printf 'Failed %s request for ACME: STATUSCODE=%s (%s)\n' "$REQTYPE" "$STATUSCODE" "$REASON" >&2 +} + +exit_hook() { + return 0 +} + +HANDLER="$1"; shift +if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then + "$HANDLER" "$@" +fi diff --git a/roles/acme_front/defaults/main.yml b/roles/acme_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/acme_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/acme_front/handlers/main.yml b/roles/acme_front/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/acme_front/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/acme_front/meta.OK/main.yml b/roles/acme_front/meta.OK/main.yml new file mode 100644 index 0000000..7f3de13 --- /dev/null +++ b/roles/acme_front/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: dmz_nginx diff --git a/roles/acme_front/tasks/main.yml b/roles/acme_front/tasks/main.yml new file mode 100644 index 0000000..8207a3b --- /dev/null +++ b/roles/acme_front/tasks/main.yml @@ -0,0 +1,31 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: ensure /srv/acme exists + file: + path: /srv/acme + state: directory + mode: 01777 + +- name: let nginx serve ACME requests + copy: + content: | + location ^~ /.well-known/acme-challenge { + alias /srv/acme; + } + dest: /etc/nginx/inc.d/acme.http.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: ACME +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/aur.inc/tasks/install.yml b/roles/aur.inc/tasks/install.yml new file mode 100644 index 0000000..90b2f54 --- /dev/null +++ b/roles/aur.inc/tasks/install.yml @@ -0,0 +1,98 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# mandatory parameters: pkg_name and aur_user + +- name: AUR → {{pkg_name}} → read current version + shell: | + pacman -Qi '{{pkg_name}}' | awk -F ': ' '/^Version/{print $2}' + register: pacmanQi + changed_when: false + +- name: AUR → {{pkg_name}} → get metadata from AurJson + uri: + url: https://aur.archlinux.org/rpc/?v=5&type=info&arg={{pkg_name | mandatory}} + connection: local + register: aurjson + changed_when: false + +- name: AUR → {{pkg_name}} → get package recipe + get_url: + url: https://aur.archlinux.org{{aurjson.json.results[0].URLPath}} + dest: /tmp/ + connection: local + when: + - (aurjson.json.results[0].Version != (pacmanQi.stdout | default())) + register: aur_recipe + changed_when: false + +- name: AUR → {{pkg_name}} → proceed with the install/upgrade + block: + + - name: AUR → {{pkg_name}} → extract the recipe files + unarchive: + src: '{{aur_recipe.dest}}' + dest: /var/tmp/ + changed_when: false + + - name: AUR → {{pkg_name}} → work with the recipe + block: + + - name: AUR → {{pkg_name}} → read the real version + command: > + bash -c + " makepkg -do >/dev/null 2>&1 + ; makepkg --printsrcinfo + | awk '$1==\"pkgver\"{v=$3};$1==\"pkgrel\"{r=$3};END{printf \"%s-%s\",v,r}'" + args: + chdir: /var/tmp/{{aurjson.json.results[0].PackageBase}} + register: realVersion + changed_when: false + ignore_errors: true + + - name: AUR → {{pkg_name}} → proceed with building the package + block: + + - name: AUR → {{pkg_name}} → install dependencies and build + command: | + makepkg --noconfirm --noprogressbar -Cmfs + args: + chdir: /var/tmp/{{aurjson.json.results[0].PackageBase}} + changed_when: false + + - name: AUR → {{pkg_name}} → install package + shell: | + pacman --noconfirm --noprogressbar --needed -U *.pkg.tar.xz + args: + chdir: /var/tmp/{{aurjson.json.results[0].PackageBase}} + become: false + register: pacman_output + changed_when: + - (pacman_output.stdout is defined) + - (pacman_output.stdout.find('there is nothing to do') == -1) + + when: + - (realVersion.stdout != (pacmanQi.stdout | default())) + + always: + - name: AUR → {{pkg_name}} → clean the build directory + file: + path: /var/tmp/{{aurjson.json.results[0].PackageBase}} + state: absent + changed_when: false + + become: true + become_user: "{{aur_user}}" + when: + - (aurjson.json.results[0].Version != (pacmanQi.stdout | default())) + + always: + - name: AUR → {{pkg_name}} → clean package recipe + file: + path: '{{aur_recipe.dest}}' + state: absent + connection: local + become: false + changed_when: false diff --git a/roles/aur.inc/tasks/main.yml b/roles/aur.inc/tasks/main.yml new file mode 100644 index 0000000..9c54989 --- /dev/null +++ b/roles/aur.inc/tasks/main.yml @@ -0,0 +1,27 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# mandatory parameters: +# - pkg_names (json-encoded list) +# - aur_user + +- name: AUR → base-devel needed while building + become: false + command: | + pacman -S --noconfirm --noprogressbar --asdeps --needed base-devel + changed_when: false + +- name: AUR → proceed with installation + block: + - name: AUR → installation + include_tasks: install.yml + vars: + pkg_name: "{{item}}" + with_items: "{{pkg_names}}" + always: + - name: AUR → remove base-devel and dependencies + shell: | + pacman -Rns --noconfirm --noprogressbar $(pacman -Qtdqg base-devel) || true + changed_when: false diff --git a/roles/cleanupdate/defaults/main.yml b/roles/cleanupdate/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/cleanupdate/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/cleanupdate/meta.OK/main.yml b/roles/cleanupdate/meta.OK/main.yml new file mode 100644 index 0000000..a03553a --- /dev/null +++ b/roles/cleanupdate/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init diff --git a/roles/cleanupdate/tasks/main.yml b/roles/cleanupdate/tasks/main.yml new file mode 100644 index 0000000..7e03e5b --- /dev/null +++ b/roles/cleanupdate/tasks/main.yml @@ -0,0 +1,44 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: pacman clean update +### ⇐ UPSTREAM BEGIN ### + +- name: check for unused software + shell: | + pacman -Qtdq || true + register: unused_software + changed_when: false + +- name: remove unused software + command: | + pacman -Rns --noconfirm --noprogressbar {{unused_software.stdout}} + when: + - (unused_software.stdout is defined) + - (unused_software.stdout != "") + +- name: clean packages metadata and cache + command: | + pacman -Sc --noconfirm --noprogressbar + changed_when: false + +- name: update installed software + command: | + pacman -Syuq --noconfirm --noprogressbar --ignore {{software_to_ignore}} + register: update_result + changed_when: + - (update_result.stdout is defined) + - (update_result.stdout.find('there is nothing to do') == -1) + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: pacman clean update +### ⇐ UPSTREAM END ### diff --git a/roles/ddclient.inc/meta.OK/main.yml b/roles/ddclient.inc/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/ddclient.inc/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/ddclient.inc/tasks/main.yml b/roles/ddclient.inc/tasks/main.yml new file mode 100644 index 0000000..1d0240a --- /dev/null +++ b/roles/ddclient.inc/tasks/main.yml @@ -0,0 +1,46 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: ddclient +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: ddclient + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: ddclient +### ⇐ UPSTREAM END ### + +- name: create a generic ddclient service + copy: + content: | + [Unit] + Description=Dynamic DNS Update Client (%I) + After=network-online.target + Wants=network-online.target + [Service] + RuntimeDirectory=ddclient + PIDFile=/run/ddclient/%i.pid + ;ExecStartPre=-/usr/bin/mkdir /var/cache/ddclient/%i + ExecStart=/usr/bin/ddclient -syslog -daemon 1800 -foreground -file /etc/ddclient/%i.conf -cache /var/cache/ddclient/%i -pid /run/ddclient/%i.pid + [Install] + WantedBy=multi-user.target + dest: '/etc/systemd/system/ddclient@.service' + mode: 0644 + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: ddclient +### ⇐ LOCAL COMMIT ### diff --git a/roles/ddclient_FreeDNS_example/defaults/main.yml b/roles/ddclient_FreeDNS_example/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ddclient_FreeDNS_example/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ddclient_FreeDNS_example/handlers/main.yml b/roles/ddclient_FreeDNS_example/handlers/main.yml new file mode 100644 index 0000000..d34a94a --- /dev/null +++ b/roles/ddclient_FreeDNS_example/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart ddclient@fdns.service + systemd: + daemon_reload: true + name: ddclient@fdns.service + state: restarted diff --git a/roles/ddclient_FreeDNS_example/meta.OK/main.yml b/roles/ddclient_FreeDNS_example/meta.OK/main.yml new file mode 100644 index 0000000..a03553a --- /dev/null +++ b/roles/ddclient_FreeDNS_example/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init diff --git a/roles/ddclient_FreeDNS_example/tasks/main.yml b/roles/ddclient_FreeDNS_example/tasks/main.yml new file mode 100644 index 0000000..77ae82b --- /dev/null +++ b/roles/ddclient_FreeDNS_example/tasks/main.yml @@ -0,0 +1,59 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: initialize the ddclient software + include_role: + name: ddclient.inc + +- name: prepare to override systemd settings + file: + path: '/etc/systemd/system/ddclient@fdns.service.d' + state: directory + mode: 0755 + +- name: systemd settings for afraid.org + copy: + content: | + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_SYS_CHROOT + PrivateTmp=yes + PrivateDevices=yes + ProtectSystem=yes + ProtectHome=yes + NoNewPrivileges=yes + dest: '/etc/systemd/system/ddclient@fdns.service.d/secure-{{nickname}}.conf' + mode: 0644 + notify: + - restart ddclient@fdns.service + +- name: ddclient settings for afraid.org + copy: + content: | + use=web + web=http://checkip.dns.he.net/ + ssl=yes + server=freedns.afraid.org + protocol=freedns + login=your_login + password=your_password + your-subdomain.at.afraid.org + dest: /etc/ddclient/fdns.conf + mode: 0644 + notify: + - restart ddclient@fdns.service + +- name: enable ddclient@fdns.service + systemd: + daemon_reload: true + name: ddclient@fdns.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: ddclient@fdns +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/ddclient_HE_example/defaults/main.yml b/roles/ddclient_HE_example/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ddclient_HE_example/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ddclient_HE_example/handlers/main.yml b/roles/ddclient_HE_example/handlers/main.yml new file mode 100644 index 0000000..19a00b1 --- /dev/null +++ b/roles/ddclient_HE_example/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart ddclient@henet.service + systemd: + daemon_reload: true + name: ddclient@henet.service + state: restarted diff --git a/roles/ddclient_HE_example/meta.OK/main.yml b/roles/ddclient_HE_example/meta.OK/main.yml new file mode 100644 index 0000000..f5b99db --- /dev/null +++ b/roles/ddclient_HE_example/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: iodine diff --git a/roles/ddclient_HE_example/tasks/main.yml b/roles/ddclient_HE_example/tasks/main.yml new file mode 100644 index 0000000..485de4e --- /dev/null +++ b/roles/ddclient_HE_example/tasks/main.yml @@ -0,0 +1,73 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: initialize the ddclient software + include_role: + name: ddclient.inc + +- name: prepare to override systemd settings + file: + path: '/etc/systemd/system/ddclient@henet.service.d' + state: directory + mode: 0755 + +- name: systemd settings for he.net + copy: + content: | + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_SYS_CHROOT + PrivateTmp=yes + PrivateDevices=yes + ProtectSystem=yes + ProtectHome=yes + NoNewPrivileges=yes + dest: '/etc/systemd/system/ddclient@henet.service.d/secure-{{nickname}}.conf' + mode: 0644 + notify: + - restart ddclient@henet.service + +- name: ddclient settings for he.net + copy: + content: | + use=web + web=http://checkip.dns.he.net/ + server=dyn.dns.he.net + login=your_login + password=your_password + postscript=/etc/ddclient/henet_post.sh + example.org + dest: /etc/ddclient/henet.conf + mode: 0644 + notify: + - restart ddclient@henet.service + +- name: post-update script for he.net + copy: + content: | + #!/bin/bash + # $1: new IP address + if [ -f /etc/conf.d/iodined ]; then + sed -i "s/^IODINE_EXT_IP=.*/IODINE_EXT_IP='$1'/" /etc/conf.d/iodined + etc_stop_local 'IP update on dns.he.net' + systemctl restart iodined.service + fi + dest: /etc/ddclient/henet_post.sh + mode: 0755 + notify: + - restart ddclient@henet.service + +- name: enable ddclient@henet.service + systemd: + daemon_reload: true + name: ddclient@henet.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: ddclient@henet +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_dotclear_front/defaults/main.yml b/roles/dmz_dotclear_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_dotclear_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_dotclear_front/handlers/main.yml b/roles/dmz_dotclear_front/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/dmz_dotclear_front/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_dotclear_front/meta.OK/main.yml b/roles/dmz_dotclear_front/meta.OK/main.yml new file mode 100644 index 0000000..7f3de13 --- /dev/null +++ b/roles/dmz_dotclear_front/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: dmz_nginx diff --git a/roles/dmz_dotclear_front/tasks/main.yml b/roles/dmz_dotclear_front/tasks/main.yml new file mode 100644 index 0000000..b93721b --- /dev/null +++ b/roles/dmz_dotclear_front/tasks/main.yml @@ -0,0 +1,69 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: make sure the path to dotclear exists + file: + path: "{{dotclear_root | dirname}}" + state: directory + mode: 0755 + +- name: download dotclear + get_url: + url: 'http://download.dotclear.org/latest.tar.gz' + dest: /tmp/ + connection: local + register: targz + changed_when: false + +- name: upload dotclear to host and extract it + unarchive: + src: "{{targz.dest}}" + dest: "{{dotclear_root | dirname}}" + creates: "{{dotclear_root}}" + owner: http + group: http + mode: ug+rwX,o= + +- name: create the LDAP authentication class + template: + src: templates/ldap_auth.php.j2 + dest: "{{dotclear_root}}/inc/ldap_auth.php" + owner: http + group: http + mode: 0400 + +- name: configure dotclear + template: + src: templates/config.php.j2 + dest: "{{dotclear_root}}/inc/config.php" + owner: http + group: http + mode: 0400 + +- name: configure nginx for dotclear + copy: + content: | + location = / { + rewrite ^ $scheme://{{net_soa}}{{http_pfx_dotclear}} redirect; + } + location {{http_pfx_dotclear}} { + alias {{dotclear_root}}; + autoindex on; + rewrite ^({{http_pfx_dotclear}})(/.*?\.php)(/.*)?$ /php...$document_root/...$1/...$2/...$3 last; + } + dest: /etc/nginx/inc.d/dotclear.http.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: dotclear +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_dotclear_front/templates/config.php.j2 b/roles/dmz_dotclear_front/templates/config.php.j2 new file mode 100644 index 0000000..484003c --- /dev/null +++ b/roles/dmz_dotclear_front/templates/config.php.j2 @@ -0,0 +1,29 @@ +con->begin(); + $cur = $this->con->openCursor($this->user_table); + + # $pwd has been approved; take it + $cur->user_pwd = $pwd; + + if ($this->core->userExists($user_id)) { + + # Store the password only, if the user is already known + $this->sudo(array($this->core,'updUser'),$user_id,$cur); + $this->con->commit(); + } else { + + # Insert the user if it is new… + + if (!($info = ldap_read( + $ldap, + $dn, + '(objectclass=posixAccount)', + array('cn', 'sn') + ))) { + + # … except if the user is not allowed to login + $this->con->rollback(); + ldap_unbind($ldap); + return false; + } + + $cur->user_id = $user_id; + $cur->user_email = $info['mail']; + $cur->user_name = $info['sn']; + $cur->user_firstname = $info['cn']; + $cur->user_lang = '{{locales_default | truncate(2, True, '', 0)}}'; + $cur->user_tz = '{{timezone}}'; + $cur->user_default_blog = 'default'; + + $this->sudo(array($this->core,'addUser'),$cur); + $this->sudo(array($this->core,'setUserBlogPermissions'), + $user_id, 'default', array('usage'=>true)); + $this->con->commit(); + } + + ldap_unbind($ldap); + return parent::checkUser($user_id, $pwd, null, $check_blog); + + } catch (Exception $e) { + $this->con->rollback(); + ldap_unbind($ldap); + return false; + } + } +} diff --git a/roles/dmz_exim/defaults/main.yml b/roles/dmz_exim/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_exim/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_exim/files/example.org_dkim.privk.pem b/roles/dmz_exim/files/example.org_dkim.privk.pem new file mode 100644 index 0000000..aa1f57a --- /dev/null +++ b/roles/dmz_exim/files/example.org_dkim.privk.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +… +… +… +… +… +… +… +… +… +… +… +… +… +-----END RSA PRIVATE KEY----- diff --git a/roles/dmz_exim/handlers/main.yml b/roles/dmz_exim/handlers/main.yml new file mode 100644 index 0000000..18d1fa1 --- /dev/null +++ b/roles/dmz_exim/handlers/main.yml @@ -0,0 +1,22 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart spamassassin-update.timer + systemd: + daemon_reload: true + name: spamassassin-update.timer + state: restarted + +- name: restart spamassassin.service + systemd: + daemon_reload: true + name: spamassassin.service + state: restarted + +- name: restart exim.service + systemd: + daemon_reload: true + name: exim.service + state: restarted diff --git a/roles/dmz_exim/meta.OK/main.yml b/roles/dmz_exim/meta.OK/main.yml new file mode 100644 index 0000000..e7dc7df --- /dev/null +++ b/roles/dmz_exim/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: ldap diff --git a/roles/dmz_exim/tasks/main.yml b/roles/dmz_exim/tasks/main.yml new file mode 100644 index 0000000..1897fcd --- /dev/null +++ b/roles/dmz_exim/tasks/main.yml @@ -0,0 +1,585 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Exim +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - exim + - spamassassin + # sa-compile from spamassassin needs make, gcc. + - make + - gcc + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Exim +### ⇐ UPSTREAM END ### + +- name: prepare overriding exim settings + file: + name: /etc/systemd/system/exim.service.d + state: directory + mode: 0755 + +- name: secure exim systemd settings + copy: + content: | + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_CHOWN CAP_FSETID CAP_IPC_LOCK CAP_LEASE CAP_LINUX_IMMUTABLE CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETFCAP CAP_SETPCAP CAP_SETUID CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_PACCT CAP_SYS_RAWIO CAP_WAKE_ALARM + PrivateTmp=true + PrivateDevices=true + ProtectSystem=full + ProtectHome=true + NoNewPrivileges=true + dest: "/etc/systemd/system/exim.service.d/secure-{{nickname}}.conf" + mode: 0644 + notify: + - restart exim.service + +- name: declare automatic updating and compilation of SpamAssassin rules + copy: + content: | + [Unit] + Description=Update and compile SpamAssassin rules + [Service] + User=root + Group=spamd + Type=oneshot + ExecStart=/usr/bin/vendor_perl/sa-update --allowplugins + # Exit status: 0 = updated, 1 = no updates available + SuccessExitStatus=0 1 + ExecStart=/usr/bin/vendor_perl/sa-compile + SuccessExitStatus=0 + dest: /etc/systemd/system/spamassassin-update.service + mode: 0644 + notify: + - restart spamassassin-update.timer + +- name: plan automatic updating and compilation of SpamAssassin rules + copy: + content: | + [Unit] + Description=Update and compile SpamAssassin rules + [Timer] + OnCalendar=daily + Persistent=true + [Install] + WantedBy=timers.target + dest: /etc/systemd/system/spamassassin-update.timer + mode: 0644 + notify: + - restart spamassassin-update.timer + +- name: prepare overriding spamassassin settings + file: + name: /etc/systemd/system/spamassassin.service.d + state: directory + mode: 0755 + +- name: make spamassassin take into account changes in rules + copy: + content: | + [Unit] + PartOf=spamassassin-update.service + Wants=spamassassin-update.service + After=spamassassin-update.service + dest: /etc/systemd/system/spamassassin.service.d/restart-when-update.conf + mode: 0644 + notify: + - restart spamassassin-update.timer + +- name: secure spamassassin systemd settings + copy: + content: | + [Service] + ExecStart= + ExecStart=/usr/bin/vendor_perl/spamd -x -u spamd -g spamd --listen=/run/shared_sockets/spamd + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_LEASE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT + PrivateTmp=true + PrivateDevices=true + ProtectSystem=full + ProtectHome=true + NoNewPrivileges=true + dest: /etc/systemd/system/spamassassin.service.d/secure-{{nickname}}.conf + mode: 0644 + notify: + - restart spamassassin.service + +- name: make spamassassin trust local machines + lineinfile: + path: /etc/mail/spamassassin/local.cf + regexp: '^trusted_networks\s' + line: "trusted_networks {{net_trusted_ranges}}" + insertafter: '^# trusted_networks' + notify: + - restart spamassassin.service + +- name: be less tolerent with spam + lineinfile: + path: /etc/mail/spamassassin/local.cf + regexp: '^required_score\s' + line: 'required_score 4.0' + insertafter: '^# required_score' + notify: + - restart spamassassin.service + +- name: leave spamd when the network is trusted + lineinfile: + path: /etc/mail/spamassassin/local.cf + regexp: '^shortcircuit\s+ALL_TRUSTED\s' + line: 'shortcircuit ALL_TRUSTED on' + insertafter: '^# shortcircuit\s+ALL_TRUSTED' + notify: + - restart spamassassin.service + +- name: Aliases LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: 'ou=Aliases,{{ldap_root}}' + objectClass: + - top + - organizationalUnit + attributes: + ou: Aliases + +- name: declare existing aliases + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "cn={{item.alias}},ou=Aliases,{{ldap_root}}" + objectClass: + - top + - nisMailAlias + attributes: + cn: "{{item.alias}}" + with_items: "{{mail_alias_memberships}}" + +- name: declare existing aliases’ members + ldap_attr: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "cn={{item.alias}},ou=Aliases,{{ldap_root}}" + name: rfc822MailMember + values: "{{item.member}}" + state: present + with_items: "{{mail_alias_memberships}}" + +- name: declare “dmarc” in system-aliases file + lineinfile: + path: /etc/mail/aliases + regexp: '^(?:#\s*)?dmarc:' + line: "dmarc: postmaster" + +- name: finalize system-aliases file + lineinfile: + path: /etc/mail/aliases + regexp: '^(?:#\s*)?root:' + line: "root: {{mail_forward_root_to}}" + +- name: send DKIM private key + copy: + src: files/{{net_soa}}_dkim.privk.pem + dest: /etc/mail/{{net_soa}}_dkim.privk.pem + owner: exim + group: exim + mode: 0400 + notify: + - restart exim.service + +- name: set log destination + lineinfile: + path: /etc/mail/exim.conf + regexp: '^log_file_path\s*=' + line: | + log_file_path = syslog + insertafter: 'MAIN CONFIGURATION SETTINGS' + notify: + - restart exim.service + +- name: disable log dupplicates + lineinfile: + path: /etc/mail/exim.conf + regexp: '^syslog_duplication\s*=' + line: | + syslog_duplication = false + insertafter: '^log_file_path\s*=' + notify: + - restart exim.service + +- name: set LDAP URL + lineinfile: + path: /etc/mail/exim.conf + regexp: '^ldap_default_servers\s*=' + line: | + ldap_default_servers = /run/shared_sockets/ldapi : {{SafeZone}} + insertafter: '^syslog_duplication\s*=' + notify: + - restart exim.service + +- name: set local domains + lineinfile: + path: /etc/mail/exim.conf + regexp: '^domainlist\s+local_domains\s*=' + line: > + domainlist local_domains = @ : + {{mail_local_domains | replace(' ', ' : ')}} + notify: + - restart exim.service + +- name: set relay hosts + lineinfile: + path: /etc/mail/exim.conf + regexp: '^hostlist\s+relay_from_hosts\s*=' + line: > + hostlist relay_from_hosts = <; ; + {{net_trusted_ranges | replace(' ', ' ; ')}} + notify: + - restart exim.service + +- name: use a Unix socket for SpamAssassin + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?spamd_address\s*=' + line: 'spamd_address = /run/shared_sockets/spamd' + notify: + - restart exim.service + +- name: advertise TLS + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?tls_advertise_hosts\s*=' + line: 'tls_advertise_hosts = *' + notify: + - restart exim.service + +- name: set TLS parameters for OpenSSL + blockinfile: + path: /etc/mail/exim.conf + marker: '# {mark} OpenSSL parameters' + block: | + tls_require_ciphers = {{tls_ciphers}} + insertafter: '^tls_advertise_hosts\s*=' + +- name: set TLS certificate + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?tls_certificate\s*=' + line: 'tls_certificate = /etc/mail/exim.crt' + notify: + - restart exim.service + +- name: set TLS key + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?tls_privatekey\s*=' + line: 'tls_privatekey = /etc/mail/exim.pem' + notify: + - restart exim.service + +- name: set SMTP ports + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?daemon_smtp_ports\s*=' + line: 'daemon_smtp_ports = 25 : 465 : 587' + notify: + - restart exim.service + +- name: set TLS port + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?tls_on_connect_ports\s*=' + line: 'tls_on_connect_ports = 465' + notify: + - restart exim.service + +- name: set qualified domain + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?qualify_domain\s*=' + line: "qualify_domain = {{net_soa}}" + notify: + - restart exim.service + +- name: disable reverse-DNS lookups + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?host_lookup\s*=' + line: '# host_lookup = *' + notify: + - restart exim.service + +- name: enable lenient message decoding + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(?:#\s*)?check_rfc2047_length\s*=' + line: 'check_rfc2047_length = false' + notify: + - restart exim.service + +- name: set more lenient checks for local email + replace: + path: /etc/mail/exim.conf + regexp: '(control\s*=\s*submission)\s*$' + replace: '\1/sender_retain' + notify: + - restart exim.service + +- name: allow recipient verification through LMTP + replace: + path: /etc/mail/exim.conf + regexp: '#?(\s*require\s+verify\s*=\s*recipient)$' + replace: '\1/callout=no_cache' + notify: + - restart exim.service + +- name: deny mail RCPT from SpamHaus SBL + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} SpamHaus SBL ACL' + block: | + deny message = rejected because $sender_host_address is in a \ + black list at SpamHaus SBL + dnslists = sbl.spamhaus.org + insertbefore: '^\s*#\s*warn\s+dnslists\s*=' + notify: + - restart exim.service + +- name: check mail DATA with SpamAssassin + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} SpamAssassin ACL' + block: | + accept condition = ${if >={$message_size}{500k}{yes}{no}} + accept hosts = +relay_from_hosts + warn spam = nobody/defer_ok + add_header = X-Spam_score: $spam_score ($spam_bar)\n\ + X-Spam_score_int: $spam_score_int + warn spam = nobody/defer_ok + add_header = X-Spam_report: $spam_report + condition = ${if >{$spam_score_int}{0}{1}{0}} + deny spam = nobody:true/defer_ok + message = This message scored $spam_score spam points. + condition = ${if >{$spam_score_int}{120}{1}{0}} + insertbefore: '^\s*#\s*warn\s+spam\s*=' + notify: + - restart exim.service + +# TODO: https://github.com/Exim/exim/wiki/SimpleGreylisting (with SPAM≥1.0) + +- name: disable the dnslookup router + replace: + path: /etc/mail/exim.conf + regexp: '^dnslookup:\n(?:.+\n)*' + replace: | + # dnslookup: + # driver = dnslookup + # domains = ! +local_domains + # transport = remote_smtp + # ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1 + # no_more + notify: + - restart exim.service + +- name: enable the smarthost router + replace: + path: /etc/mail/exim.conf + regexp: '^#\s*smarthost:\n(?:.+\n)*' + replace: | + smarthost: + driver = manualroute + domains = ! +local_domains + transport = remote_smtp + route_list = * ISP.SMART.HOST + ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1 + no_more + notify: + - restart exim.service + +- name: set the smarthost hostname + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(\s*route_list\s*=)' + backrefs: true + line: "\\1 * {{mail_smtp_smarthost}}" + notify: + - restart exim.service + +- name: set IP addresses to be ignored + lineinfile: + path: /etc/mail/exim.conf + regexp: '^(\s*ignore_target_hosts\s*=.*::1)(?! ; {{mail_ignore_ip | replace(" ", " ; ")}}$)' + backrefs: true + line: "\\1 ; {{mail_ignore_ip | replace(' ', ' ; ')}}" + when: + - mail_ignore_ip != "" + notify: + - restart exim.service + +# http://www.openldap.org/lists/openldap-software/200209/msg00792.html +# http://www.exim.org/exim-html-current/doc/html/spec_html/ch-file_and_database_lookups.html +# https://www.howtoforge.com/setting-up-a-mail-server-using-exim4-clamav-dovecot-spamassassin-and-many-more-on-debian-p3 +- name: read aliases in OpenLDAP + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} OpenLDAP aliases' + block: | + ldap_aliases: + driver = redirect + allow_fail + allow_defer + data = ${lookup ldap \ + {ldapi:///cn=${quote_ldap_dn:$local_part},ou=Aliases,{{ldap_root}}\ + ?rfc822MailMember?sub?\ + (&(objectClass=nisMailAlias))} \ + } + insertbefore: 'forwarding using traditional .forward' + notify: + - restart exim.service + +- name: disable the userforward router + replace: + path: /etc/mail/exim.conf + regexp: '^userforward:\n(?:.+\n)*' + replace: | + #userforward: + # driver = redirect + # check_local_user + ## local_part_suffix = +* : -* + ## local_part_suffix_optional + # file = $home/.forward + ## allow_filter + # no_verify + # no_expn + # check_ancestor + # file_transport = address_file + # pipe_transport = address_pipe + # reply_transport = address_reply + notify: + - restart exim.service + +- name: disable the localuser router + replace: + path: /etc/mail/exim.conf + regexp: '^localuser:\n(?:.+\n)*' + replace: | + #localuser: + # driver = accept + # check_local_user + ## local_part_suffix = +* : -* + ## local_part_suffix_optional + # transport = local_delivery + # cannot_route_message = Unknown user + notify: + - restart exim.service + +- name: add a router to LMTP + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} LMTP router' + block: | + lmtp_user: + debug_print = "R: lmtp_user for $local_part@$domain" + driver = accept + domains = +local_domains + transport = lmtp_transport + cannot_route_message = Unknown user + insertbefore: '^#localuser:' + notify: + - restart exim.service + +- name: add a transport for LMTP + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} LMTP transport' + block: | + lmtp_transport: + driver = lmtp + socket = /run/shared_sockets/lmtp + user = exim + current_directory = /var/spool/exim + insertbefore: '^# This transport is used' + notify: + - restart exim.service + +- name: enable DKIM on outgoing emails + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} outgoing DKIM signing' + block: | + dkim_canon = relaxed + dkim_domain = {{net_soa}} + dkim_private_key = /etc/mail/{{net_soa}}_dkim.privk.pem + dkim_selector = {{mail_dkim_selector}} + insertafter: '^\s*driver\s*=\s*smtp\s*$' + notify: + - restart exim.service + +- name: add authenticators based on LDAP + blockinfile: + path: /etc/mail/exim.conf + marker: ' # {mark} LDAP authentication' + block: | + plain_server: + driver = plaintext + public_name = PLAIN + server_prompts = : + server_condition = ${if ldapauth \ + {user="uid=${quote_ldap_dn:$2},ou=Users,{{ldap_root}}" \ + pass=${quote:$3} \ + ldapi:///}{yes}{no}} + server_set_id = $2 + login_server: + driver = plaintext + public_name = LOGIN + server_prompts = "Username:: : Password::" + server_condition = ${if ldapauth \ + {user="uid=${quote_ldap_dn:$1},ou=Users,{{ldap_root}}" \ + pass=${quote:$2} \ + ldapi:///}{yes}{no}} + server_set_id = $1 + insertafter: '^begin\s+authenticators' + notify: + - restart exim.service + +- name: enable spamassassin updater + systemd: + daemon_reload: true + name: spamassassin-update.timer + enabled: true + +- name: enable spamassassin + systemd: + daemon_reload: true + name: spamassassin.service + enabled: true + +- name: enable exim + systemd: + daemon_reload: true + name: exim.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Exim +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_haproxy/defaults/main.yml b/roles/dmz_haproxy/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_haproxy/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_haproxy/handlers/main.yml b/roles/dmz_haproxy/handlers/main.yml new file mode 100644 index 0000000..485fd41 --- /dev/null +++ b/roles/dmz_haproxy/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart haproxy.service + systemd: + daemon_reload: true + name: haproxy.service + state: restarted diff --git a/roles/dmz_haproxy/meta.OK/main.yml b/roles/dmz_haproxy/meta.OK/main.yml new file mode 100644 index 0000000..320bb95 --- /dev/null +++ b/roles/dmz_haproxy/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: ssh + - role: cleanupdate + - role: dmz_nginx diff --git a/roles/dmz_haproxy/tasks/main.yml b/roles/dmz_haproxy/tasks/main.yml new file mode 100644 index 0000000..a8f7324 --- /dev/null +++ b/roles/dmz_haproxy/tasks/main.yml @@ -0,0 +1,79 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: haproxy +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - haproxy + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: haproxy +### ⇐ UPSTREAM END ### + +- name: prepare overriding haproxy settings + file: + name: /etc/systemd/system/haproxy.service.d + state: directory + mode: 0755 + +- name: set nginx as a dependency (for sockets) + copy: + content: | + [Unit] + Wants=nginx.service + After=nginx.service + dest: /etc/systemd/system/haproxy.service.d/wants_nginx.conf + mode: 0644 + notify: + - restart haproxy.service + +- name: secure haproxy systemd settings + copy: + content: | + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_CHROOT + PrivateTmp=true + PrivateDevices=true + ProtectSystem=full + ProtectHome=true + NoNewPrivileges=true + dest: "/etc/systemd/system/haproxy.service.d/secure-{{nickname}}.conf" + mode: 0644 + notify: + - restart haproxy.service + +- name: configure HAProxy + template: + src: templates/haproxy.conf.j2 + dest: /etc/haproxy/haproxy.cfg + mode: 0600 + notify: + - restart haproxy.service + +- name: enable haproxy + systemd: + daemon-reload: true + name: haproxy.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: haproxy +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_haproxy/templates/haproxy.conf.j2 b/roles/dmz_haproxy/templates/haproxy.conf.j2 new file mode 100644 index 0000000..bd4fc18 --- /dev/null +++ b/roles/dmz_haproxy/templates/haproxy.conf.j2 @@ -0,0 +1,73 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +global + tune.ssl.default-dh-param 2048 + ssl-default-bind-ciphers {{tls_ciphers}} + ssl-default-bind-options {{tls_options}} + ssl-default-server-ciphers {{tls_ciphers}} + ssl-default-server-options {{tls_options}} + log /dev/log local0 info + pidfile /run/haproxy.pid + daemon + +defaults + mode tcp + timeout connect 5s + timeout client 5m + timeout server 5m + timeout tunnel 1h + timeout client-fin 5s + timeout server-fin 5s + log global + option logasap + option log-separate-errors + log-format "%ci:%cp [%t] %ft %b[%bi:%bp]/%s %Tw/%Tc/%Tt %B %ts %ac/%fc/%bc/%sc/%rc %sq/%bq" + +frontend imaps + bind :993 ssl crt /etc/haproxy/tls.pem + default_backend imap + +backend imap + server dovecot {{SafeZone_IP}}:220 send-proxy-v2 + +frontend text + bind :80 + default_backend http + +frontend tls + bind :443 ssl crt /etc/haproxy/tls.pem + + tcp-request inspect-delay 2s + # check SNI for the SSH domain + acl SNI ssl_fc_sni -i {{net_subdom_ssh}}.{{net_soa}} + # client-first SSH: wait for SSH-2.0 + acl cSSH req.payload(0,7) -m bin 5353482d322e30 + # server-first SSH: https://314es.pl/https-openvpn-and-ssh-on-one-port-thanks-to-haproxy + acl sSSH req.len eq 0 + + tcp-request content accept if HTTP + tcp-request content accept if cSSH + + use_backend ssh if SNI cSSH + use_backend ssh if SNI sSSH + use_backend ssh if SNI !HTTP + default_backend https + +frontend tls_plus + bind :444 ssl crt /etc/haproxy/tls.pem + default_backend https_plus + +backend ssh + server ssh 127.0.0.1:22 + timeout server 2h + +backend http + server nginx unix@/run/shared_sockets/http.pp send-proxy + +backend https + server nginx unix@/run/shared_sockets/https.pp send-proxy + +backend https_plus + server nginx unix@/run/shared_sockets/https+.pp send-proxy diff --git a/roles/dmz_ihmgit_front/defaults/main.yml b/roles/dmz_ihmgit_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_ihmgit_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_ihmgit_front/handlers/main.yml b/roles/dmz_ihmgit_front/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/dmz_ihmgit_front/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_ihmgit_front/meta.OK/main.yml b/roles/dmz_ihmgit_front/meta.OK/main.yml new file mode 100644 index 0000000..7f3de13 --- /dev/null +++ b/roles/dmz_ihmgit_front/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: dmz_nginx diff --git a/roles/dmz_ihmgit_front/tasks/main.yml b/roles/dmz_ihmgit_front/tasks/main.yml new file mode 100644 index 0000000..be88e09 --- /dev/null +++ b/roles/dmz_ihmgit_front/tasks/main.yml @@ -0,0 +1,30 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: configure nginx for Gitea + copy: + content: | + location = /git { + rewrite ^ /git/ last; + } + location {{http_pfx_gitea}}/ { + proxy_pass http://unix:/run/shared_sockets/gitea:/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + dest: /etc/nginx/inc.d/gitea.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Gitea +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_ihmldap/defaults/main.yml b/roles/dmz_ihmldap/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_ihmldap/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_ihmldap/handlers/main.yml b/roles/dmz_ihmldap/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/dmz_ihmldap/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_ihmldap/meta.OK/main.yml b/roles/dmz_ihmldap/meta.OK/main.yml new file mode 100644 index 0000000..0eecb09 --- /dev/null +++ b/roles/dmz_ihmldap/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: dmz_nginx + - role: ldap diff --git a/roles/dmz_ihmldap/tasks/main.yml b/roles/dmz_ihmldap/tasks/main.yml new file mode 100644 index 0000000..24abb3a --- /dev/null +++ b/roles/dmz_ihmldap/tasks/main.yml @@ -0,0 +1,184 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: LDAP Account Manager +### ⇐ UPSTREAM BEGIN ### + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "ldap-account-manager" + ] + aur_user: git + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: LDAP Account Manager +### ⇐ UPSTREAM END ### + +- name: ensure some directories exist + file: + path: /etc/webapps/ldap-account-manager/{{item}} + state: directory + group: http + mode: 0770 + with_items: + - pdf + - profiles + - pdf/{{nickname}} + - profiles/{{nickname}} + +- name: create the template-based PDF configuration + shell: | + cp -al templates/pdf/* "pdf/{{nickname}}/" + args: + chdir: /etc/webapps/ldap-account-manager + creates: /etc/webapps/ldap-account-manager/pdf/{{nickname}}/* + +- name: create the template-based profile configuration + shell: | + cp -al templates/pdf/* templates/profiles/* "profiles/{{nickname}}/" + args: + chdir: /etc/webapps/ldap-account-manager + creates: /etc/webapps/ldap-account-manager/profiles/{{nickname}}/* + +- name: main lam configuration + copy: + remote_src: true + src: /etc/webapps/ldap-account-manager/config.cfg.sample + dest: /etc/webapps/ldap-account-manager/config.cfg + group: http + mode: 0660 + force: false + +- name: custom lam configuration + lineinfile: + path: /etc/webapps/ldap-account-manager/config.cfg + regexp: '^{{item.key}}:' + line: '{{item.key}}: {{item.value}}' + with_dict: + default: '{{nickname}}' + logLevel: 4 + logDestination: SYSLOG + encryptSession: false + password: '{{lam_master_password}}' + passwordMinLength: '{{lam_passwordMinLength}}' + passwordMinUpper: '{{lam_passwordMinUpper}}' + passwordMinLower: '{{lam_passwordMinLower}}' + passwordMinNumeric: '{{lam_passwordMinNumeric}}' + passwordMinSymbol: '{{lam_passwordMinSymbol}}' + passwordMinClasses: '{{lam_passwordMinClasses}}' + checkedRulesCount: '{{lam_checkedRulesCount}}' + passwordMustNotContain3Chars: '{{lam_passwordMustNotContain3Chars}}' + passwordMustNotContainUser: '{{lam_passwordMustNotContainUser}}' + +- name: custom lam profile + lineinfile: + path: /etc/webapps/ldap-account-manager/{{nickname}}.conf + create: true + group: http + mode: 0660 + regexp: '^{{item.k}}:' + line: '{{item.k}}: {{item.v}}' + with_items: + - {k: 'Passwd', v: '{{lam_master_password}}'} + - {k: 'ServerURL', v: 'ldapi://%2Frun%2Fshared_sockets%2Fldapi/'} + - {k: 'serverDisplayName', v: '{{nickname}}'} + - {k: 'defaultLanguage', v: '{{locales_default}}'} + - {k: 'timeZone', v: '{{timezone}}'} + - {k: 'loginMethod', v: 'search'} + - {k: 'loginSearchSuffix', v: 'ou=Users,{{ldap_root}}'} + - {k: 'loginSearchFilter', v: 'uid=%USER%'} + - {k: 'loginSearchDN', v: ''} + - {k: 'loginSearchPassword', v: ''} + - {k: 'httpAuthentication', v: 'true'} + - {k: 'useTLS', v: 'no'} + - {k: 'treesuffix', v: '{{ldap_root}}'} + - {k: 'pwdResetAllowSpecificPassword', v: 'true'} + - {k: 'pwdResetAllowScreenPassword', v: 'true'} + - {k: 'pwdResetForcePasswordChange', v: 'true'} + - {k: 'pwdResetDefaultPasswordOutput', v: '2'} + - {k: 'tools: tool_hide_toolSchemaBrowser', v: 'true'} + - {k: 'tools: tool_hide_toolTests', v: 'true'} + - {k: 'tools: tool_hide_toolServerInformation', v: 'true'} + - {k: 'tools: tool_hide_toolProfileEditor', v: 'true'} + - {k: 'tools: tool_hide_toolPDFEditor', v: 'true'} + - {k: 'tools: tool_hide_toolOUEditor', v: 'true'} + - {k: 'tools: tool_hide_toolFileUpload', v: 'true'} + - {k: 'tools: tool_hide_toolMultiEdit', v: 'true'} + - {k: 'activeTypes', v: 'user,mailAlias'} + - {k: 'types: suffix_user', v: 'ou=Users,{{ldap_root}}'} + - {k: 'types: attr_user', v: '#uid;#givenName;#cn;#sn;#mail'} + - {k: 'types: modules_user', v: 'inetOrgPerson'} + - {k: 'types: suffix_group', v: 'ou=Groups,{{ldap_root}}'} + - {k: 'types: attr_group', v: '#cn;#memberUID'} + - {k: 'types: modules_group', v: 'posixGroup'} + - {k: 'types: suffix_mailAlias', v: 'ou=Aliases,{{ldap_root}}'} + - {k: 'types: attr_mailAlias', v: '#cn;#rfc822MailMember'} + - {k: 'types: modules_mailAlias', v: 'nisMailAlias'} + - {k: 'modules: posixAccount_pwdHash', v: 'SSHA'} + - {k: 'modules: inetOrgPerson_hideDescription', v: 'true'} + - {k: 'modules: inetOrgPerson_hideStreet', v: 'true'} + - {k: 'modules: inetOrgPerson_hidePostOfficeBox', v: 'true'} + - {k: 'modules: inetOrgPerson_hidePostalCode', v: 'true'} + - {k: 'modules: inetOrgPerson_hideLocation', v: 'true'} + - {k: 'modules: inetOrgPerson_hideState', v: 'true'} + - {k: 'modules: inetOrgPerson_hidePostalAddress', v: 'true'} + - {k: 'modules: inetOrgPerson_hideRegisteredAddress', v: 'true'} + - {k: 'modules: inetOrgPerson_hideOfficeName', v: 'true'} + - {k: 'modules: inetOrgPerson_hideRoomNumber', v: 'true'} + - {k: 'modules: inetOrgPerson_hideTelephoneNumber', v: 'true'} + - {k: 'modules: inetOrgPerson_hideHomeTelephoneNumber', v: 'true'} + - {k: 'modules: inetOrgPerson_hideMobileNumber', v: 'true'} + - {k: 'modules: inetOrgPerson_hideFaxNumber', v: 'true'} + - {k: 'modules: inetOrgPerson_hidePager', v: 'true'} + - {k: 'modules: inetOrgPerson_hideJobTitle', v: 'true'} + - {k: 'modules: inetOrgPerson_hideCarLicense', v: 'true'} + - {k: 'modules: inetOrgPerson_hideEmployeeType', v: 'true'} + - {k: 'modules: inetOrgPerson_hideBusinessCategory', v: 'true'} + - {k: 'modules: inetOrgPerson_hideDepartments', v: 'true'} + - {k: 'modules: inetOrgPerson_hideManager', v: 'true'} + - {k: 'modules: inetOrgPerson_hideOu', v: 'true'} + - {k: 'modules: inetOrgPerson_hideO', v: 'true'} + - {k: 'modules: inetOrgPerson_hideEmployeeNumber', v: 'true'} + - {k: 'modules: inetOrgPerson_hideInitials', v: 'true'} + - {k: 'modules: inetOrgPerson_hideuserCertificate', v: 'true'} + - {k: 'modules: inetOrgPerson_addAddressbook', v: 'false'} + - {k: 'modules: inetOrgPerson_readOnly_mail', v: 'true'} + - {k: 'modules: inetOrgPerson_readOnly_uid', v: 'true'} + - {k: 'modules: inetOrgPerson_hideUID', v: 'false'} + +- name: configure nginx for ldap-account-manager + copy: + content: | + location {{http_pfx_lam}} { + alias /usr/share/webapps/ldap-account-manager; + autoindex on; + rewrite ^({{http_pfx_lam}})(/.*?\.php)(/.*)?$ /php...$document_root/...$1/...$2/...$3 last; + } + dest: /etc/nginx/inc.d/lam.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: LDAP Account Manager +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_movim_front/defaults/main.yml b/roles/dmz_movim_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_movim_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_movim_front/handlers/main.yml b/roles/dmz_movim_front/handlers/main.yml new file mode 100644 index 0000000..5f86405 --- /dev/null +++ b/roles/dmz_movim_front/handlers/main.yml @@ -0,0 +1,16 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart movim.service + systemd: + daemon_reload: true + name: movim.service + state: restarted + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_movim_front/meta.OK/main.yml b/roles/dmz_movim_front/meta.OK/main.yml new file mode 100644 index 0000000..432cb9a --- /dev/null +++ b/roles/dmz_movim_front/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: dmz_prosody_front + - role: dmz_nginx diff --git a/roles/dmz_movim_front/tasks/main.yml b/roles/dmz_movim_front/tasks/main.yml new file mode 100644 index 0000000..f084bb2 --- /dev/null +++ b/roles/dmz_movim_front/tasks/main.yml @@ -0,0 +1,135 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: movim +### ⇐ UPSTREAM BEGIN ### + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "php-zmq", + "movim" + ] + aur_user: git + register: software + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: movim +### ⇐ UPSTREAM END ### + +# https://github.com/movim/movim_ynh/blob/master/scripts/install +- name: make sure that Movim works in a subdir + lineinfile: + path: /usr/share/webapps/movim/app/assets/js/movim_websocket.js + backrefs: true + regexp: "^(.*'wss://'\\s*\\+\\s*BASE_HOST\\s*\\+\\s*')(/ws/'.*$)" + line: '\1{{http_pfx_movim}}\2' + notify: + - restart movim.service + +- name: configure movim + lineinfile: + path: /etc/webapps/movim/db.inc.php + regexp: "^\\s*'{{item.key}}'" + line: " '{{item.key}}' => {{item.value}}," + with_dict: + type: "'pgsql'" + username: "'{{movim_db_user}}'" + password: "'{{movim_db_password}}'" + host: "'/run/shared_sockets'" + port: 5432 + database: "'{{movim_db}}'" + notify: + - restart movim.service + +- name: configure movim launch + lineinfile: + path: /etc/default/movim + regexp: "^{{item.key}}=" + line: "{{item.key}}={{item.value}}" + with_dict: + MOVIM_URL: https://{{net_soa}}{{http_pfx_movim}} + MOVIM_PORT: '{{movim_private_port}}' + MOVIM_INTERFACE: '127.0.0.1' + notify: + - restart movim.service + +- name: prepare to override movim security + file: + path: /etc/systemd/system/movim.service.d + state: directory + mode: 0755 + +- name: override movim security with systemd + copy: + content: | + [Service] + User=http + Group=http + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_LEASE CAP_NET_BIND_SERVICE CAP_SYS_CHROOT + PrivateTmp=true + PrivateDevices=true + ProtectSystem=true + ProtectHome=true + NoNewPrivileges=true + ExecStartPre=/usr/bin/php mud.php config --username={{movim_admin_user}} --password={{movim_admin_password}} + dest: /etc/systemd/system/movim.service.d/secure-{{nickname}}.conf + mode: 0644 + notify: + - restart movim.service + +- name: enable movim.service + systemd: + daemon_reload: true + name: movim.service + enabled: true + +- name: configure nginx for movim + copy: + content: | + location {{http_pfx_movim}} { + alias /usr/share/webapps/movim; + autoindex on; + include inc.d/{{nickname}}_php-fast.inc; + } + location {{http_pfx_movim}}/ws/ { + proxy_pass http://127.0.0.1:{{movim_private_port}}/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_redirect off; + } + location ~ ^{{http_pfx_movim}}/(?:log|config)/ { + deny all; + } + dest: /etc/nginx/inc.d/movim.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: movim +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_nextcloud_front/defaults/main.yml b/roles/dmz_nextcloud_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_nextcloud_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_nextcloud_front/handlers/main.yml b/roles/dmz_nextcloud_front/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/dmz_nextcloud_front/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_nextcloud_front/meta.OK/main.yml b/roles/dmz_nextcloud_front/meta.OK/main.yml new file mode 100644 index 0000000..7f3de13 --- /dev/null +++ b/roles/dmz_nextcloud_front/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: dmz_nginx diff --git a/roles/dmz_nextcloud_front/tasks/main.yml b/roles/dmz_nextcloud_front/tasks/main.yml new file mode 100644 index 0000000..ead832b --- /dev/null +++ b/roles/dmz_nextcloud_front/tasks/main.yml @@ -0,0 +1,32 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: configure Nginx for Nextcloud + template: + src: templates/nginx_nextcloud.j2 + dest: /etc/nginx/inc.d/nextcloud.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +- name: configure Nginx for LibreOffice OnLine + template: + src: templates/nginx_lool.j2 + dest: /etc/nginx/inc.d/lool.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Nextcloud +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_nextcloud_front/templates/nginx_lool.j2 b/roles/dmz_nextcloud_front/templates/nginx_lool.j2 new file mode 100644 index 0000000..04096eb --- /dev/null +++ b/roles/dmz_nextcloud_front/templates/nginx_lool.j2 @@ -0,0 +1,49 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# Nextcloud BUG +location ~ {{http_pfx_nextcloud}}/index.php/apps/richdocuments/(?:css|js|img|l10n|assets) { + rewrite ^{{http_pfx_nextcloud}}/index.php/apps/richdocuments(.*)$ {{http_pfx_nextcloud}}/index.php/xapps/richdocuments$1$is_args$query_string; +} + +# static files +location ^~ /loleaflet { + proxy_pass http://{{SafeZone}}:9980; + proxy_set_header Host $http_host; +} + +# WOPI discovery URL +location ^~ /hosting/discovery { + proxy_pass http://{{SafeZone}}:9980; + proxy_set_header Host $http_host; +} + +location ^~ /lool { + # this parent location makes sure that a global PHP catch-all + # won’t pre-empt the first two child locations below + + # main websocket + location ~ ^/lool/(.*)/ws$ { + proxy_pass http://{{SafeZone}}:9980; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $http_host; + proxy_read_timeout 36000s; + } + + # download, presentation and image upload + location ~ ^/lool { + proxy_pass http://{{SafeZone}}:9980; + proxy_set_header Host $http_host; + } + + # Admin Console websocket + location ^~ /lool/adminws { + proxy_pass http://{{SafeZone}}:9980; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $http_host; + proxy_read_timeout 36000s; + } +} diff --git a/roles/dmz_nextcloud_front/templates/nginx_nextcloud.j2 b/roles/dmz_nextcloud_front/templates/nginx_nextcloud.j2 new file mode 100644 index 0000000..a287df7 --- /dev/null +++ b/roles/dmz_nextcloud_front/templates/nginx_nextcloud.j2 @@ -0,0 +1,58 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# https://docs.nextcloud.com/server/12/admin_manual/installation/nginx.html + +location ~ ^/\.well-known/ca(?:rd|l)dav { + rewrite ^/.well-known/carddav https://{{net_soa}}{{http_pfx_nextcloud}}/remote.php/dav/ redirect; + rewrite ^/.well-known/caldav https://{{net_soa}}{{http_pfx_nextcloud}}/remote.php/dav/ redirect; +} + +location {{http_pfx_nextcloud}} { + # BUG Nextcloud/Polls + location ~ ^(?:{{http_pfx_nextcloud}})+/apps/polls { + rewrite ^(?:{{http_pfx_nextcloud}})+/apps/polls(.*)$ {{http_pfx_nextcloud}}/xapps/polls$1$is_args$query_string; + } + + location {{http_pfx_nextcloud}} { + rewrite ^{{http_pfx_nextcloud}}(.*) {{http_pfx_nextcloud}}/index.php$1$is_args$query_string; + } + + location ~ ^{{http_pfx_nextcloud}}/(?:build|tests|config|lib|3rdparty|templates|data)/ { + deny all; + } + location ~ ^{{http_pfx_nextcloud}}/(?:\.|autotest|occ|issue|indie|db_|console) { + deny all; + } + + location ~ ^(?:{{http_pfx_nextcloud}})+(/(?:(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12])\.php|(?:updater|ocs-provider)(?:/(?:.(?.*)/\.\.\.(?.*)/\.\.\.(?.*)/\.\.\.(?.*)$ { + fastcgi_pass unix:/run/shared_sockets/php-fpm; + fastcgi_index index.php; + fastcgi_send_timeout 60m; + fastcgi_read_timeout 60m; + include fastcgi.conf; + fastcgi_param HTTPS $proxy_https if_not_empty; + fastcgi_param REQUEST_SCHEME $proxy_https if_not_empty; + fastcgi_param SERVER_PORT $proxy_port if_not_empty; + fastcgi_param SCRIPT_FILENAME $p_doc_root$p_script; + fastcgi_param SCRIPT_NAME $p_prefix$p_script; + fastcgi_param REQUEST_URI $p_prefix$p_script$p_pathinfo$is_args$query_string; + fastcgi_param DOCUMENT_URI $p_prefix$p_script$p_pathinfo; + fastcgi_param DOCUMENT_ROOT $p_doc_root; + fastcgi_param PATH_INFO $p_pathinfo if_not_empty; + } +} diff --git a/roles/dmz_prosody_front/defaults/main.yml b/roles/dmz_prosody_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_prosody_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_prosody_front/handlers/main.yml b/roles/dmz_prosody_front/handlers/main.yml new file mode 100644 index 0000000..904177f --- /dev/null +++ b/roles/dmz_prosody_front/handlers/main.yml @@ -0,0 +1,16 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart prosody.service + systemd: + daemon_reload: true + name: prosody.service + state: restarted + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_prosody_front/meta.OK/main.yml b/roles/dmz_prosody_front/meta.OK/main.yml new file mode 100644 index 0000000..5961be5 --- /dev/null +++ b/roles/dmz_prosody_front/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: ldap + - role: dmz_nginx diff --git a/roles/dmz_prosody_front/tasks/main.yml b/roles/dmz_prosody_front/tasks/main.yml new file mode 100644 index 0000000..40df8fd --- /dev/null +++ b/roles/dmz_prosody_front/tasks/main.yml @@ -0,0 +1,330 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Prosody +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - prosody + - postgresql-libs + - lua51-sec + - lua51-bitop + - lua51-dbi + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "lua51-event", + "lua51-lpty", + "prosody-mod-auth-external-hg", + "prosody-mod-auto-accept-subscriptions-hg", + "prosody-mod-csi-hg", + "prosody-mod-filter-chatstates-hg", + "prosody-mod-http-upload-external-hg", + "prosody-mod-offline-email-hg", + "prosody-mod-smacks", + "prosody-mod-throttle_presence" + ] + aur_user: git + # "prosody-mod-log-auth", + # "prosody-mod-mam-archive", + # "prosody-mod-mam-muc", + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Prosody +### ⇐ UPSTREAM END ### + +- name: set ownership of prosody’s working directory + file: + path: /var/lib/prosody + state: directory + owner: prosody + group: jabber + mode: 0751 + +- name: create a directory for HTTP files + file: + path: /var/lib/prosody/httpd + state: directory + owner: prosody + group: jabber + mode: 0750 + +- name: create a directory for HTTP uploads + file: + path: /var/lib/prosody/http_upload + state: directory + owner: http + group: jabber + mode: 06770 + +- name: prepare overriding prosody settings + file: + name: /etc/systemd/system/prosody.service.d + state: directory + mode: 0755 + +- name: secure prosody systemd settings + copy: + content: | + [Service] + User=prosody + Group=jabber + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_LEASE CAP_SYS_CHROOT + PrivateTmp=true + PrivateDevices=true + ProtectSystem=full + ProtectHome=true + NoNewPrivileges=true + dest: /etc/systemd/system/prosody.service.d/secure-{{nickname}}.conf + mode: 0644 + notify: + - restart prosody.service + +- name: set XMPP admins + lineinfile: + path: /etc/prosody/prosody.cfg.lua + regexp: '^admins\s*=' + line: 'admins = { ''{{xmpp_admins | replace(" ", "'', ''")}}'' }' + notify: + - restart prosody.service + +- name: enable libevent + lineinfile: + path: /etc/prosody/prosody.cfg.lua + regexp: '^-*use_libevent\s*=' + line: 'use_libevent = true;' + notify: + - restart prosody.service + +- name: enable some modules + replace: + path: /etc/prosody/prosody.cfg.lua + regexp: '^(\s*)-+("{{item}}";.*)$' + replace: '\1\2' + with_items: + - blocklist + - bosh + - carbons + - groups + - http_files + - mam + - saslauth + - websocket + notify: + - restart prosody.service + +- name: enable additional modules + blockinfile: + path: /etc/prosody/prosody.cfg.lua + marker: ' -- {mark} Additional modules' + block: | + "auto_accept_subscriptions"; -- friends automatically accepted + "csi"; -- filter activity depending on mobile state + "filter_chatstates"; -- csi: filter chat states when inactive + "http_upload_external"; -- share files in MUCs + "lastactivity"; -- query users’ idle time + --"log_auth"; -- log authentication failures for fail2ban + "mam_adhoc"; -- manage mam from the client + --"mam_archive"; -- allow mam-enabled clients to read MUC logs + --"mam_muc"; -- record MUC messages using mam + "offline_email"; -- get missed messages by email + "pubsub"; -- publish-suscribe / lien social + "smacks"; -- ignore temporary disconnects + "throttle_presence"; -- csi: limit presence updates when inactive + insertafter: '^\s*modules_enabled\s*=\s*{' + notify: + - restart prosody.service + +- name: set BASH authentication + lineinfile: + path: /etc/prosody/prosody.cfg.lua + regexp: '^\s*authentication\s*=' + line: 'authentication = "external"' + notify: + - restart prosody.service + +- name: send authentication script + copy: + content: | + #!/bin/bash + function ldap_esc() { + printf %s "$1" | (LANG=C + grep -o . | while IFS='' read -r c; do + [[ "$c" =~ [-.A-Za-z0-9] ]] && printf %s "$c" || printf \\%02x "'$c" + done) + } + function do_auth() { + local u d p + IFS=: read u d p <<<"$1" + ldapwhoami -H 'ldapi://%2Frun%2Fshared_sockets%2Fldapi/' \ + -D "uid=$(ldap_esc "$u"),ou=Users,{{ldap_root}}" -w "$p" + } + function do_isuser() { + local u d + IFS=: read u d <<<"$1" + ldapsearch -H 'ldapi://%2Frun%2Fshared_sockets%2Fldapi/' -A -s sub -x \ + -b 'ou=Users,{{ldap_root}}' "(uid=$(ldap_esc "$u"))" | grep ^uid: + } + function do_setpass() { + false + } + while true; do + IFS=: read fct params || { sleep 1s; continue; } + case "$fct" in + auth) do_auth "$params" ;; + isuser) do_isuser "$params" ;; + setpass) do_setpass "$params" ;; + *) false ;; + esac >/dev/null + if [ $? -eq 0 ]; then + echo "$fct:${params%%:*} YES" | systemd-cat -t "prosody_auth" -p notice + echo 1 + else + echo "$fct:${params%%:*} NO" | systemd-cat -t "prosody_auth" -p notice + echo 0 + fi + done + dest: /etc/prosody/external_auth.sh + owner: prosody + mode: 0500 + +- name: set SQL storage + lineinfile: + path: /etc/prosody/prosody.cfg.lua + regexp: '^\s*(?:--)?storage\s*=' + line: 'storage = "sql" -- Default is "internal"' + notify: + - restart prosody.service + +- name: enable PostgreSQL access + lineinfile: + path: /etc/prosody/prosody.cfg.lua + regexp: '^\s*(?:--)?sql\s*=.*PostgreSQL' + line: > + sql = { + driver = "PostgreSQL", + database = "{{prosody_db}}", + username = "{{prosody_db_user}}", + password = "{{prosody_db_password}}", + host = "/run/shared_sockets"} + +- name: custom extra configuration + blockinfile: + path: /etc/prosody/prosody.cfg.lua + marker: '-- {mark} Additional configuration' + block: | + -- configure bash authentication + external_auth_command = "/etc/prosody/external_auth.sh" + -- hide OS type from mod_version output + hide_os_type = true + -- limit registration + allow_registration = true + whitelist_registration_only = true + registration_whitelist = { '{{xmpp_registration_hosts | replace(" ", "', '")}}' } + -- configure HTTP + http_files_dir = "/var/lib/prosody/httpd" + http_paths = { + websocket = "{{http_pfx_prosody}}websocket"; + bosh = "{{http_pfx_prosody}}bind"; + files = "{{http_pfx_prosody}}shared"; + upload = "{{http_pfx_prosody}}upload"; + } + http_default_host = "{{net_soa}}" + http_external_url = "https://{{net_soa}}" + -- configure uploads + http_upload_external_base_url = "https://{{net_soa}}/xmpp-upload/" + http_upload_external_secret = "{{xmpp_upload_secret}}" + http_upload_file_size_limit = 5 * 1024 * 1024 -- 5MB in bytes + --http_upload_expire_after = 60 * 60 * 24 * 7 -- a week in seconds + -- configure websockets (ws:localhost:5280/websocket) + cross_domain_websocket = true + consider_websocket_secure = true + -- configure BOSH (http://localhost:5280/bind) + cross_domain_bosh = true + consider_bosh_secure = true + -- configure MAM + default_archive_policy = "roster" + archive_expires_after = "1m" + archive_cleanup_interval = 24 * 60 * 60 -- once a day + muc_log_by_default = true + max_history_messages = 500 + -- configure email sending + smtp_from = "xmpp-offline-do-not-reply@{{net_soa}}" + -- setup the virtual host + VirtualHost "{{net_soa}}" + -- declare publish-suscribe + Component "{{net_subdom_pubsub}}.{{net_soa}}" "pubsub" + -- declare Multi-User Chat + Component "{{net_subdom_muc}}.{{net_soa}}" "muc" + insertbefore: '^--+ Virtual hosts' + notify: + - restart prosody.service + +- name: use http_upload_external’s PHP handler + template: + src: templates/xep0363_http_upload.php.j2 + dest: /srv/webapps/xep0363_http_upload.php + group: http + mode: 0755 + +- name: configure nginx for prosody + copy: + content: | + location {{http_pfx_prosody}} { + proxy_pass http://localhost:5280; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_buffering off; + tcp_nodelay on; + } + location {{http_pfx_prosody}}websocket { + proxy_pass http://localhost:5280; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 30m; + proxy_buffering off; + tcp_nodelay on; + } + location {{http_pfx_prosody}}upload { + rewrite ^({{http_pfx_prosody}}upload)(/.*)?$ /php.../srv/webapps/xep0363_http_upload.php/...$1/.../...$2 last; + } + dest: /etc/nginx/inc.d/prosody.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +- name: enable prosody + systemd: + daemon_reload: true + name: prosody.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Prosody +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dmz_prosody_front/templates/xep0363_http_upload.php.j2 b/roles/dmz_prosody_front/templates/xep0363_http_upload.php.j2 new file mode 100644 index 0000000..53ecc8d --- /dev/null +++ b/roles/dmz_prosody_front/templates/xep0363_http_upload.php.j2 @@ -0,0 +1,141 @@ + + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\*/ +/* CONFIGURATION OPTIONS */ +/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\*/ + +/* Change this to a directory that is writable by your web server, but is outside your web root */ +$CONFIG_STORE_DIR = '/var/lib/prosody/http_upload'; + +/* This must be the same as 'http_upload_external_secret' that you set in Prosody's config file */ +$CONFIG_SECRET = '{{xmpp_upload_secret}}'; + +/* Debug your server setup by replacing `false` with `true` on this line */ +$DEBUG = false; + +/* For people who need options to tweak that they don't understand... here you are */ +$CONFIG_CHUNK_SIZE = 4096; + +/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\*/ +/* END OF CONFIGURATION */ +/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\*/ + +/* Do not edit below this line unless you know what you are doing (spoiler: nobody does) */ + +if ($DEBUG) { + error_log('$_SERVER[DOCUMENT_URI] ='.$_SERVER['DOCUMENT_URI']); + error_log('$_SERVER[SCRIPT_NAME] ='.$_SERVER['SCRIPT_NAME']); + error_log('$_SERVER[REQUEST_METHOD] ='.$_SERVER['REQUEST_METHOD']); + error_log('$_SERVER[HTTP_CONTENT_LENGTH]='.$_SERVER['HTTP_CONTENT_LENGTH']); + error_log('$_GET[v] ='.$_GET['v']); +} + +// $_SERVER['PATH_INFO'] would serve us better here, but it _might_ be incorrectly set... +$upload_file_name = substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME'])+1); +$store_file_name = $CONFIG_STORE_DIR . '/store-' . hash('sha256', $upload_file_name); + +$request_method = $_SERVER['REQUEST_METHOD']; + +if(array_key_exists('v', $_GET) === TRUE && $request_method === 'PUT') { + $upload_file_size = array_key_exists('CONTENT_LENGTH', $_SERVER) + ? $_SERVER['CONTENT_LENGTH'] + : (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER) + ? $_SERVER['HTTP_CONTENT_LENGTH'] + : getallheaders()['Content-Length']); + $upload_token = $_GET['v']; + + $calculated_token = hash_hmac('sha256', "$upload_file_name $upload_file_size", $CONFIG_SECRET); + if($upload_token !== $calculated_token) { + if ($DEBUG) error_log('EXIT 403 (Content-Length probably wrong)'); + header('HTTP/1.0 403 Forbidden'); + exit; + } + + /* Open a file for writing */ + $store_file = fopen($store_file_name, 'x'); + + if($store_file === FALSE) { + if ($DEBUG) error_log('EXIT 409 (permissions probably wrong on the storage directory)'); + header('HTTP/1.0 409 Conflict'); + exit; + } + + /* PUT data comes in on the stdin stream */ + $incoming_data = fopen('php://input', 'r'); + + /* Read the data a chunk at a time and write to the file */ + while ($data = fread($incoming_data, $CONFIG_CHUNK_SIZE)) { + fwrite($store_file, $data); + } + + /* Close the streams */ + fclose($incoming_data); + fclose($store_file); +} else if($request_method === 'GET' || $request_method === 'HEAD') { + // Send file (using X-Sendfile would be nice here...) + if(file_exists($store_file_name)) { + if ($DEBUG) error_log('EXIT 200 (all seems to be fine)'); + header('Content-Disposition: attachment'); + header('Content-Type: '.mime_content_type($store_file_name)); + header('Content-Length: '.filesize($store_file_name)); + if($request_method !== 'HEAD') { + readfile($store_file_name); + } + } else { + if ($DEBUG) error_log('EXIT 404 (file probably absent or not readable by your web server)'); + header('HTTP/1.0 404 Not Found'); + } +} else { + if ($DEBUG) error_log('EXIT 400 (REQUEST_METHOD not set, or PUT without a token)'); + header('HTTP/1.0 400 Bad Request'); +} + +exit; diff --git a/roles/dmz_wallabag_front/defaults/main.yml b/roles/dmz_wallabag_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dmz_wallabag_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dmz_wallabag_front/handlers/main.yml b/roles/dmz_wallabag_front/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/dmz_wallabag_front/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/dmz_wallabag_front/meta.OK/main.yml b/roles/dmz_wallabag_front/meta.OK/main.yml new file mode 100644 index 0000000..d65c8b6 --- /dev/null +++ b/roles/dmz_wallabag_front/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: dmz_nginx diff --git a/roles/dmz_wallabag_front/tasks/main.yml b/roles/dmz_wallabag_front/tasks/main.yml new file mode 100644 index 0000000..3af7ba7 --- /dev/null +++ b/roles/dmz_wallabag_front/tasks/main.yml @@ -0,0 +1,101 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: wallabag +### ⇐ UPSTREAM BEGIN ### + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "wallabag" + ] + aur_user: git + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: wallabag +### ⇐ UPSTREAM END ### + +- name: configure wallabag + lineinfile: + path: /etc/webapps/wallabag/parameters.yml + regexp: '^\s*{{item.key}}:' + line: ' {{item.key}}: {{item.value}}' + with_dict: + database_driver: pdo_pgsql + database_host: /run/shared_sockets + database_port: 5432 + database_name: '{{wallabag_db}}' + database_user: '{{wallabag_db_user}}' + database_password: '{{wallabag_db_password}}' + database_path: 'null' + database_table_prefix: wallabag_ + database_socket: 'null' + locale: '{{locales_default | truncate(2, True, "", 0)}}' + +#- name: check that wallabag is already initialized +# shell: > +# grep -E '^[[:blank:]]*init_done:[[:blank:]]*true$' +# /etc/webapps/wallabag/parameters.yml || true +# register: check_init_wallabag +# changed_when: false +# +#- name: run the initialization script +# command: > +# ./bin/console wallabag:install -e prod --quiet --no-interaction --no-debug +# become: true +# become_user: http +# args: +# chdir: /usr/share/webapps/wallabag +# when: +# - ((check_init_wallabag.stdout is not defined) or (check_init_wallabag.stdout == '')) +# +#- name: initialization done +# lineinfile: +# path: /etc/webapps/wallabag/parameters.yml +# regexp: '^\s*init_done:' +# line: ' init_done: true' + +- name: configure nginx for wallabag + copy: + content: | + location {{http_pfx_wallabag}} { + alias /usr/share/webapps/wallabag/web; + try_files $uri @wallabag; + } + location @wallabag { + rewrite ^ {{http_pfx_wallabag}}/app.php/$is_args$args; + } + location ~ ^{{http_pfx_wallabag}}/app\.php(/|$) { + alias /usr/share/webapps/wallabag/web; + rewrite ^({{http_pfx_wallabag}})(/.*?\.php)(/.*)?$ /php...$document_root/...$1/...$2/...$3 last; + } + location ~ ^{{http_pfx_wallabag}}.*\.php$ { + return 404; + } + dest: /etc/nginx/inc.d/wallabag.https.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: wallabag +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dotclear_back/defaults/main.yml b/roles/dotclear_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dotclear_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dotclear_back/meta.OK/main.yml b/roles/dotclear_back/meta.OK/main.yml new file mode 100644 index 0000000..5d48b7c --- /dev/null +++ b/roles/dotclear_back/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: postgresql diff --git a/roles/dotclear_back/tasks/main.yml b/roles/dotclear_back/tasks/main.yml new file mode 100644 index 0000000..df5da44 --- /dev/null +++ b/roles/dotclear_back/tasks/main.yml @@ -0,0 +1,28 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: PostgreSQL user for dotClear + postgresql_user: + login_unix_socket: /run/shared_sockets + name: "{{dotclear_db_user}}" + password: "{{dotclear_db_password}}" + encrypted: true + become: true + become_user: postgres + +- name: PostgreSQL database for dotClear + postgresql_db: + login_unix_socket: /run/shared_sockets + name: "{{dotclear_db}}" + owner: "{{dotclear_db_user}}" + become: true + become_user: postgres + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: dotClear +### ⇐ LOCAL COMMIT ### diff --git a/roles/dovecot/defaults/main.yml b/roles/dovecot/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/dovecot/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/dovecot/handlers/main.yml b/roles/dovecot/handlers/main.yml new file mode 100644 index 0000000..63726b5 --- /dev/null +++ b/roles/dovecot/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart dovecot.service + systemd: + daemon_reload: true + name: dovecot.service + state: restarted diff --git a/roles/dovecot/meta.OK/main.yml b/roles/dovecot/meta.OK/main.yml new file mode 100644 index 0000000..93cf1c9 --- /dev/null +++ b/roles/dovecot/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: sockets + - role: ldap diff --git a/roles/dovecot/tasks/main.yml b/roles/dovecot/tasks/main.yml new file mode 100644 index 0000000..978be59 --- /dev/null +++ b/roles/dovecot/tasks/main.yml @@ -0,0 +1,91 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Dovecot +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - dovecot + - pigeonhole + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Dovecot +### ⇐ UPSTREAM END ### + +- name: prepare overriding dovecot settings + file: + name: /etc/systemd/system/dovecot.service.d + state: directory + mode: 0755 + +- name: override dovecot.service settings + copy: + content: | + [Unit] + After=systemd-tmpfiles-setup.service + dest: /etc/systemd/system/dovecot.service.d/shared_sockets.conf + mode: 0644 + notify: + - restart dovecot.service + +- name: secure Dovecot systemd settings + copy: + content: | + [Service] + ProtectSystem=full + dest: /etc/systemd/system/dovecot.service.d/secure-{{nickname}}.conf + mode: 0644 + notify: + - restart dovecot.service + +- name: main configuration + template: + src: templates/dovecot.conf.j2 + dest: /etc/dovecot/dovecot.conf + mode: 0644 + notify: + - restart dovecot.service + +- name: passdb configuration + template: + src: templates/dovecot-ldap-passdb.conf.j2 + dest: /etc/dovecot/dovecot-ldap-passdb.conf + mode: 0600 + notify: + - restart dovecot.service + +- name: userdb configuration + copy: + remote_src: true + src: /etc/dovecot/dovecot-ldap-passdb.conf + dest: /etc/dovecot/dovecot-ldap-userdb.conf + mode: 0600 + notify: + - restart dovecot.service + +- name: enable Dovecot + systemd: + daemon_reload: true + name: dovecot.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Dovecot +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/dovecot/templates/dovecot-ldap-passdb.conf.j2 b/roles/dovecot/templates/dovecot-ldap-passdb.conf.j2 new file mode 100644 index 0000000..46aad22 --- /dev/null +++ b/roles/dovecot/templates/dovecot-ldap-passdb.conf.j2 @@ -0,0 +1,26 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# This file is opened as root, so it should be owned by root and mode 0600. + +hosts = {{hostname}} +uris = ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + +# LDAP library debug level as specified by LDAP_DEBUG_* in ldap_log.h. +# -1 = everything. You may need to recompile OpenLDAP with debugging enabled +# to get enough output. +#debug_level = 0 + +auth_bind = yes +auth_bind_userdn = uid=%n,ou=Users,{{ldap_root}} + +user_attrs = \ + =home=%{ldap:homeDirectory}, \ + =uid=%{ldap:uidNumber}, \ + =gid=%{ldap:gidNumber}, \ + =mail=maildir:/var/spool/mail/%n/Maildir + +user_filter = (&(objectClass=posixAccount)(uid=%n)) +base = ou=Users,{{ldap_root}} +scope = onelevel diff --git a/roles/dovecot/templates/dovecot.conf.j2 b/roles/dovecot/templates/dovecot.conf.j2 new file mode 100644 index 0000000..f606323 --- /dev/null +++ b/roles/dovecot/templates/dovecot.conf.j2 @@ -0,0 +1,148 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +## Dovecot configuration file (/usr/share/doc/dovecot/example-config) + +# "doveconf -n" command gives a clean output of the changed settings. Use it +# instead of copy&pasting files when posting to the Dovecot mailing list. + +# Most (but not all) settings can be overridden by different protocols and/or +# source/destination IPs by placing the settings inside sections, for example: +# protocol imap { }, local 127.0.0.1 { }, remote 10.0.0.0/8 { } + +## dovecot.conf +protocols = imap lmtp sieve +instance_name = dovecot +verbose_proctitle = yes +login_greeting = Ready. +login_trusted_networks = {{net_trusted_ranges}} +import_environment = TZ LANG + +## 10-auth.conf +auth_default_realm = {{net_soa}} +auth_username_format = %Ln +disable_plaintext_auth = no + +## 10-logging.conf +auth_verbose = yes + +## 10-mail.conf +mail_server_admin = mailto:postmaster@{{net_soa}} +mail_temp_dir = /var/tmp +first_valid_uid = 1 +first_valid_gid = 1 +mailbox_list_index = yes + +namespace inbox { + inbox = yes +} + +## 10-master.conf +haproxy_trusted_networks = {{DMZ_IP}} + +service imap-login { + inet_listener imap { + port = 143 + } + inet_listener imap_haproxy { + port = 220 + haproxy = yes + } +} +service lmtp { + unix_listener /run/shared_sockets/lmtp { + mode = 0666 + } +# # Create inet listener only if you can't use the above UNIX socket +# #inet_listener lmtp { +# # Avoid making LMTP visible for the entire internet +# #address = +# #port = +# #} +} +service imap { +} +service auth { + unix_listener auth-userdb { + } +} +service auth-worker { + user = $default_internal_user +} +#service dict { +# # If dict proxy is used, mail processes should have access to its socket. +# # For example: mode=0660, group=vmail and global mail_access_groups=vmail +# unix_listener dict { +# #mode = 0600 +# #user = +# #group = +# } +#} + +## 10-ssl.conf +ssl = no + +## 15-lda.conf +postmaster_address = postmaster@{{net_soa}} +submission_host = {{DMZ}} +lda_mailbox_autocreate = yes +lda_mailbox_autosubscribe = yes + +## 15-mailboxes.conf +namespace inbox { + mailbox Drafts { + special_use = \Drafts + } + mailbox Junk { + special_use = \Junk + } + mailbox Trash { + special_use = \Trash + } + mailbox Sent { + special_use = \Sent + } +} + +## 20-imap.conf +protocol imap { +} + +## 20-lmtp.conf +protocol lmtp { + mail_plugins = sieve + auth_username_format = %Ln +} + +## 20-managesieve.conf +service managesieve-login { + inet_listener sieve { + } +} +service managesieve { +} +protocol sieve { + managesieve_implementation_string = sieve +} + +## 90-sieve.conf +plugin { + sieve = file:~/sieve;active=~/.dovecot.sieve + sieve_redirect_envelope_from = orig_recipient + sieve_trace_level = actions +} + +## auth-ldap.conf.ext +# https://wiki2.dovecot.org/AuthDatabase/LDAP +# https://wiki2.dovecot.org/AuthDatabase/LDAP/AuthBinds +# https://wiki2.dovecot.org/AuthDatabase/LDAP/Userdb +# https://wiki2.dovecot.org/UserDatabase/ExtraFields +passdb { + driver = ldap + args = /etc/dovecot/dovecot-ldap-passdb.conf +} +userdb { + driver = ldap + args = /etc/dovecot/dovecot-ldap-userdb.conf +} diff --git a/roles/etckeeper.inc/tasks/init.yml b/roles/etckeeper.inc/tasks/init.yml new file mode 100644 index 0000000..6f141a6 --- /dev/null +++ b/roles/etckeeper.inc/tasks/init.yml @@ -0,0 +1,26 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: install etckeeper + package: + name: etckeeper + when: (chroot == "") + +- name: send some scripts in {{chroot}}/usr/local/bin/ + copy: + src: files/bin/ + dest: "{{chroot}}/usr/local/bin/" + mode: 0700 + +- name: one more script in {{chroot}}/usr/local/bin/ + template: + src: templates/etc_init.j2 + dest: "{{chroot}}/usr/local/bin/etc_init" + mode: 0700 + +- name: enable etckeeper + command: "{{chroot}}/usr/local/bin/etc_init" + args: + creates: "{{chroot}}/etc/.git" diff --git a/roles/etckeeper.inc/tasks/local.yml b/roles/etckeeper.inc/tasks/local.yml new file mode 100644 index 0000000..c396d8e --- /dev/null +++ b/roles/etckeeper.inc/tasks/local.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: store /etc changes + command: "{{chroot}}/usr/local/bin/etc_stop_local '{{msg}}'" + changed_when: false diff --git a/roles/etckeeper.inc/tasks/merge.yml b/roles/etckeeper.inc/tasks/merge.yml new file mode 100644 index 0000000..6c08fab --- /dev/null +++ b/roles/etckeeper.inc/tasks/merge.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: switch Git to run + command: "{{chroot}}/usr/local/bin/etc_stop_upstream '{{msg}}'" + changed_when: false diff --git a/roles/etckeeper.inc/tasks/upstream.yml b/roles/etckeeper.inc/tasks/upstream.yml new file mode 100644 index 0000000..994775a --- /dev/null +++ b/roles/etckeeper.inc/tasks/upstream.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: switch Git to master + command: "{{chroot}}/usr/local/bin/etc_start_upstream '{{msg}}'" + changed_when: false diff --git a/roles/front/defaults/main.yml b/roles/front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/front/files/DMZ.nspawn b/roles/front/files/DMZ.nspawn new file mode 100644 index 0000000..0866ebe --- /dev/null +++ b/roles/front/files/DMZ.nspawn @@ -0,0 +1,15 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +[Exec] +NotifyReady=yes +Capability=CAP_NET_ADMIN +;DropCapability=CAP_AUDIT_CONTROL CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FSETID CAP_SYS_PTRACE + +[Files] +Bind=/var/cache/pacman/pkg +Bind=/run/shared_sockets + +[Network] +Bridge=wire diff --git a/roles/front/files/nspawn_override.conf b/roles/front/files/nspawn_override.conf new file mode 100644 index 0000000..4aa77b1 --- /dev/null +++ b/roles/front/files/nspawn_override.conf @@ -0,0 +1,12 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +[Unit] +After=systemd-tmpfiles-setup.service + +[Service] +ExecStart= +ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=guest --settings=override --machine=%i +TimeoutStartSec=300 +TimeoutStopSec=300 diff --git a/roles/front/meta.OK/main.yml b/roles/front/meta.OK/main.yml new file mode 100644 index 0000000..2487b03 --- /dev/null +++ b/roles/front/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init + - role: cleanupdate diff --git a/roles/front/tasks/main.yml b/roles/front/tasks/main.yml new file mode 100644 index 0000000..9070bd2 --- /dev/null +++ b/roles/front/tasks/main.yml @@ -0,0 +1,163 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: 'arch-install-scripts' +### ⇐ UPSTREAM BEGIN ### + +- name: install arch-install-scripts + package: + name: arch-install-scripts + state: present + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: 'arch-install-scripts' +### ⇐ UPSTREAM END ### + +- name: send a remote-exec script to the host + template: + src: templates/DMZ.j2 + dest: "/usr/local/bin/{{DMZ}}" + mode: 0755 + +- name: create the DMZ container directory and needed paths + file: + path: "{{item}}" + state: directory + mode: 0755 + with_items: + - "{{front_dir}}" + +- name: install an Archlinux container + shell: > + pacstrap -c -d {{front_dir}} + $( + pacman -Qg base + | grep -vE + ' (bzip2|dhcpcd|gzip|licenses|linux|lvm2|mdadm|pciutils|reiserfsprogs|systemd-sysvcompat|texinfo|usbutils|xfsprogs)$' + | cut -d' ' -f2 + ) + busybox openssh python2 etckeeper + args: + creates: "{{front_dir}}/usr" + register: arch + notify: + - restart DMZ + +- name: enable BusyBox… + command: | + arch-chroot {{front_dir}} /usr/bin/busybox --install + args: + creates: "{{front_dir}}/usr/bin/ash" + +- name: … but not for some binaries + file: + path: "{{front_dir}}/usr/bin/{{item}}" + state: absent + when: (arch.changed) + with_items: + # base-devel needs patch + - patch + # exim owns sendmail + - sendmail + # spamassassin needs gcc needs binutils owns ar + strings + - ar + - strings + # util-linux owns setpriv, rfkill + - setpriv + - rfkill + # net-tools owns arp, ifconfig, iptunnel, nameif, netstat, route, slattach + - arp + - ifconfig + - iptunnel + - nameif + - netstat + - route + - slattach + # vim owns xxd + - xxd + +- name: copy some files from host to container + copy: + remote_src: true + src: "{{item}}" + dest: "{{front_dir}}{{item}}" + mode: 0644 + with_items: + - /etc/pacman.conf + - /etc/resolv.conf + when: + - (arch.changed) + +- name: create .ssh in the container + file: + path: "{{front_dir}}/root/.ssh" + state: directory + mode: 0700 + +- name: init the container + include_role: + name: init + vars_from: front_chroot.yml + +- name: init SSH in the container + include_role: + name: ssh + vars_from: front_chroot.yml + +- name: ensure systemd-nspawn@.service.d exists + file: + path: /etc/systemd/system/systemd-nspawn@.service.d + state: directory + mode: 0755 + +- name: override nspawn default settings for journald + copy: + src: files/nspawn_override.conf + dest: /etc/systemd/system/systemd-nspawn@.service.d/override.conf + mode: 0644 + notify: + - restart DMZ + +- name: enable machines.target + systemd: + daemon_reload: true + name: machines.target + state: started + enabled: true + +- name: ensure /etc/systemd/nspawn exists + file: + path: /etc/systemd/nspawn + state: directory + mode: 0755 + +- name: create a unit file for the container + copy: + src: files/DMZ.nspawn + dest: "/etc/systemd/nspawn/{{DMZ}}.nspawn" + mode: 0644 + notify: + - restart DMZ + +- name: enable systemd-nspawn@{{DMZ}}.service + systemd: + daemon_reload: true + name: "systemd-nspawn@{{DMZ}}.service" + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: 'DMZ init+SSH' +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/front/templates/DMZ.j2 b/roles/front/templates/DMZ.j2 new file mode 100644 index 0000000..5399a67 --- /dev/null +++ b/roles/front/templates/DMZ.j2 @@ -0,0 +1,10 @@ +#!/bin/bash +# $1: bash script; $2…: arguments (bash -c "…script…" 'bash' …arguments…) +# +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +cmd="$1"; shift +nsenter -t $(machinectl status {{DMZ}} | awk '$1=="Leader:"{print $2;exit}') \ + -a -F /usr/bin/bash -c "$cmd" bash "$@" diff --git a/roles/front/vars/main.yml b/roles/front/vars/main.yml new file mode 100644 index 0000000..2d51058 --- /dev/null +++ b/roles/front/vars/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +front_dir: /var/lib/machines/{{DMZ}} diff --git a/roles/front_run/defaults/main.yml b/roles/front_run/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/front_run/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/front_run/handlers/main.yml b/roles/front_run/handlers/main.yml new file mode 100644 index 0000000..53f0a33 --- /dev/null +++ b/roles/front_run/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart DMZ + systemd: + daemon_reload: true + name: "systemd-nspawn@{{DMZ}}.service" + state: restarted diff --git a/roles/front_run/meta.OK/main.yml b/roles/front_run/meta.OK/main.yml new file mode 100644 index 0000000..bbfb196 --- /dev/null +++ b/roles/front_run/meta.OK/main.yml @@ -0,0 +1,19 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: dotclear_back + - role: dovecot + - role: front + - role: ihmgit_back + - role: movim_back + - role: nextcloud_back + - role: postgresql + - role: prosody_back + - role: slapd + - role: sockets + - role: ssh + - role: transmission_back + - role: wallabag_back diff --git a/roles/front_run/tasks/main.yml b/roles/front_run/tasks/main.yml new file mode 100644 index 0000000..6b6c45d --- /dev/null +++ b/roles/front_run/tasks/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: start systemd-nspawn@{{DMZ}}.service + systemd: + daemon_reload: true + name: "systemd-nspawn@{{DMZ}}.service" + state: started diff --git a/roles/ihmgit_back/defaults/main.yml b/roles/ihmgit_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ihmgit_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ihmgit_back/handlers/main.yml b/roles/ihmgit_back/handlers/main.yml new file mode 100644 index 0000000..23f4a10 --- /dev/null +++ b/roles/ihmgit_back/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart gitea.service + systemd: + daemon_reload: true + name: gitea.service + state: restarted diff --git a/roles/ihmgit_back/meta.OK/main.yml b/roles/ihmgit_back/meta.OK/main.yml new file mode 100644 index 0000000..22c0d48 --- /dev/null +++ b/roles/ihmgit_back/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: sockets + - role: postgresql diff --git a/roles/ihmgit_back/tasks/main.yml b/roles/ihmgit_back/tasks/main.yml new file mode 100644 index 0000000..e95b788 --- /dev/null +++ b/roles/ihmgit_back/tasks/main.yml @@ -0,0 +1,155 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Gitea +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + with_items: + - asciidoctor + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "gitea" + ] + aur_user: git + +- name: set git home to Gitea repos + user: + name: git + home: /var/lib/gitea/repos + create_home: true + shell: /bin/sh + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Gitea +### ⇐ UPSTREAM END ### + +- name: PostgreSQL user for Gitea + postgresql_user: + login_unix_socket: /run/shared_sockets + name: "{{gitea_db_user}}" + password: "{{gitea_db_password}}" + encrypted: true + become: true + become_user: postgres + +- name: PostgreSQL database for Gitea + postgresql_db: + login_unix_socket: /run/shared_sockets + name: "{{gitea_db}}" + owner: "{{gitea_db_user}}" + become: true + become_user: postgres + +- name: configure Gitea + ini_file: + path: /etc/gitea/app.ini + section: '{{item.s}}' + option: '{{item.o}}' + value: '{{item.v}}' + with_items: + - {s: repository, o: ROOT, v: /var/lib/gitea/repos} + - {s: server, o: PROTOCOL, v: unix} + - {s: server, o: DOMAIN, v: '{{net_soa}}'} + - {s: server, o: ROOT_URL, v: 'https://{{net_soa}}{{http_pfx_gitea}}/'} + - {s: server, o: HTTP_ADDR, v: /run/shared_sockets/gitea} + - {s: server, o: LOCAL_ROOT_URL, v: ''} + - {s: server, o: SSH_DOMAIN, v: '{{net_soa}}'} + - {s: server, o: SSH_PORT, v: 2222} + - {s: server, o: SSH_ROOT_PATH, v: /var/lib/gitea/repos/.ssh} + - {s: server, o: MINIMUM_KEY_SIZE_CHECK, v: 'true'} + - {s: server, o: LFS_START_SERVER, v: 'false'} + - {s: ssh.minimum_key_sizes, o: ECDSA, v: '-1'} + - {s: ssh.minimum_key_sizes, o: DSA, v: '-1'} + - {s: database, o: DB_TYPE, v: postgres} + - {s: database, o: HOST, v: /run/shared_sockets} + - {s: database, o: NAME, v: '{{gitea_db}}'} + - {s: database, o: USER, v: '{{gitea_db_user}}'} + - {s: database, o: PASSWD, v: '{{gitea_db_password}}'} + - {s: indexer, o: REPO_INDEXER_ENABLED, v: 'true'} + - {s: admin, o: DISABLE_REGULAR_ORG_CREATION, v: '{{gitea_disable_org_creation}}'} + - {s: security, o: REVERSE_PROXY_AUTHENTICATION_USER, v: Remote-User} + - {s: service, o: REGISTER_EMAIL_CONFIRM, v: 'true'} + - {s: service, o: DISABLE_REGISTRATION, v: '{{gitea_disable_registration}}'} + - {s: service, o: ENABLE_NOTIFY_MAIL, v: '{{gitea_enable_notify_email}}'} + - {s: service, o: ENABLE_REVERSE_PROXY_AUTHENTICATION, v: 'true'} + - {s: service, o: ENABLE_REVERSE_PROXY_AUTO_REGISTRATION, v: 'true'} + - {s: service, o: DEFAULT_KEEP_EMAIL_PRIVATE, v: 'true'} + - {s: service, o: NO_REPLY_ADDRESS, v: masked.invalid} + - {s: mailer, o: ENABLED, v: 'true'} + - {s: mailer, o: FROM, v: 'git@{{net_soa}}'} + - {s: mailer, o: USE_SENDMAIL, v: 'true'} + - {s: session, o: PROVIDER, v: file} + - {s: session, o: COOKIE_SECURE, v: 'true'} + - {s: log, o: MODE, v: console} + - {s: log, o: LEVEL, v: Warn} + - {s: log.console, o: LEVEL, v: Warn} + - {s: markup.asciidoc, o: ENABLED, v: 'true'} + - {s: markup.asciidoc, o: RENDER_COMMAND, v: 'asciidoctor --out-file=- -'} + - {s: other, o: SHOW_FOOTER_VERSION, v: 'false'} + - {s: other, o: SHOW_FOOTER_TEMPLATE_LOAD_TIME, v: 'false'} + notify: + - restart gitea.service + +- name: prepare to override gitea.service + file: + path: /etc/systemd/system/gitea.service.d + state: directory + mode: 0755 + +- name: make sure Gitea runs after its dependencies + copy: + content: | + [Unit] + After=postgresql.service + After=systemd-tmpfiles-setup.service + dest: /etc/systemd/system/gitea.service.d/after_psql+sockets.conf + mode: 0644 + notify: + - restart gitea.service + +- name: make Gitea more secure + copy: + content: | + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_LEASE CAP_SYS_CHROOT + PrivateDevices=true + PrivateTmp=true + ProtectHome=true + ProtectSystem=true + NoNewPrivileges=true + dest: /etc/systemd/system/gitea.service.d/secure-{{nickname}}.conf + mode: 0644 + notify: + - restart gitea.service + +- name: enable gitea.service + systemd: + daemon_reload: true + name: gitea.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Gitea +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/init/defaults/main.yml b/roles/init/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/init/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/init/files/bin/.etc_checkroot.inc b/roles/init/files/bin/.etc_checkroot.inc new file mode 100644 index 0000000..1bc3ad7 --- /dev/null +++ b/roles/init/files/bin/.etc_checkroot.inc @@ -0,0 +1,15 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +if [ ${0:0:1} == / ]; then + this="$0" +elif [[ "$0" =~ / ]]; then + this="$PWD/$0" +else + this="$(which "$0")" +fi +if [ "${this#/usr/}" == "$this" ]; then + exec arch-chroot "${this%/usr/*}" "/usr/${this##*/usr/}" "$@" +fi +unset this diff --git a/roles/init/files/bin/etc_start_upstream b/roles/init/files/bin/etc_start_upstream new file mode 100644 index 0000000..4505202 --- /dev/null +++ b/roles/init/files/bin/etc_start_upstream @@ -0,0 +1,11 @@ +#!/bin/bash +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +. "$(dirname "$0")/.etc_checkroot.inc" +cd /etc +etckeeper unclean && git stash save --include-untracked "etc_local_unclean $1" +git checkout master +git checkout run -- pacman.conf pacman.d/mirrorlist ssh/sshd_config +etckeeper init diff --git a/roles/init/files/bin/etc_stop_local b/roles/init/files/bin/etc_stop_local new file mode 100644 index 0000000..8945cfb --- /dev/null +++ b/roles/init/files/bin/etc_stop_local @@ -0,0 +1,15 @@ +#!/bin/bash +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +. "$(dirname "$0")/.etc_checkroot.inc" +ret=0 +cd /etc +git add -A +if etckeeper unclean; then + git commit -m "local $(date -Iseconds)${1:+: $1}" + ret=$? +fi +systemctl daemon-reload +exit $ret diff --git a/roles/init/files/bin/etc_stop_upstream b/roles/init/files/bin/etc_stop_upstream new file mode 100644 index 0000000..f635fcf --- /dev/null +++ b/roles/init/files/bin/etc_stop_upstream @@ -0,0 +1,65 @@ +#!/bin/bash +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +. "$(dirname "$0")/.etc_checkroot.inc" +cd /etc + +# where are we in the history tree? +ancestor=$(git merge-base run master) +curhead=$(git rev-parse HEAD) + +# restore the “master”-state of “run”-files that were needed in order to update “master” +for f in pacman.conf pacman.d/mirrorlist ssh/sshd_config; do + if git ls-tree --name-only -r $ancestor | grep -qxF "$f"; then + git checkout $ancestor -- "$f" + else + rm -f "$f" + fi +done + +# automatically accept new versions of files that were seen as changed because of the above hack +find . -name '*.pacnew' -exec bash -c 'mv -f "$0" "${0%.pacnew}"' {} \; + +# enforce local policy in case etckeeper was updated +local_policy='etckeeper/post-install.d/00-local-policy' +for d in list-installed.d post-install.d pre-install.d; do + chmod a-x etckeeper/$d/[0-9][0-9]* +done +[ -x "$local_policy" ] || { \ + cat >"$local_policy" <<-"END" +#!/bin/sh +pacman -Q >/etc/etckeeper/packages.list +END + chmod 755 "$local_policy" +} + +# commit the changes +amend= +if etckeeper unclean; then + git add -A + if [ "$ancestor" != "$curhead" ]; then + amend=--amend + fi + git commit -m "upstream $(date -Iseconds)${1:+: $1}" $amend +fi + +# switch to branch “run” and merge “master” +git checkout run +git merge master -Xtheirs -m "merge $(date -Iseconds)${1:+: $1}" + +# if no errors occurred, restore the uncommited changed that were stashed +status=$? +if [ $status -eq 0 ]; then + stash=$(git stash list | grep -F etc_local_unclean | cut -d: -f1) + if [ -n "$stash" ]; then + git stash pop --index "$stash" + status=$? + fi +fi + +# restore empty directories and file modes +etckeeper init +systemctl daemon-reload +exit $status diff --git a/roles/init/files/hosts b/roles/init/files/hosts new file mode 100644 index 0000000..e7565ee --- /dev/null +++ b/roles/init/files/hosts @@ -0,0 +1,14 @@ +# +# /etc/hosts: static lookup table for host names +# + +# +127.0.0.1 localhost.localdomain localhost +::1 localhost.localdomain localhost + +192.168.1.1 router +192.168.1.254 dmz example.org www.example.org pubsub.example.org +192.168.1.253 home +192.168.1.252 pc +192.168.1.251 phone +# End of file diff --git a/roles/init/files/network_dmz/00-wired.network b/roles/init/files/network_dmz/00-wired.network new file mode 100644 index 0000000..0489ac2 --- /dev/null +++ b/roles/init/files/network_dmz/00-wired.network @@ -0,0 +1,9 @@ +[Match] +Name=host* +Virtualization=container + +[Network] +Address=192.168.1.254/24 +Gateway=192.168.1.1 +LLDP=yes +EmitLLDP=customer-bridge diff --git a/roles/init/files/network_home/bridge.netdev b/roles/init/files/network_home/bridge.netdev new file mode 100644 index 0000000..bfe4839 --- /dev/null +++ b/roles/init/files/network_home/bridge.netdev @@ -0,0 +1,3 @@ +[NetDev] +Name=wire +Kind=bridge diff --git a/roles/init/files/network_home/bridge.network b/roles/init/files/network_home/bridge.network new file mode 100644 index 0000000..7c98f82 --- /dev/null +++ b/roles/init/files/network_home/bridge.network @@ -0,0 +1,7 @@ +[Match] +Name=wire + +[Network] +IPForward=yes +Address=192.168.1.253/24 +Gateway=192.168.1.1 diff --git a/roles/init/files/network_home/wired.network b/roles/init/files/network_home/wired.network new file mode 100644 index 0000000..a84d136 --- /dev/null +++ b/roles/init/files/network_home/wired.network @@ -0,0 +1,5 @@ +[Match] +Name=en* + +[Network] +Bridge=wire diff --git a/roles/init/tasks/main.yml b/roles/init/tasks/main.yml new file mode 100644 index 0000000..c0e01af --- /dev/null +++ b/roles/init/tasks/main.yml @@ -0,0 +1,220 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# WARNING: This file may be used inside a mounted chroot. +# The running system should not be assumed to be the target system. + +### INIT ⇒ ### +- name: init EtcKeeper + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=init.yml + vars: + msg: init +### ⇐ INIT ### + +### UPSTREAM BEGIN ⇒ ### +- name: settings necessary for pulling from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: local settings +### ⇐ UPSTREAM BEGIN ### + +# +# https://wiki.archlinux.org/index.php/Installation_guide +# + +# Time zone +- name: set the time zone + file: + src: "/usr/share/zoneinfo/{{timezone}}" + dest: "{{chroot}}/etc/localtime" + state: link + register: tz + +- name: set hardware clock + command: hwclock --systohc + when: + - (chroot == "") + - (tz.changed) + - (inventory_hostname in groups['back']) + +# Locale +- name: enable the wanted locales + replace: + path: "{{chroot}}/etc/locale.gen" + regexp: "^#\\s*({{locales_enabled | regex_escape() | replace('\\ ', '|')}})\\s*$" + replace: '\1' + register: locales + +- name: set default locale + copy: + content: | + LANG={{locales_default}} + dest: "{{chroot}}/etc/locale.conf" + mode: 0644 + +- name: generate locales (main) + command: locale-gen + when: + - (locales.changed) + - (chroot == "") + +- name: generate locales (chroot) + command: arch-chroot {{chroot}} locale-gen + when: + - (locales.changed) + - (chroot != "") + +- name: set keymap + copy: + content: | + KEYMAP={{default_keymap}} + dest: "{{chroot}}/etc/vconsole.conf" + mode: 0644 + +# Host names +- name: set hostname + copy: + content: | + {{hostname}} + dest: "{{chroot}}/etc/hostname" + mode: 0644 + +- name: set the hosts file + copy: + src: files/hosts + dest: "{{chroot}}/etc/hosts" + mode: 0644 + +# Networking +- name: set systemd network settings + copy: + src: "files/network_{{hostname}}/" + dest: "{{chroot}}/etc/systemd/network/" + mode: 0644 + register: network + +- name: ensure overriding directories of network settings exist + file: + path: "{{chroot}}/etc/systemd/system/{{item}}" + state: directory + mode: 0755 + with_items: + - multi-user.target.wants + - sockets.target.wants + - systemd-networkd-wait-online.service.d + +- name: enable networking service + file: + src: /usr/lib/systemd/system/systemd-networkd.service + dest: "{{chroot}}/etc/systemd/system/multi-user.target.wants/systemd-networkd.service" + state: link + +- name: enable networking socket + file: + src: /usr/lib/systemd/system/systemd-networkd.socket + dest: "{{chroot}}/etc/systemd/system/sockets.target.wants/systemd-networkd.socket" + state: link + +- name: ensure proper working of “wait-online” on host + copy: + content: | + [Service] + ExecStart= + ExecStart=/usr/lib/systemd/systemd-networkd-wait-online --interface=wire + dest: /etc/systemd/system/systemd-networkd-wait-online.service.d/wait.conf + mode: 0644 + when: + - (chroot == '') + - (inventory_hostname in groups['back']) + +- name: start networking + systemd: + daemon_reload: true + name: systemd-networkd.service + state: restarted + when: + - (chroot == "") + - (network.changed) + +# DNS +- name: ensure resolved.conf.d exists + file: + path: "{{chroot}}/etc/systemd/resolved.conf.d" + state: directory + mode: 0755 + +- name: set DNS settings using http://wiki.ipfire.org/en/dns/public-servers + copy: + content: | + [Resolve] + DNS={{dns_hosts}} + DNSSEC={{dns_sec}} + dest: "{{chroot}}/etc/systemd/resolved.conf.d/{{nickname}}.conf" + mode: 0644 + register: DNS + +- name: set resolv.conf to systemd-resolvd + file: + src: /usr/lib/systemd/resolv.conf + dest: "{{chroot}}/etc/resolv.conf" + state: link + force: true + when: + - (chroot == "") + +- name: enable DNS service + file: + src: /usr/lib/systemd/system/systemd-resolved.service + dest: "{{chroot}}/etc/systemd/system/multi-user.target.wants/systemd-resolved.service" + state: link + +- name: start DNS + systemd: + daemon_reload: true + name: systemd-resolved.service + state: restarted + when: + - (chroot == "") + - DNS.changed + +# +# https://wiki.archlinux.org/index.php/General_recommendations +# + +# Printing + +- name: set default paper size + copy: + content: | + {{default_papersize}} + dest: /etc/papersize + mode: 0644 + +### UPSTREAM END ⇒ ### +- name: merge local settings + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: local settings +### ⇐ UPSTREAM END ### + +# Pacman mirrors (after init) +- name: enable the chosen Pacman mirrors + replace: + path: "{{chroot}}/etc/pacman.d/mirrorlist" + regexp: "^#(.*//(?:{{software_mirrors | regex_escape() | replace('\\ ', '|')}})/.*)$" + replace: '\1' + +- name: disable other Pacman mirrors + replace: + path: "{{chroot}}/etc/pacman.d/mirrorlist" + regexp: "^([^#](?:(?!//(?:{{software_mirrors | regex_escape() | replace('\\ ', '|')}})/).)*)$" + replace: '#\1' + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: pacman mirrors (after init) +### ⇐ LOCAL COMMIT ### diff --git a/roles/init/templates/etc_init.j2 b/roles/init/templates/etc_init.j2 new file mode 100644 index 0000000..9befdd1 --- /dev/null +++ b/roles/init/templates/etc_init.j2 @@ -0,0 +1,15 @@ +#!/bin/bash +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +. "$(dirname "$0")/.etc_checkroot.inc" +cd /etc +etckeeper init +echo /.updated >>.gitignore +git config user.email {{git_contact_email}}; git config user.name root +git add -A +git ls-files | grep -qxF .updated && git rm --cached .updated +git commit -m 'upstream init' +git branch run master +"$(dirname "$0")/etc_stop_upstream" 'upstream init' diff --git a/roles/init/vars/front_chroot.yml b/roles/init/vars/front_chroot.yml new file mode 120000 index 0000000..dc1902a --- /dev/null +++ b/roles/init/vars/front_chroot.yml @@ -0,0 +1 @@ +../../../group_vars/front_chroot \ No newline at end of file diff --git a/roles/iodine/defaults/main.yml b/roles/iodine/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/iodine/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/iodine/handlers/main.yml b/roles/iodine/handlers/main.yml new file mode 100644 index 0000000..4a245e9 --- /dev/null +++ b/roles/iodine/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart iodined.service + systemd: + daemon_reload: true + name: iodined.service + state: restarted diff --git a/roles/iodine/meta.OK/main.yml b/roles/iodine/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/iodine/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/iodine/tasks/main.yml b/roles/iodine/tasks/main.yml new file mode 100644 index 0000000..07e1ed9 --- /dev/null +++ b/roles/iodine/tasks/main.yml @@ -0,0 +1,79 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: iodine +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: iodine + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: iodine +### ⇐ UPSTREAM END ### + +- name: configure iodined + lineinfile: + path: /etc/conf.d/iodined + backrefs: true + regexp: '^\s*#?\s*({{item.key}})=' + line: '\1="{{item.value}}"' + with_dict: + TUN_IP: "{{iodine_net}}" + IODINE_PASSWORD: "{{iodine_password}}" + TOP_DOMAIN: "{{iodine_domain}}" + IODINE_BIND_ADDRESS: "{{DMZ_IP}}" + notify: + - restart iodined.service + +- name: set a temporary external IP if the field is not set correctly + lineinfile: + path: /etc/conf.d/iodined + backrefs: true + regexp: "^\\s*#?\\s*IODINE_EXT_IP=([\"']?)(?:auto)?\\1\\s*$" + line: 'IODINE_EXT_IP="{{DMZ_IP}}"' + notify: + - restart iodined.service + +- name: prepare to override iodined settings + file: + path: /etc/systemd/system/iodined.service.d + state: directory + mode: 0755 + +- name: secure iodined.service + copy: + content: | + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_LEASE CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT + PrivateTmp=true + ProtectSystem=full + ProtectHome=true + NoNewPrivileges=true + dest: "/etc/systemd/system/iodined.service.d/secure-{{nickname}}.conf" + mode: 0644 + notify: + - restart iodined.service + +- name: enable iodined.service + systemd: + daemon_reload: true + name: iodined.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: iodine +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/ldap/defaults/main.yml b/roles/ldap/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ldap/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ldap/handlers/main.yml b/roles/ldap/handlers/main.yml new file mode 100644 index 0000000..6f8e600 --- /dev/null +++ b/roles/ldap/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nslcd.service + systemd: + daemon_reload: true + name: nslcd.service + state: restarted diff --git a/roles/ldap/meta.OK/main.yml b/roles/ldap/meta.OK/main.yml new file mode 100644 index 0000000..3cbf8f1 --- /dev/null +++ b/roles/ldap/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: postinstall diff --git a/roles/ldap/tasks/main.yml b/roles/ldap/tasks/main.yml new file mode 100644 index 0000000..ddb7bc2 --- /dev/null +++ b/roles/ldap/tasks/main.yml @@ -0,0 +1,179 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: OpenLDAP client +### ⇐ UPSTREAM BEGIN ### + +- name: install packages + package: + name: "{{item}}" + state: present + with_items: + - python2-ldap + - openldap + - nss-pam-ldapd + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: OpenLDAP client +### ⇐ UPSTREAM END ### + +- name: manage users and groups in LDAP + lineinfile: + path: /etc/nsswitch.conf + backrefs: true + regexp: '^{{item}}:((?:(?!ldap).)*\s)?(files|compat)(\s(?:(?!ldap).)*)?$' + line: '{{item}}:\1\2 ldap\3' + with_items: + - passwd + - group + +- name: let the LDAP domain be known for logins + lineinfile: + path: /etc/nslcd.conf + regexp: '^{{item.key}}(?!\s+(?:passwd|shadow))\s' + line: '{{item.key}} {{item.value}}' + with_dict: + base: "{{ldap_root}}" + uri: 'ldapi://%2Frun%2Fshared_sockets%2Fldapi/' + notify: + - restart nslcd.service + +- name: customize LDAP URI for user logins + lineinfile: + path: /etc/nslcd.conf + regexp: '^#?base\s+{{item}}\s' + line: 'base {{item}} ou=Users,{{ldap_root}}' + with_items: + - passwd + - shadow + notify: + - restart nslcd.service + +- name: filter LDAP accounts that can login + lineinfile: + path: /etc/nslcd.conf + regexp: '^filter\s+{{item}}\s' + line: 'filter {{item}} (objectClass=shadowAccount)' + insertafter: '^base\s+shadow\s' + with_items: + - shadow + - passwd + notify: + - restart nslcd.service + +- name: let the LDAP domain be known for lookups + lineinfile: + path: /etc/openldap/ldap.conf + regexp: '^#?{{item.key}}\s' + line: '{{item.key}} {{item.value}}' + with_dict: + BASE: "{{ldap_root}}" + URI: 'ldapi://%2Frun%2Fshared_sockets%2Fldapi/' + notify: + - restart nslcd.service + +- name: enable nslcd.service + systemd: + daemon_reload: true + name: nslcd.service + enabled: true + +- meta: flush_handlers + +# https://github.com/ansible/ansible/issues/20582 +# - name: configure PAM for LDAP +# lineinfile: +# path: '/etc/pam.d/{{item.file}}' +# regexp: '^{{item.name}}\s.*ldap' +# line: '{{item.name}}{{item.pad}}sufficient pam_ldap.so' +# insertbefore: '^{{item.name}}\s.*required' +# with_items: +# - {file: 'system-auth', name: 'auth', pad: ' '} +# - {file: 'system-auth', name: 'account', pad: ' '} +# - {file: 'system-auth', name: 'password', pad: ' '} +# - {file: 'su-l', name: 'auth', pad: ' '} +# - {file: 'su-l', name: 'account', pad: ' '} +# - {file: 'su-l', name: 'session', pad: ' '} +# - {file: 'su', name: 'auth', pad: ' '} +# - {file: 'su', name: 'account', pad: ' '} +# - {file: 'su', name: 'session', pad: ' '} +# - {file: 'passwd', name: 'password', pad: ' '} +# - {file: 'sudo', name: 'auth', pad: ' '} + +- name: configure PAM for LDAP + lineinfile: + path: '/etc/pam.d/{{item.file}}' + regexp: '^{{item.name}}\s.*ldap' + line: '{{item.name}}{{item.pad}}sufficient pam_ldap.so' + insertafter: '{{item.after}}' + with_items: + - {file: 'system-auth', name: 'auth', pad: ' ', after: '^#%PAM'} + - {file: 'system-auth', name: 'account', pad: ' ', after: '^auth'} + - {file: 'system-auth', name: 'password', pad: ' ', after: '^account'} + - {file: 'su-l', name: 'auth', pad: ' ', after: '^auth.*pam_rootok\.so'} + - {file: 'su-l', name: 'account', pad: ' ', after: '^auth'} + - {file: 'su-l', name: 'session', pad: ' ', after: '^account'} + - {file: 'su', name: 'auth', pad: ' ', after: '^auth.*pam_rootok\.so'} + - {file: 'su', name: 'account', pad: ' ', after: '^auth'} + - {file: 'su', name: 'session', pad: ' ', after: '^account'} + - {file: 'passwd', name: 'password', pad: ' ', after: '#%PAM'} + - {file: 'sudo', name: 'auth', pad: ' ', after: '#%PAM'} + +- name: configure PAM for the session + lineinfile: + path: /etc/pam.d/system-auth + regexp: '^session\s.*ldap' + line: 'session optional pam_ldap.so' + insertafter: '^session.*required' + +- name: configure PAM for su/sudo + lineinfile: + path: '/etc/pam.d/{{item}}' + backrefs: true + regexp: '^(auth\s+required\s+pam_unix.so(?:(?!(?:try|use)_first_pass).)*)$' + line: '\1 try_first_pass' + with_items: + - 'su-l' + - 'su' + - 'sudo' + +- name: configure PAM for automatic home-folder creation (login) + lineinfile: + path: /etc/pam.d/system-login + regexp: '^session\s+required\s+pam_mkhomedir.so' + line: 'session required pam_mkhomedir.so skel=/etc/skel umask=0022' + insertbefore: EOF + +- name: configure PAM for automatic home-folder creation (su) + lineinfile: + path: '/etc/pam.d/{{item}}' + regexp: '^session\s+required\s+pam_mkhomedir.so' + line: | + session required pam_mkhomedir.so skel=/etc/skel umask=0022 + insertbefore: '^session\s+sufficient\s+pam_ldap.so' + with_items: + - 'su-l' + - 'su' + +- name: configure LDAP for sudo + lineinfile: + path: /etc/openldap/ldap.conf + regexp: '^sudoers_base\s' + line: "sudoers_base ou=SUDOers,{{ldap_root}}" + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: OpenLDAP client +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/mediaplayer/defaults/main.yml b/roles/mediaplayer/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/mediaplayer/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/mediaplayer/handlers/main.yml b/roles/mediaplayer/handlers/main.yml new file mode 100644 index 0000000..f5b8c14 --- /dev/null +++ b/roles/mediaplayer/handlers/main.yml @@ -0,0 +1,19 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: enforce max_user_watches limit + command: sysctl --system + +- name: restart minidlna.service + systemd: + daemon_reload: true + name: minidlna.service + state: restarted + +- name: restart lxdm.socket + systemd: + daemon_reload: true + name: lxdm.socket + state: restarted diff --git a/roles/mediaplayer/meta.OK/main.yml b/roles/mediaplayer/meta.OK/main.yml new file mode 100644 index 0000000..0c2f272 --- /dev/null +++ b/roles/mediaplayer/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: ldap + - role: cleanupdate diff --git a/roles/mediaplayer/tasks/main.yml b/roles/mediaplayer/tasks/main.yml new file mode 100644 index 0000000..767b95e --- /dev/null +++ b/roles/mediaplayer/tasks/main.yml @@ -0,0 +1,175 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: media player +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + with_items: + - minidlna + - kodi + - kodi-addons-adsp + - kodi-addons-audioencoder + - lxdm + +- name: create the Kodi user + user: + name: "{{kodi_user}}" + createhome: true + home: "{{kodi_data}}" + system: true + groups: + - users + - uucp + - lock + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: media player +### ⇐ UPSTREAM END ### + +- name: ensure ownership of minidlna directory + file: + path: /var/lib/minidlna + state: directory + owner: minidlna + group: minidlna + +- name: disable the default media_dir + lineinfile: + path: /etc/minidlna.conf + backrefs: true + regexp: '^(media_dir=/opt.*)$' + line: '#\1' + +- name: send the minidlna configuration + blockinfile: + path: /etc/minidlna.conf + marker: '# {mark} {{nickname}} configuration' + block: "{{media_minidlna_conf}}" + insertbefore: BOF + notify: + - restart minidlna.service + +- name: increase the number of allowed inotify watches + copy: + content: | + fs.inotify.max_user_watches=65536 + dest: /etc/sysctl.d/max_user_watches.conf + mode: 0600 + notify: + - enforce max_user_watches limit + - restart minidlna.service + +- name: prepare to override minidlna systemd settings + file: + name: /etc/systemd/system/minidlna.service.d + state: directory + mode: 0755 + +- name: override minidlna systemd settings + copy: + content: | + [Unit] + Requires=nslcd.service + After=nslcd.service + [Service] + Group={{media_group}} + dest: /etc/systemd/system/minidlna.service.d/{{nickname}}.conf + mode: 0644 + notify: + - restart minidlna.service + +- name: enable minidlna.service + systemd: + daemon_reload: true + name: minidlna.service + enabled: true + +- name: enable the kodi X11 session + copy: + content: | + [Desktop] + Session=kodi + dest: "{{kodi_data}}/.dmrc" + mode: 0644 + +- name: autologin kodi on lxdm + lineinfile: + path: /etc/lxdm/lxdm.conf + regexp: "^autologin=" + line: "autologin={{kodi_user}}" + insertafter: "^\\[base\\]" + +- name: stop lxdm after kodi quits + lineinfile: + path: /etc/lxdm/PostLogout + regexp: "systemctl stop lxdm" + line: "sudo /usr/bin/systemctl stop lxdm.service" + insertafter: EOF + +- name: ensure stopping lxdm works + lineinfile: + path: /etc/sudoers + regexp: "systemctl stop lxdm" + line: "ALL ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop lxdm.service" + insertafter: EOF + +- name: prepare to override lxdm systemd settings + file: + name: /etc/systemd/system/lxdm.service.d + state: directory + mode: 0755 + +- name: override lxdm systemd settings + copy: + content: | + [Unit] + Description=Kodi on-demand + Conflicts=kodi.socket + [Service] + ProtectSystem=full + ReadWriteDirectories={{kodi_data}} + # the client has 10 seconds to stop sending network packets to the socket + ExecStopPost=/usr/bin/bash -c "/usr/bin/sleep 10s; exec /usr/bin/systemctl --no-block start lxdm.socket" + Restart=no + dest: /etc/systemd/system/lxdm.service.d/{{nickname}}.conf + mode: 0644 + +- name: set socket activation for kodi + copy: + content: | + [Unit] + Conflicts=lxdm.service + [Socket] + ListenStream=8080 + [Install] + WantedBy=sockets.target + dest: /etc/systemd/system/lxdm.socket + mode: 0644 + notify: + - restart lxdm.socket + +- name: enable lxdm.socket + systemd: + daemon_reload: true + name: lxdm.socket + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: media player +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/movim_back/defaults/main.yml b/roles/movim_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/movim_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/movim_back/meta.OK/main.yml b/roles/movim_back/meta.OK/main.yml new file mode 100644 index 0000000..5d48b7c --- /dev/null +++ b/roles/movim_back/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: postgresql diff --git a/roles/movim_back/tasks/main.yml b/roles/movim_back/tasks/main.yml new file mode 100644 index 0000000..2ebe5b8 --- /dev/null +++ b/roles/movim_back/tasks/main.yml @@ -0,0 +1,28 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: PostgreSQL user for movim + postgresql_user: + login_unix_socket: /run/shared_sockets + name: "{{movim_db_user}}" + password: "{{movim_db_password}}" + encrypted: true + become: true + become_user: postgres + +- name: PostgreSQL database for movim + postgresql_db: + login_unix_socket: /run/shared_sockets + name: "{{movim_db}}" + owner: "{{movim_db_user}}" + become: true + become_user: postgres + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: movim +### ⇐ LOCAL COMMIT ### diff --git a/roles/msmtp/defaults/main.yml b/roles/msmtp/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/msmtp/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/msmtp/meta.OK/main.yml b/roles/msmtp/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/msmtp/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/msmtp/tasks/main.yml b/roles/msmtp/tasks/main.yml new file mode 100644 index 0000000..f846d1d --- /dev/null +++ b/roles/msmtp/tasks/main.yml @@ -0,0 +1,44 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: msmtp +### ⇐ UPSTREAM BEGIN ### + +- name: install msmtp + package: + name: "{{item}}" + with_items: + - msmtp + - msmtp-mta + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: msmtp +### ⇐ UPSTREAM END ### + +- name: msmtp configuration + copy: + content: | + account default + host {{DMZ}} + auto_from on + maildomain {{net_soa}} + tls off + syslog LOG_MAIL + dest: /etc/msmtprc + mode: 0644 + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: msmtp +### ⇐ LOCAL COMMIT ### diff --git a/roles/nextcloud_back/defaults/main.yml b/roles/nextcloud_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/nextcloud_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/nextcloud_back/handlers/main.yml b/roles/nextcloud_back/handlers/main.yml new file mode 100644 index 0000000..373693e --- /dev/null +++ b/roles/nextcloud_back/handlers/main.yml @@ -0,0 +1,28 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart loolwsd.service + systemd: + daemon_reload: true + name: loolwsd.service + state: restarted + +- name: stop uwsgi@nextcloud.service + systemd: + daemon_reload: true + name: uwsgi@nextcloud.service + state: stopped + +- name: restart uwsgi@nextcloud.socket + systemd: + daemon_reload: true + name: uwsgi@nextcloud.socket + state: restarted + +- name: restart nextcloud-maintenance.timer + systemd: + daemon_reload: true + name: nextcloud-maintenance.timer + state: restarted diff --git a/roles/nextcloud_back/meta.OK/main.yml b/roles/nextcloud_back/meta.OK/main.yml new file mode 100644 index 0000000..cdbd3cc --- /dev/null +++ b/roles/nextcloud_back/meta.OK/main.yml @@ -0,0 +1,11 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: ldap + - role: sockets + - role: php + - role: postgresql diff --git a/roles/nextcloud_back/tasks/main.yml b/roles/nextcloud_back/tasks/main.yml new file mode 100644 index 0000000..a188989 --- /dev/null +++ b/roles/nextcloud_back/tasks/main.yml @@ -0,0 +1,476 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Nextcloud +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - mailcap + - uwsgi + - uwsgi-plugin-php + - nextcloud + - ffmpeg + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "collabora-online-server-nodocker" + ] + aur_user: git + +- name: create the nextcloud user + user: + name: "{{nextcloud_user}}" + create_home: true + home: "{{nextcloud_data}}" + system: true + groups: + - users + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Nextcloud +### ⇐ UPSTREAM END ### + +- name: PostgreSQL user for Nextcloud + postgresql_user: + login_unix_socket: /run/shared_sockets + name: "{{nextcloud_db_user}}" + password: "{{nextcloud_db_password}}" + encrypted: true + become: true + become_user: postgres + +- name: PostgreSQL database for Nextcloud + postgresql_db: + login_unix_socket: /run/shared_sockets + name: "{{nextcloud_db}}" + owner: "{{nextcloud_db_user}}" + become: true + become_user: postgres + +- name: change some loolwsd settings + lineinfile: + path: /etc/loolwsd/loolwsd.xml + backrefs: true + regexp: '^(.*<{{item.key}}\s[^>]*>)[^<]*(<.*)$' + line: '\g<1>{{item.value}}\2' + with_dict: + server_name: '{{net_soa}}:443' + memproportion: '{{loolwsd_maxmem_asdouble}}' + enable: 'false' + termination: 'true' + username: '{{loolwsd_admin_user}}' + password: '{{loolwsd_admin_password}}' + notify: + - restart loolwsd.service + +- name: allow Nextcloud to connect to loolwsd + lineinfile: + path: /etc/loolwsd/loolwsd.xml + regexp: '(?:.*\.)?{{net_soa | replace(".", "\.")}}' + insertafter: ' + grep -E '=>[[:blank:]]*($|array|\[)' "{{nextcloud_conf}}/config.php" + | grep -vE '[])],?[[:blank:]]*$' || true + register: check_conf_arrays_1 + changed_when: false + +- name: replace multi-line arrays (reformated by nextcloud) + shell: | + sed -rni ' + : start + /=>[[:blank:]]*($|array|\[)/ b array + p;d + : array + /[])],?[[:blank:]]*$/ b check + N;b array + : check + h;n + /(["'"'"'])[^[:blank:]]+\1[[:blank:]]*=>|\);/ { + x + s/[[:blank:]]*[\r\n]+[[:blank:]]*/ /g + p;g + b start + } + H;x;b array' \ + "{{nextcloud_conf}}/config.php" + when: + - (check_conf_arrays_1.stdout is defined) + - (check_conf_arrays_1.stdout != '') + +- name: send the custom configuration that is needed for the setup + lineinfile: + path: "{{nextcloud_conf}}/config.php" + regexp: "^\\s*['\"]{{item.key}}['\"]\\s*=" + line: " '{{item.key}}' => {{item.value}}," + insertbefore: '\);\s*$' + with_dict: + apps_paths: "array( 0 => array( 'path' => '{{nextcloud_root}}/apps', 'url' => '{{http_pfx_nextcloud}}/apps', 'writable' => false, ), 1 => array( 'path' => '{{nextcloud_data}}/apps', 'url' => '{{http_pfx_nextcloud}}/xapps', 'writable' => true, ), )" + check_for_working_htaccess: 'false' + memcache.local: "'\\\\OC\\\\Memcache\\\\APCu'" + overwritewebroot: "'{{http_pfx_nextcloud}}'" + skeletondirectory: "''" + tempdirectory: "'/var/tmp/nextcloud'" + overwrite.cli.url: "'https://{{net_soa}}{{http_pfx_nextcloud}}'" + notify: + - stop uwsgi@nextcloud.service + - restart uwsgi@nextcloud.socket + +- name: ensure ownership of the nextcloud configuration file + file: + path: "{{nextcloud_conf}}/config.php" + owner: "{{nextcloud_user}}" + group: "{{nextcloud_user}}" + mode: 0640 + +- name: check that nextcloud is already configured + shell: > + grep -E "[\"']installed[\"'][[:blank:]]*=>[[:blank:]]*true" + "{{nextcloud_conf}}/config.php" || true + register: check_nextcloud_installed + changed_when: false + +- name: run the installation script + command: > + /usr/bin/env NEXTCLOUD_CONFIG_DIR={{nextcloud_conf}} + /usr/bin/php ./occ maintenance:install + --database pgsql + --database-host "localhost:/run/shared_sockets" + --database-name "{{nextcloud_db}}" + --database-user "{{nextcloud_db_user}}" + --database-pass "{{nextcloud_db_password}}" + --admin-user "{{nextcloud_admin_user}}" + --admin-pass "{{nextcloud_admin_password}}" + --data-dir "{{nextcloud_data}}/data" + become: true + become_user: "{{nextcloud_user}}" + args: + chdir: "{{nextcloud_root}}" + when: + - ((check_nextcloud_installed.stdout is not defined) or (check_nextcloud_installed.stdout == '')) + +- name: enable the ldap backend + command: > + /usr/bin/env NEXTCLOUD_CONFIG_DIR={{nextcloud_conf}} + /usr/bin/php ./occ app:enable user_ldap + become: true + become_user: "{{nextcloud_user}}" + args: + chdir: "{{nextcloud_root}}" + when: + - ((check_nextcloud_installed.stdout is not defined) or (check_nextcloud_installed.stdout == '')) + +- name: create an empty ldap configuration + command: > + /usr/bin/env NEXTCLOUD_CONFIG_DIR={{nextcloud_conf}} + /usr/bin/php ./occ ldap:create-empty-config -p + become: true + become_user: "{{nextcloud_user}}" + args: + chdir: "{{nextcloud_root}}" + register: ldap_conf_num + when: + - ((check_nextcloud_installed.stdout is not defined) or (check_nextcloud_installed.stdout == '')) + +- name: configure the ldap backend + command: > + /usr/bin/env NEXTCLOUD_CONFIG_DIR={{nextcloud_conf}} + /usr/bin/php ./occ ldap:set-config + '{{ldap_conf_num.stdout | default() | trim}}' + '{{item.key}}' '{{item.value}}' + become: true + become_user: "{{nextcloud_user}}" + args: + chdir: "{{nextcloud_root}}" + with_dict: + ldapBase: '{{ldap_root}}' + ldapBaseGroups: 'ou=Groups,{{ldap_root}}' + ldapBaseUsers: 'ou=Users,{{ldap_root}}' + ldapGidNumber: 'gidNumber' + ldapGroupDisplayName: 'cn' + ldapGroupFilterObjectclass: 'posixGroup' + ldapGroupMemberAssocAttr: 'gidNumber' + ldapHost: 'ldapi://%2Frun%2Fshared_sockets%2Fldapi/' + ldapLoginFilter: '(&(|(objectclass=posixAccount))(uid=%uid))' + ldapLoginFilterEmail: '0' + ldapLoginFilterUsername: '1' + ldapPort: '7636' + ldapTLS: '0' + ldapUserDisplayName: 'cn' + ldapUserFilter: '(|(objectclass=posixAccount))' + ldapUserFilterObjectclass: 'posixAccount' + ldapUuidGroupAttribute: 'auto' + ldapUuidUserAttribute: 'auto' + turnOnPasswordChange: '1' + useMemberOfToDetectMembership: '1' + ldapExpertUUIDGroupAttr: 'cn' + ldapExpertUUIDUserAttr: 'uid' + ldapConfigurationActive: '1' + when: + - ((check_nextcloud_installed.stdout is not defined) or (check_nextcloud_installed.stdout == '')) + +- name: check for multi-line arrays (reformated by nextcloud) + shell: > + grep -E '=>[[:blank:]]*($|array|\[)' "{{nextcloud_conf}}/config.php" + | grep -vE '[])],?[[:blank:]]*$' || true + register: check_conf_arrays_2 + changed_when: false + +- name: replace multi-line arrays (reformated by nextcloud) + shell: | + sed -rni ' + : start + /=>[[:blank:]]*($|array|\[)/ b array + p;d + : array + /[])],?[[:blank:]]*$/ b check + N;b array + : check + h;n + /(["'"'"'])[^[:blank:]]+\1[[:blank:]]*=>|\);/ { + x + s/[[:blank:]]*[\r\n]+[[:blank:]]*/ /g + p;g + b start + } + H;x;b array' \ + "{{nextcloud_conf}}/config.php" + when: + - (check_conf_arrays_2.stdout is defined) + - (check_conf_arrays_2.stdout != '') + +- name: send the rest of the custom configuration + lineinfile: + path: "{{nextcloud_conf}}/config.php" + regexp: "^\\s*['\"]{{item.key}}['\"]\\s*=" + line: " '{{item.key}}' => {{item.value}}," + insertbefore: '\);\s*$' + with_dict: + datadirectory: "'{{nextcloud_data}}/data'" + dbhost: "'localhost:/run/shared_sockets'" + dbname: "'{{nextcloud_db}}'" + dbpassword: "'{{nextcloud_db_password}}'" + dbtype: "'pgsql'" + dbuser: "'{{nextcloud_db_user}}'" + filelocking.enabled: 'false' + log.condition: " array ( )" + log_type: "'syslog'" + mail_domain: "'{{net_soa}}'" + mail_smtphost: "'{{DMZ}}'" + mail_smtpmode: "'smtp'" + mail_smtpport: 25 + maintenance: 'false' + trusted_domains: "array ( 0 => 'localhost', 1 => '{{net_soa}}', )" + notify: + - stop uwsgi@nextcloud.service + - restart uwsgi@nextcloud.socket + +- name: ensure ownership of the nextcloud configuration file + file: + path: "{{nextcloud_conf}}/config.php" + owner: "{{nextcloud_user}}" + group: "{{nextcloud_user}}" + mode: 0640 + +- name: send uwsgi configuration + template: + src: templates/nextcloud.ini.j2 + dest: /etc/uwsgi/nextcloud.ini + mode: 0644 + notify: + - stop uwsgi@nextcloud.service + - restart uwsgi@nextcloud.socket + +- name: prepare to override some uwsgi settings for nextcloud + file: + path: "/etc/systemd/system/uwsgi@nextcloud.{{item}}.d" + state: directory + mode: 0755 + with_items: + - service + - socket + +- name: set the dependency on the sockets directory, and get socket activation + copy: + content: | + [Unit] + After=systemd-tmpfiles-setup.service + [Install] + Also=uwsgi@nextcloud.socket + dest: /etc/systemd/system/uwsgi@nextcloud.service.d/shared_sockets.conf + mode: 0644 + +- name: compute the list of nfs-exported directories, writeable by nextcloud + set_fact: + nextcloud_rw_nfs: '{{ nextcloud_rw_nfs | default("") }} {{ item.path }}' + with_items: "{{ nfs_exports }}" + +- name: override the uwsgi service for nextcloud + copy: + content: | + [Service] + Restart=on-failure + SuccessExitStatus=0 1 + PrivateDevices=yes + ProtectSystem=full + ReadWriteDirectories={{nextcloud_conf}} {{nextcloud_data}}{{nextcloud_rw_nfs}} + NoNewPrivileges=yes + User={{nextcloud_user}} + Group={{nextcloud_user}} + dest: /etc/systemd/system/uwsgi@nextcloud.service.d/secure-{{nickname}}.conf + mode: 0644 + notify: + - stop uwsgi@nextcloud.service + - restart uwsgi@nextcloud.socket + +- name: override the uwsgi socket for nextcloud + copy: + content: | + [Unit] + After=systemd-tmpfiles-setup.service + [Socket] + ListenStream=/run/shared_sockets/nextcloud + SocketUser={{nextcloud_user}} + SocketGroup={{nextcloud_user}} + SocketMode=0666 + dest: /etc/systemd/system/uwsgi@nextcloud.socket.d/{{nickname}}.conf + mode: 0644 + notify: + - stop uwsgi@nextcloud.service + - restart uwsgi@nextcloud.socket + +- name: create a cron service for nextcloud + copy: + content: | + [Unit] + Description=Maintenance job for Nextcloud + [Service] + Environment=NEXTCLOUD_CONFIG_DIR={{nextcloud_conf}} + Type=oneshot + ; https://help.nextcloud.com/t/11-0-2-incredibly-slow-login/9475/18 + ExecStartPre=+-/usr/bin/su - postgres -c '/usr/bin/psql -d "{{nextcloud_db}}" -c "delete from oc_bruteforce_attempts;"' + ExecStart=/usr/bin/php -f {{nextcloud_root}}/cron.php + ExecStartPost=-/usr/bin/find /var/lib/nextcloud/apps -type f -name '*.scss' -exec mv {} {}.{{nickname}}-disabled \; + PrivateDevices=yes + ProtectSystem=full + ReadWriteDirectories={{nextcloud_conf}} {{nextcloud_data}} + NoNewPrivileges=yes + User={{nextcloud_user}} + Group={{nextcloud_user}} + dest: /etc/systemd/system/nextcloud-maintenance.service + mode: 0644 + notify: + - restart nextcloud-maintenance.timer + +- name: create a cron timer for nextcloud + copy: + content: | + [Unit] + Description=Maintenance job for Nextcloud + [Timer] + OnBootSec=5min + OnUnitActiveSec=15min + [Install] + WantedBy=timers.target + dest: /etc/systemd/system/nextcloud-maintenance.timer + mode: 0644 + notify: + - restart nextcloud-maintenance.timer + +- name: upgrade Nextcloud in case an update occured + command: > + /usr/bin/env NEXTCLOUD_CONFIG_DIR={{nextcloud_conf}} + /usr/bin/php ./occ upgrade --no-interaction --no-ansi -q + become: true + become_user: "{{nextcloud_user}}" + args: + chdir: "{{nextcloud_root}}" + when: + - (check_nextcloud_installed.stdout is defined) + - (check_nextcloud_installed.stdout != '') + +- name: enable loolwsd.service + systemd: + daemon_reload: true + name: loolwsd.service + enabled: true + +- name: enable Nextcloud + systemd: + daemon_reload: true + name: uwsgi@nextcloud.socket + enabled: true + +- name: enable Nextcloud maintenance + systemd: + daemon_reload: true + name: nextcloud-maintenance.timer + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Nextcloud +### ⇐ LOCAL COMMIT ### + +- meta: flush_handlers diff --git a/roles/nextcloud_back/templates/nextcloud.ini.j2 b/roles/nextcloud_back/templates/nextcloud.ini.j2 new file mode 100644 index 0000000..63446c2 --- /dev/null +++ b/roles/nextcloud_back/templates/nextcloud.ini.j2 @@ -0,0 +1,64 @@ +; The home-server project produces a multi-purpose setup using Ansible. +; Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +; Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +[uwsgi] +nextcloud_root = {{nextcloud_root}} +nextcloud_conf = {{nextcloud_conf}} +nextcloud_apps = {{nextcloud_data}}/apps +static_ext = (?:css|js|woff|svg|gif|png|html|ttf|ico|jpg|jpeg|docx|xlsx|pptx|odt|ods|odp) + +; load the required plugins, php is loaded as the default (0) modifier +plugins = 0:php + +; enable the PHP opcode cache +php-sapi-name = apache + +; reload whenever this config file changes +; %p is the full path of the current config file +touch-reload = %p + +;=> system settings +procname-master = uwsgi[nextcloud] +master = true +socket = /run/shared_sockets/nextcloud +idle = 1800 +die-on-idle = true +;disable-logging = true +;logformat = %(uri) = %(var.SCRIPT_NAME) [%(var.PATH_INFO)] +pty-log +processes = 4 +cheaper = 1 +offload-threads = 2 +umask = 007 +buffer-size = 8192 + +;=> local routing / rewriting +route-uri = ^/xapps(/(?:[^?](? application settings +env = NEXTCLOUD_CONFIG_DIR=%(nextcloud_conf) + +static-expires = ^ 15768000 + +php-docroot = %(nextcloud_root) +php-index = index.php + +php-set = expose_php=false +php-set = zend_extension=opcache.so +php-set = extension=apcu.so +; https://github.com/krakjoe/apcu/blob/master/INSTALL +php-set = apc.ttl=7200 +php-set = apc.enable_cli=1 +php-set = apc.ttl=3600 +; from Nextcloud recommendations +php-set = opcache.enable=1 +php-set = opcache.enable_cli=1 +php-set = opcache.interned_strings_buffer=8 +php-set = opcache.max_accelerated_files=10000 +php-set = opcache.memory_consumption=128 +php-set = opcache.save_comments=1 +php-set = opcache.revalidate_freq=1 diff --git a/roles/nextcloud_davfs/defaults/main.yml b/roles/nextcloud_davfs/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/nextcloud_davfs/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/nextcloud_davfs/meta.OK/main.yml b/roles/nextcloud_davfs/meta.OK/main.yml new file mode 100644 index 0000000..eb0bf6d --- /dev/null +++ b/roles/nextcloud_davfs/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: ldap + - role: front_run + - role: cleanupdate diff --git a/roles/nextcloud_davfs/tasks/main.yml b/roles/nextcloud_davfs/tasks/main.yml new file mode 100644 index 0000000..b94e268 --- /dev/null +++ b/roles/nextcloud_davfs/tasks/main.yml @@ -0,0 +1,58 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: automount Nextcloud with davfs2 +### ⇐ UPSTREAM BEGIN ### + +- name: install packages + package: + name: "{{item}}" + state: present + with_items: + - davfs2 + - pam_mount + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: automount Nextcloud with davfs2 +### ⇐ UPSTREAM END ### + +- name: add the Nextcloud volume to pam_mount + lineinfile: + path: /etc/security/pam_mount.conf.xml + regexp: ' + + insertbefore: '' + +- name: configure PAM for auto-mounting + lineinfile: + path: '/etc/pam.d/{{item.file}}' + regexp: '^{{item.name}}\s.*pam_mount' + line: '{{item.name}}{{item.pad}}optional pam_mount.so' + insertafter: '{{item.after}}' + with_items: + - {file: 'system-auth', name: 'auth', pad: ' ', after: '^#%PAM'} + - {file: 'system-auth', name: 'session', pad: ' ', after: '^session.*required'} + - {file: 'su', name: 'auth', pad: ' ', after: '^auth.*sufficient.*pam_rootok.so'} + - {file: 'su', name: 'session', pad: ' ', after: '^sess.*required.*pam_mkhomedir.so'} + - {file: 'su-l', name: 'auth', pad: ' ', after: '^auth.*sufficient.*pam_rootok.so'} + - {file: 'su-l', name: 'session', pad: ' ', after: '^sess.*required.*pam_mkhomedir.so'} + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: automount Nextcloud with davfs2 +### ⇐ LOCAL COMMIT ### diff --git a/roles/nfs/defaults/main.yml b/roles/nfs/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/nfs/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/nfs/handlers/main.yml b/roles/nfs/handlers/main.yml new file mode 100644 index 0000000..3746e2a --- /dev/null +++ b/roles/nfs/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nfs-server.service + systemd: + daemon_reload: true + name: nfs-server.service + state: restarted diff --git a/roles/nfs/meta.OK/main.yml b/roles/nfs/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/nfs/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/nfs/tasks/main.yml b/roles/nfs/tasks/main.yml new file mode 100644 index 0000000..fa52932 --- /dev/null +++ b/roles/nfs/tasks/main.yml @@ -0,0 +1,134 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: NFS +### ⇐ UPSTREAM BEGIN ### + +- name: install packages + package: + name: "{{item}}" + state: present + with_items: + - nfs-utils + +- name: create the NFS root + file: + path: /srv/nfs + state: directory + +- name: mount the exported directories + mount: + src: '{{item.path}}' + path: '/srv/nfs/{{item.name}}' + state: mounted + fstype: none + opts: 'bind,x-systemd.requires-mounts-for={{item.path}},x-systemd.requires-mounts-for={{("/srv/nfs/" + item.name) | dirname}}' + dump: 0 + passno: 0 + with_items: "{{nfs_exports}}" + when: + - ((item.path | truncate(8, True, '', 0)) != '/srv/nfs') + notify: + - restart nfs-server.service + +- name: set the nfs-server dependencies on the mount points + lineinfile: + path: /etc/systemd/system/nfs-server.service.d/mount-exports.conf + create: true + mode: 0660 + line: RequiresMountsFor=/srv/nfs/{{item.name}} + insertafter: EOF + with_items: "{{nfs_exports}}" + when: + - ((item.path | truncate(8, True, '', 0)) != '/srv/nfs') + notify: + - restart nfs-server.service + +- name: make the nfs-server dependencies file syntactically correct + lineinfile: + path: /etc/systemd/system/nfs-server.service.d/mount-exports.conf + create: false + line: '[Unit]' + insertbefore: BOF + ignore_errors: true + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: NFS +### ⇐ UPSTREAM END ### + +- name: create the exports file + template: + src: templates/exports.j2 + dest: /etc/exports.d/{{nickname}}.exports + mode: 0600 + notify: + - restart nfs-server.service + +- name: set the nfsd settings + blockinfile: + path: /etc/nfs.conf + marker: '# {mark} nfsd' + block: | + [nfsd] + port=2049 + insertbefore: '^#\[nfsd\]' + notify: + - restart nfs-server.service + +- name: set the statd settings + blockinfile: + path: /etc/nfs.conf + marker: '# {mark} statd' + block: | + [statd] + port=2050 + outgoing-port=2051 + insertbefore: '^#\[statd\]' + notify: + - restart nfs-server.service + +- name: set the mountd settings + blockinfile: + path: /etc/nfs.conf + marker: '# {mark} mountd' + block: | + [mountd] + port=2052 + insertbefore: '^#\[mountd\]' + notify: + - restart nfs-server.service + +- name: set the lockd settings + blockinfile: + path: /etc/nfs.conf + marker: '# {mark} lockd' + block: | + [lockd] + port=2053 + udp-port=2053 + insertbefore: '^#\[lockd\]' + notify: + - restart nfs-server.service + +- name: enable nfs-server + systemd: + daemon_reload: true + name: nfs-server.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: NFS +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/nfs/templates/exports.j2 b/roles/nfs/templates/exports.j2 new file mode 100644 index 0000000..181d5e5 --- /dev/null +++ b/roles/nfs/templates/exports.j2 @@ -0,0 +1,13 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +{% macro clients(opts) %} +{% for net in net_trusted_ranges.split(' ') %} + {{net}}({{opts}}) +{%- endfor %} +{% endmacro %} +/srv/nfs{{ clients('rw,fsid=root,crossmnt') }} +{% for e in nfs_exports | from_json %} +/srv/nfs/{{e['name']}}{{ clients(nfs_options) }} +{% endfor %} diff --git a/roles/nftables.inc/handlers/main.yml b/roles/nftables.inc/handlers/main.yml new file mode 100644 index 0000000..b858859 --- /dev/null +++ b/roles/nftables.inc/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nftables.service + systemd: + daemon_reload: true + name: nftables.service + state: restarted diff --git a/roles/nftables.inc/tasks/main.yml b/roles/nftables.inc/tasks/main.yml new file mode 100644 index 0000000..d68cefb --- /dev/null +++ b/roles/nftables.inc/tasks/main.yml @@ -0,0 +1,60 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: nftables +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - nftables + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: nftables +### ⇐ UPSTREAM END ### + +- name: resolve the allowed domain names + connection: local + command: dig +short -t {{item[1]}} {{item[0]}} + with_nested: + - "{{(software_mirrors + ' ' + net_allowed_domains).split(' ')}}" + - [ 'A', 'AAAA' ] + changed_when: false + register: ips + +- name: store the resolved IP addresses + set_fact: + allowed_domains_ip: "{{ips.results | map(attribute='stdout_lines') | sum(start=[]) | join(' ')}}" + +- name: generate the nft script + template: + src: templates/nftables.conf.j2 + dest: /etc/nftables.conf + mode: 0600 + notify: + - restart nftables.service + +- name: enable nftables + systemd: + daemon-reload: true + name: nftables.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: nftables +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/nftables_back/defaults/main.yml b/roles/nftables_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/nftables_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/nftables_back/handlers/main.yml b/roles/nftables_back/handlers/main.yml new file mode 100644 index 0000000..58dcbcb --- /dev/null +++ b/roles/nftables_back/handlers/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: apply sysctl immediately + command: + sysctl --system diff --git a/roles/nftables_back/meta.OK/main.yml b/roles/nftables_back/meta.OK/main.yml new file mode 100644 index 0000000..2487b03 --- /dev/null +++ b/roles/nftables_back/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init + - role: cleanupdate diff --git a/roles/nftables_back/tasks/main.yml b/roles/nftables_back/tasks/main.yml new file mode 100644 index 0000000..42f7bc4 --- /dev/null +++ b/roles/nftables_back/tasks/main.yml @@ -0,0 +1,37 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: DMZ firewall + include_role: + name: nftables.inc + allow_duplicates: true + +- name: enable IP forward + copy: + content: | + net.ipv4.ip_forward=1 + net.ipv6.conf.default.forwarding=1 + net.ipv6.conf.all.forwarding=1 + dest: /etc/sysctl.d/30-ipforward.conf + mode: 0600 + notify: + - apply sysctl immediately + +- name: enable kernel logging + copy: + content: | + net.netfilter.nf_log_all_netns=1 + dest: /etc/sysctl.d/30-kernellog.conf + mode: 0600 + notify: + - apply sysctl immediately + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: back firewall +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/nftables_back/templates/nftables.conf.j2 b/roles/nftables_back/templates/nftables.conf.j2 new file mode 100644 index 0000000..dcdda2d --- /dev/null +++ b/roles/nftables_back/templates/nftables.conf.j2 @@ -0,0 +1,125 @@ +#!/usr/bin/env nft -f +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +flush ruleset + +{% for V in ['4', '6'] %} +{% set v = V | replace('4', '') %} +{% macro trust(list) %} +{% for net in list.split(' ') %} +{% if not net is match('127(?:\.\d{1,3}){3}(?:/\d+)?|::1|^$') %} +{% if (net is match('\d{1,3}(?:\.\d{1,3}){3}(?:/\d+)?') + and V == '4') or (net is search(':') and V == '6') %} +{{caller(net)}} +{% endif %} +{% endif %} +{% endfor %} +{% endmacro %} + +table ip{{v}} Inet{{V}} { + chain CheckNet { +{% call(net) trust(net_trusted_ranges) %} + ip{{v}} saddr {{net}} return +{% endcall %} +{% if V == '4' %} + reject with icmp type admin-prohibited +{% else %} + reject with icmpv6 type admin-prohibited +{% endif %} + } + chain FilterIn { + type filter hook input priority 0 + policy drop + + # allow established/related connections + ct state {established, related} accept + + # early drop of invalid connections + ct state invalid drop + + # allow from loopback + meta iif lo accept + + # allow icmp +{% if V == '4' %} + ip protocol icmp accept +{% else %} + ip6 nexthdr icmpv6 accept +{% endif %} + + # allow multicast (for DLNA) + meta pkttype multicast accept + + # git/ssh + tcp dport 2222 accept + + # kodi upnp +{% call(net) trust(net_trusted_ranges + ' ' + fw_dlna_clients) %} + tcp dport 1088 ip{{v}} saddr {{net}} accept + udp dport 1900 ip{{v}} saddr {{net}} accept +{% endcall %} + + # minidlna +{% call(net) trust(net_trusted_ranges + ' ' + fw_dlna_clients) %} + tcp dport 8200 ip{{v}} saddr {{net}} accept + udp dport 8200 ip{{v}} saddr {{net}} accept +{% endcall %} + + # trusted hosts + jump CheckNet + + # ssh + tcp dport 22 accept + + # portmapper + tcp dport 111 accept + udp dport 111 accept + + # imap + tcp dport 143 accept + tcp dport 220 accept + + # ldap + tcp dport 389 accept + + # cups + tcp dport 631 accept + + # nfsd, statd (×2), mountd, lockd + tcp dport 2049 accept + udp dport 2049 accept + tcp dport 2050 accept + udp dport 2050 accept + tcp dport 2051 accept + udp dport 2051 accept + tcp dport 2052 accept + udp dport 2052 accept + tcp dport 2053 accept + udp dport 2053 accept + + # pulseaudio + tcp dport 4713 accept + + # zeroconf + udp dport 5353 accept + + # sane + tcp dport 6515-6566 accept + + # kodi + tcp dport 8080 accept +{% call(net) trust(DMZ_IP) %} + + # libreoffice online + tcp dport 9980 ip saddr {{net}} accept +{% endcall %} + } + + chain FilterOut { + type filter hook output priority 0 + policy accept + } +} +{% endfor %} diff --git a/roles/nftables_front/defaults/main.yml b/roles/nftables_front/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/nftables_front/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/nftables_front/meta.OK/main.yml b/roles/nftables_front/meta.OK/main.yml new file mode 100644 index 0000000..4b4b6b3 --- /dev/null +++ b/roles/nftables_front/meta.OK/main.yml @@ -0,0 +1,11 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init + - role: cleanupdate + - role: dmz_exim + - role: dmz_prosody_front + - role: transmission diff --git a/roles/nftables_front/tasks/main.yml b/roles/nftables_front/tasks/main.yml new file mode 100644 index 0000000..8c9bb68 --- /dev/null +++ b/roles/nftables_front/tasks/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: SafeZone firewall + include_role: + name: nftables.inc + allow_duplicates: true diff --git a/roles/nftables_front/templates/nftables.conf.j2 b/roles/nftables_front/templates/nftables.conf.j2 new file mode 100644 index 0000000..4aca4d6 --- /dev/null +++ b/roles/nftables_front/templates/nftables.conf.j2 @@ -0,0 +1,213 @@ +#!/usr/bin/env nft -f +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +flush ruleset +{% for V in ['4', '6'] %} +{% set v = V | replace('4', '') %} +{% macro trust(list) %} +{% for net in list.split(' ') %} +{% if not net is match('127(?:\.\d{1,3}){3}(?:/\d+)?|::1|^$') %} +{% if (net is match('\d{1,3}(?:\.\d{1,3}){3}(?:/\d+)?') + and V == '4') or (net | search(':') and V == '6') %} +{{caller(net)}} +{% endif %} +{% endif %} +{% endfor %} +{% endmacro %} + +table ip{{v}} Inet{{V}} { + set https_ban { + type ipv{{V}}_addr + flags timeout + } + set mail_ban { + type ipv{{V}}_addr + flags timeout + } + set sshd_ban { + type ipv{{V}}_addr + flags timeout + } + +{% set seq = fw_portknock_seq.split(' ') %} +{% for port in seq %} + set Knocked_{{loop.index}} { + type ipv{{V}}_addr + flags timeout +{% if loop.last %} + timeout {{fw_knock_timeout_min}}m +{% else %} + timeout 10s +{% endif %} + gc-interval 4s + } +{% endfor %} + + chain doBan { + type filter hook prerouting priority -200; policy accept; + + ip{{v}} saddr @https_ban tcp dport {80, 443} drop + ip{{v}} saddr @mail_ban tcp dport {25, 465, 587, 993} drop + ip{{v}} saddr @sshd_ban tcp dport {22, 2222, 22000} drop + } + + chain NAT_in { + type nat hook prerouting priority -100 +{% call(net) trust(SafeZone_IP) %} + + # Git SSH + tcp dport 2222 log prefix "DNAT/git: " dnat to {{net}} +{% endcall %} + + # Trusted hosts + tcp dport 443 ip{{v}} saddr @Knocked_{{seq | length}} log prefix "DNAT/HTTPS after Port-Knock: " redirect to 444 +{% call(net) trust(net_trusted_ranges) %} + tcp dport 443 ip{{v}} saddr {{net}} redirect to 444 +{% endcall %} + tcp dport 22 ip{{v}} saddr @Knocked_{{seq | length}} log prefix "DNAT/SSH after Port-Knock: " redirect to 23 +{% call(net) trust(net_trusted_ranges) %} + tcp dport 22 ip{{v}} saddr {{net}} redirect to 23 +{% endcall %} + } + chain NAT_out { + type nat hook postrouting priority 100 + ct status dnat masquerade + } + +{% for port in seq %} + chain Knock_{{loop.index}} { +{% if not loop.first %} + set update ip{{v}} saddr timeout 0s @Knocked_{{loop.index-1}} +{% endif %} + set add ip{{v}} saddr @Knocked_{{loop.index}}{% if loop.last %} log prefix "Port-Knock accepted: "{% endif %} + + } +{% if not loop.last %} + chain Unknock_{{loop.index}} { + set update ip{{v}} saddr timeout 0s @Knocked_{{loop.index}} + } +{% endif %} +{% endfor %} + + chain RefreshKnock { + set update ip{{v}} saddr timeout {{fw_knock_timeout_min}}m @Knocked_{{seq | length}} + } + + chain PortKnock { +{% for port in seq | reverse %} +{% if loop.first %} + ct state new ip{{v}} saddr @Knocked_{{loop.revindex}} goto RefreshKnock +{% else %} + tcp dport {{seq[loop.revindex]}} ct state new ip{{v}} saddr @Knocked_{{loop.revindex}} goto Knock_{{loop.revindex+1}} + tcp dport {{port}} ct state new ip{{v}} saddr @Knocked_{{loop.revindex}} return + ip{{v}} saddr @Knocked_{{loop.revindex}} ct state new goto Unknock_{{loop.revindex}} +{% endif %} +{% if loop.last %} + tcp dport {{port}} ct state new goto Knock_{{loop.revindex}} +{% endif %} +{% endfor %} + } + + chain FilterIn { + type filter hook input priority 0 + policy drop + + # allow established/related connections + ct state {established, related} accept + + # early drop of invalid connections + ct state invalid drop + + # allow from loopback + meta iif lo accept + + # allow icmp +{% if V == '4' %} + ip protocol icmp accept +{% else %} + ip6 nexthdr icmpv6 accept +{% endif %} + + # allow iodine + meta iifname dns0 accept + + # port-knocking + jump PortKnock + + # trusted ssh and https + ct status dnat accept + + # smtp + tcp dport 25 accept + + # iodine + tcp dport 53 accept + udp dport 53 accept + + # http + tcp dport 80 accept + + # https + tcp dport 443 accept + + # smtps + tcp dport 465 accept + + # submission (smtp) + tcp dport 587 accept + + # ipp +{% call(net) trust(net_trusted_ranges) %} + tcp dport 631 ip{{v}} saddr {{net}} accept + udp dport 631 ip{{v}} saddr {{net}} accept +{% endcall %} + + # imaps + tcp dport 993 accept + + # xmpp client + tcp dport 5222 accept + + # xmpp server + tcp dport 5269 accept + + # xmpp components + tcp dport 5347 accept + + # zeroconf +{% call(net) trust(net_trusted_ranges) %} + udp dport 5353 ip{{v}} saddr {{net}} accept +{% endcall %} + + # remote-help ssh + tcp dport 22000 accept +{% call(net) trust(net_trusted_ranges) %} + tcp dport 22001-22009 ip{{v}} saddr {{net}} accept +{% endcall %} + + # transmission + tcp dport {{transmission_bt_port}} accept + udp dport {{transmission_bt_port}} accept + } + + chain FilterOut { + type filter hook output priority 0 + policy drop + ct state {established, related} accept + meta oif lo accept +{% call(net) trust(SafeZone_IP + ' ' + dns_hosts + ' ' + allowed_domains_ip + ' ' + ntp_hosts) %} + ip{{v}} daddr {{net}} accept +{% endcall %} + meta skuid {{aur_user}} accept + meta skuid exim tcp dport 25 accept + meta skuid prosody accept + meta skgid spamd accept + meta skuid transmission tcp dport 443 accept + meta skuid transmission udp dport 443 accept + meta skuid transmission tcp dport > 1024 accept + meta skuid transmission udp dport > 1024 accept + } +} +{% endfor %} diff --git a/roles/ntp/defaults/main.yml b/roles/ntp/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ntp/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ntp/handlers/main.yml b/roles/ntp/handlers/main.yml new file mode 100644 index 0000000..5254fda --- /dev/null +++ b/roles/ntp/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart NTP + systemd: + daemon_reload: true + name: systemd-timesyncd.service + state: restarted diff --git a/roles/ntp/meta.OK/main.yml b/roles/ntp/meta.OK/main.yml new file mode 100644 index 0000000..a03553a --- /dev/null +++ b/roles/ntp/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init diff --git a/roles/ntp/tasks/main.yml b/roles/ntp/tasks/main.yml new file mode 100644 index 0000000..b0e3205 --- /dev/null +++ b/roles/ntp/tasks/main.yml @@ -0,0 +1,33 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: create systemd override-directories + file: + path: /etc/systemd/timesyncd.conf.d + state: directory + mode: 0755 + +- name: configure systemd NTP + copy: + content: | + [Time] + NTP={{ntp_hosts}} + dest: "/etc/systemd/timesyncd.conf.d/{{nickname}}.conf" + mode: 0644 + notify: restart NTP + +- name: enable NTP + systemd: + daemon_reload: true + name: systemd-timesyncd.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: NTP +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/php/defaults/main.yml b/roles/php/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/php/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/php/handlers/main.yml b/roles/php/handlers/main.yml new file mode 100644 index 0000000..e28078e --- /dev/null +++ b/roles/php/handlers/main.yml @@ -0,0 +1,13 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart php-fpm.service (front) + systemd: + daemon_reload: true + name: php-fpm.service + state: restarted + ignore_errors: true + when: + - (inventory_hostname in groups['front']) diff --git a/roles/php/meta.OK/main.yml b/roles/php/meta.OK/main.yml new file mode 100644 index 0000000..3cbf8f1 --- /dev/null +++ b/roles/php/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: postinstall diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml new file mode 100644 index 0000000..0bb552d --- /dev/null +++ b/roles/php/tasks/main.yml @@ -0,0 +1,148 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: php +### ⇐ UPSTREAM BEGIN ### + +- name: remove obsolete software + package: + name: php-mcrypt + state: absent + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - php-apcu + - php-gd + - php-intl + - php-pgsql + - php-pspell + - php-sqlite + - php-xsl + - php-geoip + - geoip-database-extra + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "php-imagick" + ] + aur_user: git + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: php +### ⇐ UPSTREAM END ### + +- name: activate PHP extensions + lineinfile: + path: /etc/php/php.ini + backrefs: true + regexp: '^;*((?:zend_)?extension={{item}}(?:\.so)?)\s*$' + line: '\1' + with_items: + - bcmath + - bz2 + - calendar + - dba + - exif + - gd + - gettext + - gmp + - iconv + - intl + - ldap + - opcache + - pdo_pgsql + - pdo_sqlite + - pgsql + - pspell + - shmop + - soap + - sockets + - sqlite3 + - sysvmsg + - xmlrpc + - xsl + notify: + - restart php-fpm.service (front) + +#- name: look for .so in php.ini +# command: | +# grep -q '^extension=.*\.so[[:blank:]]*$' /etc/php/php.ini +# register: checkSO +# check_mode: false +# ignore_errors: true +# changed_when: false +# +#- name: add PHP extensions (old PHP version) +# lineinfile: +# path: /etc/php/php.ini +# regexp: '^;*(extension={{item}}\.so)\s*$' +# line: 'extension={{item}}.so' +# insertafter: 'extension=zip' +# with_items: +# - mcrypt +# when: +# - (checkSO.rc == 0) +# +#- name: add PHP extensions (new PHP version) +# lineinfile: +# path: /etc/php/php.ini +# regexp: '^;*(extension={{item}})\s*$' +# line: 'extension={{item}}' +# insertafter: 'extension=zip' +# with_items: +# - mcrypt +# when: +# - (checkSO.rc != 0) + +- name: disable PHP configuration lines + lineinfile: + path: /etc/php/php.ini + backrefs: true + regexp: '^({{item}}\s*=.*)$' + line: ';\1' + with_items: + - output_buffering + notify: + - restart php-fpm.service (front) + +- name: alter PHP configuration lines + lineinfile: + path: /etc/php/php.ini + regexp: '^;*{{item.name}}\s*=' + line: '{{item.name}}={{item.value}}' + with_items: + - {name: max_execution_time, value: 0} + - {name: max_input_time, value: -1} + - {name: memory_limit, value: 512M} + - {name: post_max_size, value: 0} + - {name: 'cgi.fix_pathinfo', value: 0} + - {name: upload_tmp_dir, value: /var/tmp/} + - {name: upload_max_filesize, value: "{{http_max_upload}}"} + - {name: 'date.timezone', value: "{{timezone}}"} + notify: + - restart php-fpm.service (front) + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: php +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/postgresql/defaults/main.yml b/roles/postgresql/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/postgresql/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/postgresql/files/postgresql.conf b/roles/postgresql/files/postgresql.conf new file mode 100644 index 0000000..1909017 --- /dev/null +++ b/roles/postgresql/files/postgresql.conf @@ -0,0 +1,12 @@ +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# cf. /var/lib/postgres/data/postgresql.conf + +#listen_addresses = '' # comma-separated list of addresses; defaults to 'localhost'; use '*' for all (change requires restart) +max_connections = 50 +unix_socket_directories = '/run/shared_sockets,/run/postgresql' +shared_buffers = 64MB # min 128kB (change requires restart) +log_destination = 'syslog' +lc_messages = C # log messages in English diff --git a/roles/postgresql/handlers/main.yml b/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..81e5a29 --- /dev/null +++ b/roles/postgresql/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart postgresql.service + systemd: + daemon_reload: true + name: postgresql.service + state: restarted diff --git a/roles/postgresql/meta.OK/main.yml b/roles/postgresql/meta.OK/main.yml new file mode 100644 index 0000000..a28aeef --- /dev/null +++ b/roles/postgresql/meta.OK/main.yml @@ -0,0 +1,9 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: postinstall + - role: sockets diff --git a/roles/postgresql/tasks/main.yml b/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..d76d4e0 --- /dev/null +++ b/roles/postgresql/tasks/main.yml @@ -0,0 +1,105 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: PostgreSQL +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + state: present + with_items: + - postgresql + - postgresql-old-upgrade + - python2-psycopg2 + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: PostgreSQL +### ⇐ UPSTREAM END ### + +- name: ensure ownership of postgresql directory + file: + path: /var/lib/postgres + state: directory + owner: postgres + group: postgres + +- name: init PostgreSQL cluster + command: > + bash -c "initdb --locale {{locales_default}} -E UTF8 + -D /var/lib/postgres/data + -A md5 --pwfile <(echo \"$PGPASSWORD\")" + environment: + PGPASSWORD: "{{pgpassword}}" + become: true + become_user: postgres + args: + creates: /var/lib/postgres/data/* + notify: + - restart postgresql.service + +- name: allow local peer authent to PostgreSQL + lineinfile: + path: /var/lib/postgres/data/pg_hba.conf + regexp: '^local.*peer\s*$' + line: 'local all postgres peer' + insertafter: '^#\s*TYPE\s+DATABASE\s' + notify: + - restart postgresql.service + +- name: include custom settings + lineinfile: + path: /var/lib/postgres/data/postgresql.conf + line: "include '/etc/postgresql.conf'" + insertafter: EOF + notify: + - restart postgresql.service + +- name: send custom settings + copy: + src: files/postgresql.conf + dest: /etc/postgresql.conf + owner: root + group: postgres + mode: 0640 + notify: + - restart postgresql.service + +- name: make sure /etc/systemd/system/postgresql.service.d exists + file: + path: /etc/systemd/system/postgresql.service.d + state: directory + mode: 0755 + +- name: override postgresql.service settings + copy: + content: | + [Unit] + After=systemd-tmpfiles-setup.service + dest: /etc/systemd/system/postgresql.service.d/shared_sockets.conf + mode: 0644 + +# TODO: https://www.postgresql.org/docs/current/static/creating-cluster.html + +- name: enable PostgreSQL + systemd: + daemon_reload: true + name: postgresql.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: PostgreSQL +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/postinstall/defaults/main.yml b/roles/postinstall/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/postinstall/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/postinstall/meta.OK/main.yml b/roles/postinstall/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/postinstall/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/postinstall/tasks/main.yml b/roles/postinstall/tasks/main.yml new file mode 100644 index 0000000..a490130 --- /dev/null +++ b/roles/postinstall/tasks/main.yml @@ -0,0 +1,116 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: 'Post-install' +### ⇐ UPSTREAM BEGIN ### + +- name: remove unneeded software + package: + name: '{{item}}' + state: absent + with_items: "{{software_to_del}}" + +- name: install sudo + package: + name: sudo + +- name: install wanted software + package: + name: '{{item}}' + with_items: "{{software_to_add}}" + +- name: allow AUR user to install software + lineinfile: + path: /etc/sudoers + regexp: "^{{aur_user}}.*/usr/bin/pacman" + line: > + {{aur_user}} ALL=(ALL) NOPASSWD: + /usr/bin/pacman *-S* , /usr/bin/pacman *-U* + insertafter: EOF + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: 'Post-install' +### ⇐ UPSTREAM END ### + +# Pacman mirrors (after updates) +- name: enable the chosen Pacman mirrors + replace: + path: "{{chroot}}/etc/pacman.d/mirrorlist" + regexp: "^#(.*//(?:{{software_mirrors | regex_escape() | replace('\\ ', '|')}})/.*)$" + replace: '\1' + +- name: disable other Pacman mirrors + replace: + path: "{{chroot}}/etc/pacman.d/mirrorlist" + regexp: "^([^#](?:(?!//(?:{{software_mirrors | regex_escape() | replace('\\ ', '|')}})/).)*)$" + replace: '#\1' + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: pacman mirrors (after updates) +### ⇐ LOCAL COMMIT ### + +- name: create systemd override-directories + file: + path: "/etc/systemd/{{item}}.d" + state: directory + mode: 0755 + with_items: + - coredump.conf + - logind.conf + - system.conf + - user.conf + - system/tmp.mount + +- name: secure systemd settings + copy: + content: "{{item.content}}" + dest: "/etc/systemd/{{item.path}}.d/secure-{{nickname}}.conf" + mode: 0644 + with_items: + - path: coredump.conf + content: | + [Coredump] + Storage=none + - path: logind.conf + content: | + [Login] + NAutoVTs=2 + KillUserProcesses=yes + HandlePowerKey=reboot + PowerKeyIgnoreInhibited=yes + - path: system.conf + content: | + [Manager] + DumpCore=no + DefaultMemoryAccounting=yes + DefaultLimitCORE=0 + - path: user.conf + content: | + [Manager] + DumpCore=no + DefaultMemoryAccounting=yes + DefaultLimitCORE=0 + - path: system/tmp.mount + content: | + [Mount] + Options= + Options=mode=1777,strictatime,nosuid,nodev,noexec + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: 'Post-install' +### ⇐ LOCAL COMMIT ### diff --git a/roles/printscan/defaults/main.yml b/roles/printscan/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/printscan/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/printscan/meta.OK/main.yml b/roles/printscan/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/printscan/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/printscan/tasks/main.yml b/roles/printscan/tasks/main.yml new file mode 100644 index 0000000..5dbedb8 --- /dev/null +++ b/roles/printscan/tasks/main.yml @@ -0,0 +1,85 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: printer and scanner +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: "{{item}}" + with_items: + - cups + - hplip + - sane + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: printer and scanner +### ⇐ UPSTREAM END ### + +- name: enable and start avahi-daemon + systemd: + daemon_reload: true + name: avahi-daemon.service + enabled: true + state: started + +- name: enable and start cups + systemd: + daemon_reload: true + name: org.cups.cupsd.service + enabled: true + state: started + +- name: configure cups + command: | + /usr/bin/cupsctl --remote-admin --share-printers --user-cancel-any + +- name: enable the chosen sane drivers + replace: + path: /etc/sane.d/dll.conf + regexp: "^#\\s*({{sane_drivers | regex_escape() | replace('\\ ', '|')}})\\s*$" + replace: '\1' + +- name: disable other sane drivers + replace: + path: /etc/sane.d/dll.conf + regexp: "^(?!\\s*(?:{{sane_drivers | regex_escape() | replace('\\ ', '|')}})\\s*)(\\s*[^#\\s].*)$" + replace: '#\1' + +- name: enable network-scanning + lineinfile: + path: /etc/sane.d/saned.conf + regexp: "^\\+$" + line: "+" + insertafter: "^## Access list" + +- name: set the sane port-range + lineinfile: + path: /etc/sane.d/saned.conf + regexp: "^#?\\s*data_portrange\\s*=" + line: "data_portrange = 6515 - 6565" + insertafter: "^## Daemon options" + +- name: enable and start sane + systemd: + daemon_reload: true + name: saned.socket + enabled: true + state: started + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: printer and scanner +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/privatebin/defaults/main.yml b/roles/privatebin/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/privatebin/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/privatebin/handlers/main.yml b/roles/privatebin/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/privatebin/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/privatebin/meta.OK/main.yml b/roles/privatebin/meta.OK/main.yml new file mode 100644 index 0000000..d65c8b6 --- /dev/null +++ b/roles/privatebin/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: dmz_nginx diff --git a/roles/privatebin/tasks/main.yml b/roles/privatebin/tasks/main.yml new file mode 100644 index 0000000..68a22ff --- /dev/null +++ b/roles/privatebin/tasks/main.yml @@ -0,0 +1,66 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: privatebin +### ⇐ UPSTREAM BEGIN ### + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "privatebin" + ] + aur_user: git + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: privatebin +### ⇐ UPSTREAM END ### + +- name: configure privatebin + template: + src: templates/conf.ini.j2 + dest: /etc/webapps/privatebin/conf.ini + group: http + mode: 0640 + +- name: create the data directory + file: + path: /var/lib/privatebin + state: directory + owner: http + group: http + mode: 0770 + +- name: configure nginx for privatebin + copy: + content: | + location {{http_pfx_privatebin}} { + alias /usr/share/webapps/privatebin; + rewrite ^({{http_pfx_privatebin}})(/.*?\.php)(/.*)?$ /php...$document_root/...$1/...$2/...$3 last; + } + dest: /etc/nginx/inc.d/privatebin.http.inc + mode: 0440 + owner: http + group: http + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: privatebin +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/privatebin/templates/conf.ini.j2 b/roles/privatebin/templates/conf.ini.j2 new file mode 100644 index 0000000..359a6c4 --- /dev/null +++ b/roles/privatebin/templates/conf.ini.j2 @@ -0,0 +1,49 @@ +; The home-server project produces a multi-purpose setup using Ansible. +; Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +; Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +; An explanation of each setting can be find online at https://github.com/PrivateBin/PrivateBin/wiki/Configuration. + +[main] +discussion = {{privatebin_enable_discussion}} +opendiscussion = {{privatebin_open_discussion}} +password = {{privatebin_enable_passwords}} +fileupload = {{privatebin_enable_uploads}} +burnafterreadingselected = false +defaultformatter = "plaintext" +sizelimit = {{privatebin_bytes_limit}} +template = "bootstrap" +languageselection = false +zerobincompatibility = false + +[expire] +default = "1week" + +[expire_options] +5min = 300 +10min = 600 +1hour = 3600 +1day = 86400 +1week = 604800 +1month = 2592000 +1year = 31536000 + +[formatter_options] +plaintext = "Plain Text" +syntaxhighlighting = "Source Code" +markdown = "Markdown" + +[traffic] +limit = 10 +dir = PATH "/var/lib/privatebin" + +[purge] +limit = {{privatebin_purge_delay}} +batchsize = 10 +dir = PATH "/var/lib/privatebin" + +[model] +class = Filesystem + +[model_options] +dir = PATH "/var/lib/privatebin" diff --git a/roles/prosody_back/defaults/main.yml b/roles/prosody_back/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/prosody_back/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/prosody_back/handlers/main.yml b/roles/prosody_back/handlers/main.yml new file mode 100644 index 0000000..d77b39e --- /dev/null +++ b/roles/prosody_back/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart prosody.service + systemd: + daemon_reload: true + name: prosody.service + state: restarted diff --git a/roles/prosody_back/meta.OK/main.yml b/roles/prosody_back/meta.OK/main.yml new file mode 100644 index 0000000..5d48b7c --- /dev/null +++ b/roles/prosody_back/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: postgresql diff --git a/roles/prosody_back/tasks/main.yml b/roles/prosody_back/tasks/main.yml new file mode 100644 index 0000000..1b19e20 --- /dev/null +++ b/roles/prosody_back/tasks/main.yml @@ -0,0 +1,28 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: PostgreSQL user for Prosody + postgresql_user: + login_unix_socket: /run/shared_sockets + name: "{{prosody_db_user}}" + password: "{{prosody_db_password}}" + encrypted: true + become: true + become_user: postgres + +- name: PostgreSQL database for Prosody + postgresql_db: + login_unix_socket: /run/shared_sockets + name: "{{prosody_db}}" + owner: "{{prosody_db_user}}" + become: true + become_user: postgres + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Prosody +### ⇐ LOCAL COMMIT ### diff --git a/roles/pyruse/defaults/main.yml b/roles/pyruse/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/pyruse/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/pyruse/handlers/main.yml b/roles/pyruse/handlers/main.yml new file mode 100644 index 0000000..56d22ca --- /dev/null +++ b/roles/pyruse/handlers/main.yml @@ -0,0 +1,16 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart pyruse-boot@action_nftBan.service + systemd: + daemon_reload: true + name: pyruse-boot@action_nftBan.service + state: restarted + +- name: restart pyruse.service + systemd: + daemon_reload: true + name: pyruse.service + state: restarted diff --git a/roles/pyruse/meta.OK/main.yml b/roles/pyruse/meta.OK/main.yml new file mode 100644 index 0000000..5ca780f --- /dev/null +++ b/roles/pyruse/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: msmtp diff --git a/roles/pyruse/tasks/main.yml b/roles/pyruse/tasks/main.yml new file mode 100644 index 0000000..cb3a857 --- /dev/null +++ b/roles/pyruse/tasks/main.yml @@ -0,0 +1,69 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Pyruse +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: python-systemd + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "pyruse" + ] + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Pyruse +### ⇐ UPSTREAM END ### + +- name: send the configuration file + template: + src: templates/pyruse.json.j2 + dest: /etc/pyruse/pyruse.json + mode: 0600 + notify: + - restart pyruse.service + +- name: action_nftBan depends on the DMZ + copy: + content: | + [Unit] + Requires=systemd-nspawn@{{DMZ}}.service + After=systemd-nspawn@{{DMZ}}.service + dest: /etc/systemd/system/pyruse-boot@action_nftBan.service.d/nftBan_wants_{{DMZ}}.conf + notify: + - restart pyruse-boot@action_nftBan.service + +- name: enable pyruse-boot@action_nftBan.service + systemd: + daemon_reload: true + name: pyruse-boot@action_nftBan.service + enabled: true + +- name: enable pyruse.service + systemd: + daemon_reload: true + name: pyruse.service + enabled: true + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Pyruse +### ⇐ LOCAL COMMIT ### diff --git a/roles/pyruse/templates/pyruse.json.j2 b/roles/pyruse/templates/pyruse.json.j2 new file mode 100644 index 0000000..1898e90 --- /dev/null +++ b/roles/pyruse/templates/pyruse.json.j2 @@ -0,0 +1,1332 @@ +{ + "actions": { + "Filter-out uninteresting services’ entries": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 4 } + }, + { + "filter": "filter_in", + "args": { "field": "SYSLOG_IDENTIFIER", "values": [ "exportfs", "gitea", "kill", "ldapsearch", "ldapwhoami", "mandb", "mount.davfs", "movim", "msmtp", "postgres", "prosody_auth", "pyruse", "sa-compile", "systemd-fsck", "systemd-gpt-auto-generator", "systemd-logind" ] }, + "then": "… NOOP" + } + ], + "Filter-out uninteresting generic services’ entries": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 4 } + }, + { + "filter": "filter_pcreAny", + "args": { "field": "SYSLOG_IDENTIFIER", "re": [ "^ansible-" ] }, + "then": "… NOOP" + } + ], + "Detect request errors with Nextcloud": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "uwsgi" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\[[^]]+\\] ([^ ]+) .*\\] ([A-Z]+ /[^?]*)(?:\\?.*)? => .*\\(HTTP/1.1 5..\\)", "save": [ "thatIP", "HTTPrequest" ] }, + "else": "… Report insufficient buffer-size for Nextcloud QUERY_STRING" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "IP {thatIP} failed to {HTTPrequest} on Nextcloud", "details": "FIRSTLAST" } + } + ], + "… Report insufficient buffer-size for Nextcloud QUERY_STRING": [ + { + "filter": "filter_equals", + "args": { "field": "MESSAGE", "value": "not enough buffer space to add QUERY_STRING variable, consider increasing it with the --buffer-size option" }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Nextcloud query failed because the buffer-size was too low" } + } + ], + "Detect successful XMPP logins": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "prosody" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "Authenticated as (.*)$", "save": [ "JID" ] }, + "else": "… Notify of unsecured XMPP servers" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login as {JID} by XMPP", "details": "NONE" } + } + ], + "… Notify of unsecured XMPP servers": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "->(.*) closed: Encrypted server-to-server communication is required but was not offered$", "save": [ "xmppServer" ] }, + "else": "… NOOP if PRIORITY 3+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "XMPP server {xmppServer} did not provide a secure connection" } + } + ], + "Map client IP:port to proxy IP:port": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "haproxy" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^([^ ]+):([^: ]+) \\[.{24}\\](?: [^ ]+){2}\\[(?!-)([^] ]+):([^]: ]+)\\]/", "save": [ "dnatSaddr", "dnatSport", "dnatAddr", "dnatPort" ] }, + "else": "… NOOP if PRIORITY 3+" + }, + { + "action": "action_dnatCapture", + "args": { "saddr": "dnatSaddr", "sport": "dnatSport", "addr": "dnatAddr", "addrValue": "127.0.0.1", "port": "dnatPort", "keepSeconds": 240 } + } + ], + "Detect successful IMAP logins": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "dovecot" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^imap-login: Login: user=<([^>]+)>, method=[^,]*, rip=([^,]+),", "save": [ "thatUser", "thatIP" ] }, + "else": "… Detect IMAP resource hogs" + }, + { + "action": "action_counterReset", + "args": { "counter": "mail", "for": "thatIP", "graceSeconds": 432000 } + }, + { + "action": "action_counterReset", + "args": { "counter": "mail_recidive", "for": "thatIP" } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login from {thatIP} as {thatUser}@{_HOSTNAME} by IMAP", "details": "NONE" } + } + ], + "… Detect IMAP resource hogs": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "^imap-login: Disconnected \\(no auth attempts in [0-9]{2,} secs\\): user=<>, rip=(?P[^,]+),", + "^imap-login: Disconnected: Too many invalid commands.*, rip=(?P[^,]+)," + ] }, + "then": "… Check network before an email ban", + "else": "… Detect failed IMAP logins" + } + ], + "… Detect failed IMAP logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^imap-login: Disconnected \\(auth failed, [0-9]+ attempts in [0-9]+ secs\\): user=<([^>]*)>.*, rip=([^,]+),", "save": [ "thatUser", "thatIP" ] }, + "else": "… Discard Dovecot debug entries" + }, + { + "filter": "filter_userExists", + "args": { "field": "thatUser" }, + "else": "… Report inexisting IMAP user" + }, + { + "action": "action_email", + "args": { "subject": "Pyruse Warning", "message": "WARNING: Failed login from {thatIP} as {thatUser}@{_HOSTNAME} by IMAP on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Failed login from {thatIP} as {thatUser}@{_HOSTNAME} by IMAP", "details": "FIRSTLAST" }, + "then": "… Check network before an email ban" + } + ], + "… Report inexisting IMAP user": [ + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Failed login from {thatIP} as {thatUser}@{_HOSTNAME} by IMAP", "details": "FIRSTLAST" }, + "then": "… Check network before an email ban" + } + ], + "… Check network before an email ban": [ + { + "filter": "filter_inNetworks", + "args": { "field": "thatIP", "nets": [ "{{net_trusted_ranges | replace(' ', '", "')}}" ] }, + "then": "… NOOP", + "else": "… Detect repeated mail failures" + } + ], + "… Detect repeated mail failures": [ + { + "action": "action_counterRaise", + "args": { "counter": "mail", "for": "thatIP", "keepSeconds": 86400, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 4 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for mail abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "nftBan from email for {thatIP}" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 432000, "nftSetIPv4": "ip Inet4 mail_ban", "nftSetIPv6": "ip6 Inet6 mail_ban" } + } + ], + "… Discard Dovecot debug entries": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 4 }, + "then": "… NOOP", + "else": "… Warn of Dovecot-to-LDAP errors" + } + ], + "… Warn of Dovecot-to-LDAP errors": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^auth: Error: LDAP: Can't connect to server: ldapi:" }, + "else": "… NOOP" + }, + { + "action": "action_email", + "args": { "subject": "Dovecot-to-LDAP error", "message": "Dovecot could not connect to LDAP (ldapi) on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Dovecot could not connect to LDAP (ldapi)", "details": "FIRSTLAST" } + } + ], + "Notify of unexpected HTTP disconnections": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "nginx" } + }, + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "epoll_wait\\(\\) reported that client prematurely closed connection, so upstream connection is closed too while sending request to upstream, client: (?P[^,]+), server: , request: \"[^ ]+ (?P/[^/ \"]*)[^\"]*\"", + "client prematurely closed connection while sending to client, client: (?P[^,u][^,]+), server: , request: \"[^ ]+ (?P/[^/ \"]*)[^\"]*\"" + ] }, + "else": "… Warn of CONNECT attempts" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Aborted connection from {thatIP} to {urlPrefix}", "details": "FIRSTLAST" } + } + ], + "… Warn of CONNECT attempts": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": ", request: \"CONNECT [^ ]+ HTTP/[^\"]+\"$" }, + "else": "… Discard other HTTP debug entries" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Nginx detected a CONNECT attempt on {_HOSTNAME}" } + } + ], + "… Discard other HTTP debug entries": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 6 }, + "then": "… NOOP", + "else": "… Detect successful HTTPS logins" + } + ], + "… Detect successful HTTPS logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^.{19} \\[notice\\] [0-9]*#[0-9]*: \\*[0-9]* \\[lua\\] .* authenticate\\(\\): Connected as: ([^,]*), client: ([^,]*),", "save": [ "thatUser", "thatIP" ] }, + "else": "… Detect failed HTTPS logins" + }, + { + "action": "action_counterReset", + "args": { "counter": "https", "for": "thatIP", "graceSeconds": 432000 } + }, + { + "action": "action_counterReset", + "args": { "counter": "https_recidive", "for": "thatIP" } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login from {thatIP} as {thatUser}@{_HOSTNAME} by HTTPS", "details": "NONE" } + } + ], + "… Detect failed HTTPS logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "Redirect to: https://[^/]*{{net_soa | replace('.', '\\\\.')}}/sso/\\?r=(.*), client: (?P.*), server: , request: \"POST /sso/\\?r=\\1 HTTP/1\\.1\", host: \"[^/]*{{net_soa | replace('.', '\\\\.')}}\", referrer: \"https://[^/]*{{net_soa | replace('.', '\\\\.')}}/sso/\\?r=\\1\"$" }, + "else": "… Detect abnormal HTTP 404 errors" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Failed login from {thatIP} on {_HOSTNAME} by HTTPS", "details": "FIRSTLAST" }, + "then": "… Check network before an HTTPS ban" + } + ], + "… Detect abnormal HTTP 404 errors": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "open\\(\\) \"[^\"]*\\.(?:cgi|php|pl|py|ss?h)\" failed \\(2: No such file or directory\\), client: (?P[^,]+),", + "Unable to open primary script: .*\\.(?:cgi|php|pl|py|sh) \\(No such file or directory[^,]+, client: (?P[^,]+)," + ] }, + "then": "… Check network before an HTTPS ban", + "else": "… Immediate warning for connectivity errors" + } + ], + "… Check network before an HTTPS ban": [ + { + "filter": "filter_inNetworks", + "args": { "field": "thatIP", "nets": [ "{{net_trusted_ranges | replace(' ', '", "')}}" ] }, + "then": "… NOOP", + "else": "… Detect repeated HTTPS failures" + } + ], + "… Detect repeated HTTPS failures": [ + { + "action": "action_counterRaise", + "args": { "counter": "https", "for": "thatIP", "keepSeconds": 900, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 6 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for HTTP abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "nftBan from HTTP for {thatIP}" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 7200, "nftSetIPv4": "ip Inet4 https_ban", "nftSetIPv6": "ip6 Inet6 https_ban" } + } + ], + "… Immediate warning for connectivity errors": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^.{19} \\[crit\\] [0-9]*#[0-9]*: \\*[0-9]* connect\\(\\) to ([^ ]*) failed", "save": [ "nginxUpstream" ] }, + "else": "… Immediate warning for module version errors" + }, + { + "action": "action_email", + "args": { "subject": "Nginx connectivity error", "message": "Nginx could not connect to {nginxUpstream} on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Nginx could not connect to {nginxUpstream}", "details": "FIRSTLAST" } + } + ], + "… Immediate warning for module version errors": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "module \"([^\"]+)\" version [0-9]+ instead of [0-9]+ in /.*$", "save": [ "badModule" ] }, + "else": "… Immediate warning for LUA errors" + }, + { + "action": "action_email", + "args": { "subject": "Bad Nginx module version", "message": "Nginx could not load a module on {_HOSTNAME}:\n{MESSAGE}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Nginx could not load module {badModule}", "details": "FIRSTLAST" } + } + ], + "… Immediate warning for LUA errors": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "runtime error: ([^ ]+): (.*)$", "save": [ "luaFile", "luaError" ] }, + "else": "… Warn of upstream HTTP disconnections" + }, + { + "action": "action_email", + "args": { "subject": "Lua error in Nginx", "message": "Lua error at {luaFile}:\n{MESSAGE}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Nginx file {luaFile} ran into error: {luaError}", "details": "FIRSTLAST" } + } + ], + "… Warn of upstream HTTP disconnections": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "(?:upstream prematurely closed connection|Connection reset by peer\\)) while reading response header from upstream.*, request: \"([^?\"]+)[^\"]*\", upstream: \"([^\"]+)\"", "save": [ "failedRequest", "failedUpstream" ] }, + "else": "… NOOP if PRIORITY 3+" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Nginx got disconnected from {failedUpstream} on request {failedRequest}", "details": "FIRSTLAST" } + } + ], + "Notify of new custom systemd services": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "systemd" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^Started (/.*)\\.$", "save": [ "customCmd" ] }, + "else": "… Warn of unclean mounts" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Custom systemd service started: {customCmd}" } + } + ], + "… Warn of unclean mounts": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^Directory (/.*) to mount over is not empty, mounting anyway\\.$", "save": [ "mountPath" ] }, + "else": "… Notify of systemd-gpt-auto-generator errors" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Device mounted on non-empty {mountPath}" } + } + ], + "… Notify of systemd-gpt-auto-generator errors": [ + { + "filter": "filter_equals", + "args": { "field": "MESSAGE", "value": "/usr/lib/systemd/system-generators/systemd-gpt-auto-generator failed with error code 1." }, + "else": "… Warn of time-outs" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "{MESSAGE}", "details": "NONE" } + } + ], + "… Warn of time-outs": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^(/.*): Start operation timed out\\. Terminating\\.$", "save": [ "systemdUnit" ] }, + "else": "… Notify of user logins" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Unit {systemdUnit}/{_HOSTNAME} timed out while starting" } + } + ], + "… Notify of user logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^pam_unix\\(systemd-user:session\\): session opened for user (.*) by root\\(uid=0\\)$", "save": [ "thatUser" ] }, + "else": "… Warn of failed systemd units" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login as {thatUser}@{_HOSTNAME} by systemd-user:session" } + } + ], + "… Warn of failed systemd units": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^(/.*\\.mount|.*\\.service): Failed ", "save": [ "systemdUnit" ] }, + "else": "… Discard other systemd debug entries" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Unit {systemdUnit}/{_HOSTNAME} failed" } + } + ], + "… Discard other systemd debug entries": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 4 }, + "then": "… NOOP", + "else": "… Notify of systemd failed states" + } + ], + "… Notify of systemd failed states": [ + { + "action": "action_email", + "args": { "subject": "systemd failure", "message": "On {_HOSTNAME} on {__REALTIME_TIMESTAMP}:\n{MESSAGE}" } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "{MESSAGE}" } + } + ], + "Warn of su errors": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "su" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^FAILED SU \\([^)]+\\) (.*) on [^ ]+$", "save": [ "thatUser" ] }, + "else": "… Notify of su logins" + }, + { + "action": "action_email", + "args": { "subject": "SU error!", "message": "SU error from user {thatUser} on {_HOSTNAME} on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "SU error from user {thatUser} on {_HOSTNAME}" } + } + ], + "… Notify of su logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\(to (.*)\\) (.*) on [^ ]+$", "save": [ "thatUser", "fromUser" ] }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login as {thatUser}@{_HOSTNAME} by {fromUser}:su", "details": "NONE" } + } + ], + "Warn of Nextcloud maintenance errors": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "php" } + }, + { + "filter": "filter_equals", + "args": { "field": "MESSAGE", "value": "Cannot write into \"config\" directory!" }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_email", + "args": { "subject": "Nextcloud config is read-only!", "message": "Nextcloud maintenance could not write to the configuration file on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Nextcloud maintenance could not write to the configuration file", "details": "FIRSTLAST" } + } + ], + "Warn of bad SSH configuration": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "sshd" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^/etc/ssh/sshd_config line " }, + "else": "… Detect successful SSH logins" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "SSH: {MESSAGE}", "details": "FIRSTLAST" } + } + ], + "… Detect successful SSH logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^Accepted (password|publickey) for (.*) from ([^ ]+) port ([^ ]+) ", "save": [ "SSHmethod", "thatUser", "thatIP", "port" ] }, + "else": "… Detect failed SSH logins" + }, + { + "action": "action_dnatReplace", + "args": { "addr": "thatIP", "port": "port", "saddrInto": "thatIP" } + }, + { + "action": "action_counterReset", + "args": { "counter": "sshd", "for": "thatIP", "graceSeconds": 432000 } + }, + { + "action": "action_counterReset", + "args": { "counter": "sshd_recidive", "for": "thatIP" } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login from {thatIP} as {thatUser}@{_HOSTNAME} by SSH {SSHmethod}" } + } + ], + "… Detect failed SSH logins": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "^Failed password for (?P.*) from (?P[^ ]+) port (?P[^ ]+) ", + "^Connection closed by invalid user (?P.*) (?P[^ ]+) port (?P[^ ]+) \\[preauth\\]$", + "^error: maximum authentication attempts exceeded for invalid user (?P.*) from (?P[^ ]+) port (?P[^ ]+) ssh2 \\[preauth\\]$" + ] }, + "else": "… Forbid antiquated clients" + }, + { + "action": "action_dnatReplace", + "args": { "addr": "thatIP", "port": "port", "saddrInto": "thatIP" } + }, + { + "filter": "filter_userExists", + "args": { "field": "thatUser" }, + "else": "… Report inexisting SSH user" + }, + { + "action": "action_email", + "args": { "subject": "Pyruse Warning", "message": "WARNING: Failed login from {thatIP} as {thatUser}@{_HOSTNAME} by SSH on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Failed login from {thatIP} as {thatUser}@{_HOSTNAME} by SSH", "details": "FIRSTLAST" }, + "then": "… Check network before an SSH ban" + } + ], + "… Report inexisting SSH user": [ + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Failed login from {thatIP} as {thatUser}@{_HOSTNAME} by SSH", "details": "FIRSTLAST" }, + "then": "… Check network before an SSH ban" + } + ], + "… Forbid antiquated clients": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^Unable to negotiate with ([^ ]*) port", "save": [ "thatIP" ] }, + "then": "… Check network before an SSH ban", + "else": "… Discard some SSH error messages" + } + ], + "… Check network before an SSH ban": [ + { + "filter": "filter_inNetworks", + "args": { "field": "thatIP", "nets": [ "{{net_trusted_ranges | replace(' ', '", "')}}" ] }, + "then": "… NOOP", + "else": "… Detect repeated SSH login failures" + } + ], + "… Detect repeated SSH login failures": [ + { + "action": "action_counterRaise", + "args": { "counter": "sshd", "for": "thatIP", "keepSeconds": 86400, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 4 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for SSH abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "nftBan from SSH for {thatIP}" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 432000, "nftSetIPv4": "ip Inet4 sshd_ban", "nftSetIPv6": "ip6 Inet6 sshd_ban" } + } + ], + "… Discard some SSH error messages": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "conv->conv\\(...\\): Conversation error$", + "warning: could not obtain password interactively either$" + ] }, + "then": "… NOOP", + "else": "… NOOP if PRIORITY 6+" + } + ], + "Notify of Exim smarthost deliveries": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "exim" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": " => [^ ]+ R=smarthost T=remote_smtp H=([^ ]+ \\[[^]]+\\]) C=\"250 ", "save": [ "smarthost" ] }, + "else": "… Notify of Exim local deliveries" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Email message sent through {smarthost}", "details": "NONE" } + } + ], + "… Notify of Exim local deliveries": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "<([^ @]+)@{{net_soa}}> [^ ]+ Saved\"$", "save": [ "recipient" ] }, + "else": "… Frozen Exim email" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Email message delivered to {recipient}", "details": "NONE" } + } + ], + "… Frozen Exim email": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "Message is frozen$" }, + "else": "… Warn of a failure for Exim" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Frozen email on {_HOSTNAME}.", "details": "FIRSTLAST" } + } + ], + "… Warn of a failure for Exim": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "(?Pall spamd servers failed)$", + "(?PNetwork is unreachable)$" + ] }, + "else": "… Immediate ban of crackers" + }, + { + "action": "action_email", + "args": { "subject": "Exim detected a failure", "message": "Failure detected by Exim on {_HOSTNAME} on {__REALTIME_TIMESTAMP}:\n{MESSAGE}" } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Exim detected a failure ({failReason})", "details": "FIRSTLAST" } + } + ], + "… Immediate ban of crackers": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "\\[([^ ]+)\\] NULL character\\(s\\) present \\(shown as '\\?'\\)$", "save": [ "thatIP" ] }, + "else": "… Some leniency to allow for manual SMTP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for mail abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "nftBan from email for {thatIP}" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 432000, "nftSetIPv4": "ip Inet4 mail_ban", "nftSetIPv6": "ip6 Inet6 mail_ban" } + } + ], + "… Some leniency to allow for manual SMTP": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "^.{19} SMTP syntax error in \"[^\"]*\" H=(?:\\([^)]*\\) )?\\[(?P[^]]+)\\]", + "SMTP command timeout on connection from (?:\\([^)]*\\) )?\\[(?P[^]]+)\\]$" + ] }, + "then": "… Check network before an email ban", + "else": "… Detect some SMTP spammers" + } + ], + "… Detect some SMTP spammers": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "\\[(?P[^ ]+)\\] AUTH command used when not advertised$", + "H=(?:\\([^)]*\\) )?\\[(?P[^]]+)\\] .* rejected after DATA: (?:maximum allowed line length is [0-9]+ octets, got [0-9]+|This message scored [0-9.]+ spam points\\.)$", + "^.{19} login_server authenticator failed for (?:\\([^)]*\\) )?\\[(?P[^]]+)\\]: 535 Incorrect authentication data", + "^.{19} H=(?:\\([^)]*\\) )?\\[(?P[^]]+)\\] .* relay not permitted$", + "^.{19} SMTP protocol synchronization error.*: rejected .* H=(?:\\([^)]*\\) )?\\[(?P[^]]+)\\]", + "\\[(?P[^ ]+)\\] rejected EXPN root$", + "unqualified verify rejected: .* H=(?:\\([^)]*\\) )?\\[(?P[^]]+)\\]$", + "rejected because (?P[^ ]+) is in a black list at", + "^.{19} rejected [EH]{2}LO from (?:\\([^)]*\\) )?\\[(?P[^]]+)\\]: syntactically invalid", + "\\[(?P[^ ]+)\\] dropped: too many nonmail commands" + ] }, + "then": "… Check network before an email ban", + "else": "… NOOP if PRIORITY 5+" + } + ], + "Warn of systemd-nspawn failures": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "systemd-nspawn" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^(?:\\[FAILED\\] )?Failed to" }, + "else": "… NOOP if PRIORITY 4+" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "{{DMZ}}: {MESSAGE}", "details": "FIRSTLAST" } + } + ], + "Discard Nextcloud coding errors": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "ownCloud" } + }, + { + "filter": "filter_in", + "args": { "field": "PRIORITY", "values": [ 2, 3 ] }, + "then": "… NOOP", + "else": "… Discard Nextcloud-to-LDAP bind errors" + } + ], + "… Discard Nextcloud-to-LDAP bind errors": [ + { + "filter": "filter_equals", + "args": { "field": "MESSAGE", "value": "{user_ldap} Bind failed: 49: Invalid credentials" }, + "then": "… NOOP", + "else": "… Notify of Nextcloud upgrades" + } + ], + "… Notify of Nextcloud upgrades": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\{core\\} starting upgrade from (.*) to (.*)$", "save": [ "fromVers", "toVers" ] }, + "else": "… Detect Nextcloud failed logins" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Nextcloud upgrade from {fromVers} to {toVers}" } + } + ], + "… Detect Nextcloud failed logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\{core\\} Login failed: '(.*)' \\(Remote IP: '(.*)'\\)", "save": [ "thatUser", "thatIP" ] }, + "else": "… Let Nextcloud core messages pass-through" + }, + { + "filter": "filter_userExists", + "args": { "field": "thatUser" }, + "else": "… Report inexisting Nextcloud user" + }, + { + "action": "action_email", + "args": { "subject": "Pyruse Warning", "message": "WARNING: Failed login from {thatIP} as {thatUser}@{_HOSTNAME} on Nextcloud on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Failed login from {thatIP} as {thatUser}@{_HOSTNAME} on Nextcloud", "details": "FIRSTLAST" }, + "then": "… Check network before a Nextcloud ban" + } + ], + "… Report inexisting Nextcloud user": [ + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Failed login from {thatIP} as {thatUser}@{_HOSTNAME} on Nextcloud", "details": "FIRSTLAST" }, + "then": "… Check network before a Nextcloud ban" + } + ], + "… Check network before a Nextcloud ban": [ + { + "filter": "filter_inNetworks", + "args": { "field": "thatIP", "nets": [ "{{net_trusted_ranges | replace(' ', '", "')}}" ] }, + "then": "… NOOP", + "else": "… Detect repeated Nextcloud login failures" + } + ], + "… Detect repeated Nextcloud login failures": [ + { + "action": "action_counterRaise", + "args": { "counter": "https", "for": "thatIP", "keepSeconds": 300, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 6 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for HTTP abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "nftBan from HTTP for {thatIP}" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 900, "nftSetIPv4": "ip Inet4 https_ban", "nftSetIPv6": "ip6 Inet6 https_ban" } + } + ], + "… Let Nextcloud core messages pass-through": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\{" }, + "else": "… NOOP if PRIORITY 5+" + } + ], + "Manage recidives for Pyruse bans from HTTPS": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "python" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^nftBan from HTTP for (?P.*)$" }, + "else": "… Manage recidives for Pyruse bans from email" + }, + { + "action": "action_counterRaise", + "args": { "counter": "https_recidive", "for": "thatIP", "keepSeconds": 432000, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 7 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for repeated HTTP abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "Strong nftBan from HTTP for {thatIP}", "level": "WARNING" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 2592000, "nftSetIPv4": "ip Inet4 https_ban", "nftSetIPv6": "ip6 Inet6 https_ban" } + } + ], + "… Manage recidives for Pyruse bans from email": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^nftBan from email for (?P.*)$" }, + "else": "… Manage recidives for Pyruse bans from SSH" + }, + { + "action": "action_counterRaise", + "args": { "counter": "mail_recidive", "for": "thatIP", "keepSeconds": 2592000, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 7 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for repeated mail abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "Strong nftBan from email for {thatIP}", "level": "WARNING" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 2592000, "nftSetIPv4": "ip Inet4 mail_ban", "nftSetIPv6": "ip6 Inet6 mail_ban" } + } + ], + "… Manage recidives for Pyruse bans from SSH": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^nftBan from SSH for (?P.*)$" }, + "else": "… Discard Pyruse’s own ban messages" + }, + { + "action": "action_counterRaise", + "args": { "counter": "sshd_recidive", "for": "thatIP", "keepSeconds": 2592000, "save": "IPfailures" } + }, + { + "filter": "filter_greaterOrEquals", + "args": { "field": "IPfailures", "value": 2 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Ban of IP {thatIP} for repeated SSH abuse", "details": "FIRSTLAST" } + }, + { + "action": "action_log", + "args": { "message": "Strong nftBan from SSH for {thatIP}", "level": "WARNING" } + }, + { + "action": "action_nftBan", + "args": { "IP": "thatIP", "banSeconds": 2592000, "nftSetIPv4": "ip Inet4 sshd_ban", "nftSetIPv6": "ip6 Inet6 sshd_ban" } + } + ], + "… Discard Pyruse’s own ban messages": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^Strong nftBan from" }, + "then": "… NOOP" + } + ], + "Notify of identified SPAM messages": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "spamd" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^spamd: identified spam" }, + "else": "… NOOP if PRIORITY 4+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Spam identified", "details": "NONE" } + } + ], + "Map client IP:port to DNAT IP:port": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "kernel" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^DNAT/git:.* SRC=([^ ]+) DST=([^ ]+) .* SPT=([^ ]+) ", "save": [ "dnatSaddr", "dnatAddr", "dnatPort" ] }, + "else": "… Notify of accepted port-knocks" + }, + { + "action": "action_dnatCapture", + "args": { "saddr": "dnatSaddr", "addr": "dnatAddr", "port": "dnatPort", "dportValue": "2222", "keepSeconds": 240 } + } + ], + "… Notify of accepted port-knocks": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^Port-Knock accepted:.* SRC=([^ ]+) ", "save": [ "thatIP" ] }, + "else": "… NOOP if PRIORITY 4+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Port-Knock accepted from {thatIP}" } + } + ], + "Warn of package errors with loolwsd": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "loolwsd" } + }, + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "^/usr/bin/loolwsd: error ", + "^FATAL:", + "^Failed " + ] }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "CollaboraOnline: {MESSAGE}" } + } + ], + "Warn of local authentication errors": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "nslcd" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\[[^]]+\\] <([^>]+)> .*Can't contact LDAP server: (.*)$", "save": [ "nslcdClient", "nslcdError" ] }, + "else": "… NOOP if PRIORITY 3+" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "nslcd: {nslcdError} for {nslcdClient}@{_HOSTNAME}", "details": "FIRSTLAST" } + } + ], + "Notify of NFS access by clients": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "rpc.mountd" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^authenticated mount request from (.*):[0-9]+ for (.*) \\(.*\\)$", "save": [ "nfsClient", "nfsPath" ] }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "NFS access from {nfsClient} to {nfsPath}", "details": "NONE" } + } + ], + "Warn of minidlna errors while reading media files": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "minidlnad" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^metadata\\.c:.*Opening (.*) failed! \\[", "save": [ "torrentName" ] }, + "else": "… Notify of unhandled formats" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Minidlna error for {torrentName}" } + } + ], + "… Notify of unhandled formats": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^metadata\\.c:[0-9]+: warn: (.*): Unhandled format: (.*)$", "save": [ "torrentName", "mediaFormat" ] }, + "else": "… Warn of permission errors for minidlna" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Minidlna does not handle {mediaFormat} for {torrentName}", "details": "NONE" } + } + ], + "… Warn of permission errors for minidlna": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^monitor\\.c:[0-9]+: error: inotify_add_watch\\((.*)\\) \\[Permission non accordée\\]$", "save": [ "torrentName" ] }, + "else": "… NOOP if PRIORITY 4+" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Minidlna is not allowed to read {torrentName}", "details": "FIRSTLAST" } + } + ], + "Warn of sudo errors": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "sudo" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^ (.*) : user NOT in sudoers ;", "save": [ "thatUser" ] }, + "else": "… Notify of sudo logins" + }, + { + "action": "action_email", + "args": { "subject": "SUDO error!", "message": "Sudo error from user {thatUser} on {_HOSTNAME} on {__REALTIME_TIMESTAMP}." } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Sudo error from user {thatUser} on {_HOSTNAME}" } + } + ], + "… Notify of sudo logins": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^pam_unix\\(sudo:session\\): session opened for user (.*) by [^(]*\\(uid=([^)]+)\\)$", "save": [ "thatUser", "fromUID" ] }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login as {thatUser}@{_HOSTNAME} by {fromUID}:sudo", "details": "NONE" } + } + ], + "Notify of certificate renewals": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "dehydrated" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^ (?:\\+Requesting |rewrite )" }, + "else": "… Warn of dehydrated errors" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "ACME: {MESSAGE}" } + } + ], + "… Warn of dehydrated errors": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "ERROR|WARNING|FAILURE" }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "ACME: {MESSAGE}", "details": "FIRSTLAST" } + } + ], + "Discard ddclient debug entries": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "ddclient" }, + "then": "… NOOP if PRIORITY 6+" + } + ], + "Warn of core dumps": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "systemd-coredump" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "of user (.*) dumped core\\.$", "save": [ "thatUser" ] }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "Core dump for {thatUser}@{_HOSTNAME}" } + } + ], + "Notify of getty user logins": [ + { + "filter": "filter_pcre", + "args": { "field": "SYSLOG_IDENTIFIER", "re": "login" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "session opened for user (.*) by LOGIN\\(uid=0\\)$", "save": [ "thatUser" ] }, + "else": "… Immediate warning for getty failures" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Login as {thatUser}@{_HOSTNAME} by login:session" } + } + ], + "… Immediate warning for getty failures": [ + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^FAILED LOGIN " }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_email", + "args": { "subject": "Failed getty login", "message": "Failed getty login on {_HOSTNAME} on {__REALTIME_TIMESTAMP}:\n{MESSAGE}" } + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Failed getty login on {_HOSTNAME}" } + } + ], + "Notify of tunnel access through iodine": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "iodined" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^accepted" }, + "else": "… NOOP if PRIORITY 5+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "iodine: {MESSAGE}" } + } + ], + "Warn of SpamAssassin update failures": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "sa-update" } + }, + { + "filter": "filter_equals", + "args": { "field": "MESSAGE", "value": "channel: could not find working mirror, channel failed" }, + "else": "… NOOP if PRIORITY 4+" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "SpamAssassin update failed", "details": "FIRSTLAST" } + } + ], + "Notify of important PHP debug messages": [ + { + "filter": "filter_equals", + "args": { "field": "SYSLOG_IDENTIFIER", "value": "php-fpm" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\[[A-Z](?!OTICE)(?!EBUG)" }, + "else": "… Notify of PHP error messages" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "PHP: {MESSAGE}", "details": "FIRSTLAST" } + } + ], + "… Notify of PHP error messages": [ + { + "filter": "filter_lowerOrEquals", + "args": { "field": "PRIORITY", "value": 3 }, + "else": "… NOOP" + }, + { + "action": "action_dailyReport", + "args": { "level": "WARN", "message": "PHP: {MESSAGE}", "details": "FIRSTLAST" } + } + ], + "Notify of bad torrents": [ + { + "filter": "filter_equals", + "args": { "field": "_SYSTEMD_UNIT", "value": "transmission.service" } + }, + { + "filter": "filter_pcre", + "args": { "field": "MESSAGE", "re": "^\\[.{23}\\] (.*[^:]) (?:Scrape error: )?Could not connect to tracker", "save": [ "torrentName" ] }, + "else": "… Warn of Transmission errors" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Transmission could not connect to tracker for {torrentName}", "details": "LAST" } + } + ], + "… Warn of Transmission errors": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "(?PAll nameservers have failed) \\([^():]+:[0-9]+\\)$", + "(?PNo such file or directory) \\([^():]+:[0-9]+\\)$", + "(?PToo many open files) \\([^():]+:[0-9]+\\)$", + "(?PPermission denied) \\([^():]+:[0-9]+\\)$" + ] }, + "else": "… Filter-out uninteresting Transmission events" + }, + { + "action": "action_dailyReport", + "args": { "level": "INFO", "message": "Transmission error: {errMsg}", "details": "FIRSTLAST" } + } + ], + "… Filter-out uninteresting Transmission events": [ + { + "filter": "filter_pcreAny", + "args": { "field": "MESSAGE", "re": [ + "^\\[.{23}\\] (?:Bound socket|Cache Maximum cache size set to|RPC Server (?:Adding|Serving|Started|Stopped)|DHT (?:Bootstrapping|Finished bootstrapping|DHT initialized|Initializing|Reusing|Done uninitializing DHT|Saving|Not saving nodes|Uninitializing)|Port Forwarding Stopped|Saved \"|Using settings from|Watching \"|Searching for web interface file \"|Deleting input \\.torrent file|Parsing \\.torrent file successful|watchdir Callback decided to accept|Changed open file limit|(?:SO_RCVBUF|SO_SNDBUF) size is|Closing libevent|Loaded [0-9]+ torrent|watchdir Callback decided|Nameserver |Preallocated file \"|UDP Couldn't parse UDP tracker packet)", + "(?:Queued for verification|bytes per second\\)|[vV]erifying torrent\\.*|Announcing to tracker|Retrying (?:announce|scrape) in [0-9]+ seconds\\.|seconds from now\\.|Got [0-9]+ peers from tracker|checking just-completed piece [0-9]+|Starting IPv4 DHT announce \\([^)]+\\)|IPv4 peers from DHT|Pausing|Removing torrent|started|peers from resume file|\\.resume\"|files marked for download|Requested download is not authorized for use with this tracker\\.|Connection failed|\\(No Response\\)|(?:State changed from|moving) \"[^\"]+\" to \"[^\"]+\"|DHT announce done|failed its checksum test|403 \\(Forbidden\\)|404 \\(Not Found\\)|Tracker did not respond) \\([^():]+:[0-9]+\\)$" + ] }, + "then": "… NOOP" + } + ], + "… NOOP if PRIORITY 3+": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 3 }, + "then": "… NOOP" + } + ], + "… NOOP if PRIORITY 4+": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 4 }, + "then": "… NOOP" + } + ], + "… NOOP if PRIORITY 5+": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 5 }, + "then": "… NOOP" + } + ], + "… NOOP if PRIORITY 6+": [ + { + "filter": "filter_greaterOrEquals", + "args": { "field": "PRIORITY", "value": 6 }, + "then": "… NOOP" + } + ], + "… NOOP": [ + { + "action": "action_noop" + } + ], + "all_filters_failed": [ + { + "action": "action_dailyReport", + "args": { "level": "OTHER", "message": " [{PRIORITY}/{SYSLOG_IDENTIFIER}] {_UID}:{_GID}@{_HOSTNAME}:{_CMDLINE} ({_SYSTEMD_UNIT})\n{MESSAGE}" } + } + ] + }, + "email": { + "from": "pyruse@{{net_soa}}", + "to": [ + "hostmaster@{{net_soa}}" + ], + "subject": "Pyruse Daily Report", + "sendmail": [ "/usr/bin/sendmail", "-t" ] + }, + "nftBan": { + "nft": [ "/usr/local/bin/{{DMZ}}", "/usr/bin/nft \"$1\"" ] + }, + "8bit-message-encoding": "iso-8859-15", + "storage": "/var/lib/pyruse", + "debug": false +} diff --git a/roles/slapd/defaults/main.yml b/roles/slapd/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/slapd/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/slapd/handlers/main.yml b/roles/slapd/handlers/main.yml new file mode 100644 index 0000000..3f81636 --- /dev/null +++ b/roles/slapd/handlers/main.yml @@ -0,0 +1,18 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart slapd.service + command: | + bash -c ' + systemctl daemon-reload + systemctl stop slapd.service + rm -rf /etc/openldap/slapd.d/* + systemctl start slapd.service + systemctl stop slapd.service + slaptest -f /etc/openldap/slapd.conf -F /etc/openldap/slapd.d/ + slapindex + chown -R ldap:ldap /var/lib/openldap/openldap-data + chown -R ldap:ldap /etc/openldap/slapd.d + systemctl start slapd.service' diff --git a/roles/slapd/meta.OK/main.yml b/roles/slapd/meta.OK/main.yml new file mode 100644 index 0000000..38034be --- /dev/null +++ b/roles/slapd/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: sockets diff --git a/roles/slapd/tasks/main.yml b/roles/slapd/tasks/main.yml new file mode 100644 index 0000000..a72cdae --- /dev/null +++ b/roles/slapd/tasks/main.yml @@ -0,0 +1,269 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: OpenLDAP daemon +### ⇐ UPSTREAM BEGIN ### + +- name: install packages + package: + name: "{{item}}" + state: present + with_items: + - python2-ldap + - openldap + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: OpenLDAP daemon +### ⇐ UPSTREAM END ### + +- name: make sure /etc/systemd/system/slapd.service.d exists + file: + path: /etc/systemd/system/slapd.service.d + state: directory + mode: 0755 + +- name: override slapd.service settings + copy: + content: | + [Unit] + After=systemd-tmpfiles-setup.service + [Service] + Group=ldap + RuntimeDirectory=openldap + RuntimeDirectoryMode=0775 + ExecStart= + ExecStart=/usr/bin/slapd -u ldap -g ldap -h 'ldap:// ldapi://%%2Frun%%2Fshared_sockets%%2Fldapi' + dest: /etc/systemd/system/slapd.service.d/shared_sockets.conf + mode: 0644 + notify: + - restart slapd.service + +- name: create LDAP data directory + file: + path: /var/lib/openldap/openldap-data + state: directory + +- name: set LDAP root credentials + lineinfile: + path: /etc/openldap/slapd.conf + regexp: '^{{item.var}}' + line: '{{item.var}} {{item.val}}' + with_items: + - {var: suffix, val: '"{{ldap_root}}"'} + - {var: rootdn, val: '"cn=root,{{ldap_root}}"'} + - {var: rootpw, val: '"{{ldap_rootpw_sha}}"'} + notify: + - restart slapd.service + +- name: include LDAP schemas + blockinfile: + path: /etc/openldap/slapd.conf + marker: + '# {mark} https://wiki.archlinux.org/index.php/OpenLDAP - include' + block: | + include /etc/openldap/schema/cosine.schema + include /etc/openldap/schema/inetorgperson.schema + include /etc/openldap/schema/nis.schema + include /etc/openldap/schema/misc.schema + insertafter: '^include' + validate: slaptest -f %s -F /etc/openldap/slapd.d/ -u + notify: + - restart slapd.service + +- name: set LDAP indexes + blockinfile: + path: /etc/openldap/slapd.conf + marker: + '# {mark} https://wiki.archlinux.org/index.php/OpenLDAP - index' + block: | + index uid pres,eq + index mail pres,sub,eq + index cn pres,sub,eq + index sn pres,sub,eq + index dc eq + insertafter: '^index' + validate: slaptest -f %s -F /etc/openldap/slapd.d/ -u + notify: + - restart slapd.service + +- name: set LDAP access rules + blockinfile: + path: /etc/openldap/slapd.conf + marker: '# {mark} https://wiki.archlinux.org/index.php/LDAP_authentication - access' + block: | + {{ldap_extra_acl}} + access to attrs=userPassword,givenName,sn,photo + by self write + by anonymous auth + by dn.base="cn=root,{{ldap_root}}" write + by * none + access to * + by self read + by dn.base="cn=root,{{ldap_root}}" write + by * read + insertbefore: '^# Sample access control policy' + validate: slaptest -f %s -F /etc/openldap/slapd.d/ -u + notify: + - restart slapd.service + +- name: create LDAP DB_CONFIG + command: bash -c 'cp /var/lib/openldap/openldap-data/DB_CONFIG{.example,}' + args: + creates: /var/lib/openldap/openldap-data/DB_CONFIG + notify: + - restart slapd.service + +- name: LDAP enabled + systemd: + daemon_reload: true + name: slapd.service + enabled: true + +- meta: flush_handlers + +- name: domain LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "{{ldap_root}}" + objectClass: + - top + - dcObject + - organization + attributes: + dc: "{{ldap_root | regex_replace('^dc=([^,]+)(?:,.*)?$', '\\1')}}" + o: "{{ldap_o_name}}" + +- name: root LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "cn=root,{{ldap_root}}" + objectClass: + - top + - organizationalRole + attributes: + cn: root + description: LDAP administrator + roleOccupant: "{{ldap_root}}" + +- name: Users and Groups categories LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "ou={{item}},{{ldap_root}}" + objectClass: + - top + - organizationalUnit + attributes: + ou: "{{item}}" + with_items: + - Users + - Groups + - SUDOers + +- name: system users LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "uid={{item.uid}},ou=Users,{{ldap_root}}" + objectClass: + - top + - person + - inetOrgPerson + - organizationalPerson + - posixAccount + - shadowAccount + attributes: + uid: "{{item.uid}}" + uidNumber: "{{item.uidNumber}}" + gidNumber: "{{item.gidNumber}}" + userPassword: "{{item.password}}" + homeDirectory: "/home/{{item.uid}}" + loginShell: /bin/bash + givenName: "{{item.cn}}" + cn: "{{item.cn}}" + sn: "{{item.sn}}" + mail: "{{item.uid}}@{{net_soa}}" + with_items: "{{ldap_system_users}}" + +- name: virtual users LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "uid={{item.uid}},ou=Users,{{ldap_root}}" + objectClass: + - top + - person + - inetOrgPerson + - organizationalPerson + - posixAccount + attributes: + uid: "{{item.uid}}" + uidNumber: "{{ldap_virtual_user_uid}}" + gidNumber: "{{ldap_virtual_user_gid}}" + userPassword: "{{item.password}}" + homeDirectory: "/var/spool/mail/{{item.uid}}" + loginShell: /usr/bin/nologin + givenName: "{{item.cn}}" + cn: "{{item.cn}}" + sn: "{{item.sn}}" + mail: "{{item.uid}}@{{net_soa}}" + with_items: "{{ldap_virtual_users}}" + +- name: all users’ properties LDIF + ldap_attr: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "uid={{item.uid}},ou=Users,{{ldap_root}}" + name: "{{item.attr}}" + values: "{{item.value}}" + state: exact + with_items: "{{ldap_users_attrs}}" + +- name: groups LDIF + ldap_entry: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "cn={{item.cn}},ou=Groups,{{ldap_root}}" + objectClass: + - top + - posixGroup + attributes: + gidNumber: "{{item.gidNumber}}" + with_items: "{{ldap_system_groups}}" + +- name: declare existing groups’ members + ldap_attr: + server_uri: ldapi://%2Frun%2Fshared_sockets%2Fldapi/ + bind_dn: "cn=root,{{ldap_root}}" + bind_pw: "{{ldap_rootpw}}" + dn: "cn={{item.group}},ou=Groups,{{ldap_root}}" + name: memberuid + values: "{{item.member}}" + state: present + with_items: "{{ldap_system_group_members}}" + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: OpenLDAP daemon +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/sockets/defaults/main.yml b/roles/sockets/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/sockets/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/sockets/handlers/main.yml b/roles/sockets/handlers/main.yml new file mode 100644 index 0000000..e670dec --- /dev/null +++ b/roles/sockets/handlers/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: create tmpfiles + command: systemd-tmpfiles --create diff --git a/roles/sockets/tasks/main.yml b/roles/sockets/tasks/main.yml new file mode 100644 index 0000000..b9d455f --- /dev/null +++ b/roles/sockets/tasks/main.yml @@ -0,0 +1,22 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: create a directory for shared sockets + copy: + content: | + #Type Path Mode UID GID Age Argument + d /run/shared_sockets 1777 root root - - + dest: /etc/tmpfiles.d/shared_sockets.conf + mode: 0644 + notify: + - create tmpfiles + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: Shared sockets +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/ssh/defaults/main.yml b/roles/ssh/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ssh/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ssh/files/dmz.ssh_host_rsa_key b/roles/ssh/files/dmz.ssh_host_rsa_key new file mode 100644 index 0000000..5f383ee --- /dev/null +++ b/roles/ssh/files/dmz.ssh_host_rsa_key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +-----END RSA PRIVATE KEY----- diff --git a/roles/ssh/files/dmz.ssh_host_rsa_key.pub b/roles/ssh/files/dmz.ssh_host_rsa_key.pub new file mode 100644 index 0000000..5102e49 --- /dev/null +++ b/roles/ssh/files/dmz.ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa … diff --git a/roles/ssh/files/home.ssh_host_rsa_key b/roles/ssh/files/home.ssh_host_rsa_key new file mode 100644 index 0000000..5f383ee --- /dev/null +++ b/roles/ssh/files/home.ssh_host_rsa_key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +… +-----END RSA PRIVATE KEY----- diff --git a/roles/ssh/files/home.ssh_host_rsa_key.pub b/roles/ssh/files/home.ssh_host_rsa_key.pub new file mode 100644 index 0000000..5102e49 --- /dev/null +++ b/roles/ssh/files/home.ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa … diff --git a/roles/ssh/handlers/main.yml b/roles/ssh/handlers/main.yml new file mode 100644 index 0000000..be26a73 --- /dev/null +++ b/roles/ssh/handlers/main.yml @@ -0,0 +1,12 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart sshd.service + systemd: + daemon_reload: true + name: sshd.service + state: restarted + when: + - (chroot == "") diff --git a/roles/ssh/meta.OK/main.yml b/roles/ssh/meta.OK/main.yml new file mode 100644 index 0000000..a03553a --- /dev/null +++ b/roles/ssh/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: init diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml new file mode 100644 index 0000000..1cfee13 --- /dev/null +++ b/roles/ssh/tasks/main.yml @@ -0,0 +1,246 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +# WARNING: This file may be used inside a mounted chroot. +# The running system should not be assumed to be the target system. + +### UPSTREAM BEGIN ⇒ ### +- name: settings necessary for pulling from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: SSH +### ⇐ UPSTREAM BEGIN ### + +- name: create the bastion user (front only) + user: + name: "{{ssh_bastion_user}}" + password: "{{ssh_bastion_pwd_sha512}}" + create_home: true + system: true + when: + - (inventory_hostname in groups['front']) + +- name: send secure SSH host RSA key + copy: + src: "files/{{hostname}}.{{item.name}}" + dest: "{{chroot}}/etc/ssh/{{item.name}}" + mode: "{{item.perm}}" + with_items: + - {name: ssh_host_rsa_key, perm: '0400'} + - {name: ssh_host_rsa_key.pub, perm: '0444'} + notify: + - restart sshd.service + +- name: force mode of other secure keys (no error) + file: + path: "{{chroot}}/etc/ssh/{{item.name}}" + mode: "{{item.perm}}" + ignore_errors: true + with_items: + - {name: ssh_host_ed25519_key, perm: '0400'} + - {name: ssh_host_ed25519_key.pub, perm: '0444'} + +- name: send Ansible’s forced-command + copy: + content: | + #!/bin/bash + eval $SSH_ORIGINAL_COMMAND + dest: "{{chroot}}/root/.ssh/force_ansible.sh" + mode: 0700 + notify: + - restart sshd.service + +- name: copy Ansible key to root’s home + lineinfile: + path: "{{chroot}}/root/.ssh/authorized_keys" + regexp: "{{ansible_authorized_key}}" + line: > + from="{{ansible_master}}",restrict,command="/root/.ssh/force_ansible.sh" + {{ansible_authorized_key}} + create: true + mode: 0600 + notify: + - restart sshd.service + +- name: enable sshd + file: + src: /usr/lib/systemd/system/sshd.service + dest: "{{chroot}}/etc/systemd/system/multi-user.target.wants/sshd.service" + state: link + +- meta: flush_handlers + +### UPSTREAM END ⇒ ### +- name: merge local settings + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: SSH +### ⇐ UPSTREAM END ### + +- name: force mode of other secure keys + file: + path: "{{chroot}}/etc/ssh/{{item.name}}" + mode: "{{item.perm}}" + with_items: + - {name: ssh_host_ed25519_key, perm: '0400'} + - {name: ssh_host_ed25519_key.pub, perm: '0444'} + when: + - (chroot == '') + notify: + - restart sshd.service + +- name: SSH hardening from https://stribika.github.io/ + blockinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + marker: + '# {mark} https://stribika.github.io/2015/01/04/secure-secure-shell.html' + block: | + KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 + Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com + insertafter: '^#?ListenAddress\s' + notify: + - restart sshd.service + +- name: enable the secure host keys + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + backrefs: true + regexp: '^#?(HostKey\s+/etc/ssh/ssh_host_{{item}}_key)' + line: '\1' + with_items: + - ed25519 + - rsa + notify: + - restart sshd.service + +- name: disable the insecure host keys + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + backrefs: true + regexp: '^#?(HostKey\s+/etc/ssh/ssh_host_{{item}}_key)' + line: '#\1' + with_items: + - dsa + - ecdsa + notify: + - restart sshd.service + +- name: restrict root login + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + regexp: '^#?PermitRootLogin\s' + line: 'PermitRootLogin forced-commands-only' + notify: + - restart sshd.service + +- name: allow TCP forwarding + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + regexp: '^#?AllowTcpForwarding\s' + line: "AllowTcpForwarding {{ssh_allow_tcpforward}}" + notify: + - restart sshd.service + +- name: allow gateway ports + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + regexp: '^#?GatewayPorts\s' + line: "GatewayPorts {{ssh_allow_gatewayports}}" + notify: + - restart sshd.service + +- name: allow X11 forwarding + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + regexp: '^#?X11Forwarding\s' + line: "X11Forwarding {{ssh_allow_x11forward}}" + notify: + - restart sshd.service + +- name: set keep-alive interval + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + regexp: '^#?ClientAliveInterval\s' + line: "ClientAliveInterval {{ssh_clientalive_interval}}" + notify: + - restart sshd.service + +- name: allow tunnel + lineinfile: + path: "{{chroot}}/etc/ssh/sshd_config" + regexp: '^#?PermitTunnel\s' + line: "PermitTunnel {{ssh_allow_tunnel}}" + notify: + - restart sshd.service + +- name: extended front setup + blockinfile: + path: "/etc/ssh/sshd_config" + marker: + '# {mark} extended setup' + block: | + # regular port + Port 22 + # alternative port + Port 23 + # remote-help port + Port 22000 + AcceptEnv {{ssh_accept_env}} + AllowUsers {{ssh_allowed_users}} + ForceCommand /usr/bin/nologin + Match Address {{(net_trusted_ranges + ' ' + (iodine_net | ipaddr('0'))) | replace(' ', ',')}} + ForceCommand none + Match LocalPort 23 + ForceCommand none + Match LocalPort 22000 + ForceCommand /usr/bin/echo 'Use: ssh -NTxR 2200x:localhost:22 -i /your/key -p 22000 {{ssh_bastion_user}}@{{net_soa}}' + PermitTTY no + AuthenticationMethods publickey + MaxAuthTries 1 + MaxSessions 0 + X11Forwarding no + insertafter: EOF + when: + - (inventory_hostname in groups['front']) + - (chroot == '') + notify: + - restart sshd.service + +- name: extended back setup + blockinfile: + path: "/etc/ssh/sshd_config" + marker: + '# {mark} extended setup' + block: | + # regular port + Port 22 + # git port + Port 2222 + AcceptEnv {{ssh_accept_env}} + AllowUsers {{ssh_allowed_users}} + ForceCommand /usr/bin/nologin + Match Address {{(net_trusted_ranges + ' ' + (iodine_net | ipaddr('0'))) | replace(' ', ',')}} + ForceCommand none + Match LocalPort 2222 + AllowUsers git + PermitRootLogin no + PasswordAuthentication no + PermitEmptyPasswords no + PubkeyAuthentication yes + insertafter: EOF + when: + - (inventory_hostname in groups['back']) + - (chroot == '') + notify: + - restart sshd.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: SSH +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/ssh/vars/front_chroot.yml b/roles/ssh/vars/front_chroot.yml new file mode 120000 index 0000000..dc1902a --- /dev/null +++ b/roles/ssh/vars/front_chroot.yml @@ -0,0 +1 @@ +../../../group_vars/front_chroot \ No newline at end of file diff --git a/roles/ssowat/defaults/main.yml b/roles/ssowat/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/ssowat/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/ssowat/handlers/main.yml b/roles/ssowat/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/ssowat/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/ssowat/meta.OK/main.yml b/roles/ssowat/meta.OK/main.yml new file mode 100644 index 0000000..511dd25 --- /dev/null +++ b/roles/ssowat/meta.OK/main.yml @@ -0,0 +1,7 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate diff --git a/roles/ssowat/tasks/main.yml b/roles/ssowat/tasks/main.yml new file mode 100644 index 0000000..a790225 --- /dev/null +++ b/roles/ssowat/tasks/main.yml @@ -0,0 +1,80 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: SSOwat +### ⇐ UPSTREAM BEGIN ### + +- name: install AUR software + include_role: + name: aur.inc + allow_duplicates: true + vars: + pkg_names: | + [ + "lua51-lualdap-git", + "ssowat-git" + ] + aur_user: git +# USUALLY NOT UP-TO-DATE… :-( +# "nginx-mainline-mod-ndk", +# "nginx-mainline-mod-lua", + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: SSOwat +### ⇐ UPSTREAM END ### + +- name: send the custom SSOwat configuration + template: + src: templates/conf.json.j2 + dest: /etc/ssowat/conf.json + group: http + mode: 0640 + +- name: add the LUA language to Nginx + copy: + content: | + load_module /usr/lib/nginx/modules/ndk_http_module.so; + load_module /usr/lib/nginx/modules/ngx_http_lua_module.so; + dest: /etc/nginx/main.inc.d/ndk+lua.inc + mode: 0644 + notify: + - restart nginx.service + +- name: init the SSO code in Nginx + copy: + content: | + lua_shared_dict cache 10m; + init_by_lua_file /etc/ssowat/init.lua; + dest: /etc/nginx/conf.d/00_ssowat.conf + group: http + mode: 0640 + notify: + - restart nginx.service + +- name: enforce SSO checking for each request + copy: + content: | + access_by_lua_file /etc/ssowat/access.lua; + header_filter_by_lua_file /etc/ssowat/headers.lua; + dest: /etc/nginx/inc.d/00_ssowat.https.inc + group: http + mode: 0640 + notify: + - restart nginx.service + +### LOCAL COMMIT ⇒ ### +- name: commit local changes + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=local.yml + vars: + msg: SSOwat +### ⇐ LOCAL COMMIT ### +- meta: flush_handlers diff --git a/roles/ssowat/templates/conf.json.j2 b/roles/ssowat/templates/conf.json.j2 new file mode 100644 index 0000000..934295d --- /dev/null +++ b/roles/ssowat/templates/conf.json.j2 @@ -0,0 +1,65 @@ +{ + "portal_scheme": "https", + "portal_domain": "{{net_soa}}", + "portal_path": "{{http_pfx_ssowat}}/", + "ldap_host": "{{SafeZone}}", + "ldap_group": "ou=Users,{{ldap_root}}", + "ldap_attributes": ["uid", "givenname", "sn", "cn", "mail"], + "ldap_enforce_crypt": false, + "skipped_urls": [ + "/hosting/discovery", + "/loleaflet", + "/lool", + "{{http_pfx_movim}}/?infos" + ], + "skipped_regex": [ + "^/[_afhitxy.]", + "^/g[^i]", + "^/li", + "^/p[^b]" + ], + "unprotected_urls": [ + "{{http_pfx_dotclear}}", + "{{http_pfx_gitea}}", + "{{http_pfx_nextcloud}}", + "{{http_pfx_privatebin}}" + ], + "protected_urls": [ + "{{http_pfx_dotclear}}/admin", + "{{http_pfx_gitea}}/admin", + "{{http_pfx_gitea}}/repo/create", + "{{http_pfx_gitea}}/repo/migrate", + "{{http_pfx_gitea}}/org/create" + ], + "protected_regex": [ + "^{{http_pfx_gitea}}/.-/wiki/_new", + "^{{http_pfx_privatebin}}/?$" + ], + "users": { + "*": { + "allow": { + "{{net_soa}}{{http_pfx_dotclear}}/admin/": "Blog", + "{{net_soa}}{{http_pfx_gitea}}/": "Git", + "{{net_soa}}{{http_pfx_lam}}/": "{{lam_sso_title}}", + "{{net_soa}}{{http_pfx_nextcloud}}/": "Cloud", + "{{net_soa}}{{http_pfx_privatebin}}/": "{{privatebin_sso_title}}" + } + }, + "you": { + "allow": { + "{{net_soa}}{{http_pfx_transmission}}": "BitTorrent" + } + }, + "me": { + "allow": { + "{{net_soa}}{{http_pfx_movim}}/": "Social", + "{{net_soa}}{{http_pfx_transmission}}": "BitTorrent", + "{{net_soa}}{{http_pfx_wallabag}}/": "Read later" + } + } + }, + "logout": { + "dcxd": "https://{{net_soa}}{{http_pfx_dotclear}}/admin/index.php?logout=1" + }, + "default_language": "{{locales_default | truncate(2, True, '', 0)}}" +} diff --git a/roles/transmission/defaults/main.yml b/roles/transmission/defaults/main.yml new file mode 100644 index 0000000..4e7b3f5 --- /dev/null +++ b/roles/transmission/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +ansible_python_interpreter: /usr/bin/python2 diff --git a/roles/transmission/handlers/main.yml b/roles/transmission/handlers/main.yml new file mode 100644 index 0000000..8f91f95 --- /dev/null +++ b/roles/transmission/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +- name: restart nginx.service + systemd: + daemon_reload: true + name: nginx.service + state: restarted diff --git a/roles/transmission/meta.OK/main.yml b/roles/transmission/meta.OK/main.yml new file mode 100644 index 0000000..784c894 --- /dev/null +++ b/roles/transmission/meta.OK/main.yml @@ -0,0 +1,8 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +dependencies: + - role: cleanupdate + - role: ssowat diff --git a/roles/transmission/tasks/main.yml b/roles/transmission/tasks/main.yml new file mode 100644 index 0000000..73884ed --- /dev/null +++ b/roles/transmission/tasks/main.yml @@ -0,0 +1,185 @@ +--- +# The home-server project produces a multi-purpose setup using Ansible. +# Copyright © 2018 Y. Gablin, under the GPL-3.0-or-later license. +# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing. + +### UPSTREAM BEGIN ⇒ ### +- name: pull prerequisites from upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=upstream.yml + vars: + msg: Transmission +### ⇐ UPSTREAM BEGIN ### + +- name: install software + package: + name: transmission-cli + +### UPSTREAM END ⇒ ### +- name: merge upstream + include_role: name=etckeeper.inc allow_duplicates=true tasks_from=merge.yml + vars: + msg: Transmission +### ⇐ UPSTREAM END ### + +- name: prepare to override systemd settings for transmission + file: + name: /etc/systemd/system/transmission.service.d + state: directory + mode: 0755 + +- name: override systemd settings for transmission + copy: + content: | + [Unit] + Requires=nslcd.service + After=nslcd.service + [Service] + CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_LEASE CAP_SYS_CHROOT CAP_SYS_NICE + PrivateDevices=yes + PrivateTmp=yes + ProtectHome=yes + ProtectSystem=full + LimitNOFILE=4096 + Group={{media_group}} + dest: /etc/systemd/system/transmission.service.d/secure-{{nickname}}.conf + mode: 0644 + +- name: ensure existence and mode of Transmission working directories + file: + path: /var/lib/transmission{{item}} + state: directory + owner: transmission + group: "{{media_group}}" + mode: 06770 + with_items: + - '' + - /.config/transmission-daemon + - /Todo + - /Doing + - /Done + +- name: set default ACL on Todo and Done + acl: + path: /var/lib/transmission{{item.path}} + default: true + entity: "{{item.e}}" + etype: "{{item.t}}" + permissions: rwx + state: present + recursive: true + with_items: + - {path: /Todo, e: transmission, t: user} + - {path: /Todo, e: "{{media_group}}", t: group} + - {path: /Done, e: transmission, t: user} + - {path: /Done, e: "{{media_group}}", t: group} + +- name: set current ACL on Todo and Done + acl: + path: /var/lib/transmission{{item.path}} + default: false + entity: "{{item.e}}" + etype: "{{item.t}}" + permissions: rwx + state: present + recursive: true + with_items: + - {path: /Todo, e: transmission, t: user} + - {path: /Todo, e: "{{media_group}}", t: group} + - {path: /Done, e: transmission, t: user} + - {path: /Done, e: "{{media_group}}", t: group} + +- name: make sure that at least an empty configuration file is present + copy: + content: | + { + } + dest: /var/lib/transmission/.config/transmission-daemon/settings.json + group: "{{media_group}}" + mode: 0640 + force: false + +- name: stop transmission.service + systemd: + daemon_reload: true + name: transmission.service + state: stopped + +- name: put a JSON terminator to avoid a trailing comma + lineinfile: + path: /var/lib/transmission/.config/transmission-daemon/settings.json + regexp: '^\s*"zzz"' + line: ' "zzz": false' + insertbefore: '^}' + +- name: send Transmission configuration + lineinfile: + path: /var/lib/transmission/.config/transmission-daemon/settings.json + regexp: '^\s*"{{item.key}}"' + line: ' "{{item.key}}": {{item.value}},' + insertbefore: '"zzz"' + with_dict: + speed-limit-up: '50' + speed-limit-up-enabled: 'true' + download-dir: '"/var/lib/transmission/Done"' + incomplete-dir: '"/var/lib/transmission/Doing"' + incomplete-dir-enabled: 'true' + rename-partial-files: 'false' + trash-original-torrent-files: 'true' + umask: '7' + watch-dir: '"/var/lib/transmission/Todo"' + watch-dir-enabled: 'true' + encryption: '2' + message-level: '1' + bind-address-ipv4: '"{{DMZ_IP}}"' + peer-port: '{{transmission_bt_port}}' + peer-port-random-on-start: 'false' + port-forwarding-enabled: 'false' + queue-stalled-minutes: '5' + rpc-authentication-required: 'false' + rpc-bind-address: '"127.0.0.1"' + rpc-port: '{{transmission_rpc_port}}' + rpc-url: '"{{http_pfx_transmission}}/"' + rpc-whitelist-enabled: 'false' + +- name: start transmission.service + systemd: + daemon_reload: true + name: transmission.service + state: started + +- name: configure nginx for Transmission + copy: + content: | + location {{http_pfx_transmission}}/web { + alias /usr/share/transmission/web; + } + location ~ ^{{http_pfx_transmission}}/?$ { + return 307 https://{{net_soa}}{{http_pfx_transmission}}/web/; + } + location ~ ^{{http_pfx_transmission}}.*$(? \\1;/p" $r/meta*/main.yml 2>/dev/null + done + echo '}' +} | tr . _ >../roles.dot +cd .. +dot -Tpdf roles.dot >dot.pdf +twopi -Tpdf roles.dot >twopi.pdf +circo -Tpdf roles.dot >circo.pdf