pax_global_header00006660000000000000000000000064143772727560014536gustar00rootroot0000000000000052 comment=fce44a50e32de67f6c25e77317746e445f075a16 ltsp-23.02/000077500000000000000000000000001437727275600125265ustar00rootroot00000000000000ltsp-23.02/.github/000077500000000000000000000000001437727275600140665ustar00rootroot00000000000000ltsp-23.02/.github/ISSUE_TEMPLATE/000077500000000000000000000000001437727275600162515ustar00rootroot00000000000000ltsp-23.02/.github/ISSUE_TEMPLATE/ltsp-issue.md000066400000000000000000000006271437727275600207100ustar00rootroot00000000000000--- name: LTSP issue about: LTSP issue title: '' labels: '' assignees: '' --- If the issue you're about to report is likely to result in LTSP code or documentation changes, proceed by replacing this text with its description. If it's a question or discussion that will probably not result in LTSP code or documentation changes, please start a discussion instead: https://github.com/ltsp/ltsp/discussions ltsp-23.02/AUTHORS000066400000000000000000000006431437727275600136010ustar00rootroot00000000000000For the current LTSP >=19 version contributors, please see: https://github.com/ltsp/ltsp/graphs/contributors In 2019, LTSP was rewritten from scratch by Alkis Georgopoulos as part of a Google Summer of Code project: https://github.com/eellak/gsoc2019-ltsp Thanks to summerofcode.withgoogle.com and to ellak.gr! For the initial LTSP <=5 version contributors, please see: https://launchpad.net/ltsp ltsp-23.02/LICENSE000066400000000000000000001045151437727275600135410ustar00rootroot00000000000000 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 . ltsp-23.02/README.md000066400000000000000000000006041437727275600140050ustar00rootroot00000000000000# Linux Terminal Server Project Linux Terminal Server Project helps in netbooting LAN clients from a single template installation that resides in a virtual machine image or a chroot on the LTSP server, or the server root (/, chrootless). This way maintaining tens or hundreds of diskless clients is as easy as maintaining a single PC. LTSP website: [https://ltsp.org](https://ltsp.org) ltsp-23.02/debian/000077500000000000000000000000001437727275600137505ustar00rootroot00000000000000ltsp-23.02/debian/changelog000066400000000000000000000220071437727275600156230ustar00rootroot00000000000000ltsp (23.02-1) unstable; urgency=medium * Match both memtest86+x32.bin and memtest86+ia32.bin (#801) -- Alkis Georgopoulos Tue, 28 Feb 2023 05:27:32 +0200 ltsp (23.01-1) unstable; urgency=medium [ Alkis Georgopoulos ] * Search for new memtest86+ binaries (#783) * Don't chmod -x epoptes anymore * Avoid systemd lingering (#778) * Avoid broken pipe warning (#767) * Better sssd autodetection (#749) * Support arctica-greeter (#742) * Search for DISABLE_SYSTEM_SERVICES in /lib too (#690) [ Vagrant Cascadian ] * debian/copyright: Update for v23.01. * debian/control: Update to Standards-Version 4.6.2, no changes. -- Vagrant Cascadian Tue, 24 Jan 2023 15:12:21 -0800 ltsp (22.01-2) unstable; urgency=medium * debian/patches: Make the generated dates timezone-agnostic. Thanks to Chris Lamb! Closes: #1005029. * debian/control: Update Standards-Version to 4.6.0. -- Vagrant Cascadian Sat, 05 Feb 2022 15:17:00 -0800 ltsp (22.01-1) unstable; urgency=medium * Use cp -u to inject ltsp (#636) * Prefer go-md2man or pandoc over ronn for man pages (#635, #640) * Use more compatible markdown (#634) * Introduce DISABLE_SYSTEM_SERVICES (#495) * debian/watch: Use tags to scan for new upstream versions * Suggest single partitions for VMs (#618) * Implement ltsp remoteapps (#597) * Avoid creating /srv/ltsp/raspios/boot/overlays/overlays (#594) * Check if iPXE has the features we need (#579) * Add e4:5f:01 rpi OUI in dnsmasq and ltsp.conf (#498) * Implement multiseat autodetection (#133) * Use correct root hash in ltsp.conf.5.md (#547) * Correctly replace BASE_DIR in ltsp.ipxe (#416) * Don't hardcode LTSP paths in 25-ro-root.sh (#485) * Use the system hostname (#568) * Use DNS=1 on ltsp clients (#373) * Introduce undocumented ltsp session * Add PATH and username for CRONTAB_x (#454) * Check if sssd.conf exists (#450) -- Alkis Georgopoulos Sat, 22 Jan 2022 09:08:11 +0200 ltsp (21.01-1) unstable; urgency=medium [ Alkis Georgopoulos ] * Update copyright in man pages * Support openssh 8.4 (#358) * Document separate partitions in ltsp image (#347) * Correctly pass SSH_OPTIONS to pamltsp (#114) * Increase SSHFS cleanup timeout * Avoid more than 2 login error messages (#353) * Remove all resume configuration files (#346) * Transform INCLUDE into section_call (#343) * Rename Raspbian to Raspberry Pi OS (#342) * Allow NFS home in live CDs (#138) * Avoid tmpfs home (#338) * Make images accessible only by root (#336) * Document the correct /etc/ltsp/bin/sshfs path (#341) * Initial SSSD/LDAP support (#338) * Update issue template for discussions * Prevent AUTOLOGIN blocking boot in unmatched clients (#157) * Disable nfs-kernel-server by default * Update ltsp.conf.5.md [ Vagrant Cascadian ] * debian/patches: Prefer resolvectl over systemd-resolve. (Closes: #979261) [ Sébastien Ducoulombier ] * make debclean remove docs/man -- Vagrant Cascadian Mon, 11 Jan 2021 14:22:12 -0800 ltsp (20.06-1) unstable; urgency=low * Use sftp auth to allow disabling ssh logins (#128) * Set upstream metadata fields (#129) * Support multiseat (#133) * Support netbooting without an initrd (#68) * Default to READ_AHEAD_KB=128 (#27) * Add simple NFS_HOME example in ltsp-nfs.5.md -- Alkis Georgopoulos Fri, 19 Jun 2020 09:11:09 +0300 ltsp (20.04-1) unstable; urgency=medium * Fix NFS chroot booting regression (#126) -- Alkis Georgopoulos Fri, 03 Apr 2020 08:27:15 +0300 ltsp (20.03.1-1) unstable; urgency=medium * Exclude account database backups from the generated image -- Alkis Georgopoulos Sun, 22 Mar 2020 16:05:40 +0200 ltsp (20.03-1) unstable; urgency=medium * Use eval_percent in PWMERGE_* (#118) * Optimize autodetection for gnome-keyring-daemon removal (#115) * Empty /var/log in ltsp init (#123) * Disable snap refresh and hardlinks (#120) * Update masked services for Ubuntu 20.04 (#124) * Use absolute symlink for ltsp.service (#121) * When disabling cups, also disable cups-browsed -- Alkis Georgopoulos Mon, 16 Mar 2020 10:46:12 +0200 ltsp (20.01-1) unstable; urgency=medium [ Alkis Georgopoulos ] * Permit netbooting by just copying the /usr/share/ltsp directory * Allow separate boot partition (#43) * Server-side support for Raspberry Pis (#68) * Introduce ADD_IMAGE_EXCLUDES and OMIT_IMAGE_EXCLUDES (#83) [ Vagrant Cascadian ] * debian/copyright: Use https URL for gnu.org licenses page [ Alkis Georgopoulos ] * Avoid warning when unlocking screensaver * If systemd units cannot be disabled, warn without failing * Restore live CD netbooting (#43) [ Vagrant Cascadian ] * docs/ltsp.conf: Fix spelling of "overridden". * debian/control: Update Standards-Version to 4.5.0, no changes. * Make cups mask only warn on failure. * Add cups.path and cups.socket to default masked services. [ Alkis Georgopoulos ] * Don't activate debug_shell while inside a subshell * Avoid partprobe race condition (#112) * Consolidate systemctl calls -- Vagrant Cascadian Thu, 30 Jan 2020 10:01:06 -0800 ltsp (19.12.1-1) unstable; urgency=medium [ Alkis Georgopoulos ] * Add var/spool/squid/* to image.excludes (#106) * Amend tftp-root path (#107) * pamltsp is not an LTSP applet and can't use is_command * Introduce CUPS_SERVER (#8) [ kvaps ] * Allow global KERNEL_PARAMETERS (#110) [ Alkis Georgopoulos ] * Allow global DEFAULT_IMAGE [ Vagrant Cascadian ] * Add var/cache/squid-deb-proxy/* to image.excludes -- Vagrant Cascadian Thu, 19 Dec 2019 23:33:44 -0800 ltsp (19.12-1) unstable; urgency=low * Use pam-auth-update to install pamltsp (#104) * Fuse3 doesn't need nonempty param (#99) * Demote sshfs to Recommends (#98) * Die after getopt errors (#94) * Introduce IMAGE_TO_RAM parameter * Introduce OMIT_FUNCTIONS parameter * Move back to ltsp.org * Show applets in ltsp --help (#80) * Workaround single quote Ronn bug (#72) * Document MENU_TIMEOUT for [server], not [clients] (#69) * Introduce textifb (#69) * Only modprobe loop if it's not loaded (#70) -- Alkis Georgopoulos Fri, 06 Dec 2019 10:49:53 +0200 ltsp (19.11-1) unstable; urgency=low * Prefer external ltsp-binaries package, fall back to ipxe (#49) * Don't exclude /snap (#63) * Make RELOGIN work in lightdm (#58) * Remove HOSTNAME_PREFIX and _SUFFIX (#57) * Enable ltsp.service with compat=10 * Amend AUTOLOGIN/PASSWORDS_x example (#58) * Use SOURCE_DATE_EPOCH to set the date when generating man pages * Command line parameters take precedence over ltsp.conf * Default to OVERWRITE=1 (#53) * Fix ltsp.conf manpage online rendering -- Alkis Georgopoulos Mon, 11 Nov 2019 09:52:19 +0200 ltsp (19.10-1) experimental; urgency=medium [ Alkis Georgopoulos ] * Correct typo, x64_32 => x86_32 * Enhance install/base64 commands in ltsp.conf * Use x86_32 in iPXE for all x86 32bit variants * Avoid dd/swap boot block on small extended partitions * Use no_root_squash for NFS3 (#25) * Use timeo=600 to avoid nfsmount lags (#27) * Use rsize=32768,wsize=32768 instead of timeo=600 (#27) * Use commas in ltsp-dnsmasq.conf dns-server option (#28) * Avoid `ltsp dnsmasq` failing on multiple proxy subnets (#30) * Set read_ahead_kb=4 for network mounts (#27) * Blacklist floppy module (#32) * Make user accounts available before 55- * Customize the greeter user list (#33) * Avoid section_list: not found warning (#36) * Correctly set _NL * Add dhcpcd to MASK_SYSTEM_SERVICES [ DI FH Markus Kienast ] * Fix PWMERGE_(SGR|DUR|DGR) (#42) [ Alkis Georgopoulos ] * Make snaps run (#44) [ Vagrant Cascadian ] * docs/ltsp.conf.5.md: Fix spelling of "loosely". * debian/control: - Build-Depends: Prefer "ronn" over "ruby-ronn". - Update Standards-Version to 4.4.1. - Set Rules-Requires-Root to "no". * debian/rules: - Override dh_installinit to not add init script snippets. - Copy ltsp.service file into debian/ and to allow dh_installsystemd to work correctly. * Fix typo in ltsp-initrd man page. * Fix debian/watch file. -- Vagrant Cascadian Sat, 19 Oct 2019 13:23:20 -0700 ltsp (19.09-1) unstable; urgency=low * Customize ltsp.ipxe from ltsp.conf parameters (#14) * Introduce ltsp service, enable NAT, disable flow control (#13) * Support Ubuntu and Debian live isos as bootable images * Implement autologin and passwordless logins * Generate a xorg.conf from ltsp.conf parameters * Implement LOCAL_SWAP * Fix various issues, inside and outside LTSP -- Alkis Georgopoulos Mon, 02 Sep 2019 20:46:26 +0300 ltsp (19.08-1) unstable; urgency=low * Import the new LTSP codebase from https://github.com/eellak/gsoc2019-ltsp. With many thanks to GSoC, ellak.gr and the mentors! -- Alkis Georgopoulos Sun, 18 Aug 2019 12:34:56 +0300 ltsp-23.02/debian/compat000066400000000000000000000000031437727275600151470ustar00rootroot0000000000000010 ltsp-23.02/debian/control000066400000000000000000000023111437727275600153500ustar00rootroot00000000000000Source: ltsp Section: misc Priority: optional Maintainer: Debian LTSP Maintainers Uploaders: Vagrant Cascadian , Alkis Georgopoulos Build-Depends: debhelper (>= 10), go-md2man (>= 2) | pandoc, Standards-Version: 4.6.2 Vcs-Git: https://github.com/ltsp/ltsp.git Vcs-Browser: https://github.com/ltsp/ltsp/ Homepage: https://ltsp.org Rules-Requires-Root: no Package: ltsp # Try to keep dependencies to what's there in plain `debootstrap`, # except for: busybox, ssh-client Depends: ${misc:Depends}, busybox | busybox-static | busybox-initramfs, procps, python3, ssh-client, systemd, Recommends: man-db, sshfs, Suggests: dnsmasq, epoptes, ethtool, ltsp-binaries | ipxe, net-tools, nfs-kernel-server, openssh-server, squashfs-tools, Architecture: all Description: Linux Terminal Server Project Make an installation able to netboot as an LTSP client. For LTSP servers, also install the suggested packages (dnsmasq etc). . LTSP helps in netbooting LAN clients from a single installation that resides in a chroot or a VM on the LTSP server. This way maintaining tens or hundreds of clients is as easy as maintaining a single PC. ltsp-23.02/debian/copyright000066400000000000000000000016251437727275600157070ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ltsp Source: https://github.com/ltsp/ltsp Files: * Copyright: 2019-2023 the LTSP team License: GPL-3.0+ License: GPL-3.0+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. . 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 . . On Debian systems, the complete text of the GNU General Public License, version 3, can be found in /usr/share/common-licenses/GPL-3. ltsp-23.02/debian/dev/000077500000000000000000000000001437727275600145265ustar00rootroot00000000000000ltsp-23.02/debian/dev/non-essentials000077500000000000000000000140531437727275600174210ustar00rootroot00000000000000#!/bin/sh # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Helper script to locate most (but not all) of the non-essential binaries # that LTSP scripts use, to make it easier to maintain the Depends/Recommends # lines. set -e die() { printf "%s\n" "$*" >&2 exit 1 } essential_packages() { dpkg-query -Wf '${Package;-40}${Essential}\n' | awk '$2 == "yes" { print $1 }' } essential_binaries() { local package file_ for package in $(essential_packages); do for file_ in $(dpkg -L "$package"); do test -f "$file_" || continue test -x "$file_" || continue echo "$file_" done done | sort } ltsp_binaries() { cd ${0%/*} || die "Error running: cd ${0%/*}" grep -r '^[[:space:]]*r[ew] ' ../ltsp | awk '{ print $3 }' | sort -u } main() { local eb lb fullb eb=$(echo " " $(essential_binaries) " ") for lb in $(ltsp_binaries); do fullb=$(which "$lb" 2>/dev/null || true) if [ -n "$fullb" ]; then case "$eb" in *" "$fullb" "*) continue ;; # Essential, don't print esac fi echo "${fullb:-$lb}" done } main "$@" false && true <<"EOF" # Essential packages base-files base-passwd bash bsdutils coreutils dash debianutils diffutils dpkg findutils grep gzip hostname init-system-helpers libc-bin login ncurses-base ncurses-bin perl-base sed sysvinit-utils tar util-linux # Essential binaries /bin/bash /bin/cat /bin/chgrp /bin/chmod /bin/chown /bin/cp /bin/dash /bin/date /bin/dd /bin/df /bin/dir /bin/dmesg /bin/dnsdomainname /bin/domainname /bin/echo /bin/egrep /bin/false /bin/fgrep /bin/findmnt /bin/grep /bin/gunzip /bin/gzexe /bin/gzip /bin/hostname /bin/ln /bin/login /bin/ls /bin/lsblk /bin/mkdir /bin/mknod /bin/mktemp /bin/more /bin/mountpoint /bin/mv /bin/nisdomainname /bin/pidof /bin/pwd /bin/rbash /bin/readlink /bin/rm /bin/rmdir /bin/run-parts /bin/sed /bin/sh /bin/sh.distrib /bin/sleep /bin/stty /bin/su /bin/sync /bin/tar /bin/tempfile /bin/touch /bin/true /bin/uname /bin/uncompress /bin/vdir /bin/wdctl /bin/which /bin/ypdomainname /bin/zcat /bin/zcmp /bin/zdiff /bin/zegrep /bin/zfgrep /bin/zforce /bin/zgrep /bin/zless /bin/zmore /bin/znew /etc/cron.daily/dpkg /etc/init.d/hwclock.sh /etc/rmt /etc/update-motd.d/00-header /etc/update-motd.d/10-help-text /etc/update-motd.d/50-motd-news /lib/init/init-d-script /lib/udev/hwclock-set /sbin/agetty /sbin/blkdiscard /sbin/blkid /sbin/blockdev /sbin/chcpu /sbin/ctrlaltdel /sbin/findfs /sbin/fsck /sbin/fsck.cramfs /sbin/fsck.minix /sbin/fsfreeze /sbin/fstab-decode /sbin/fstrim /sbin/getty /sbin/hwclock /sbin/installkernel /sbin/isosize /sbin/killall5 /sbin/ldconfig /sbin/ldconfig.real /sbin/mkfs /sbin/mkfs.bfs /sbin/mkfs.cramfs /sbin/mkfs.minix /sbin/mkswap /sbin/pivot_root /sbin/raw /sbin/runuser /sbin/start-stop-daemon /sbin/sulogin /sbin/swaplabel /sbin/switch_root /sbin/wipefs /sbin/zramctl /usr/bin/[ /usr/bin/addpart /usr/bin/arch /usr/bin/b2sum /usr/bin/base32 /usr/bin/base64 /usr/bin/basename /usr/bin/bashbug /usr/bin/captoinfo /usr/bin/catchsegv /usr/bin/chcon /usr/bin/chrt /usr/bin/cksum /usr/bin/clear /usr/bin/clear_console /usr/bin/cmp /usr/bin/comm /usr/bin/csplit /usr/bin/cut /usr/bin/deb-systemd-helper /usr/bin/deb-systemd-invoke /usr/bin/delpart /usr/bin/diff /usr/bin/diff3 /usr/bin/dircolors /usr/bin/dirname /usr/bin/dpkg /usr/bin/dpkg-deb /usr/bin/dpkg-divert /usr/bin/dpkg-maintscript-helper /usr/bin/dpkg-query /usr/bin/dpkg-split /usr/bin/dpkg-statoverride /usr/bin/dpkg-trigger /usr/bin/du /usr/bin/env /usr/bin/expand /usr/bin/expr /usr/bin/factor /usr/bin/faillog /usr/bin/fallocate /usr/bin/find /usr/bin/flock /usr/bin/fmt /usr/bin/fold /usr/bin/getconf /usr/bin/getent /usr/bin/getopt /usr/bin/groups /usr/bin/head /usr/bin/hostid /usr/bin/i386 /usr/bin/iconv /usr/bin/id /usr/bin/infocmp /usr/bin/infotocap /usr/bin/install /usr/bin/ionice /usr/bin/ipcmk /usr/bin/ipcrm /usr/bin/ipcs /usr/bin/ischroot /usr/bin/join /usr/bin/last /usr/bin/lastb /usr/bin/lastlog /usr/bin/ldd /usr/bin/link /usr/bin/linux32 /usr/bin/linux64 /usr/bin/locale /usr/bin/locale-check /usr/bin/localedef /usr/bin/logger /usr/bin/logname /usr/bin/lscpu /usr/bin/lsipc /usr/bin/lslocks /usr/bin/lslogins /usr/bin/lsmem /usr/bin/lsns /usr/bin/mcookie /usr/bin/md5sum /usr/bin/md5sum.textutils /usr/bin/mesg /usr/bin/mkfifo /usr/bin/namei /usr/bin/newgrp /usr/bin/nice /usr/bin/nl /usr/bin/nohup /usr/bin/nproc /usr/bin/nsenter /usr/bin/numfmt /usr/bin/od /usr/bin/partx /usr/bin/paste /usr/bin/pathchk /usr/bin/perl /usr/bin/perl5.26.1 /usr/bin/pinky /usr/bin/pldd /usr/bin/pr /usr/bin/printenv /usr/bin/printf /usr/bin/prlimit /usr/bin/ptx /usr/bin/realpath /usr/bin/rename.ul /usr/bin/renice /usr/bin/reset /usr/bin/resizepart /usr/bin/rev /usr/bin/rgrep /usr/bin/runcon /usr/bin/savelog /usr/bin/script /usr/bin/scriptreplay /usr/bin/sdiff /usr/bin/seq /usr/bin/setarch /usr/bin/setsid /usr/bin/setterm /usr/bin/sg /usr/bin/sha1sum /usr/bin/sha224sum /usr/bin/sha256sum /usr/bin/sha384sum /usr/bin/sha512sum /usr/bin/shred /usr/bin/shuf /usr/bin/sort /usr/bin/split /usr/bin/stat /usr/bin/stdbuf /usr/bin/sum /usr/bin/tabs /usr/bin/tac /usr/bin/tail /usr/bin/taskset /usr/bin/tee /usr/bin/test /usr/bin/tic /usr/bin/timeout /usr/bin/toe /usr/bin/tput /usr/bin/tr /usr/bin/truncate /usr/bin/tset /usr/bin/tsort /usr/bin/tty /usr/bin/tzselect /usr/bin/unexpand /usr/bin/uniq /usr/bin/unlink /usr/bin/unshare /usr/bin/update-alternatives /usr/bin/users /usr/bin/utmpdump /usr/bin/wall /usr/bin/wc /usr/bin/whereis /usr/bin/who /usr/bin/whoami /usr/bin/x86_64 /usr/bin/xargs /usr/bin/yes /usr/bin/zdump /usr/sbin/add-shell /usr/sbin/chmem /usr/sbin/chroot /usr/sbin/fdformat /usr/sbin/iconvconfig /usr/sbin/invoke-rc.d /usr/sbin/ldattach /usr/sbin/nologin /usr/sbin/readprofile /usr/sbin/remove-shell /usr/sbin/rmt-tar /usr/sbin/rtcwake /usr/sbin/service /usr/sbin/tarcat /usr/sbin/update-passwd /usr/sbin/update-rc.d /usr/sbin/zic /usr/share/doc/util-linux/examples/getopt-parse.bash /usr/share/doc/util-linux/examples/getopt-parse.tcsh EOF ltsp-23.02/debian/ltsp.dirs000066400000000000000000000000111437727275600156050ustar00rootroot00000000000000etc/ltsp ltsp-23.02/debian/ltsp.initramfs-hook000077500000000000000000000007311437727275600176120ustar00rootroot00000000000000#!/bin/sh # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Initramfs hook for ltsp PREREQ="" prereqs() { echo "$PREREQ" } case "$1" in prereqs) prereqs exit 0 ;; esac . /usr/share/initramfs-tools/hook-functions # make sure all our modules are there manual_add_modules aoe manual_add_modules overlay manual_add_modules squashfs auto_add_modules net ltsp-23.02/debian/ltsp.install000066400000000000000000000000201437727275600163120ustar00rootroot00000000000000ltsp usr/share/ ltsp-23.02/debian/ltsp.links000066400000000000000000000000421437727275600157700ustar00rootroot00000000000000usr/share/ltsp/ltsp usr/sbin/ltsp ltsp-23.02/debian/ltsp.manpages000066400000000000000000000000321437727275600164420ustar00rootroot00000000000000docs/man/man[0-9]/*.[0-9] ltsp-23.02/debian/ltsp.triggers000066400000000000000000000000421437727275600164760ustar00rootroot00000000000000activate-noawait update-initramfs ltsp-23.02/debian/patches/000077500000000000000000000000001437727275600153775ustar00rootroot00000000000000ltsp-23.02/debian/patches/series000066400000000000000000000000001437727275600166020ustar00rootroot00000000000000ltsp-23.02/debian/rules000077500000000000000000000021311437727275600150250ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 # % calls are the following params, which then call these dh_ binaries: # clean: dh_clean # build: dh_update_autotools_config # binary: dh_testroot, dh_prep, dh_installdirs, dh_install, dh_installdocs, # dh_installchangelogs, dh_perl, dh_link, dh_strip_nondeterminism, # dh_compress, dh_fixperms, dh_missing, dh_installdeb, dh_gencontrol, # dh_md5sums, dh_builddeb # dh clean: %: dh $@ override_dh_auto_configure: cd docs; ./Makefile.sh; cd - override_dh_installinit: dh_installinit --no-scripts override_dh_install: dh_install --exclude _.git sed "s/^\(_VERSION=\).*/\1'$(DEB_VERSION)'/" -i "$(CURDIR)"/debian/ltsp/usr/share/ltsp/ltsp cp ltsp/common/service/ltsp.service debian/ mkdir -p "$(CURDIR)/debian/ltsp/usr/share/initramfs-tools/hooks" # hooks/ltsp conflicts with ltsp-client-core cp -a "$(CURDIR)/debian/ltsp.initramfs-hook" "$(CURDIR)/debian/ltsp/usr/share/initramfs-tools/hooks/ltsp-next" override_dh_clean: rm -f debian/ltsp.service rm -rf docs/man dh_clean ltsp-23.02/debian/source/000077500000000000000000000000001437727275600152505ustar00rootroot00000000000000ltsp-23.02/debian/source/format000066400000000000000000000000141437727275600164560ustar00rootroot000000000000003.0 (quilt) ltsp-23.02/debian/upstream/000077500000000000000000000000001437727275600156105ustar00rootroot00000000000000ltsp-23.02/debian/upstream/metadata000066400000000000000000000003031437727275600173070ustar00rootroot00000000000000Bug-Database: https://github.com/ltsp/ltsp/issues Bug-Submit: https://github.com/ltsp/ltsp/issues/new Repository: https://github.com/ltsp/ltsp.git Repository-Browse: https://github.com/ltsp/ltsp ltsp-23.02/debian/watch000066400000000000000000000001771437727275600150060ustar00rootroot00000000000000version=4 opts=filenamemangle=s/\/(.*)v/@PACKAGE@-/ \ https://github.com/ltsp/@PACKAGE@/tags .*/v@ANY_VERSION@@ARCHIVE_EXT@ ltsp-23.02/docs/000077500000000000000000000000001437727275600134565ustar00rootroot00000000000000ltsp-23.02/docs/Makefile.sh000077500000000000000000000154611437727275600155410ustar00rootroot00000000000000#!/bin/sh # This file is part of LTSP, https://ltsp.org # Copyright 2019-2022 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Use go-md2man, pandoc or ronn to convert the .md files into manpages; # put the output in ./man/man[0-9] subdirectories, to make packaging easier, # and to be able to test with: `MANPATH=man man ltsp kernel` # A more extensive way to test is: # MAN_GENERATOR=pandoc ./Makefile.sh # mandoc -T html man/man5/ltsp.conf.5 > man/man5/ltsp.conf.5.html # man man/man5/ltsp.conf.5.html # yelp man/man5/ltsp.conf.5 # For mandoc automatic anchors to work properly, use **parameter**=_value_, # not **parameter=**_value_. # Regarding formatting, remember the `man man` advice: # bold text type exactly as shown. # italic text replace with appropriate argument. # [-abc] any or all arguments within [ ] are optional. # -a|-b options delimited by | cannot be used together. # argument ... argument is repeatable. # [expression] ... entire expression within [ ] is repeatable. # To get a list of all ltsp.conf parameters, run: # echo $(grep -r LTSP.CONF | sed 's/.*LTSP.CONF://' | tr ' ' '\n' | sort -u) _APPLET= _DATE= _DESCRIPTION= _MANUAL="LTSP Manual" _ORGANIZATION= _SECTION= _TITLE= _VERSION= _EOT=$(printf "\004") footer() { sed "s/\*\*$_APPLET\*\*($_SECTION)//;s/, , /, /;s/, $//;s/^, //" < EOF } is_command() { command -v "$@" >/dev/null } # Unresolved issues: # - Bullets in yelp wrap_md2man() { local applet_up found_name line # preprocess | go-md2man | postprocess { applet_up=$(echo "$_APPLET" | awk '{ print toupper($0) }') # https://github.com/cpuguy83/go-md2man/blob/master/go-md2man.1.md cat <]*\)>/\1/g' | go-md2man -in /dev/stdin -out /dev/stdout | { # Work around https://github.com/cpuguy83/go-md2man/issues/26 # and https://github.com/cpuguy83/go-md2man/issues/47 # They're still an issue with go-md2man 2.0.0+ds-5 sed -e 's/\\~/~/g;s/\\_/_/g' | tr '\n' "$_EOT" | sed "s/$_EOT$_EOT.fi$_EOT/$_EOT.fi$_EOT/g" | tr "$_EOT" '\n' } } # Unresolved issues: # - Bullets are
with • being a
, shown in a separate line in mandoc # - ADD_IMAGE_EXCLUDES is not a
# - Which makes mandoc produce multiple
lists instead of one # - Bullets in yelp wrap_pandoc() { local found_name line # preprocess | pandoc | postprocess { # Generate metadata cat < to

, and simpifly some macros for yelp # https://man7.org/linux/man-pages/man7/groff_char.7.html sed -e 's/^.SS/.SH/' -e 's/\\\[dq]/"/g;s/\\\[lq]/"/g;s/\\\[rq]/"/g;s/\\\[ti]/~/g;s/\\\[at]/@/g;s/\\\[ha]/^/g;s/\\\[en]/--/g' # -e 's/\\\[bu]/*/g' } } # Unresolved issues: # - Bullets are
with a big ○ as a
, shown in a separate line in mandoc # - Bullets in yelp # - Big space around code in yelp wrap_ronn() { local indent found_synopsis line # preprocess | ronn | postprocess { # Ronn needs a special `# title` with no `## NAME` section, see # https://manpages.debian.org/ronn-format cat <&2 exit 1 fi echo "Using $cmd to generate the man pages" cd "${1%/*}" if is_command dpkg-parsechangelog; then _VERSION=$(dpkg-parsechangelog -l ../debian/changelog -S VERSION) else _VERSION=$(. ../ltsp/common/ltsp/55-ltsp.sh && echo "$_VERSION") fi if [ -n "$SOURCE_DATE_EPOCH" ]; then _DATE=$(date -u -d"@$SOURCE_DATE_EPOCH" "+%Y-%m-%d") else _DATE=$(date "+%Y-%m-%d") fi _ORGANIZATION="LTSP $_VERSION" rm -rf man for mp in *.[0-9].md; do var=${mp%.md} _APPLET=${var%.[0-9]} _SECTION=${var#"$_APPLET".} _TITLE=$(echo "$_APPLET($_SECTION)" | awk '{ print toupper($0) }') _DESCRIPTION=$(sed -n '/## NAME/,+2s/.* - \(.*\)/\1/p' "$mp") mkdir -p "man/man$_SECTION" "wrap_$cmd" <"$mp" >"man/man$_SECTION/$_APPLET.$_SECTION" done } main "$@" ltsp-23.02/docs/ltsp-dnsmasq.8.md000066400000000000000000000041011437727275600165700ustar00rootroot00000000000000# ltsp-dnsmasq ## NAME **ltsp dnsmasq** - configure dnsmasq for LTSP ## SYNOPSIS **ltsp** [_ltsp-options_] **dnsmasq** [**-d** _dns_] [**-p** _proxy-dhcp_] [**-r** _real-dhcp_] [**-s** _dns-server_] [**-t** _tftp_] ## DESCRIPTION Install /etc/dnsmasq.d/ltsp-dnsmasq.conf, while adjusting the template with the provided parameters. ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. **-d**, **--dns**=_0|1_ : Enable or disable the DNS service. Defaults to 0. Enabling the DNS service of dnsmasq allows caching of client requests, custom DNS results, blacklisting etc, and automatically disables DNSStubListener in systemd-resolved on the LTSP server. **-p**, **--proxy-dhcp**=_0|1_ : Enable or disable the proxy DHCP service. Defaults to 1. Proxy DHCP means that the LTSP server sends the boot filename, but it leaves the IP leasing to an external DHCP server, for example a router or pfsense or a Windows DHCP server. It's the easiest way to set up LTSP, as it only requires a single NIC with no static IP, no need to rewire switches etc. **-r**, **--real-dhcp**=_0|1_ : Enable or disable the real DHCP service. Defaults to 1. In dual NIC setups, you only need to configure the internal NIC to a static IP of 192.168.67.1; LTSP will try to autodetect everything else. The real DHCP service doesn't take effect if your IP isn't 192.168.67.x, so there's no need to disable it in single NIC setups unless you want to run isc-dhcp-server on the LTSP server. **-s**, **--dns-server**=_"space separated list"_ : Set the DNS server DHCP option. Defaults to autodetection. Proxy DHCP clients don't receive DHCP options, so it's recommended to use the ltsp.conf DNS_SERVER parameter when autodetection isn't appropriate. **-t**, **--tftp**=_0|1_ : Enable or disable the TFTP service. Defaults to 1. ## EXAMPLES Create a default dnsmasq configuration, overwriting the old one: ```shell ltsp dnsmasq ``` A dual NIC setup with the DNS service enabled: ```shell ltsp dnsmasq -d1 -p0 --dns-server="0.0.0.0 8.8.8.8 208.67.222.222" ``` ltsp-23.02/docs/ltsp-image.8.md000066400000000000000000000161051437727275600162130ustar00rootroot00000000000000# ltsp-image ## NAME **ltsp image** - generate a squashfs image from an image source ## SYNOPSIS **ltsp** [_ltsp-options_] **image** [**-b** _backup_] [**-c** _cleanup_] [**-i** _ionice_] [**-k** _kernel-initrd_] [**-m** _mksquashfs-params_] [**-r** _revert_] [_image_] ... ## DESCRIPTION Compress a virtual machine image or chroot directory into a squashfs image, to be used as the network root filesystem of LTSP clients. It's used in similar fashion to live CDs, i.e. all clients will boot from this single read only image and then use SSHFS or NFS to mount /home/username from the server. ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. **-b**, **--backup**=_0|1_ : Backup /srv/ltsp/images/_image_.img to _image_.img.old. Defaults to 1. **-c**, **--cleanup**=_0|1_ : Create a writeable overlay on top of the image source and temporarily remove user accounts and sensitive data before calling mksquashfs. Defaults to 1. **-i**, **--ionice**=_cmdline_ : Set a prefix command to run mksquashfs with a lower priority, or specify "" to disable it completely. Defaults to `nice ionice -c3`. **-k**, **--kernel-initrd**=_glob-regex_ : Pass this parameter to the `ltsp kernel` call after the squashfs creation. See ltsp-kernel(8) for more information. **-m**, **--mksquashfs-params**=_"params"_ : Pass _$params_ to the mksquashfs call unquoted; so _params_ shouldn't contain spaces. See mksquashfs(1) for more information. **-r**, **--revert**[=_0|1_] : Move /srv/ltsp/images/_image_.img.old to _image_.img and call `ltsp kernel image`. Useful when the clients won't boot with the new image. ## IMAGE TYPES There are three "image" types in LTSP, in the following locations. The /srv/ltsp path can be configured using `ltsp --base-dir=`: **/srv/ltsp/_img\_name_.img** : Source images are placed directly under /srv/ltsp and usually are symlinks to virtual machine raw disk files. They're only used by `ltsp image`. **/srv/ltsp/_img\_name_** : Chroot directories can be used both as sources for `ltsp image` and as NFS root exports for the clients. **/srv/ltsp/images/_img\_name_.img** : Exported images (usually squashfs) are placed under the images directory and the clients can netboot from them. Images can be specified as simple names like `ltsp image img_name`, in which case the aforementioned locations are searched, or as or full paths like `ltsp image ~/VMs/vm.img`. The supported image types result in the following three methods to use LTSP. You may use either one of the methods or even all of them at the same time. ## CHROOTLESS Chrootless LTSP, previously called "ltsp-pnp", is the recommended way to maintain LTSP **if** its restrictions are acceptable. In this mode, the server operating system itself is exported into a squashfs file and used for netbooting all the clients. You, the sysadmin, would use the typical GUI tools to manage the server, like software centers or update managers. Then whenever necessary, you'd run: ```shell ltsp image / ``` This creates or updates /srv/ltsp/images/x86_64.img (the arch name comes from `uname -m`). Then, all the clients should be able to boot from x86_64.img and have a desktop environment identical to the server. The big advantage of the chrootless mode is simplicity: there are no virtual machines or chroots involved. You'd maintain the server like any "home desktop PC", and have all clients be exact replicas, which is as simple as it gets. The disadvantages are that the clients need to have the same architecture as the server (e.g. all x86_64), and that the server can't be a "full blown server" with LDAP and Apache and a lot of other services, without taking care to disable those services on the clients with the MASK_SYSTEM_SERVICES parameter of ltsp.conf. Note that MASK_SYSTEM_SERVICES already includes Apache and MySQL and a few other popular services that we don't want in LTSP clients, so it's not a problem if you install Apache on the LTSP server. If for some reason you prefer a different name to `uname -m`, you may create a symlink: ```shell ln -s / ~/amd64 ``` ...and run `ltsp image ~/amd64` instead. ## VM IMAGES If the chrootless case doesn't fit you, you may use VirtualBox, virt-manager, KVM, VMWare and similar tools to maintain one or more template images for the clients. As an example, let's suppose you create a VM in VirtualBox and call it "debian". At the disk creation dialog, select "VMDK" type and "Fixed size", not "Dynamically allocated". Proceed with installing Debian on it. In the partitioning step, make sure that the whole operating system goes in the first partition, without extra partitions for /boot etc. BIOS/MBR is easier, while if you have to use GPT/UEFI, put the EFI partition second. When you're done, close VirtualBox and symlink the VM disk so that LTSP finds it more easily: ```shell ln -rs ~/VirtualBox\ VMs/debian/debian-flat.vmdk /srv/ltsp/debian.img ``` To export this image to the clients, after the initial creation or after updates etc, you'd run: ```shell ltsp image debian ``` It's also possible to omit the symlink by running: ```shell ltsp image ~/VirtualBox\ VMs/debian/debian-flat.vmdk ``` ...but then the image name shown in the iPXE boot menu would be "debian-flat", which isn't pretty. In summary, you may symlink raw VM disks in /srv/ltsp/img_name.img, and `ltsp image img_name` will allow LTSP clients to netboot from them. Please also see the DIRECT IMAGES section of ltsp-kernel(8) for an advanced method of allowing clients to netboot directly from a VM or .iso image without even running `ltsp image`, and the ADVANCED IMAGE SOURCES section of ltsp-ipxe(8) for extreme cases like telling the LTSP clients to boot from an .iso image inside a local disk partition! ## CHROOTS Chroot directories in /srv/ltsp/img_name are properly supported as image sources by LTSP, but their creation and maintenance are left to external tools like debootstrap, lxc etc. The `ltsp-build-client` LTSPv5 tool no longer exists. LTSP users are invited to create appropriate documentation in the [community wiki](https://github.com/ltsp/ltsp/wiki/chroots). As a small example, you can use kvm to netboot a chroot and maintain it if you NFS-export /srv/ltsp/img_name in rw mode for your server IP, and then run ```shell kvm -m 512 -kernel img_name/vmlinuz -initrd img_name/initrd.img \ -append "rw root=/dev/nfs nfsroot=192.168.67.1:/srv/ltsp/img_name" ``` ## EXAMPLES Use the server installation as a template to generate a client image (chrootless, previously called ltsp-pnp): ```shell ltsp image / ``` Inform `ltsp image` that a chrootless installation uses separate /boot and /opt partitions: ```shell ltsp image /,,/boot,subdir=boot,,/opt,subdir=opt ``` Compress the /srv/ltsp/x86_64 chroot or the /srv/ltsp/x86_64.img virtual machine image, whichever exists of those two, into /srv/ltsp/images/x86_64.img, while disabling ionice: ```shell ltsp image --ionice="" x86_64 ``` Specify an absolute path to a virtual machine image: ```shell ltsp image /home/user/VirtualBox\ VMs/x86_32/x86_32-flat.vmdk ``` Revert to the the previous version of the "chrootless" image: ```shell ltsp image -r / ``` ltsp-23.02/docs/ltsp-info.8.md000066400000000000000000000005761437727275600160710ustar00rootroot00000000000000# ltsp-info ## NAME **ltsp info** - display troubleshooting information about ltsp server and images ## SYNOPSIS **ltsp** [_ltsp-options_] **info** ## DESCRIPTION Display the LTSP version and the available chroots, VMs and images. This applet is very minimal currently, it will be developed further in the future. ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. ltsp-23.02/docs/ltsp-initrd.8.md000066400000000000000000000025651437727275600164270ustar00rootroot00000000000000# ltsp-initrd ## NAME **ltsp initrd** - create the ltsp.img initrd add-on ## SYNOPSIS **ltsp** [_ltsp-options_] **initrd** ## DESCRIPTION Create a secondary initrd in /srv/tftp/ltsp/ltsp.img, that contains the LTSP client code from /usr/share/ltsp/{client,common} and everything under /etc/ltsp, including the ltsp.conf settings file. Additionally it contains the server users and groups lists (passwd/group) and public SSH keys. LTSP clients receive this initrd in addition to their usual one. This means that whenever you add new users or edit **ltsp.conf(5)**, you need to run `ltsp initrd` to update **ltsp.img**, and reboot the clients. It also means that you can very easily put template xorg.conf or sshfs or other files in /etc/ltsp, and have them on the clients in seconds, without having to run `ltsp image`. ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. ## EXAMPLES Most live CDs do not contain sshfs, so by default you can only use NFS home with them. But sshfs is a small binary without many dependencies, so you may usually provide it to the clients if you include it to ltsp.img: ```shell mkdir -p /etc/ltsp/bin cp /usr/bin/sshfs /etc/ltsp/bin/sshfs-$(uname -m) ltsp initrd ``` You can even provide multiple sshfs versions for different architectures. LTSP contains code to automatically use those sshfs binaries if it can't find the /usr/bin/sshfs one. ltsp-23.02/docs/ltsp-ipxe.8.md000066400000000000000000000061061437727275600160760ustar00rootroot00000000000000# ltsp-ipxe ## NAME **ltsp ipxe** - install iPXE binaries and configuration in TFTP ## SYNOPSIS **ltsp** [_ltsp-options_] **ipxe** [**-b** _binaries_] ## DESCRIPTION Generate the ltsp.ipxe configuration file and install the required iPXE binaries in /srv/tftp/ltsp: memtest.0, memtest.efi, snponly.efi and undionly.kpxe. An ltsp-binaries package is available in the LTSP PPA that provides them; otherwise, some of them are automatically found in the ipxe/memtest86+ packages. ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. **-b**, **--binaries**[=_0|1|""_] : Reinstall the iPXE binaries in TFTP even if they already exist. Defaults to "", which means "only install the missing ones". Note that the --overwrite flag doesn't affect the binaries, they're only controlled by the --binaries flag. ## ADVANCED IMAGE SOURCES This section is for advanced LTSP sysadmins. Normally, image sources are simple names like "x86_64" or full paths like "../path/to/image". But the "img_src" parameters are much more flexible than that; specifically, they are series of mount sources: ```shell img1,mount-options1,,img2,mount-options2,,... ``` ...where img1 may be a simple name or full path relative to the current directory, and img2+ are full paths relative to the target directory. Let's see an advanced example: suppose that your clients came with Windows, and that you copied a live CD into C:\ltsp\ubuntu.iso, and you want your LTSP clients to use that for speed. First, disable Windows fast boot and hibernation, so that Linux is able to mount its partition. Then create the following "method" in ltsp.ipxe: ```ipxe :local_image # The "local_image" method boots C:\ltsp\ubuntu.iso set cmdline_method root=/dev/sda1 ltsp.image=ltsp/ubuntu.iso,fstype=iso9660,loop,ro,,casper/filesystem.squashfs,squashfs,loop,ro loop.max_part=9 goto ltsp ``` Explanation: - The root=/dev/sda1 parameter tells the initramfs to mount /dev/sda1 into /root. - Then the LTSP code will look under /root/ltsp/ and mount ubuntu.iso using the loop,ro options over /root again. - Then the LTSP code will look under /root/casper/ and mount filesystem.squashfs over /root again. This casper/filesystem.squashfs path is where the live filesystem exists inside the Ubuntu live CDs. So while this long line gives a good example on using advanced image sources, the LTSP code is actually smart enough to autodetect Ubuntu live CDs and filesystem types, so one could simplify it to: ```ipxe :local_image # The "local_image" method boots C:\ltsp\${img}.img set cmdline_method root=/dev/sda1 ltsp.image=ltsp/${img}.img loop.max_part=9 goto ltsp ``` The ${img} parameter is the name of the menu; it would be "ubuntu" if you copied ubuntu.iso in /srv/ltsp/images/ubuntu.img and ran `ltsp ipxe`. ## EXAMPLES Initial use: ```shell ltsp ipxe ``` Regenerate ltsp.ipxe and reinstall the binaries: ```shell ltsp ipxe -b ``` Copy the binaries from a USB stick before running ltsp ipxe: ```shell mkdir -p /srv/tftp/ltsp cd /media/administrator/usb-stick cp {memtest.0,memtest.efi,snponly.efi,undionly.kpxe} /srv/tftp/ltsp ltsp ipxe ``` ltsp-23.02/docs/ltsp-kernel.8.md000066400000000000000000000056731437727275600164210ustar00rootroot00000000000000# ltsp-kernel ## NAME **ltsp kernel** - copy the kernel and initrd from an image to TFTP ## SYNOPSIS **ltsp** [_ltsp-options_] **kernel** [**-k** _kernel-initrd_] [_image_] ... ## DESCRIPTION Copy vmlinuz and initrd.img from an image or chroot to TFTP. If _image_ is unspecified, process all of them. For simplicity, only chroot directories and raw images are supported, either full filesystems (squashfs, ext4) or full disks (flat VMs). They may be sparse to preserve space. Don't use a separate /boot nor LVM in disk images. The targets will always be named vmlinuz and initrd.img to simplify ltsp.ipxe. ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. **-k**, **--kernel-initrd**=_glob-regex_ : Specify a kernel glob and an initrd regex to locate them inside the _image_; try to autodetect if undefined. See the EXAMPLES section below. ## DIRECT IMAGES This section is for advanced LTSP sysadmins. Let's suppose that you want to test if your users would prefer Xubuntu to your existing Ubuntu MATE. First, move and rename your Xubuntu CD to this location, without using symlinks, and then update kernels and ipxe: ```shell mv xubuntu-18.04-desktop-amd64.iso /srv/ltsp/images/xubuntu-18.04.img ltsp kernel xubuntu-18.04 ltsp ipxe ``` If you reboot your clients, they'll now have the option to boot with the Xubuntu live CD in LTSP mode! This is like booting with the live CD, except that all the users and their homes are available! So the users can normally login and work for days or weeks in the new environment, before you decide that they like Xubuntu and that you want to move from using the live CD to maintaining a Xubuntu image using a virtual machine. You can also do this with virtual machine images! For example: ```shell mv ~/VirtualBox\ VMs/debian/debian-flat.vmdk /srv/ltsp/images/debian-vm.img ln -rs /srv/ltsp/images/debian-vm.img ~/VirtualBox\ VMs/debian/debian-flat.vmdk ltsp kernel debian-vm ltsp ipxe ``` These commands move your "debian" VM to the LTSP images directory, symlink it back to where VirtualBox expects it, and update the kernels and ipxe. After these, you'll be able to boot directly from the "debian-vm" iPXE menu item without having to run `ltsp image`! It's the fastest way to test image changes without waiting 10 minutes for `ltsp image` each time. Some advanced users may think of using the opposite symlink instead: ```shell ln -rs ~/VirtualBox\ VMs/debian/debian-flat.vmdk /srv/ltsp/images/debian-vm.img ``` Unfortunately NFS doesn't follow symlinks outside of the exported directories, so the clients wouldn't be able to boot in this case. Advanced users may use bind mounts though, e.g.: ```shell mount --bind ~/VirtualBox\ VMs/debian/debian-flat.vmdk /srv/ltsp/images/debian-vm.img ``` ## EXAMPLES Typical use: ```shell ltsp kernel x86_64 ``` Passing a glob to locate the kernel and a regex to locate the initrd in a Debian live CD: ```shell ltsp kernel --kernel-initrd="live/vmlinuz-* s|vmlinuz|initrd.img|" ``` ltsp-23.02/docs/ltsp-nfs.8.md000066400000000000000000000032371437727275600157210ustar00rootroot00000000000000# ltsp-nfs ## NAME **ltsp nfs** - configure NFS exports for LTSP ## SYNOPSIS **ltsp** [_ltsp-options_] **nfs** [**-h** _nfs-home_] [**t** _nfs-tftp_] ## DESCRIPTION Install /etc/exports.d/ltsp-nfs.conf in order to export /srv/ltsp ($BASE_DIR), /srv/tftp/ltsp ($TFTP_DIR) and optionally /home ($HOME_DIR). ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. **-h**, **--nfs-home**=_0|1_ : Export /home over NFS3. Defaults to 0. Note that NFS3 is insecure for home, so by default SSHFS is used. To specify a different directory, set $HOME_DIR in /etc/ltsp/ltsp.conf. **-t**, **--nfs-tftp**=_0|1_ : Export /srv/tftp/ltsp over NFS3. Defaults to 1. To specify a different directory, set $TFTP_DIR in /etc/ltsp/ltsp.conf. ## EXAMPLES To export /home over NFS (insecure), use the following ltsp.conf parameters: ```shell [server] NFS_HOME=1 [clients] FSTAB_HOME="server:/home /home nfs defaults,nolock 0 0" ``` And run these commands on the server: ```shell ltsp initrd # This is needed whenever ltsp.conf is modified ltsp nfs ``` To export only some user homes over NFS while the rest still use SSHFS, use these lines in ltsp.conf instead: ```shell [server] NFS_HOME=1 HOME_DIR=/home/nfs [clients] FSTAB_HOME="server:/home/nfs /home nfs defaults,nolock 0 0" ``` Then run the following commands on the server, to move some home directories under /home/nfs and to create appropriate symlinks in case the users ever need to SSH to the server. Note that the NFS server doesn't follow symlinks outside of an export: ```shell mkdir /home/nfs for u in guest01 guest02; do mv "/home/$u" /home/nfs/ ln -s "nfs/$u" "/home/$u" done ltsp initrd ltsp nfs ``` ltsp-23.02/docs/ltsp-remoteapps.8.md000066400000000000000000000030631437727275600173070ustar00rootroot00000000000000# ltsp-remoteapps ## NAME **ltsp remoteapps** - run applications on the LTSP server via ssh -X ## SYNOPSIS **ltsp** [_ltsp-options_] **remoteapps** _application_ [_parameters_] ... **ltsp** [_ltsp-options_] **remoteapps** [**-r** _register_] _application_ ... ## DESCRIPTION Setup passwordless SSH, by ensuring that ~/.ssh/authorized_keys contains one of the public user's SSH keys. Then execute `ssh -X server app params`. If the user has no SSH keys, a new one is generated. For `ltsp remoteapps app` to work, /home/username should already be mounted via NFS (on LTSP client boot) or SSHFS (on LTSP user login). ## OPTIONS See the **ltsp(8)** man page for _ltsp-options_. **-r**, **--register** : Register the specified applications as remoteapps, by creating symlinks in /usr/local/bin/_application_ for each one of them. Since `/usr/local/bin` usually comes before `/usr/bin` in `$PATH`, the remoteapps should be invoked even if the application is selected from the system menu. ## EXAMPLES The following ltsp.conf parameter can be used to register the MATE applications `users-admin` (Menu ▸ System ▸ Administration ▸ Users and Groups) and `mate-about-me` (Menu ▸ System ▸ Preferences ▸ Personal ▸ About Me) as remoteapps: ```shell [clients] REMOTEAPPS="users-admin mate-about-me" ``` That way, LTSP users are able to change their passwords or display names. The password change takes effect immediately, while for the new display name to appear in the LTSP client, the sysadmin must run `ltsp initrd` and the client needs to be rebooted. ltsp-23.02/docs/ltsp.8.md000066400000000000000000000072721437727275600151400ustar00rootroot00000000000000# ltsp ## NAME **ltsp** - entry point to Linux Terminal Server Project applets ## SYNOPSIS **ltsp** [**-b** _base-dir_] [**-h**] [**-m** _home-dir_] [**-o** _overwrite_] [**-t** _tftp-dir_] [**-V**] [_applet_] [_applet-options_] ## DESCRIPTION Run the specified LTSP _applet_ with _applet-options_. To get help with applets and their options, run \`**man ltsp** _applet_\` or \`**ltsp --help** _applet_\`. ## APPLETS The following applets are currently defined: - **dnsmasq**: configure dnsmasq for LTSP - **image**: generate a squashfs image from an image source - **info**: gather support information about the LTSP installation - **initrd**: create the ltsp.img initrd add-on - **ipxe**: install iPXE binaries and configuration in TFTP - **kernel**: copy the kernel and initrd from an image to TFTP - **nfs**: configure NFS exports for LTSP LTSP clients also have some additional applets, like **initrd-bottom**, **init** and **login**, but they're not runnable by the user. ## OPTIONS LTSP directories can be configured by passing one or more of the following parameters, but it's recommended that an /etc/ltsp/ltsp.conf configuration file is created instead, so that you don't have to pass them in each ltsp command. **-b**, **--base-dir**=_/srv/ltsp_ : This is where the chroots, squashfs images and virtual machine symlinks are; so when you run `ltsp kernel img_name`, it will search either for a squashfs image named **/srv/ltsp/images/img_name.img**, or for a chroot named **/srv/ltsp/img_name**, if it's a directory that contains /proc. Additionally, `ltsp image img_name` will also search for a symlink to a VM disk named **/srv/ltsp/img_name.img**. $BASE_DIR is exported read-only by NFSv3, so do not put sensitive data there. **-h**, **--help** : Display a help message. **-m**, **--home-dir**=_/home_ : The default method of making /home available to LTSP clients is SSHFS. In some cases security isn't an issue, and sysadmins prefer the insecure NFSv3 speed over SSHFS. $HOME_DIR is used by `ltsp nfs` to export the correct directory, if it's different to /home, and by LTSP clients to mount it. **-o**, **--overwrite**[=_0|1_] : Overwrite existing files. Defaults to 1 as administrators are not supposed to manually edit LTSP autogenerated files, but maintain local content into separate files (e.g. /etc/exports.d/local.exports). If you manually maintain ltsp.ipxe, it might be a good idea to set OVERWRITE=0 in ltsp.conf. **-t**, **--tftp-dir**=_/srv/tftp_ : LTSP places the kernels, initrds and iPXE files in /srv/tftp/ltsp, to be retrieved by the clients via the TFTP protocol. The TFTP server of dnsmasq and tftpd-hpa are configured to use /srv/tftp as the TFTP root. **-V**, **--version** : Display the version information. ## FILES **/etc/ltsp/ltsp.conf** : All the long options can also be specified as variables in the **ltsp.conf** configuration file in UPPER_CASE, using underscores instead of hyphens. ## ENVIRONMENT All the long options can also be specified as environment variables in UPPER_CASE, for example: ```shell BASE_DIR=/opt/ltsp ltsp kernel ... ``` ## EXAMPLES The following are the typical commands to install and maintain LTSP in chrootless mode: ```shell # To install: ltsp image / ltsp dnsmasq ltsp nfs ltsp ipxe # To update the exported image, after changes in the server software: ltsp image / ``` The following are the typical commands to provide an additional x86_32 image, assuming one uses VirtualBox. If you specifically name it x86_32, then the ltsp.ipxe code automatically prefers it for 32bit clients: ```shell ln -rs $HOME/VirtualBox\ VMs/x86_32/x86_32-flat.vmdk /srv/ltsp/x86_32.img ltsp image x86_32 ltsp ipxe ``` ltsp-23.02/docs/ltsp.conf.5.md000066400000000000000000000314141437727275600160540ustar00rootroot00000000000000# ltsp.conf ## NAME **ltsp.conf** - client configuration file for LTSP ## SYNOPSIS The LTSP client configuration file is placed at `/etc/ltsp/ltsp.conf` and it loosely follows the .ini format. It is able to control various settings of the LTSP server and clients. After each ltsp.conf modification, the `ltsp initrd` command needs to be run so that it's included in the additional ltsp.img initrd that is sent when the clients boot. ## CREATION To create an initial ltsp.conf, run the following command: ```shell install -m 0660 -g sudo /usr/share/ltsp/common/ltsp/ltsp.conf /etc/ltsp/ltsp.conf ``` The optional `-g sudo` parameter allows users in the sudo group to edit ltsp.conf with any editor (e.g. gedit) without running sudo. ## SYNTAX Open and view the /etc/ltsp/ltsp.conf file that you just created, so that it's easier to understand its syntax. The configuration file is separated into sections: - The special [server] section is evaluated only by the ltsp server. - The special [common] section is evaluated by both the server and ltsp clients. - In the special [clients] section, parameters for all clients can be defined. Most ltsp.conf parameters should be placed there. - MAC address, IP address, or hostname sections can be used to apply settings to specific clients. Those support globs, for example [192.168.67.*]. - It's also possible to group parameters into named sections like [crt_monitor] in the example, and reference them from other sections with the INCLUDE= parameter. - Advanced users may also use [applet/host] sections, for example [initrd-bottom/library*] would be evaluated by the `ltsp initrd-bottom` applet only for clients that have a hostname that starts with "library". The ltsp.conf configuration file is internally transformed into a shell script, so all the shell syntax rules apply, except for the sections headers which are transformed into functions. This means that you must not use spaces around the "=" sign, and that you may write comments using the "#" character. The `ltsp initrd` command does a quick syntax check by running `sh -n /etc/ltsp/ltsp.conf` and aborts if it detects syntax errors. ## PARAMETERS The following parameters are currently defined; an example is given in each case. **ADD_IMAGE_EXCLUDES**=_"/etc/ltsp/add-image.excludes"_ **OMIT_IMAGE_EXCLUDES**=_"home/\*"_ : Add or omit items to the `ltsp image` exclusion list. Some files and directories shouldn't be included in the generated image. The initial list is defined in /usr/share/ltsp/server/image/image.excludes. It can be completely overridden by creating /etc/ltsp/image.excludes. ADD_IMAGE_EXCLUDES and OMIT_IMAGE_EXCLUDES can finetune the list by adding or removing lines to it. They can either be filenames or multiline text. **AUTOLOGIN**=_"user01"_ **RELOGIN**=_0|1_ **GDM3_CONF**=_"WaylandEnable=false"_ **LIGHTDM_CONF**=_"greeter-hide-users=true"_ **SDDM_CONF**=_"/etc/ltsp/sddm.conf"_ : Configure the display manager to log in this user automatically. If SSHFS is used, the PASSWORDS_x parameter (see below) must also be provided. AUTOLOGIN can be a simple username like "user01", or it can be a partial regular expression that transforms a hostname to a username. For example, AUTOLOGIN="pc/guest" means "automatically log in as guest01 in pc01, as guest02 in pc02 etc". Setting RELOGIN=0 will make AUTOLOGIN work only once. Finally, the *_CONF parameters can be either filenames or direct text, and provide a way to write additional content to the generated display manager configuration. **CRONTAB_x**=_"30 15 \* \* \* root poweroff"_ : Add a line in crontab. The example powers off the clients at 15:30. **CUPS_SERVER**=_"$SERVER"_ : Set the CUPS server in the client /etc/cups/client.conf. Defaults to $SERVER. You're supposed to also enable printer sharing on the server by running `cupsctl _share_printers=1` or `system-config-printer` or by visiting . Then all printers can be managed on the LTSP server. Other possible values are CUPS_SERVER="localhost", when a printer is connected to a client, or CUPS_SERVER="ignore", to skip CUPS server handling. **DEBUG_LOG**=_0|1_ : Write warnings and error messages to /run/ltsp/debug.log. Defaults to 0. **DEBUG_SHELL**=_0|1_ : Launch a debug shell when errors are detected. Defaults to 0. **DEFAULT_IMAGE**=_"x86\_64"_ **KERNEL_PARAMETERS**=_"nomodeset noapic"_ **MENU_TIMEOUT**=_"5000"_ : These parameters can be defined under [mac:address] sections in ltsp.conf, and they are used by `ltsp ipxe` to generate the iPXE menu. They control the default menu item, the additional kernel parameters and the menu timeout for each client. They can also be defined globally under [server]. **DISABLE_SESSION_SERVICES**=_"evolution-addressbook-factory obex"_ **DISABLE_SYSTEM_SERVICES**=_"anydesk teamviewerd"_ **KEEP_SESSION_SERVICES**=_"at-spi-dbus-bus"_ **KEEP_SYSTEM_SERVICES**=_"apparmor ssh"_ **MASK_SESSION_SERVICES**=_"gnome-software-service update-notifier"_ **MASK_SYSTEM_SERVICES**=_"apt-daily apt-daily-upgrade rsyslog"_ : Space separated lists of services to disable, permit or mask on LTSP clients. They mostly correspond to `systemctl disable/mask [--user]` invocations. Setting these ltsp.conf parameters adds or omits items from the default lists that are defined in `/usr/share/ltsp/client/init/56-services.sh`. Disabled services can be started on demand by e.g. dbus or socket activation, while masked services need to be manually unmasked first. Currently, MASK_SESSION_SERVICES also deletes the non-systemd user services from /etc/xdg/autostart. **DNS_SERVER**=_"8.8.8.8 208.67.222.222"_ : Specify the DNS servers for the clients. **FSTAB_x**=_"server:/home /home nfs defaults,nolock 0 0"_ : All parameters that start with FSTAB_ are sorted and then their values are written to /etc/fstab at the client init phase. **HOSTNAME**=_"pc01"_ : Specify the client hostname. Defaults to "ltsp%{IP}". HOSTNAME may contain the %{IP} pseudovariable, which is a sequence number calculated from the client IP and the subnet mask, or the %{MAC} pseudovariable, which is the MAC address without the colons. **HOSTS_x**=_"192.168.67.10 nfs-server"_ : All parameters that start with HOSTS_ are sorted and then their values are written to /etc/hosts at the client init phase. **IMAGE_TO_RAM**=_0|1_ : Specifying this option under the [clients] section copies the rootfs image to RAM during boot. That makes clients less dependent on the server, but they must have sufficient memory to fit the image. **INCLUDE**=_"other-section"_ : Include another section in this section. **LOCAL_SWAP**=_0|1_ : Activate local swap partitions. Defaults to 1. **MULTISEAT**=_0|1_ **UDEV_SEAT_n_x**=_"\*/usb?/?-[2,4,6,8,10,12,14,16,18]/\*"_ : MULTISEAT=1 tries to autodetect if an LTSP client has two graphics cards and to automatically split them along with the USB ports into two seats. Optional lines like `UDEV_SEAT_1_SOUND="*/sound/card1*"` can be used to finetune the udev rules that will be generated and placed in a file named /etc/udev/rules.d/72-ltsp-seats.rules. **NAT**=_0|1_ : Only use this under the [server] section. Normally, `ltsp service` runs when the server boots and detects if a server IP is 192.168.67.1, in which case it automatically enables IP forwarding for the clients to be able to access the Internet in dual NIC setups. But if there's a chance that the IP isn't set yet (e.g. disconnected network cable), setting NAT=1 enforces that. **OMIT_FUNCTIONS**=_"pam\_main mask\_services\_main"_ : A space separated list of function names that should be omitted. The functions specified here will not be executed when called. This option can be specified in any [section]. **PASSWORDS_x**=_"teacher/cXdlcjEyMzQK [a-z][-0-9]\*/MTIzNAo= guest[^:]\*/"_ : A space separated list of regular expressions that match usernames, followed by slash and base64-encoded passwords. At boot, `ltsp init` writes those passwords for the matching users in /etc/shadow, so that then pamltsp can pass them to SSH/SSHFS. The end result is that those users are able to login either in the console or the display manager by just pressing [Enter] at the password prompt. Passwords are base64-encoded to prevent over-the-shoulder spying and to avoid the need for escaping special characters. To encode a password in base64, run `base64`, type a single password, and then Ctrl+D. In the example above, the teacher account will automatically use "qwer1234" as the password, the a1-01, b1-02 etc students will use "1234", and the guest01 etc accounts will be able to use an empty password without even authenticating against the server; in this case, SSHFS can't be used, /home should be local or NFS. **POST_APPLET_x**=_"ln -s /etc/ltsp/xorg.conf /etc/X11/xorg.conf"_ : All parameters that start with POST\_ and then have an ltsp client applet name are sorted and their values are executed after the main function of that applet. See the ltsp(8) man page for the available applets. The usual place to run client initialization commands that don't need to daemonize is POST_INIT_x. **PRE_APPLET_x**=_"debug\_shell"_ : All parameters that start with PRE_ and then have an ltsp client applet name are sorted and their values are executed before the main function of that applet. **PWMERGE_SUR**=, **PWMERGE_SGR**=, **PWMERGE_DGR**=, **PWMERGE_DUR**= : Normally, all the server users are listed on the client login screens and are permitted to log in. To exclude some of them, define one or more of those regular expressions. For more information, read /usr/share/ltsp/client/login/pwmerge. For example, if you name your clients pc01, pc02 etc, and your users a01, a02, b01, b02 etc, then the following line only shows/allows a01 and b01 to login to pc01: `PWMERGE_SUR=".*%{HOSTNAME#pc}"` **REMOTEAPPS**=_"users-admin mate-about-me"_ : Register the specified applications as remoteapps, so that they're executed on the LTSP server via `ssh -X` instead of on the clients. For more information, see **ltsp-remoteapps(8)**. **RPI_IMAGE**=_"raspios"_ : Select this LTSP image to boot Raspberry Pis from. This symlinks all $BASE_DIR/$RPI_IMAGE/boot/* files directly under $TFTP_DIR when `ltsp kernel $RPI_IMAGE` is called. See the [Raspberry Pi OS documentation page](https://ltsp.org/docs/installation/raspios) for more information. **SEARCH_DOMAIN**=_"ioa.sch.gr"_ : A search domain to add to resolv.conf and to /etc/hosts. Usually provided by DHCP. **SERVER**=_"192.168.67.1"_ : The LTSP server is usually autodetected; it can be manually specified if there's need for it. **X_DRIVER**=_"vesa"_ **X_HORIZSYNC**=_"28.0-87.0"_ **X_MODELINE**=_'"1024x768\_85.00" 94.50 1024 1096 1200 1376 768 771 775 809 -hsync +vsync'_ **X_MODES**=_'"1024x768" "800x600" "640x480"'_ **X_PREFERREDMODE**=_"1024x768"_ **X_VERTREFRESH**=_"43.0-87.0"_ **X_VIRTUAL**=_"800 600"_ : If any of these parameters are set, the /usr/share/ltsp/client/init/xorg.conf template is installed to /etc/X11/xorg.conf, while applying the parameters. Read that template and consult xorg.conf(5) for more information. The most widely supported method to set a default resolution is X_MODES. If more parameters are required, create a custom xorg.conf as described in the EXAMPLES section. ## EXAMPLES To specify a hostname and a user to autologin in a client: ```shell [3c:07:71:a2:02:e3] HOSTNAME=pc01 AUTOLOGIN=user01 PASSWORDS_PC01="user01/cGFzczAxCg==" ``` The password above is "pass01" in base64 encoding. To calculate it, the `base64` command was run in a terminal: ```shell base64 pass01 cGFzczAxCg== ``` If some clients need a custom xorg.conf file, create it in e.g. `/etc/ltsp/xorg-nvidia.conf`, and put the following in ltsp.conf to dynamically symlink it for those clients at boot: ```shell [pc01] INCLUDE=nvidia [nvidia] POST_INIT_LN_XORG="ln -sf ../ltsp/xorg-nvidia.conf /etc/X11/xorg.conf" ``` Since ltsp.conf is transformed into a shell script and sections into functions, it's possible to directly include code or to call sections at POST_APPLET_x hooks. ```shell [clients] # Allow local root logins by setting a password hash for the root user. # The hash contains $, making it hard to escape in POST_INIT_x="sed ...". # So put sed in a section and call it at POST_INIT like this: POST_INIT_SET_ROOT_HASH="section_set_root_hash" # This is the hash of "qwer1234"; cat /etc/shadow to see your hash. [set_root_hash] sed 's|^root:[^:]*:|root:$6$VRfFL349App5$BfxBbLE.tYInJfeqyGTv2lbk6KOza3L2AMpQz7bMuCdb3ZsJacl9Nra7F/Zm7WZJbnK5kvK74Ik9WO2qGietM0:|' -i /etc/shadow ``` ltsp-23.02/ltsp/000077500000000000000000000000001437727275600135105ustar00rootroot00000000000000ltsp-23.02/ltsp/client/000077500000000000000000000000001437727275600147665ustar00rootroot00000000000000ltsp-23.02/ltsp/client/init/000077500000000000000000000000001437727275600157315ustar00rootroot00000000000000ltsp-23.02/ltsp/client/init/25-ro-root.sh000066400000000000000000000054431437727275600201200ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2020-2022 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # If the root file system is read-only, it means that `ltsp initrd-bottom` # didn't run and didn't create the tmpfs overlay. Create it now. # Also process ltsp.image, if defined. # Tested over NBD and NFSv3, but it currently has issues over NFSv4: # https://bugzilla.kernel.org/show_bug.cgi?id=199013 ro_root_main() { test -w / && return 0 # The following are currently only tested on Raspberry Pi OS if [ "$ADMINISTRATIVE_CLIENT" = "1" ]; then echo "Administrative client; booting in NFS-RW mode" re mount -o remount,rw / re exec /sbin/init fi # Raspberry Pi OS kernel has only /dev mounted at that point re mount_devices re mkdir -p /run/ltsp/client re kernel_vars re detect_server if [ -n "$IMAGE" ] && [ -n "$SERVER" ]; then # We want an existing mount point for the tmpfs outside of /run, # otherwise switch_root can't move the /run mount as it's in use. # Let's use /root that is used by initramfs-tools and always exists. re vmount -t nfs -o vers=3,nolock "$SERVER:$BASE_DIR" /root re vmount -o loop,ro "/root/$IMAGE" /root re mount_img_src /root /root /run/initramfs/ltsp else re mount_img_src / /root /run/initramfs/ltsp fi re set_readahead re fetch_ltsp_img rw at_exit -EXIT re exec switch_root /root /usr/share/ltsp/client/init/init } # Fetch and apply ltsp.img. Use NFS as some may use HTTP instead of TFTP. fetch_ltsp_img() { re mkdir -p /run/ltsp/tmp/tftp re vmount --no-exit -t nfs -o vers=3,tcp,nolock \ "$SERVER:${TFTP_DIR}/ltsp" /run/ltsp/tmp/tftp re test -f /run/ltsp/tmp/tftp/ltsp.img re mkdir -p /run/ltsp/tmp/ltsp-img ( re cd /run/ltsp/tmp/ltsp-img re cpio -i < /run/ltsp/tmp/tftp/ltsp.img ) || die re umount /run/ltsp/tmp/tftp install_ltsp /run/ltsp/tmp/ltsp-img /root re rm -rf /run/ltsp/tmp } # Similar to initrd-bottom>install_ltsp, but not specific to initramfs-tools install_ltsp() { local src dst src=$1 dst=$2 re cp -au "$src/usr/share/ltsp" "$dst/usr/share/" re cp -au "$src/etc/ltsp" "$dst/etc/" # Symlink the ltsp binary re ln -sf ../share/ltsp/ltsp "$dst/usr/sbin/ltsp" # Symlink the service; use absolute symlink due to /usr/lib migration re ln -sf /usr/share/ltsp/common/service/ltsp.service "$dst/lib/systemd/system/ltsp.service" re ln -sf ../ltsp.service "$dst/lib/systemd/system/multi-user.target.wants/ltsp.service" # Copy our modules configuration if [ -f "$src/etc/modprobe.d/ltsp.conf" ] && [ -d "$dst/etc/modprobe.d" ] then re cp -a "$src/etc/modprobe.d/ltsp.conf" "$dst/etc/modprobe.d/" fi } ltsp-23.02/ltsp/client/init/45-networking.sh000066400000000000000000000175361437727275600207160ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Handle networking # @LTSP.CONF: DNS_SERVER SERVER SEARCH_DOMAIN HOSTNAME # TODO: this only handles Debian-based distributions currently networking_main() { # Vars already set: DEVICE, GATEWAY, IP_ADDRESS, MAC_ADDRESS re import_ipconfig re detect_server re config_hostname re config_hosts re config_dns re config_ifupdown re config_network_manager re config_validlft re store_networking } config_dns() { local var resolv # DNS=1 under [common] means to use the LTSP server IP if [ "$DNS" = 1 ] && [ -n "$SERVER" ]; then DNS_SERVER="$SERVER" fi # If no DNS_SERVER was defined in ltsp.conf or in DHCP (e.g. IPAPPEND=3), # check the LTSP server, gateway, and Google Public DNS. if [ -z "$DNS_SERVER" ] && [ -x /usr/bin/dig ]; then warn "No DNS_SERVER, trying autodetection!" for var in $SERVER $GATEWAY 8.8.8.8; do if dig +time=1 +tries=1 +short "@$var" localhost >/dev/null 2>&1 then DNS_SERVER="$var" break fi done fi test -n "$DNS_SERVER" || return 0 # The symlink may be relative or absolute, so better use grep. if readlink /etc/resolv.conf | grep -q /run/systemd/resolve/; then # Deal with systemd-resolved. # We can't do per link DNS without systemd-networkd # (e.g. when using network-manager), so define them globally re mkdir -p /etc/systemd/resolved.conf.d { echo "# Generated by \`ltsp init\`, see man:ltsp(8)" echo "[Resolve]" test -n "$DNS_SERVER" && echo "DNS=$DNS_SERVER" test -n "$SEARCH_DOMAIN" && echo "Domains=$SEARCH_DOMAIN" } > /etc/systemd/resolved.conf.d/ltsp.conf else if [ -x /sbin/resolvconf ] && ( [ -L /etc/resolv.conf ] || [ -e /var/lib/resolvconf/convert ] ) then # Deal with resolvconf re mkdir -p /etc/resolvconf/resolv.conf.d/ resolv=/etc/resolvconf/resolv.conf.d/base else # Plain resolv.conf resolv=/etc/resolv.conf # Remove possibly dangling symlinks re rm -f "$resolv" fi { echo "# Generated by \`ltsp init\`, see man:ltsp(8)" test -n "$SEARCH_DOMAIN" && echo "search $SEARCH_DOMAIN" for var in $DNS_SERVER; do echo "nameserver $var" done } > "$resolv" fi return 0 } config_hostname() { local IP MAC # Remove spaces and default to e.g. ltsp123 HOSTNAME=${HOSTNAME%% *} HOSTNAME=${HOSTNAME:-ltsp%{IP\}} case "$HOSTNAME" in *%{IP}*) IP=$( ip -oneline -family inet address show dev "$DEVICE" | sed 's/.* \([0-9.]*\)\/\([0-9]*\) .*/\1.\2/' | awk -F "." '{ print (2^24*$1+2^16*$2+2^8*$3+$4)%(2^(32-$5)) }') ;; *%{MAC}*) MAC=$(echo "$MAC_ADDRESS" | tr -d ':') ;; esac HOSTNAME=$(re eval_percent "$HOSTNAME") echo "$HOSTNAME" >/etc/hostname re hostname "$HOSTNAME" } config_hosts() { { printf "# Generated by \`ltsp init\`, see man:ltsp(8) 127.0.0.1\tlocalhost 127.0.1.1\t%s%s %s\tserver # The following lines are desirable for IPv6 capable hosts ::1\tip6-localhost ip6-loopback fe00::0\tip6-localnet ff00::0\tip6-mcastprefix ff02::1\tip6-allnodes ff02::2\tip6-allrouters " "${SEARCH_DOMAIN:+$HOSTNAME.$SEARCH_DOMAIN }" "$HOSTNAME" "$SERVER" rw echo_values "HOSTS_[[:alnum:]_]*" } >/etc/hosts } config_ifupdown() { # Prohibit ifupdown from managing the boot interface test -f /etc/network/interfaces && printf "# Generated by \`ltsp init\`, see man:ltsp(8) # interfaces(5) file used by ifup(8) and ifdown(8) auto lo iface lo inet loopback auto %s iface %s inet manual " "$DEVICE" "$DEVICE"> /etc/network/interfaces # Never ifdown anything. Safer! test ! -x /sbin/ifdown || rw ln -sf ../bin/true /sbin/ifdown } config_network_manager() { re test "DEVICE=$DEVICE" != "DEVICE=" rw rm -rf /run/netplan /etc/netplan /lib/systemd/system-generators/netplan # Prohibit network-manager from managing the boot interface test -d /etc/NetworkManager/conf.d && printf "%s" "[keyfile] unmanaged-devices=interface-name:$DEVICE " > /etc/NetworkManager/conf.d/ltsp.conf return 0 } # Dracut sets the IP using the valid_lft parameter. From `man ip-address`: # > the valid lifetime of this address; see section 5.5.4 of RFC 4862. # > When it expires, the address is removed by the kernel. # We don't want the address to be removed by the kernel, so we change it here # to "forever". config_validlft() { local ip mask brd # On the other hand some setups exist, e.g. Fedora with Network Manager, # that renew the lease only if valid_lft != "forever". # In such cases, just return: grep -qs "^BOOTPROTO=dhcp" "/etc/sysconfig/network-scripts/ifcfg-$DEVICE" && return 0 # Example output: # 2: enp2s0 inet 10.161.254.11/24 brd 10.161.254.255 scope global enp2s0\ valid_lft 25147sec preferred_lft 25147sec # We don't match it and we don't do anything if valid_lft = "forever". re ip -4 -oneline addr show dev "$DEVICE" | sed -n 's/.* \([0-9.]*\)\/\([0-9]*\) brd \([0-9.]*\) .* valid_lft [0-9][^ ]* .*/\1 \2 \3/p' | while read -r ip mask brd; do re ip -4 addr change "$ip/$mask" broadcast "$brd" dev "$DEVICE" done } # To detect the server, we don't want to use the following: # - IPCONFIG_ROOTSERVER may be invalid in case of proxyDHCP. # - `ps -fC nbd-client` doesn't work as it's now a kernel thread. # - It may be available in /proc/cmdline, but it's complex to check # for all the variations of ip=, root=, netroot=, nbdroot= etc. # So assume that the first TCP connection is to the server (NFS etc) detect_server() { local cmd test -z "$SERVER" || return 0 grep -Eqw 'root=/dev/nbd.*|root=/dev/nfs' /proc/cmdline || return 0 if is_command ss; then cmd="ss -tn" elif is_command netstat; then cmd="netstat -tn" elif is_command busybox; then cmd="busybox netstat -tn" else warn "Not found: ss, netstat, busybox!" unset cmd fi if [ -n "$cmd" ]; then SERVER=$(rw $cmd | sed -n 's/.*[[:space:]]\([0-9.:]*\):\([0-9]*\)[^0-9]*/\1/p' | head -1) fi # If unable to detect, default to those: SERVER=${SERVER:-$IPCONFIG_ROOTSERVER} SERVER=${SERVER:-$GATEWAY} SERVER=${SERVER:-192.168.67.1} } # Import values saved by klibc ipconfig. Example: # DEVICE='enp0s3' # PROTO='dhcp' # IPV4ADDR='10.161.254.38' # IPV4BROADCAST='10.161.254.255' # IPV4NETMASK='255.255.255.0' # IPV4GATEWAY='10.161.254.1' # IPV4DNS0='194.63.237.4' # IPV4DNS1='194.63.239.164' # HOSTNAME='' # DNSDOMAIN='' # NISDOMAIN='' # ROOTSERVER='10.161.254.1' # ROOTPATH='' # filename='' # UPTIME='6' # DHCPLEASETIME='25200' # DOMAINSEARCH='' import_ipconfig() { local var test -f "/run/net-$DEVICE.conf" || return 0 # Fetch the variables but prefix IPCONFIG_ in them eval "$(sed 's/^[[:alpha:]]/IPCONFIG_&/' "/run/net-$DEVICE.conf")" || die "Error sourcing /run/net-$DEVICE.conf" if [ -z "$DNS_SERVER" ]; then for var in $IPCONFIG_IPV4DNS0 $IPCONFIG_IPV4DNS1; do # ignore nameserver of 0.0.0.0, which ipconfig may return # if both nameservers aren't specified. if [ "$var" != "0.0.0.0" ]; then DNS_SERVER="${DNS_SERVER+$DNS_SERVER }$var" fi done fi SEARCH_DOMAIN=${SEARCH_DOMAIN:-$IPCONFIG_DNSDOMAIN} HOSTNAME=${HOSTNAME:-$IPCONFIG_HOSTNAME} } # TODO: store networking information to /run/ltsp/environ store_networking() { : } ltsp-23.02/ltsp/client/init/54-pam.sh000066400000000000000000000026231437727275600172730ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2020 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # @LTSP.CONF: PWMERGE_SUR PWMERGE_SGR PWMERGE_DUR PWMERGE_DGR # @LTSP.CONF: PASSWORDS_x PAM_AUTH_TYPE SSH_SERVER SSH_OPTIONS pam_main() { local userpass user pass test "$PAM_AUTH_TYPE" = "0" && return 0 re "$_LTSP_DIR/client/login/pwmerge" \ ${PWMERGE_SUR:+"--sur=$(re eval_percent "$PWMERGE_SUR")"} \ ${PWMERGE_SGR:+"--sgr=$(re eval_percent "$PWMERGE_SGR")"} \ ${PWMERGE_DUR:+"--dur=$(re eval_percent "$PWMERGE_DUR")"} \ ${PWMERGE_DGR:+"--dgr=$(re eval_percent "$PWMERGE_DGR")"} \ -lq /etc/ltsp /etc /etc if [ -n "$PAM_AUTH_TYPE$SSH_SERVER$SSH_OPTIONS" ]; then cat >/etc/ltsp/pamltsp.conf </etc/cron.d/ltsp fi # Disable several cronjobs that are usually present but that # cause unnecessary load. while read -r var; do rm -f "$var" done </dev/null); then # AUTOLOGIN can either be a valid sed regex or a username _ALUSER=$AUTOLOGIN fi # Ignore non-existing users as e.g. lightdm displays artifacts if [ -n "$_ALUSER" ] && ! getent passwd "$_ALUSER" >/dev/null; then _ALUSER= fi if [ -n "$_ALUSER" ]; then _AUTOLOGIN=true else _AUTOLOGIN=false fi if [ "$RELOGIN" = "0" ]; then _RELOGIN=false else _RELOGIN=true fi re configure_gdm re configure_lightdm re configure_sddm re ltspdm } configure_gdm() { local conf is_command gdm3 || return 0 re mkdir -p /etc/gdm3 # Some distributions used daemon.conf instead of upstream custom.conf? if [ -f /etc/gdm3/daemon.conf ]; then conf=/etc/gdm3/daemon.conf else conf=/etc/gdm3/custom.conf fi { echo " # Generated by \`ltsp init\`, see man:ltsp(8) # You can append content here by specifying the GDM3_CONF parameter [daemon] AutomaticLoginEnable=$_AUTOLOGIN AutomaticLogin=$_ALUSER" if [ -f "$GDM3_CONF" ]; then re cat "$GDM3_CONF" elif [ -n "$GDM3_CONF" ]; then echo "$GDM3_CONF" fi } >>"$conf" } configure_lightdm() { local timeout is_command lightdm || return 0 # Work around https://github.com/canonical/lightdm/issues/97 if [ "$RELOGIN" = "0" ]; then timeout=${RELOGIN_TIMEOUT:-0} else timeout=${RELOGIN_TIMEOUT:-2} fi re mkdir -p /etc/lightdm { echo " # Generated by \`ltsp init\`, see man:ltsp(8) # You can append content here by specifying the LIGHTDM_CONF parameter [Seat:*] # Work around https://github.com/CanonicalLtd/lightdm/issues/49 greeter-show-manual-login=true greeter-hide-users=false autologin-user=$_ALUSER autologin-user-timeout=$timeout" if [ -f "$LIGHTDM_CONF" ]; then re cat "$LIGHTDM_CONF" elif [ -n "$LIGHTDM_CONF" ]; then echo "$LIGHTDM_CONF" fi } >>/etc/lightdm/lightdm.conf } configure_sddm() { is_command sddm || return 0 # Defining a session is required for autologin to work in sddm for session in /usr/share/xsessions/plasma.desktop \ /usr/share/xsessions/*.desktop; do test -f "$session" && break done if [ -f "$session" ]; then session=${session#/usr/share/xsessions/} else session= fi { echo " # Generated by \`ltsp init\`, see man:ltsp(8) # You can append content here by specifying the SDDM_CONF parameter [Autologin] User=$_ALUSER Session=$session Relogin=$_RELOGIN" if [ -f "$SDDM_CONF" ]; then re cat "$SDDM_CONF" elif [ -n "$SDDM_CONF" ]; then echo "$SDDM_CONF" fi } >>/etc/sddm.conf } # Create a system group called ltspdm. Add some users that match a regex # there. Touch accountsservice info for these users, delete info for all # other users, and try to configure the DM to display only these users. ltspdm() { local dmregex dmusers user test -n "$LTSPDM_USERS" || return 0 dmregex=$(re eval_percent "$LTSPDM_USERS") dmusers=$(re getent passwd | re awk -F: '$1 ~ "^'"$dmregex"'$" { print $1 }') if [ -z "$dmusers" ]; then warn "No users matched LTSPDM_USERS=$dmregex" return 0 fi if [ -d /var/lib/AccountsService/users ]; then # Some DMs list (or sort) users that have accountsservice information # Delete information for all undefined users for user in /var/lib/AccountsService/users/*; do test -f "$user" || continue user=${user#/var/lib/AccountsService/users/} case "$_NL$dmusers$_NL" in *"$_NL$user$_NL"*) ;; *) re rm -f "/var/lib/AccountsService/users/$user" ;; esac done # And touch/create information for all defined users for user in $dmusers; do re touch "/var/lib/AccountsService/users/$user" done fi # Derivatives of unity-greeter support showing only the members of a group # Create an ltspdm group if it doesn't exist if ! getent group ltspdm 2>/dev/null; then re groupadd --system ltspdm fi # Set the group members; this doesn't keep any existing ones re gpasswd -M "$(printf "%s" "$dmusers" | tr "$_NL" ",")" ltspdm # Create the appropriate slick-greeter configuration if is_command slick-greeter; then { echo " # Generated by \`ltsp init\`, see man:ltsp(8) # You can append content here by specifying the SLICK_CONF parameter [Greeter] group-filter=ltspdm" if [ -f "$SLICK_CONF" ]; then re cat "$SLICK_CONF" elif [ -n "$SLICK_CONF" ]; then echo "$SLICK_CONF" fi } >>/etc/lightdm/slick-greeter.conf fi # Inject a dconf key for arctica-greeter as it doesn't support conffiles if is_command arctica-greeter && [ -f /usr/lib/lightdm/lightdm-greeter-session ]; then sed "s|^exec|dconf write /org/ArcticaProject/arctica-greeter/group-filter \"['ltspdm']\"\nexec|" \ -i /usr/lib/lightdm/lightdm-greeter-session fi } ltsp-23.02/ltsp/client/init/55-init.sh000066400000000000000000000027761437727275600174730ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Override /sbin/init to run some LTSP code, then restore the real init init_cmdline() { # Verify that this is a valid init environment test "$$" = "1" || die "ltsp init can run only as pid 1" # Create the directory that indicates an "ltsp mode" boot test -w /run && re mkdir -p /run/ltsp/client # OK, ready to run all the main functions re run_main_functions "$_SCRIPTS" "$@" # Since we don't return, we need to run the POST parameters manually re run_parameters "POST" rw at_exit -EXIT re exec /sbin/init } # Mount options from Debian initramfs-tools/init mount_devices() { local mp mounts for mp in /dev /proc /run /sys; do test -d "$mp" || die "Missing directory: $mp" done test -f /proc/mounts || re vmount -t proc -o nodev,noexec,nosuid proc /proc mounts=$(re awk '{ printf " %s ",$2 }' < /proc/mounts) test "$mounts" != "${mounts#* /sys }" || re vmount -t sysfs -o nodev,noexec,nosuid sysfs /sys test "$mounts" != "${mounts#* /dev }" || re vmount -t devtmpfs -o nosuid,mode=0755 udev /dev test -d /dev/pts || re mkdir -p /dev/pts test "$mounts" != "${mounts#* /dev/pts }" || re vmount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts test "$mounts" != "${mounts#* /run }" || re vmount -t tmpfs -o noexec,nosuid,size=10%,mode=0755 tmpfs /run } ltsp-23.02/ltsp/client/init/55-multiseat.sh000066400000000000000000000040671437727275600205320ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2020-2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Provide a way to create udev rules that match hardware to seats # @LTSP.CONF: MULTISEAT UDEV_SEAT_x multiseat_main() { local seat_vars var value seat # First do some voodoo to autodetect multiseat if [ "$MULTISEAT" = "1" ]; then detect_multiseat fi # But also allow finetuning the UDEV rules seat_vars=$(re echo_vars "UDEV_SEAT_[[:digit:]]_[[:alnum:]_]*") if [ -z "$seat_vars" ]; then # Delete our rules file in case a chrootless LTSP server contains it rm -f /etc/udev/rules.d/72-ltsp-seats.rules return 0 fi for var in $seat_vars; do eval "value=\$$var" seat=${var#UDEV_SEAT_} seat=${seat%%_*} echo "TAG==\"seat\", DEVPATH==\"$value\", ENV{ID_SEAT}=\"seat-$seat\"" done >/etc/udev/rules.d/72-ltsp-seats.rules } detect_multiseat() { local minbusnum count busnum UDEV_SEAT_1_GRAPHICS=${UDEV_SEAT_1_GRAPHICS:-auto} if [ "$UDEV_SEAT_1_GRAPHICS" = "auto" ]; then minbusnum="z" count=0 while read -r busnum; do count=$((count + 1)) busnum=${busnum%*/boot_vga} busnum=${busnum#/sys} # busnum=${busnum##*/} test "$busnum" "<" "$minbusnum" && minbusnum=$busnum done <no inside these address ranges re sed -e '// s/\(\)[^<]* /lib/systemd/system/snapd.hold.service re ln -s ../snapd.hold.service /lib/systemd/system/snapd.service.wants/ # Work around snap hardlinking that wastes tmpfs space (LP #1867415) test "$SYMLINK_SNAPS" = 0 && return 0 for var in /var/lib/snapd/seed/snaps/*.snap; do test -f "$var" || continue var=${var#/var/lib/snapd/seed/snaps/} test -f "/var/lib/snapd/snaps/$var" && continue re ln -s "/var/lib/snapd/seed/snaps/$var" "/var/lib/snapd/snaps/$var" done } ltsp-23.02/ltsp/client/init/55-swap.sh000066400000000000000000000015571437727275600174760ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Activate swap partitions. Code adapted from casper. # @LTSP.CONF: LOCAL_SWAP swap_main() { re local_swap } local_swap() { local devices device magic test "$LOCAL_SWAP" != "0" || return 0 devices="" for device in /dev/[hsv]d[a-z][0-9]*; do if ! [ -b "$device" ]; then continue fi blkid -o udev -p "${device%%[0-9]*}" | grep -q "^ID_FS_USAGE=raid" && continue magic=$(rw dd if="$device" bs=1 skip=4086 count=10 2>/dev/null) if [ "$magic" = "SWAPSPACE2" ] || [ "$magic" = "SWAP-SPACE" ]; then # log "Found $device" devices="$devices $device" fi done for device in $devices; do re swapon "$device" done } ltsp-23.02/ltsp/client/init/55-systemd.sh000066400000000000000000000025231437727275600202060ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2020 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Configure systemd related things systemd_main() { # Use loglevel from /proc/cmdline instead of resetting it # Remember, `false && false` doesn't exit; use `||` when not using re/rw grep -qsw netconsole /proc/cmdline && rw rm -f "/etc/sysctl.d/10-console-messages.conf" test -f "/usr/lib/tmpfiles.d/systemd.conf" && rw sed "s|^[aA]|# &|" -i "/usr/lib/tmpfiles.d/systemd.conf" # Silence dmesg: Failed to open system journal: Operation not supported # Cap journal to 1M; TODO: make it configurable test -f "/etc/systemd/journald.conf" && rw sed -e "s|[^[alpha]]*Storage=.*|Storage=volatile|" \ -e "s|[^[alpha]]*RuntimeMaxUse=.*|RuntimeMaxUse=1M|" \ -e "s|[^[alpha]]*ForwardToSyslog=.*|ForwardToSyslog=no|" \ -i "/etc/systemd/journald.conf" # Shorten the waiting time for problematic shutdown tasks test -f "/etc/systemd/system.conf" && rw sed "s|[^[alpha]]*DefaultTimeoutStopSec=.*|DefaultTimeoutStopSec=10s|" \ -i "/etc/systemd/system.conf" test -f "/etc/systemd/user.conf" && rw sed "s|[^[alpha]]*DefaultTimeoutStopSec=.*|DefaultTimeoutStopSec=10s|" \ -i "/etc/systemd/user.conf" } ltsp-23.02/ltsp/client/init/55-various.sh000066400000000000000000000100701437727275600202020ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Handle various little things that don't deserve a separate file # @LTSP.CONF: CUPS_SERVER FSTAB_x IGNORE_EPOPTES REMOTEAPPS various_main() { # initrd-bottom may have renamed the real init if [ -f /sbin/init.ltsp ]; then re rm /sbin/init re mv /sbin/init.ltsp /sbin/init fi re rm -f "/var/crash/"* # Some live CDs don't have sshfs; allow the user to provide it if ! is_command sshfs && [ -x "/etc/ltsp/bin/sshfs-$(uname -m)" ]; then re ln -s "../../etc/ltsp/bin/sshfs-$(uname -m)" /usr/bin/sshfs fi # Disable systemd-gpt-auto-generator (DiscoverablePartitionsSpec) rw rm -f /lib/systemd/system-generators/systemd-gpt-auto-generator config_cups_server config_epoptes config_fstab config_gnome_software config_locale config_logs config_machine_id config_motd config_remoteapps } config_cups_server() { if [ "$CUPS_SERVER" = "ignore" ] || [ ! -d /etc/cups ]; then return 0 fi echo "ServerName ${CUPS_SERVER:-$SERVER}" >/etc/cups/client.conf if [ "$CUPS_SERVER" != "localhost" ]; then rw systemctl mask --quiet --root=/ --no-reload cups cups.path cups.socket cups-browsed fi } config_epoptes() { # Symlink server epoptes certificate if [ "$IGNORE_EPOPTES" != "1" ] && [ -f /etc/ltsp/epoptes.crt ]; then re mkdir -p /etc/epoptes re ln -sf ../ltsp/epoptes.crt /etc/epoptes/server.crt fi } config_fstab() { { echo "# Generated by \`ltsp init\`, see man:ltsp(8)" re echo_values "FSTAB_[[:alnum:]_]*" } >/etc/fstab # Allow NFS home in live CDs by substituting mount.nfs with nfsmount if [ ! -f /usr/sbin/mount.nfs ] && [ -x /usr/lib/klibc/bin/nfsmount ]; then re ln -s /usr/lib/klibc/bin/nfsmount /usr/sbin/mount.nfs # Klibc nfsmount only supports IPs sed "s/^server\(:.*\bnfs.*\)/$SERVER\1/" -i /etc/fstab fi } # Disable the gnome-software shell search provider # Adapted from casper/31disable_update_notifier config_gnome_software() { local var var=/usr/share/gnome-shell/search-providers/org.gnome.Software-search-provider.ini if [ -e "$var" ]; then echo "DefaultDisabled=true" >>"$var" fi } config_locale() { # TODO: quick hack for ubuntu live CDs; revisit later test -f /etc/default/locale || return 0 grep -q LANG /etc/default/locale && return 0 echo "# Generated by \`ltsp init\`, see man:ltsp(8) LANG=${LANG:-C.UTF-8} ${LANGUAGE:+LANGUAGE=$LANGUAGE}" >>/etc/default/locale } # The dbus machine id should be unique for each client, otherwise problems may # occur, e.g. if a thin client has the same id as the server, then `sudo gedit` # on the client session which runs on the server gives "access denied"! # It also helps if it's constant, so we generate it from the client MAC # address. That way we don't pollute e.g. ~/.pulse/* with random entries on # fat clients. See also `man machine-id`. config_machine_id() { printf "%s00000000000000000000\n" "$(echo "$MAC_ADDRESS" | tr -d ':')" \ >/etc/machine-id if [ -f /var/lib/dbus/machine-id ]; then re ln -sf ../../../etc/machine-id /var/lib/dbus/machine-id fi } # /etc/init/mounted-run.conf calls `run-parts /etc/update-motd.d`, and that # takes more than a (completely useless) second. But we don't want to remove # the whole mounted-run service as it prepares /run too. So remove most scripts # there but leave the header and footer. config_motd() { re rm -f /etc/update-motd.d/[1-9][0-8]* } # Remove logs to save tmpfs on log rotation, in case rootfs is a chroot or vmdk config_logs() { # Completely remove old logs re rm -rf /var/log/*.[1-9] /var/log/*.gz /var/log/*.old /var/log/*/* # Zero big files, but avoid using `truncate` on overlayfs (LP: #1494660) re find "/var/log/" -type f -size +1k -exec tee {} + > /scripts/init-bottom/ORDER fi } ltsp-23.02/ltsp/client/initrd-bottom/initramfs-tools/000077500000000000000000000000001437727275600227135ustar00rootroot00000000000000ltsp-23.02/ltsp/client/initrd-bottom/initramfs-tools/ltsp-hook.conf000066400000000000000000000117571437727275600255150ustar00rootroot00000000000000# /bin/sh -n # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Inject LTSP code under initramfs-tools # Sourced by initramfs-tools init before any other scripts. Specifically: # /conf/conf.d/*, /scripts/functions, /proc/cmdline, break:top, # /scripts/init-top, break:modules, break:premount, /scripts/init-premount, # break:mount, /scripts/local, /scripts/nfs, break:mountroot, # /scripts/local-top, /scripts/local-premount, mountroot, /scripts/nfs-bottom, # /scripts/local-bottom, break:bottom, /scripts/init-bottom, mount move run, # break:init, run-init. # Another way to hook would be rdinit=. # Notes: # It needs the .conf extension, it can't be .sh. # Don't exit from here! It's sourced! (bad shebang is for gedit highlighting) # Use functions and local variables, to avoid namespace pollution debug_shell() { echo "Spawning a shell at '$0 $*', type exit to continue:" >&2 sh -i /dev/console 2>&1 } # Work around https://github.com/NetworkBlockDevice/nbd/issues/87 # Additionally, blockdev may be needed when nbd is not a single partition patch_nbd() { # Live code patching is ugly, but there's no easy alternative in this case. # At least try to match the whole line, to only patch affected versions. if grep -qs 'systemd-mark$' /scripts/local-top/nbd; then sed 's/^$NBDCLIENT $nbdsrv -N $nbdpath $nbdport $nbdrootdev -swap -persist -systemd-mark$/& -b 512; blockdev --rereadpt $nbdrootdev/' -i /scripts/local-top/nbd fi } # Work around https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1755863 patch_casper() { if [ -d /scripts/casper-bottom ]; then echo 'ln -s /dev/null /root/lib/systemd/system/cdrom.mount' \ > /scripts/casper-bottom/25disable_cdrom.mount chmod +x /scripts/casper-bottom/25disable_cdrom.mount fi # This was added in eoan and hardcodes BOOT=casper rm -f /conf/conf.d/default-boot-to-casper.conf } # Work around https://bugs.launchpad.net/ubuntu/+source/isc-dhcp/+bug/1840965 patch_dhclient() { if [ -x /etc/ltsp/dhclient-config ] && [ -d /etc/dhcp/dhclient-enter-hooks.d ] then cp -a /etc/ltsp/dhclient-config /etc/dhcp/dhclient-enter-hooks.d/config fi } install_ltsp_hooks() { local script entry order # If an LTSP5 boot was requested, return grep -qs "init=/sbin/init-ltsp" /proc/cmdline && return 0 # If a casper boot was requested, return grep -qs "boot=casper" /proc/cmdline && return 0 # Neutralize all LTSP5 scripts to prevent them from interfering. # Symlinking to /bin/true won't work since that's a symlink to busybox. # Removing their entries from ORDER is hard, so don't use rm either. for script in /scripts/*/*ltsp*; do test -f "$script" || continue echo '#!/bin/true' > "$script" done # Initramfs-tools run_scripts() sources /scripts/*/ORDER, which in turn # runs the scripts and reads possible output from /conf/param.conf. # Hook our scripts at the end of each ORDER, except for init-bottom. # E.g. ltsp-bottom in Ubuntu needs to run before udev moves /dev to /root/dev. for script in /scripts/init-bottom/ltsp-initrd-bottom; do mkdir -p "${script%/*}" # In buster+ (contrary to bionic-), /run isn't yet mounted at this # point, so we can't mkdir /run/ltsp/client now. So inject it below. echo "#!/bin/sh mkdir -p /run/ltsp/client ltsp ${script#*ltsp-}" > "$script" chmod +x "$script" entry=$(printf "%s %s" "$script" '"$@" [ -e /conf/param.conf ] && . /conf/param.conf') if [ "$script" != "${script#/scripts/init-bottom/}" ]; then if [ -f "${script%/*}/ORDER" ]; then order=$(cat "${script%/*}/ORDER") else order="" fi printf "%s\n%s\n" "$entry" "$order" >"${script%/*}/ORDER" else printf "%s\n" "$entry" >>"${script%/*}/ORDER" fi done # Create the ltsp binary symlink mkdir -p /usr/sbin ln -s ../share/ltsp/ltsp /usr/sbin/ltsp # Don't search for a resume device rm -f /conf/conf.d/*resume* # TODO: these belong in a new `ltsp initrd-top` applet # Empty fstab; don't erase it as dhclient-script complains echo "# Generated by \`ltsp initrd-top\`, see man:ltsp(8)" > /etc/fstab # TODO: to fsck or not to fsck? If it doesn't fsck read-only netmounts, # then it's best to leave the defaults, so that it checks local disks. # E.g. local disk that needs to be used as ltsphome, AND that has our rootfs. # export fastboot=y case " ${BLACKLIST_MODULES-floppy} " in *" floppy "*) mkdir -p /etc/modprobe.d echo "# Generated by \`ltsp initrd-top\`, see man:ltsp(8) # Blacklist floppy (#32) blacklist floppy" >/etc/modprobe.d/ltsp.conf ;; esac } patch_nbd patch_casper patch_dhclient install_ltsp_hooks unset patch_nbd unset patch_casper unset patch_dhclient unset install_ltsp_hooks unset debug_shell ltsp-23.02/ltsp/client/login/000077500000000000000000000000001437727275600160765ustar00rootroot00000000000000ltsp-23.02/ltsp/client/login/pamltsp000077500000000000000000000275701437727275600175170ustar00rootroot00000000000000#!/bin/sh # This file is part of LTSP, https://ltsp.org # Copyright 2019-2022 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Provide PAM authentication to a server via SSH and optionally SSHFS $HOME. # It's not an LTSP applet in order to be able to run without LTSP. # It may be called by the user (see install below), # or by pam_exec, either as root for the initial login, # or as the user for screensaver unlocking etc. die() { printf "%s\n" "$*" >&2 exit 1 } require_root() { if [ "$_UID" != 0 ]; then die "${1:-$0 must be run as root}" fi } # Install pamltsp in the system PAM configuration install() { local auth_control tab require_root # Currently the PAM configuration is Debian/Ubuntu specific if [ "$PAM_AUTH_TYPE" = "Primary" ]; then auth_control="[success=end default=ignore]" elif [ "$PAM_AUTH_TYPE" = "Additional" ]; then tab=$(printf "\t") auth_control="optional$tab$tab" else die "Aborting due to PAM_AUTH_TYPE=$PAM_AUTH_TYPE" fi # The seteuid option is needed for `getent shadow`, for mkdir/chown $HOME, # for caching shadow options to /run/ltsp/pam/, for using systemd-run etc. printf "Name: SSH based authentication for LTSP Default: yes Priority: 0 Auth-Type: %s Auth: \t%s\tpam_exec.so expose_authtok seteuid stdout quiet %s pam_auth Session-Interactive-Only: yes Session-Type: Additional Session: \toptional\tpam_exec.so seteuid stdout quiet %s pam_session\n" \ "$PAM_AUTH_TYPE" "$auth_control" "$_SELF" "$_SELF" \ >/usr/share/pam-configs/ltsp pam-auth-update --package ltsp || die "Could not configure PAM for SSH authentication!" sed 's/.*\(KillUserProcesses=\).*/\1yes/' -i /etc/systemd/logind.conf } # May be called: # As root by pam auth>Primary, for SSH or SSHFS or passwordless logins # As root by pam auth>Additional, for LDAP SSHFS mounting # As root by pam open_session, for autologin SSHFS mounting # As user by pam auth, for screensaver unlocking # As the user doesn't have enough rights to `getent shadow`; # necessary information should be cached in /run/ltsp/pam/ on login. pam_auth() { local pw_entry remote pw_name pw_passwd pw_uid pw_gid pw_gecos pw_dir \ pw_shell sp_entry sp_namp sp_pwdp _dummy sshfs_params msg pam_log # Verify that we're being called from PAM and fetch the user entry if [ -z "$PAM_USER" ] || ! pw_entry=$(getent passwd "$PAM_USER"); then die "User $PAM_USER doesn't exist" fi # Detect if the user is local or remote if grep -q "^$PAM_USER:" /etc/passwd; then remote=0 else remote=1 # For remote users, we *may* do SSHFS; nothing more test "$PAM_TYPE" = "open_session" && return 0 test "$PAM_AUTH_TYPE" = "Primary" && return 1 test "$SSHFS" = 0 && return 0 fi # Retrieve the user's sp_entry from shadow or cache if [ "$remote" = 1 ]; then # Don't ask for remote shadow entries, e.g. # https://bugzilla.redhat.com/show_bug.cgi?id=751291#c4 sp_entry="$PAM_USER:pamltsp:remote-user" elif [ "$_UID" = 0 ]; then sp_entry=$(getent shadow "$PAM_USER") elif [ -f "/run/ltsp/pam/$PAM_USER/shadow" ]; then sp_entry=$(cat "/run/ltsp/pam/$PAM_USER/shadow") else die "Unhandled user $PAM_USER" fi IFS=: # Variable names from `man getpwent/getspent` read -r pw_name pw_passwd pw_uid pw_gid pw_gecos pw_dir pw_shell <"/run/ltsp/pam/$pw_name/shadow" fi # Support "pamltsp=" for passwordless logins without SSH authentication # for guest-like accounts with NFS/local home. pass=${sp_pwdp#pamltsp} pass=${pass%%,*} test "$pass" = "=" && return 0 # jessie-mate breaks with IdentityFile=/dev/null and shows: # Enter passphrase for key '/dev/null': # It works with IdentityFile=/nonexistent set -- -F /dev/null -o UserKnownHostsFile="/etc/ltsp/ssh_known_hosts" \ -o IdentitiesOnly=yes -o IdentityFile=/nonexistent \ -o NumberOfPasswordPrompts=1 $SSH_OPTIONS unset success # Indicate that an authentication attempt is in progress # See https://github.com/libfuse/sshfs/issues/183 trap "rm -f '/run/ltsp/pam/$PAM_USER/pid'" HUP INT QUIT SEGV PIPE TERM EXIT echo "$$" >"/run/ltsp/pam/$PAM_USER/pid" # Check if SSHFS is required. # `mountpoint` blocks until the sshfs password in entered. # That may be a good thing, to avoid race conditions. if [ "$SSHFS" = 0 ] || ! command -v sshfs >/dev/null || mountpoint -q /home || mountpoint -q "$pw_dir"; then if [ "$PAM_TYPE" = "open_session" ]; then # This point is reached when we already mounted home in auth, # or for passwordless logins with NFS or local home return 0 fi # The ssh call logic is documented in ssh-askpass export DISPLAY=" " export SSH_ASKPASS="${_SELF%/*}/ssh-askpass" if ssh -qns "$@" "$pw_name@$SSH_SERVER" sftp; then success=1 if [ ! -e "$pw_dir" ] && [ "$MKHOMEDIR" != 0 ]; then # Emulate pam_mkhomedir mkdir -p -m 0755 "$pw_dir" cp -a /etc/skel/. "$pw_dir" chown -R "$pw_uid:$pw_gid" "$pw_dir" fi fi else test "$_UID" = 0 || die "SSHFS needed without root?!" # On SSHFS gnome-keyring requires disable_hardlinks, but this # breaks ICEauthority, so just remove gnome-keyring for now # https://bugzilla.gnome.org/show_bug.cgi?id=730587 rm -f /usr/bin/gnome-keyring-daemon # $pw_dir must not be in use to be mounted; cd elsewhere cd / || true # If a previous sshfs was killed instead of properly unmounted, # `mountpoint` returns 1 "Transport endpoint is not connected", # yet the mount point is there in /proc/mounts, and a new sshfs fails. # Run an extra fusermount for this case. # To reproduce, login as admin, and run: systemctl stop gdm3 # It needs a few seconds to kill all the processes. # Logins will fail for those seconds, and work properly afterwards. grep -qs "$pw_dir fuse.sshfs" /proc/mounts && fusermount -u "$pw_dir" # Create an empty home dir if it's not there; nope, no skel for SSHFS if [ ! -d "$pw_dir" ] && [ "$MKHOMEDIR" != 0 ]; then mkdir -p -m 0755 "$pw_dir" chown "$pw_uid:$pw_gid" "$pw_dir" fi # SSHFS doesn't appear to support SSH_ASKPASS, but it can read stdin # TODO: but why doesn't it read it directly from pam_exec? # allow_other,default_permissions: allow the user but not other users # We don't use `sudo -u` for sshfs to avoid modprobe'ing fuse, # sed'ing fuse.conf, chown'ing home etc; if we did, that would be: # allow_root: for the DM to setup .Xauthority # nonempty: in case of .bash_history or local home sshfs_params=password_stdin,allow_other,default_permissions # fuse3 defaults to nonempty and doesn't accept it command -v fusermount3 >/dev/null || sshfs_params="$sshfs_params,nonempty" if msg=$("${_SELF%/*}/ssh-askpass" | sshfs -o "$sshfs_params" "$@" "$pw_name@$SSH_SERVER:" "$pw_dir" 2>&1); then success=1 else # If it's empty, remove it to avoid a tmpfs home rmdir --ignore-fail-on-non-empty "$pw_dir" fi fi if [ "$success" = 1 ]; then return 0 fi if [ "$PAM_TYPE" = "auth" ] && [ "$PAM_AUTH_TYPE" = "Primary" ]; then # su: gettext -d Linux-PAM "Authentication failure" # login: gettext -d shadow 'Login incorrect' (this works in fedora30 too) msg=$(gettext -d shadow "Login incorrect") msg=${msg:-Authentication failure} else msg="Pamltsp failed to mount home via SSHFS: $msg" fi echo ".$msg." >&2 return 1 } pam_log() { return 0 } pam_session() { pam_log case "$PAM_TYPE" in close_session) unmount_sshfs || return $? ;; open_session) pam_auth || return $? ;; esac } unmount_sshfs() { local pw_entry pw_name pw_passwd pw_uid pw_gid pw_gecos pw_dir pw_shell # Verify that we're being called from PAM and that the user exists if [ -z "$PAM_USER" ] || ! pw_entry=$(getent passwd "$PAM_USER"); then die "User $PAM_USER doesn't exist" fi IFS=: # Variable names from `man getpwnam` read -r pw_name pw_passwd pw_uid pw_gid pw_gecos pw_dir pw_shell </dev/null 2>/dev/null [params], not |$*|" ;; esac } main "$@" ltsp-23.02/ltsp/client/login/pamltsp.md000066400000000000000000000201311437727275600200750ustar00rootroot00000000000000# PAM notes ## Design All LTSP code is interpreted, to be sent via the initramfs in systems of different architectures. Thus, pamltsp is written in shell/python and runs from pam_exec hooks; which results in certain limitations. To allow both local and pamltsp users to login, pam_exec is run after pam_unix. This means that pamltsp users must have a password that: * Is invalid, so that pam_unix fails * Is not a locked password, so that display managers won't hide them This is implemented by putting "pamltsp[=base64-password]" in the place of the password hash in the shadow file, and comes with the following benefits: * The sysadmin and pamltsp can easily detect remote users; it wouldn't be nice if roaming laptop users were attempting to authenticate against the server every time they misttyped their password * Passwordless logins can be implemented if the sysadmin provides the password in base64 encoding there. Plaintext passwords can't be used as they may contain invalid characters; and base64 additionally hides them from over-the-shoulder spying * To convert remote users to local, the sysadmin may run `passwd user` * Converting local users to remote is done with `usermod -p pamltsp[=...]` Another limitation is that pam_exec sometimes runs without root permissions, for example for screensaver unlocking. In this case, pamltsp isn't able to run `getent shadow` and see if the user is local or remote. To overcome this, pamltsp caches the necessary data in /run/ltsp/pam on user login. ## Autologin and passwordless login Implementing autologin is easy; LTSP scripts or the sysadmin store the base64 password in shadow and configure the DM; pamltsp is not called for the auth phase, so it does the SSHFS mount in the open_session phase. One way to do passwordless logins is by running `passwd -d`; some DMs then even allow single-click logins, without asking for a password. This isn't a suitable method because pam_unix then authenticates the user and pamltsp is never called, so users would be able to login in vt1 without their home mounted over SSHFS, they'd be able to run `passwd` and change it etc. So the best passwordless login that we can offer, is a [click] to select the user, and [enter] to enter an empty password that pamltsp will accept. This rationale is valid for screensaver unlocking as well. TODO: are all pamltsp=base64-pass entries automatically passwordless? Or is pamltsp=base64-pass,passwordless=1 needed? TODO: is a global SSHFS=[01] option enough, or is ",sshfs=[01]" needed? TODO: is a global server= option enough? TODO: is old password caching helpful, i.e. ",cached="? Only makes sense with local home, of course... ## Notes for DM behavior for locked/blank passwords B=blank password (can run `passwd` but not `ssh`; `passwd -S` returns P, not B) L=locked password NP=no password (empty, e.g. live user) O=if there's an "other user/non listed user" entry P=valid password https://termbin.com/ml6d blank=$(python3 -c 'import crypt; print(crypt.crypt(""))') echo -e '1\n1\n' | sudo adduser b; sudo usermod -p "$blank" b echo -e '1\n1\n' | sudo adduser l; sudo passwd -l l echo -e '1\n1\n' | sudo adduser np; sudo passwd -d np echo -e '1\n1\n' | sudo adduser p ps aux | grep dm bionic-gnome (gdm) B=prompt, L=hidden, NP=single-click, O="Not listed?", P=prompt disco-mate (lightdm/slick greeter) B=?, L=prompt, NP="Log In", O=no, P=prompt bionic-kde (kdm?) B=?, L=prompt, NP=prompt, O="Different User", P=prompt disco-budgie (lightdm/slick greeter) B=prompt, L=prompt, NP="Log In", O=no, P=prompt jessie-mate screensaver: B=disappear-on-move, L=prompt, NP=disappear-on-move, P=prompt ## Alternative ideas that were dropped An alternative idea was to use the pw_pass field of passwd, but it was dropped because if a sysadmin later on ran `passwd user`, the new hash would be stored in passwd instead of shadow. A pamltsp group similar to nopasswdlogin could also help, but since we need an invalid hash anyway, it wasn't chosen. The nopasswdlogin group is mostly a PAM thing anyway, e.g. the gdm code doesn't even mention it anymore. https://bugzilla.gnome.org/show_bug.cgi?id=675780 Instead of /run/ltsp/pam, the date of last password change in shadow could be abused, and set to a far distant future date. `passwd -S` then would have enough permissions then retrieve that. `pwck` would report a warning for this, but all programs should be able to cope with it, e.g. imagine BIOS battery failures. The date would be automatically reset by the password change if the sysadmin decided to make the user local again. ## common-auth PAM_TYPE=auth means that the user typed a password. At that point we can use ssh to authenticate them, or sshfs to authenticate them and mount their home. We mount their home if $SSHFS!=0 and :ssh: is in passwd and it's not already mounted. If seteuid is not passed to pam_exec, then running `su - user2` as user1, can't sshfs-mount /home/user2 as TODO pam is running as user1? Maybe running without seteuid, and with fuse allow_root, is the safest option. ## common-session open PAM_TYPE=open_session means that a user switch has happened without the need of a password. Examples: * Display manager autologin * (as root) `su - user` * Note, /etc/pam.d/sudo includes common-session-noninteractive, not common-session. This means it doesn't trigger `systemd --user`, it doesn't involve a seat etc. We probably shouldn't hook there, and document that `sudo -u user` doesn't sshfs-mount the home directory. ## sshfs-unmount Ideally, we can `fusermount -u /home/user` on PAM_TYPE=close_session if no user processes are still running. In this case pam_exec must be listed after pam_systemd, so that `systemd --user` has finished? ==> no, systemd --user is still running at that point; we'd need to delay 1 sec or exclude it, i.e. hacky code. And maybe for the non-ideal cases like mate-session bugs, an LTSP hook can pkill dbus-daemon etc, so that the pam related code is clean and constant. HMMM systemd is probably using cgroups to see when to mount/unmount /run/user/XXX let's try to bind to that, it appears very consistent! ## KillUserProcesses - HERE We want KillUserProcesses=yes because of various bugs in sessions etc. It's doing a great job at killing processes. It won't kill sshfs even if we run it with the user uid, as it's in a different scope (we wouldn't want sshfs to die before user processes get a chance to flush their file buffers). We can't run fusermount on PAM_TYPE=session_close as KillUserProcesses hasn't take effect yet. No, not even if we (sleep 5; fusermount) & first; somehow systemd manages to wait that (cgroups/scopes?) OOOOh wait, maybe on session_close we can run: systemd-run on-different-scope 'sleep 5; check if /run/user gone; fusermount -u` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ## Password caching When ssh authentication succeeds, I could store the hash to shadow!!!, so that if "offline" is detected, the user would authenticate locally. This would be useful for ...roaming/laptop ltsp clients with local disk, squashfs mirror locally, ... Nope. It would be better if it's a local account, not a :pamltsp: one. Use case: administrator = teacher can take the school laptop and work at home, yet he'd allow :ssh: student accounts to work via sshfs at school. We actually don't want caching there. ===> Well at least keep it in mind, so that it might be stored in pamltsp=base64-pass,cached=xxx ==> comma separated variables. ## To delete user settings, for benchmarking sshfs/nfs: find ~ -mindepth 1 -maxdepth 1 -name '.*' -exec rm -rf {} + sshfs first login: 105 sec?!! => gnome-keyring problem sshfs first logout: 30 sec, at_spi hang sshfs second login: 6 sec sshfs second logout: 1 sec (clear again) sshfs third login: 120 sec?!! sshfs third logout: 30 sec, at_spi hang -o kernel_cache => 114, meh nfs first login: 7 sec firefox: 6 sec ## pam_mount, autofs, ipsec Those don't sound very suitable; but here's a link for pam_mount and sshfs: https://sourceforge.net/p/fuse/mailman/message/32563925/ http://manpages.ubuntu.com/manpages/bionic/man8/pam_mount.8.html http://manpages.ubuntu.com/manpages/bionic/man5/pam_mount.conf.5.html ltsp-23.02/ltsp/client/login/pwmerge000077500000000000000000000442271437727275600175030ustar00rootroot00000000000000#!/usr/bin/python3 # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later """ Usage: pwmerge [--sur=] [--sgr=] [--dur=] [--dgr=] [-l] [-q] sdir ddir mdir Merge passwd, group and optionally shadow and gshadow from source directory sdir and destination directory ddir into a merge directory mdir. Options: --sur=regex: source user accounts to import --sgr=regex: source groups; their member users are also imported --dur=regex: destination user accounts to preserve --dgr=regex: destination groups; their member users are also preserved -l, --ltsp: activate the LTSP account merging mode; read below -q, --quiet: only show warnings and errors, not informational messages If UIDs or primary GIDs collide in the final merging, execution is aborted. Using a regex allows one to define e.g.: "^(?!administrator)(a|b|c).*", which matches accounts starting from a, b, or c, but not administrator. Group regexes may also match system groups if they are prefixed with ":", e.g. ":sudo" matches all sudoers. Btw, ".*" = match all, "" = match none. All regexes default to none; except if sgr = "", then sur defaults to all. Default mode: The default mode is useful for migrating the users of an older installation to a new one. All available [g]shadow information is preserved. Source [g]shadow must exist. Destination [g]shadow need to exist only if dur or dgr are specified. Example use, from the new installation: pwmerge /root/pwold/ /etc/ /root/pwmerged/ cp -a /root/pwmerged/* /etc/ LTSP mode: By specifying --ltsp, the LTSP mode is activated. This allows the merged installation to have some users authenticate against a remote SSH server, and optionally some others authenticate locally. * Source [g]shadow aren't processed at all. Password hashes for imported users are not stored; they'll authenticate against the SSH server. * Users are tagged for pamltsp logins by setting their pw_passwd to "pamltsp". The sysadmin may run `passwd user` to set a local password and untag them, or `usermod -p pamltsp user` to tag other users. * Local passwordless logins for remote users are supported by running `usermod -p pamltsp=$base64_password user`. * Guest passwordless logins without SSH authentication are supported by `usermod -p pamltsp= user`; /home/$user must be NFS or local, not SSHFS. * Destination [g]shadow are only processed if dur or dgr are specified. Those preserved users will authenticate locally. """ import getopt import os import re import shutil import sys # Names are from `man getpwent/getgrent/getspent/getsgent` PW_NAME = 0 PW_PASSWD = 1 PW_UID = 2 PW_GID = 3 PW_GECOS = 4 PW_DIR = 5 PW_SHELL = 6 # SP_NAMP is the same as PW_NAME so we ignore it SP_PWDP = 7 SP_LSTCHG = 8 SP_MIN = 9 SP_MAX = 10 SP_WARN = 11 SP_INACT = 12 SP_EXPIRE = 13 SP_FLAG = 14 # These additional fields are for internal use PW_GRNAME = 15 # The primary group name of the user PW_MARK = 16 # Boolean, True if the user is marked to import/preserve SP_DEFS = [ # Default values when source shadow doesn't exist (man 5 shadow) "!", # "!" is better than "*", as `useradd` and `passwd -l` use "!" "", # Disable password aging; it's managed on the server "", "", "", "", "", "", "", False] GR_NAME = 0 GR_PASSWD = 1 GR_GID = 2 GR_MEM = 3 # SG_NAMP is the same as GR_NAME so we ignore it SG_PASSWD = 4 # Currently we process SG_ADM (group administrator list) as a simple string; # file a bug report if you need it properly merged: SG_ADM = 5 # SG_MEM is the same as GR_MEM so we ignore it # Default values when source gshadow doesn't exist: SG_DEFS = ["*", "", ""] QUIET = False def log(*args, end='\n', error=False): """Print errors to stderr; print everything if --quiet wasn't specified""" if error or not QUIET: print(*args, end=end, file=sys.stderr) def create(name, mode): """For better security, create files directly with the correct mode""" # If the file already exists, delete it, otherwise it gets the old mode if os.path.lexists(name): os.remove(name) fds = os.open(name, os.O_WRONLY | os.O_CREAT, mode) return open(fds, "w") class PwMerge: """Merge passwd and group from source directory "sdir" to "ddir".""" def __init__(self, sdir, ddir, mdir, sur="", sgr="", dur="", dgr="", ltsp=False): self.spasswd, self.sgroup = self.read_dir(sdir, not ltsp) self.dpasswd, self.dgroup = self.read_dir(ddir, dur or dgr) self.mdir = mdir if not sur and not sgr: self.sur = ".*" else: self.sur = sur self.sgr = sgr self.dur = dur self.dgr = dgr self.ltsp = ltsp # Read [ug]id min/max from login.defs or default to 1000/60000 self.uid_min = 1000 self.uid_max = 60000 self.gid_min = 1000 self.gid_max = 60000 with open("/etc/login.defs", "r") as file: for line in file.readlines(): words = line.split() if not words: continue if words[0] == "UID_MIN": self.uid_min = int(words[1]) elif words[0] == "UID_MAX": self.uid_max = int(words[1]) elif words[0] == "GID_MIN": self.gid_min = int(words[1]) elif words[0] == "GID_MAX": self.gid_max = int(words[1]) @staticmethod def read_dir(xdir, read_shadow): """Read xdir/{passwd,group,shadow,gshadow} into dictionaries""" # passwd is a dictionary with keys=PW_NAME string, values=PW_ENTRY list passwd = {} with open("{}/passwd".format(xdir), "r") as file: for line in file.readlines(): pwe = line.strip().split(":") if len(pwe) != 7: raise ValueError("Invalid passwd line:\n{}".format(line)) # Add defaults in case shadow doesn't exist pwe += SP_DEFS # Convert uid/gid to ints to be able to do comparisons pwe[PW_UID] = int(pwe[PW_UID]) pwe[PW_GID] = int(pwe[PW_GID]) passwd[pwe[PW_NAME]] = pwe # g2n is a temporary dictionary to map from gid to group name # It's used to construct pwe[PW_GRNAME] and discarded after that g2n = {} # group is a dictionary with keys=GR_NAME string, values=GR_ENTRY list # Note that group["user"][GR_MEM] is a set of members (strings) group = {} with open("{}/group".format(xdir), "r") as file: for line in file.readlines(): gre = line.strip().split(":") if len(gre) != 4: raise ValueError("Invalid group line:\n{}".format(line)) # Add defaults in case gshadow doesn't exist gre += SG_DEFS gre[GR_GID] = int(gre[GR_GID]) # Use set for group members, to avoid duplicates # Keep only non-empty group values gre[GR_MEM] = set( [x for x in gre[GR_MEM].split(",") if x]) group[gre[GR_NAME]] = gre # Construct g2n g2n[gre[GR_GID]] = gre[GR_NAME] # Usually system groups are like: "saned:x:121:" # while user groups frequently are like: ltsp:x:1000:ltsp # For simplicity, explicitly mention the primary user for all groups # In the same iteration, set pwe[PW_GRNAME] for pwn, pwe in passwd.items(): grn = g2n[pwe[PW_GID]] pwe[PW_GRNAME] = grn if pwn not in group[grn][GR_MEM]: group[grn][GR_MEM].add(pwn) if read_shadow: with open("{}/shadow".format(xdir), "r") as file: for line in file.readlines(): pwe = line.strip().split(":") if len(pwe) != 9: # It's invalid; displaying it isn't a security issue raise ValueError( "Invalid shadow line:\n{}".format(line)) if pwe[0] in passwd: # List slice passwd[pwe[0]][SP_PWDP:SP_FLAG+1] = pwe[1:9] if read_shadow: with open("{}/gshadow".format(xdir), "r") as file: for line in file.readlines(): gre = line.strip().split(":") if len(gre) != 4: # It's invalid; displaying it isn't a security issue raise ValueError( "Invalid gshadow line:\n{}".format(line)) if gre[0] in group: # List slice group[gre[0]][SG_PASSWD:SG_ADM+1] = gre[1:3] return (passwd, group) def mark_users(self, xpasswd, xgroup, xur, xgr): """Mark users in [sd]passwd that match the [sd]ur/[sd]gr regexes. Called twice, once for source and once for destination.""" # Mark all users that match xgr if xgr: # grn = GRoup Name, gre = GRoup Entry for grn, gre in xgroup.items(): if not self.gid_min <= gre[GR_GID] <= self.gid_max: # Match ":sudo"; don't match "s.*" # grnm = modified GRoup Name used for Matching grnm = ":{}".format(grn) else: grnm = grn # re.fullmatch needs Python 3.4 (xenial+ /jessie+) if not re.fullmatch(xgr, grnm): continue for grm in gre[GR_MEM]: xpasswd[grm][PW_MARK] = True # Mark all users that match xur if xur: for pwn, pwe in xpasswd.items(): if not self.uid_min <= pwe[PW_UID] <= self.uid_max: continue if re.fullmatch(xur, pwn): xpasswd[pwn][PW_MARK] = True for pwn, pwe in xpasswd.items(): if pwe[PW_MARK]: log("", pwn, end="") log() def merge(self): """Merge while storing the result to dpasswd/dgroup""" # Mark all destination users that match dur/dgr # Note that non-system groups that do match dgr # are discarded in the end if they don't have any members log("Marked destination users for regexes '{}', '{}':".format( self.dur, self.dgr)) self.mark_users(self.dpasswd, self.dgroup, self.dur, self.dgr) # Remove the unmarked destination users log("Removed destination users:") for pwn in list(self.dpasswd): # list() as we're removing items pwe = self.dpasswd[pwn] if not self.uid_min <= pwe[PW_UID] <= self.uid_max: continue if not self.dpasswd[pwn][PW_MARK]: log("", pwn, end="") del self.dpasswd[pwn] log() # Remove the destination non-system groups that are empty, # to allow source groups with the same gid to be merged # Do not delete primary groups log("Removed destination groups:") for grn in list(self.dgroup): # list() as we're removing items gre = self.dgroup[grn] if not self.gid_min <= gre[GR_GID] <= self.gid_max: continue remove = True for grm in gre[GR_MEM]: if grm in self.dpasswd: remove = False break # A member exists; continue with the next group if remove: log("", grn, end="") del self.dgroup[grn] log() # Mark all source users that match sur/sgr log("Marked source users for regexes '{}', '{}':".format( self.sur, self.sgr)) self.mark_users(self.spasswd, self.sgroup, self.sur, self.sgr) # Transfer all the marked users and their primary groups # Collisions in this step are considered fatal errors log("Transferred users:") for pwn, pwe in self.spasswd.items(): if not pwe[PW_MARK]: continue if pwn in self.dpasswd: if pwe[PW_UID] != self.dpasswd[PW_UID] or \ pwe[PW_GID] != self.dpasswd[PW_GID]: raise ValueError( "PW_[UG]ID for {} exists in destination".format(pwn)) self.dpasswd[pwn] = pwe # In ltsp mode, mark the user for ssh logins if self.ltsp: self.dpasswd[pwn][SP_PWDP] = "pamltsp" grn = pwe[PW_GRNAME] if grn in self.dgroup: if pwe[PW_GID] != self.dgroup[grn][GR_GID]: raise ValueError( "GR_GID for {} exists in destination".format(grn)) self.dgroup[grn] = self.sgroup[grn] log("", pwn, end="") log() # Try to transfer all the additional groups that have marked members, # both system and non-system ones, and warn on collisions log("Transferred groups:") needeol = False for grn, gre in self.sgroup.items(): transfer = False for grm in gre[GR_MEM]: if grm in self.spasswd and self.spasswd[grm][PW_MARK]: transfer = True break # A member is marked; try to transfer if not transfer: continue if grn not in self.dgroup: self.dgroup[grn] = gre elif gre[GR_GID] == self.dgroup[grn][GR_GID]: # Same gids, just merge members without a warning self.dgroup[grn][GR_MEM].update(gre[GR_MEM]) else: self.dgroup[grn][GR_MEM].update(gre[GR_MEM]) log(" [WARNING: group {} has sgid={}, dgid={}; ". format(grn, gre[GR_GID], self.dgroup[grn][GR_GID]), end="", error=True) # If gids are different, keep sgid if dgid is not a system one if self.gid_min <= self.dgroup[grn][GR_GID] <= self.gid_max: self.dgroup[grn][GR_GID] = grn[GR_GID] log("keeping sgid]", end="", error=True) else: log("keeping dgid]", end="", error=True) needeol = True # In all cases, keep source group password self.dgroup[grn][GR_PASSWD] = gre[GR_PASSWD] log("", grn, end="") log(error=needeol) # g2n is a temporary dictionary to map from group name to primary user # Note that some gids (e.g. sudo) don't have a primary user g2n = {} for pwn, pwe in self.dpasswd.items(): g2n[pwe[PW_GRNAME]] = pwn # Remove all unknown members from destination groups, # remove primary users from groups, # and remove non-system groups that have no members umem = set() log("Removed unknown groups:") for grn in list(self.dgroup): # list() as we're removing items gre = self.dgroup[grn] for grm in list(gre[GR_MEM]): if grm in self.dpasswd and \ self.dpasswd[grm][PW_GID] != gre[GR_GID]: continue # Here it is either unknown or primary; remove it gre[GR_MEM].remove(grm) # Don't notify when removing primary users if not grm in self.dpasswd or \ self.dpasswd[grm][PW_GID] != gre[GR_GID]: umem.add(grm) if not gre[GR_MEM]: # Don't remove primary groups if the user exists if self.gid_min <= gre[GR_GID] <= self.gid_max and \ gre[GR_NAME] not in g2n: del self.dgroup[grn] log("", grn, end="") log() log("Removed unknown members:") for grm in umem: log("", grm, end="") log() def save(self): """Save the merged result in mdir/{passwd,group,shadow,gshadow}""" with create("{}/passwd".format(self.mdir), 0o644) as file: for pwe in self.dpasswd.values(): file.write("{}:{}:{}:{}:{}:{}:{}\n".format( *pwe[PW_NAME:PW_SHELL+1])) with create("{}/group".format(self.mdir), 0o644) as file: for gre in self.dgroup.values(): file.write("{}:{}:{}:{}\n".format( gre[GR_NAME], gre[GR_PASSWD], gre[GR_GID], ",".join(gre[GR_MEM]))) with create("{}/shadow".format(self.mdir), 0o640) as file: for pwe in self.dpasswd.values(): file.write("{}:{}:{}:{}:{}:{}:{}:{}:{}\n".format( pwe[PW_NAME], *pwe[SP_PWDP:SP_FLAG+1])) with create("{}/gshadow".format(self.mdir), 0o640) as file: for gre in self.dgroup.values(): file.write("{}:{}:{}:{}\n".format( gre[GR_NAME], # In python <3.5, unpacked arguments may only be last *(gre[SG_PASSWD:SG_ADM+1] + [",".join(gre[GR_MEM])]))) if os.geteuid() == 0: shutil.chown("{}/shadow".format(self.mdir), group="shadow") shutil.chown("{}/gshadow".format(self.mdir), group="shadow") else: log("Need root permissions to chown dst files to root:shadow", error=True) def main(argv): """Run the module from the command line""" global QUIET try: opts, args = getopt.getopt( argv[1:], "lq", ["ltsp", "quiet", "sur=", "sgr=", "dur=", "dgr="]) except getopt.GetoptError as err: print("Error in command line parameters:", err, file=sys.stderr) args = [] # Trigger line below if len(args) != 3: print(__doc__.strip()) sys.exit(1) dopts = {"ltsp": False} for key, val in opts: if key == "-q" or key == "--quiet": QUIET = True elif key == "-l" or key == "--ltsp": dopts["ltsp"] = True elif key.startswith("--"): dopts[key[2:]] = val else: raise ValueError("Unknown parameter: ", key, val) pwm = PwMerge(args[0], args[1], args[2], **dopts) pwm.merge() pwm.save() if __name__ == "__main__": main(sys.argv) ltsp-23.02/ltsp/client/login/ssh-askpass000077500000000000000000000024651437727275600202730ustar00rootroot00000000000000#!/bin/sh # This file is part of LTSP, https://ltsp.org # Copyright 2019-2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # SSH_ASKPASS implementation to transfer the passphrase to SSH/SSHFS. # Very (!) long story short: # `pam_exec expose_authtok` places the user password in stdin. # To allow passwordless logins, the shadow cache is preferred over stdin. # SSH won't read it from stdin, but supports an external SSH_ASKPASS program. # It only calls SSH_ASKPASS if DISPLAY is set though. # Also, if stdin is a tty, it prefers to use it; so in cmdline it needs: # echo pass | SSH_ASKPASS=ssh-askpass setsid ssh user@server cmd # Under PAM we're already in a pipe, and setsid isn't needed. # So we just need to echo the passphrase to stdout. # Take that, SSH screen scraping! :) main() { local _sp_namp sp_pwdp _dummy var_value pass IFS=: read -r _sp_namp sp_pwdp _dummy <"/run/ltsp/pam/$PAM_USER/shadow" IFS=, unset pass for var_value in $sp_pwdp; do case "$var_value" in pamltsp=*) pass=$(echo "${var_value#pamltsp=}" | base64 -d) ;; esac done test "$_OLDIFS" = "not set" && unset IFS || IFS="$_OLDIFS" # If pass wasn't set in shadow, read it from stdin test -n "$pass" || read -r pass echo "$pass" } _OLDIFS="$IFS" main "$@" ltsp-23.02/ltsp/client/remoteapps/000077500000000000000000000000001437727275600171455ustar00rootroot00000000000000ltsp-23.02/ltsp/client/remoteapps/55-remoteapps.sh000066400000000000000000000033221437727275600221070ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Run applications on the LTSP server via ssh -X remoteapps_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "r" -l \ "register" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -r | --register) REGISTER=1 ;; --) shift break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done run_main_functions "$_SCRIPTS" "$@" } remoteapps_main() { if [ "$REGISTER" = 1 ]; then remoteapps_register "$@" else remoteapps_run "$@" fi } remoteapps_authorized_keys() { local key var contents # The first public key will be copied in authorized_keys # If that's not appropriate, do it manually beforehand unset key for var in ~/.ssh/*.pub; do test -f "$var" || continue key=${key:-$var} contents=$(cat "$var") if grep -qsF "$contents" ~/.ssh/authorized_keys; then # We're good to go return 0 fi done if [ -z "$key" ]; then # No public key exists; create a new key re ssh-keygen -qf ~/.ssh/id_ed25519 -N '' -t ed25519 key=~/.ssh/id_ed25519.pub fi ( umask 0077 cat "$key" >>~/.ssh/authorized_keys ) } remoteapps_register() { local app for app; do re ln -sfn /usr/share/ltsp/client/remoteapps/ltsp-remoteapps "/usr/local/bin/$app" done } remoteapps_run() { re remoteapps_authorized_keys re ssh -X -oUserKnownHostsFile=/etc/ltsp/ssh_known_hosts server "$@" } ltsp-23.02/ltsp/client/remoteapps/ltsp-remoteapps000077500000000000000000000004701437727275600222330ustar00rootroot00000000000000#!/bin/sh # Copyright 2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # A wrapper for `ltsp remoteapps "$0" "$@"` # It serves as a symlink target, for example: # ln -s /usr/share/ltsp/client/remoteapps/ltsp-remoteapps /usr/local/bin/users-admin exec ltsp remoteapps "${0##*/}" "$@" ltsp-23.02/ltsp/client/session/000077500000000000000000000000001437727275600164515ustar00rootroot00000000000000ltsp-23.02/ltsp/client/session/55-session.sh000066400000000000000000000027771437727275600207340ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Override desktop sessions to prefix some LTSP code session_cmdline() { local msg fname msg="" for fname in ~/.config/ltsp/session/*; do test -f "$fname" || continue msg=$( . "$fname" && printf "%s\n * On %s since %s" \ "$msg" "$HOSTNAME" "${fname##*/}" ) done if [ -n "$msg" ]; then msg="$msg Multiple simultaneous logins are not supported. If you continue, all other sessions will be logged out, and any unsaved work may be lost. Would you like to continue?" "$_LTSP_DIR/common/ltsp/dialog" "$msg" "You are already logged in!" || exit 0 fi if [ ! -d ~/.config/ltsp/session ]; then re mkdir -pm 700 ~/.config re mkdir -p ~/.config/ltsp/session fi rm -f ~/.config/ltsp/session/* fname=~/.config/ltsp/session/"$(date +%Y%m%d-%H%M%S)" printf "HOSTNAME=%s\nIP_ADDRESS=%s\nMAC_ADDRESS=%s\n" \ "$(hostname)" "$IP_ADDRESS" "$MAC_ADDRESS" >"$fname" # Cleanup on either normal or abnormal termination exit_command "pkill -u '$(id -un)'" exit_command "rm -f '$fname'" # Run the session in the background "$@" & pid=$! # `tail --follow=name` works over SSHFS and locally, # but not over NFS with nolock. Use a sleep loop instead. while [ -d "/proc/$pid" ] && [ -f "$fname" ]; do sleep 1 done } ltsp-23.02/ltsp/common/000077500000000000000000000000001437727275600150005ustar00rootroot00000000000000ltsp-23.02/ltsp/common/info/000077500000000000000000000000001437727275600157335ustar00rootroot00000000000000ltsp-23.02/ltsp/common/info/55-info.sh000066400000000000000000000015711437727275600174550ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Display troubleshooting information about ltsp server and images info_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "" -l \ "" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in --) shift; break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done run_main_functions "$_SCRIPTS" "$@" } info_main() { printf "LTSP version: %s\n" "$_VERSION" printf "\nSERVER XSESSIONS: " rw ls /usr/share/xsessions printf "\nSERVER OS: " rw grep PRETTY_NAME /etc/os-release printf "\nCHROOTS:\n" list_img_names -c printf "\nVMs:\n" list_img_names -v printf "\nIMAGES:\n" list_img_names -i } ltsp-23.02/ltsp/common/initrd/000077500000000000000000000000001437727275600162715ustar00rootroot00000000000000ltsp-23.02/ltsp/common/initrd/35-initrd.sh000066400000000000000000000036441437727275600203520ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Create the additional LTSP initrd image at $TFTP_DIR/ltsp/ltsp.img # Vendors can add to $_DST_DIR between initrd_main and cpio_main initrd_cmdline() { local args _DST_DIR args=$(getopt -n "ltsp $_APPLET" -o "" -l \ "" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in --) shift ; break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done _DST_DIR=$(re mktemp -d) run_main_functions "$_SCRIPTS" "$@" } initrd_main() { # The /usr/share/ltsp and /etc/ltsp directories are copied to the # initrd, and later on to the ltsp client file system re mkdir -p "$_DST_DIR/usr/share/ltsp" re cp -a "$_LTSP_DIR/client" "$_LTSP_DIR/common" "$_LTSP_DIR/ltsp" \ "$_DST_DIR/usr/share/ltsp/" re mkdir -p "$_DST_DIR/conf/conf.d" # Busybox doesn't support ln -r re ln -s ../../usr/share/ltsp/client/initrd-bottom/initramfs-tools/ltsp-hook.conf \ "$_DST_DIR/conf/conf.d/00-ltsp.conf" re mkdir -p "$_DST_DIR/etc/ltsp" if [ -d /etc/ltsp ]; then re cp -a /etc/ltsp/. "$_DST_DIR/etc/ltsp/" fi # Copy server public ssh keys; prepend "server" to each entry test -f "$_DST_DIR/etc/ltsp/ssh_known_hosts" || rw sed "s/^/server /" /etc/ssh/ssh_host_*_key.pub > \ "$_DST_DIR/etc/ltsp/ssh_known_hosts" # Copy server passwd and group; allow overrides for LDAP etc test -f /etc/ltsp/passwd || re cp -a /etc/passwd "$_DST_DIR/etc/ltsp/" test -f /etc/ltsp/group || re cp -a /etc/group "$_DST_DIR/etc/ltsp/" # Copy epoptes keys; but provide for a future override if [ "$IGNORE_EPOPTES" != "1" ] && [ -f /etc/epoptes/server.crt ]; then re cp -a /etc/epoptes/server.crt "$_DST_DIR/etc/ltsp/epoptes.crt" fi } ltsp-23.02/ltsp/common/initrd/65-cpio.sh000066400000000000000000000020141437727275600200040ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # For best compatibility, ltsp.img should be an uncompressed cpio that is # loaded before initrd.img (issue #9). # If it grows to be too large, we can create ltsp.gzip inside the cpio, # and use an uncompressed script to gunzip it in initrd-top. cpio_main() { local script # Syntax check all the shell scripts while read -r script <&3; do sh -n "$script" || die "Syntax error in initrd script: $script" done 3< "$_DST_DIR/ltsp.img" # Avoid the awful "NNN blocks" message of cpio } 2>&1 | sed '/^[0-9]* blocks$/d' 1>&2 re cd - >/dev/null re mkdir -p "$TFTP_DIR/ltsp" re mv "$_DST_DIR/ltsp.img" "$TFTP_DIR/ltsp/" re rm -r "$_DST_DIR" echo "Generated ltsp.img:" re ls -l "$TFTP_DIR/ltsp/ltsp.img" } ltsp-23.02/ltsp/common/ltsp/000077500000000000000000000000001437727275600157625ustar00rootroot00000000000000ltsp-23.02/ltsp/common/ltsp/55-config.sh000066400000000000000000000251361437727275600200210ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Functions related to LTSP configuration and environment variables # Output the values of all the variables that match the expression. # A second "invert-match" expression is also supported. echo_values() { local ex1 ex2 var value ex1=$1 # ex2 defaults to an unmatchable expression ex2=${2:-.^} for var in $(echo_vars "$ex1" "$ex2"); do eval "value=\$$var" echo "$value" done } # Output the names of all the variables that match the expression. # A second "invert-match" expression is also supported. echo_vars() { local ex1 ex2 var value ex1=$1 # ex2 defaults to an unmatchable expression ex2=${2:-.^} while IFS="=" read -r var value; do eval "value=\$$var" test -n "$value" || continue echo "$var" # We want "[[:alnum:]_]*" to match only English letters, hence LC_ALL=C done < "$dst" echo "Installed $_APPLET_DIR/$sname$language$sext in ${dst#%.tmp}" return 0 done die "Template file $src not found." } kernel_vars() { # Exit if already evaluated test "$kernel_vars" = "1" && return 0 || kernel_vars=1 # Extreme scenario: ltsp.image="/path/to ltsp.vbox=1" # We don't want that to set VBOX=1. # Plan: replace spaces between quotes with \001, # then split the parameters using space, # then keep the ones that look like ltsp.var=value, # and finally restore the spaces. # TODO: should we add quotes when they don't exist? # Note that it'll be hard when var=value" with "quotes" inside "it eval " $(awk 'BEGIN { FS=""; } { s=$0 # source d="" # dest inq=0 # in quote split(s, chars, "") for (i=1; i <= length(s); i++) { if (inq && chars[i] == " ") d=d "\001" else { d=d "" chars[i] if (chars[i] == "\"") inq=!inq } } split(d, vars, " ") for (i=1; i in vars; i++) { gsub("\001", " ", vars[i]) if (tolower(vars[i]) ~ /^ltsp.[a-zA-Z][-a-zA-Z0-9_]*=/) { varvalue=substr(vars[i], 6) eq=index(varvalue,"=") var=toupper(substr(varvalue, 1, eq-1)) gsub("-", "_", var) value=substr(varvalue, eq+1) printf("%s=%s\n", var, value) } } } ' < /proc/cmdline)" } # We care about the IP/MAC used to connect to the LTSP server, not all of them # To handle multiple MACs in ltsp.conf, use INCLUDE= # Note that it's "GATEWAY to 192.168.67.1", not always the real gateway network_vars() { local ip _dummy test -n "$DEVICE" && test -n "$IP_ADDRESS" && test -n "$MAC_ADDRESS" && return 0 # 192.168.67.1 is for clients and servers != 192.168.67.1, # 192.168.67.2 is to get DEVICE (instead of lo) when server = 192.168.67.1 for ip in 192.168.67.1 192.168.67.2; do # Get the words around "dev" and "src"; possible output: # client1: 192.168.67.1 dev enp3s0 src 192.168.67.20 uid 0 \ cache # client2: 192.168.67.1 via 10.161.254.1 dev enp3s0 src 10.161.254.20 uid 0 \ cache # server1: 192.168.167.1 via 10.161.254.1 dev enp2s0 src 10.161.254.11 uid 0 \ cache # server2: local 192.168.67.1 dev lo src 192.168.67.1 uid 0 \ cache # server2: 192.168.67.2 dev enp5s0 src 192.168.67.1 uid 0 \ cache read -r GATEWAY _dummy DEVICE _dummy IP_ADDRESS<&2 # insmod is availabe in Debian initramfs but not in Ubuntu "$target/sbin/insmod" "$overlayko" && grep -q overlay /proc/filesystems && return 0 fi return 1 } # Process a series of mount sources to mount an image to dst, for example: # img1,mount-options1,,img2,mount-options2,,... # The following rules apply: # * If it's a directory, it's bind-mounted over $dst[/$subdir]. # * If it's a file, the (special) mount options along with autodetection # are used to loop mount it over $dst[/$subdir]. # The following special mount options are recognized at the start of options: # * partition=1|etc # * fstype=squashfs|iso9660|ext4|vfat|etc # * subdir=boot/efi (mount $img in $dst/$subdir) # The rest are passed as mount -o options (comma separated). # After all the commands have been processed, if /proc doesn't exist, # it's considered an error. # Examples for ltsp.ipxe: # set nfs_simple root=/dev/nfs nfsroot=${srv}:/srv/ltsp/${img} (no image required) # set nfs_squashfs root=/dev/nfs nfsroot=${srv}:/srv/ltsp/${img} ltsp.image=ltsp.img # set nfs_vbox root=/dev/nfs nfsroot=${srv}:/srv/ltsp/${img} ltsp.image=${img}-flat.vmdk # set nfs_ubuntu_iso root=/dev/nfs nfsroot=${srv}:/srv/ltsp/cd ltsp.image=ubuntu-mate-18.04.1-desktop-i386.iso,fstype=iso9660,loop,ro,,casper/filesystem.squashfs,fstype=squashfs,loop,ro # root=/dev/sda1 ltsp.image=/path/to/VMs/bionic-mate-flat.vmdk,partition=1 # Examples for ltsp image: # ltsp image -c /,,/boot/efi,subdir=boot/efi mount_img_src() { local img_src dst tmpfs options img partition fstype subdir var_value value first_time img_src=$1 dst=$2 tmpfs=$3 # Ensure $dst is a directory but not / dst=$(re readlink -f "$dst") test -d "${dst%/}" || die "Error in mount_img_src: $dst" # _LOCKROOT is needed for lock_package_management() unset _LOCKROOT first_time=1 while [ -n "$img_src" ]; do img_options=${img_src%%,,*} img_src=${img_src#$img_options} img_src=${img_src#,,} img=${img_options%%,*} options=${img_options#$img} options=${options#,} partition= fstype= subdir= while [ -n "$options" ]; do var_value=${options%%,*} value=${var_value#*=} case "$options" in fstype=*) fstype=$value ;; partition=*) partition=$value ;; subdir=*) subdir=$value ;; *) break ;; esac options=${options#$var_value} options=${options#,} done if [ "$first_time" = "1" ]; then # Allow the first img to be a simple img_name img_path=$(add_path_to_src "$img") unset first_time elif [ "${img#/}" = "$img" ]; then # Submounts may be absolute or relative to $dst img_path=$dst/$img else img_path=$img fi debug "img=$img img_path=$img_path options=$options partition=$partition fstype=$fstype subdir=$subdir img_src=$img_src " # Now img_path has enough path information if [ -d "$img_path" ]; then re omount "$img_path" "$dst/$subdir" "$tmpfs" -o "${options:-ro}" _LOCKROOT="${_LOCKROOT:-$img_path}" elif [ -e "$img_path" ]; then re mount_file "$img_path" "$dst/$subdir" "$tmpfs" "$options" "$fstype" "$partition" else # Warn, don't die, to allow test-mounting image sources warn "Image doesn't exist: $img_path" return 1 fi done } # Get the mount type of a device; may also return special types for convenience mount_type() { # result=$(mount_type "$src") means we're already in a subshell, # no need to worry about namespace pollution src=$1 vars=$(re blkid -po export "$src") # blkid outputs invalid characters in e.g. APPLICATION_ID=, grep it out eval "$(echo "$vars" | grep -E '^PART_ENTRY_TYPE=|^PTTYPE=|^TYPE=')" if [ -n "$PTTYPE" ] && [ -z "$TYPE" ]; then # "gpt" or "dos" (both for the main and the extended partition table) # .iso CDs also get "dos", but they also get TYPE=, which works echo "gpt" elif [ "$PART_ENTRY_TYPE" = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" ]; then # We ignore the efi partition; it doesn't contain root nor kernels echo "" elif [ "$TYPE" = "swap" ]; then # We ignore swap partitions too echo "" else echo "$TYPE" fi } # Try to loop mount a raw partition/disk file to dst mount_file() { local src dst tmpfs options fstype partition loopdev loopparts \ lohelp loparams src=$1 dst=$2 tmpfs=$3 options=$4 fstype=$5 partition=$6 re test -e "$src" re test -d "$dst" # See https://github.com/ltsp/ltsp/issues/112#issuecomment-579704835 test -d /sys/module/loop || re modprobe loop max_part=9 fstype=${fstype:-$(mount_type "$src")} if [ "$fstype" = "gpt" ]; then # A partition table unset fstype loopdev=$(re losetup -f) || die "" unset loparams lohelp=$(losetup --help 2>&1) echo "$lohelp" | grep -q '^[[:space:]]*-r' && loparams="${loparams}r" echo "$lohelp" | grep -q '^[[:space:]]*-P' && loparams="${loparams}P" echo "Running: losetup ${loparams:+"-$loparams "}$loopdev $src" re losetup "$loopdev" ${loparams:+"-$loparams"} "$src" exit_command "rw losetup -d '$loopdev'" loopparts="${loopdev}p${partition:-*}" elif [ -n "$fstype" ]; then # A filesystem (partition) unset loopparts else die "I don't know how to mount $src" fi for image in ${loopparts:-"$src"}; do # No need to run blkid again if it was a filesystem if [ -n "$loopparts" ]; then fstype=${fstype:-$(mount_type "$image")} fi case "$fstype" in "") continue ;; ext*) options=${options:-ro,noload} ;; *) options=${options:-ro} ;; esac re omount "$image" "$dst" "$tmpfs" -t "$fstype" ${options:+-o "$options"} return 0 done die "I don't know how to mount $src" } # Overlay src into dst, unless OVERLAY=0. # Create a tmpfs on the first call. Create appropriate subdirs there to use # for up/work dirs; don't use overlayfs subdirs like ltsp-update-image did, # for efficiency reasons. omount() { local src dst tmpfs i tmpdst src=$1 dst=$2 tmpfs=$3 # The rest are parameters to mount shift 3 re test -e "$src" re test -d "$dst" if [ "$OVERLAY" = "0" ]; then # Support running LTSP inside discardable containers if [ -d "$src" ]; then re vmount --bind "$src" "$dst" else re vmount "$@" "$src" "$dst" fi return 0 fi if [ ! -d "$tmpfs" ] || [ "$(stat -fc %T "$tmpfs")" != "tmpfs" ]; then re mkdir -p "$tmpfs" re vmount -t tmpfs -o mode=0755 tmpfs "$tmpfs" fi i=0 while [ -d "$tmpfs/$i" ]; do i=$((i+1)) done tmpdst=$tmpfs/$i re mkdir -p "$tmpdst/up" "$tmpdst/work" # No need for `exit_command rm ...` on tmpfs if [ ! -d "$src" ]; then re mkdir -p "$tmpdst/looproot" re vmount "$@" "$src" "$tmpdst/looproot" src="$tmpdst/looproot" fi # Autodetect live CDs, after all the mounts but before needing overlay if [ "$_APPLET" = "initrd-bottom" ] && [ ! -d "$src/proc" ]; then for i in "$src/casper/filesystem.squashfs" \ "$src/live/filesystem.squashfs" do if [ -f "$i" ]; then echo "Autodetected live CD image: $i" re vmount -t squashfs -o ro "$i" "$src" re set_readahead "$src" break fi done fi if ! grep -q overlay /proc/filesystems; then re modprobe_overlay "$src" grep -q overlay /proc/filesystems || die "Could not modprobe overlay" fi re vmount -t overlay -o "upperdir=$tmpdst/up,lowerdir=$src,workdir=$tmpdst/work" "$tmpfs" "$dst" } # Most file systems use 128 KB readahead. NFS had a bug and used 15 MB. # In LTSP, we want all network file systems to use only 4 KB readahead, # as it lowers the network traffic to about half. # I.e. NFS, NBD, SSHFS, and loop devices. # https://github.com/ltsp/ltsp/issues/27#issuecomment-533976774 set_readahead() { local mpoint devs dev rakf rak mpoint=$1 if [ -n "$mpoint" ]; then devs=$(rw awk '$5 =="'"$mpoint"'" { print $3 }' "$rakf" break done done } # Be verbose, mount, and call exit_command if --no-exit wasn't passed. # Destination must be the last parameter. vmount() { local dst no_exit if [ "$1" = "--no-exit" ]; then no_exit=1 shift else unset no_exit fi echo "Running: mount $*" re mount "$@" # Set dst to the last argument for dst; do true; done test "$no_exit" = "1" || exit_command "rw umount $dst" } ltsp-23.02/ltsp/common/ltsp/55-ltsp.sh000066400000000000000000000075431437727275600175400ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Handle the pre-applet part of the ltsp command line # @LTSP.CONF: BASE_DIR TFTP_DIR HOME_DIR PRE_APPLET_x POST_APPLET_x # Constant variables may be set in any of the following steps: # 1) user: environment, `VAR=value ltsp ...` # 2) distro: 11-ltsp-distro.sh for all applets # 3) upstream: 55-ltsp.sh for all applets # 4) user: /etc/ltsp/ltsp.conf for all applets # 5) distro: 11-$_APPLET-distro.sh for a specific applet # 6) upstream: 55-$_APPLET-distro.sh for a specific applet # For proper ordering, upstream and distros should use `VAR=${VAR:-value}`. # We're still in the "sourcing" phase, so subsequent scripts may even use the # variables before the "execution" phase. # 7) user: cmdline `ltsp ... --VAR=value`, evaluated at the execution phase # Btw, to see all constants: grep -rIwoh '$[A-Z][_A-Z0-9]*' | sort -u BASE_DIR=${BASE_DIR:-/srv/ltsp} TFTP_DIR=${TFTP_DIR:-/srv/tftp} HOME_DIR=${HOME_DIR:-/home} ltsp_cmdline() { local show_help help_param base_dir home_dir overwrite tftp_dir args show_help=0 help_param= base_dir= home_dir= overwrite= tftp_dir= # No getopt in the initramfs; avoid it if $1 isn't an option if [ "${1#-}" != "$1" ]; then args=$(getopt -n "ltsp" -o "+b:h::m:o::t:V" -l \ "base-dir:,help::,home-dir:,overwrite::,tftp-dir:,version" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -b | --base-dir) shift BASE_DIR=$1 base_dir=$1 ;; -h | --help) shift help_param=$1 show_help=1 ;; -m | --home-dir) shift HOME_DIR=$1 home_dir=$1 ;; -o | --overwrite) shift OVERWRITE=${1:-1} overwrite=${1:-1} ;; -t | --tftp-dir) shift TFTP_DIR=$1 tftp_dir=$1 ;; -V | --version) version exit 0 ;; --) shift break ;; *) die "ltsp: error in cmdline: $*" ;; esac shift done fi # Support `ltsp --help`, `ltsp --help=applet` and `ltsp --help applet` if [ "$show_help" = "1" ]; then if [ -n "$help_param" ] || [ -n "$1" ]; then _APPLET=${help_param:-$1} fi usage 0 elif [ -z "$1" ]; then # Plain `ltsp` shows usage and exits with error usage 1 fi # "$@" is the applet parameters; don't use it for the ltsp main functions re run_main_functions "$_SCRIPTS" _APPLET="$1" shift # ltsp.conf is evaluated on every `ltsp applet` call; it needs network_vars re network_vars if [ -r /etc/ltsp/ltsp.conf ]; then re eval_ini /etc/ltsp/ltsp.conf fi # Mainly for `login` and `session` applets if [ -r "/etc/ltsp/ltsp-$_APPLET.conf" ]; then re eval_ini "/etc/ltsp/ltsp-$_APPLET.conf" fi # Command line arguments take precedence over ltsp.conf; restore them BASE_DIR=${base_dir:-$BASE_DIR} HOME_DIR=${home_dir:-$HOME_DIR} OVERWRITE=${overwrite:-$OVERWRITE} TFTP_DIR=${tftp_dir:-$TFTP_DIR} # We could put the rest of the code below in an ltsp_main() function, # but we want ltsp/scriptname_main()s to finish before any applet starts re locate_applet_scripts "$_APPLET" # Remember, locate_applet_scripts has just updated $_SCRIPTS re source_scripts "$_SCRIPTS" re omit_functions re run_parameters "PRE" re "$_APPLET_FUNCTION" "$@" re run_parameters "POST" } ltsp-23.02/ltsp/common/ltsp/dialog000077500000000000000000000021611437727275600171470ustar00rootroot00000000000000#!/usr/bin/python3 # This file is part of LTSP, https://ltsp.org # Copyright 2021 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later """ Display a question dialog. Return 0 if [OK] was clicked. """ import gi import os import sys gi.require_version("Gtk", "3.0") from gi.repository import Gtk def message_dialog(text, title): dialog = Gtk.MessageDialog( transient_for=None, flags=0, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.YES_NO, text=title, ) dialog.format_secondary_markup(text) dialog.set_position(Gtk.WindowPosition.CENTER) response = dialog.run() dialog.destroy() return response if __name__ == '__main__': if len(sys.argv) <= 1 or len(sys.argv) > 3: print("Usage: {} text [title]".format( os.path.basename(__file__), file=sys.stderr)) exit(1) text = sys.argv[1] if len(sys.argv) > 2 and sys.argv[2]: title = sys.argv[2] else: title = "" # exit(False) => 0 => considered as "true" in shell exit(message_dialog(text, title) != Gtk.ResponseType.YES) ltsp-23.02/ltsp/common/ltsp/ltsp.conf000066400000000000000000000030321437727275600176110ustar00rootroot00000000000000# /bin/sh -n # LTSP configuration file # Documentation=man:ltsp.conf(5) # The special [server] section is evaluated only by the ltsp server [server] # Enable NAT on dual NIC servers # NAT=1 # Provide a full menu name for x86_32.img when `ltsp ipxe` runs # IPXE_X86_32_IMG="Debian Bullseye" # The special [common] section is evaluated by both the server and ltsp clients [common] # Specify an alternative TFTP_DIR # TFTP_DIR=/var/lib/tftpboot # In the special [clients] section, parameters for all clients can be defined. # Most ltsp.conf parameters should be placed here. [clients] # Specify an /etc/fstab line for NFS home; note this is insecure # FSTAB_HOME="server:/home /home nfs defaults,nolock 0 0" # MAC address, IP address, or hostname sections can be used to apply settings # to specific clients. [61:6c:6b:69:73:67] # HOSTNAME=pc01 # Include parameters from another section, defined below # INCLUDE=crt_monitor # Shell "case" expressions can be used in MAC, IP, or hostname sections. # This matches all Raspberry Pi MAC addresses. [b8:27:eb:*|dc:a6:32:*|e4:5f:01:*] # FSTAB_BOOT="/dev/mmcblk0p1 /boot vfat defaults 0 2" # If you have proper DNS, you can use hostname sections [administrator-pc] # Only allow the administrator to log in to this client # PWMERGE_SUR="administrator" # You can also group parameters into named sections and INCLUDE= them elsewhere [crt_monitor] # Force EDID and resolution to 1024x768 for clients with old CRT monitors X_HORIZSYNC="28.0-87.0" X_VERTREFRESH="43.0-87.0" X_MODES='"1024x768" "800x600" "640x480"' ltsp-23.02/ltsp/common/service/000077500000000000000000000000001437727275600164405ustar00rootroot00000000000000ltsp-23.02/ltsp/common/service/55-service.sh000066400000000000000000000067211437727275600206710ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Run various tasks on network-online.target service_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "s" -l \ "stop" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -s|--stop) STOP=1 ;; --) shift; break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done run_main_functions "$_SCRIPTS" "$@" } service_main() { disable_flow_control enable_nat set_readahead } # We want to disable flow control in all interfaces, as all of them might # serve LTSP clients. disable_flow_control() { local iface output msg neg capabilities for iface in /sys/class/net/*/device; do test -e "$iface" || continue iface=${iface%/device} iface=${iface##*/} msg="Couldn't disable flow control for $iface" # If this interface supports ethtool if is_command ethtool && output=$(ethtool --show-pause "$iface") then # If flow control is enabled if echo "$output" | grep -q '^RX:.*on'; then # Some NICs like Intel [8086:10d3] require "autoneg off rx off", # while other like Marvel [11ab:4320] require "autoneg on rx off". # So we actually need to call ethtool again to check if it worked. msg="Failed to disable flow control for $iface using ethtool" for neg in off on; do ethtool --pause "$iface" autoneg "$neg" rx off || true if ethtool --show-pause "$iface" | grep -q '^RX:.*off'; then msg="Disabled flow control (autoneg $neg, rx off) for $iface using ethtool" break fi done else msg="Flow control was already disabled for $iface using ethtool" fi # If this interface supports mii-tool elif is_command mii-tool && output=$(mii-tool -v "$iface") then # If flow control is enabled if echo "$output" | grep -q 'advertising:.*flow-control'; then capabilities=$(echo "$output" | sed -n 's/.*capabilities: *//p') if mii-tool -A "$capabilities" "$iface"; then msg="Disabled flow control for $iface using mii-tool" else msg="Failed to disable flow control for $iface using mii-tool" fi else msg="Flow control was already disabled for $iface using mii-tool" fi fi >/dev/null 2>&1 warn "$msg" done } enable_nat() { local ipv4_forward # Only enable NAT on servers, if NAT=1 or IP_ADDRESS=192.168.67.1 if [ -d /run/ltsp/client ] || [ "$NAT" = "0" ] || { [ "$NAT" != "1" ] && [ "$IP_ADDRESS" != "192.168.67.1" ]; }; then return 0 fi # For now, just check if ip forwarding was already enabled; # TODO: in the future, introduce persistent_vars read -r ipv4_forward /proc/sys/net/ipv4/ip_forward rw iptables -s 192.168.67.0/24 -t nat -A POSTROUTING -j MASQUERADE warn "Enabled IP forwarding/masquerading for 192.168.67.0/24" } ltsp-23.02/ltsp/common/service/ltsp.service000066400000000000000000000007061437727275600210070ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later [Unit] Description=LTSP startup tasks Documentation=man:ltsp-service(8) After=network-online.target home.mount Wants=network-online.target # Parts copied from rc-local.service [Service] Type=forking ExecStart=/usr/sbin/ltsp service TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no [Install] WantedBy=multi-user.target ltsp-23.02/ltsp/ltsp000077500000000000000000000303761437727275600144310ustar00rootroot00000000000000#!/bin/sh # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Main entry point for all ltsp applets; also contains the essential functions # @LTSP.CONF: LTSP_DEBUG DEBUG_SHELL DEBUG_LOG # Internal global variables are uppercased and begin with underscores # Distributions should replace "1.0" below at build time using `sed` _VERSION="1.0" _NL=" " # Execution sequence: # This main() > source common/ltsp/* (and config/vendor overrides) # > ltsp_cmdline() > ltsp/scriptname_main()s > source applet/* (and overrides) # > applet_cmdline() > applet/scriptname_main()s main() { # Try to stop on unhandled errors, http://fvue.nl/wiki/Bash:_Error_handling # Prefer `false || false` as it exits while `false && false` doesn't, # except if it's the last command of a function (return value). set -e re busybox_fallbacks # Usually this script is called from a symlink like /usr/sbin/ltsp, # so $0 points to the ltsp source directory. If that's not the case, # assume we're sourced, and search for the source in /usr/share/ltsp. _LTSP_DIR=$(re readlink -f "$(command -v "$0")") _LTSP_DIR=${_LTSP_DIR%/*} if [ -x "$_LTSP_DIR/ltsp" ] && [ -d "$_LTSP_DIR/common" ]; then unset _SOURCED # Default to a sane umask for all files generated by applets umask 0022 # Default to a sane LANG; e.g. python doesn't like unset/C test "${LANG:-C}" != "C" || export LANG=C.UTF-8 else for _LTSP_DIR in "${LTSP_DIR:-/usr/share/ltsp}" "$(re pwd)"; do if [ -x "$_LTSP_DIR/ltsp" ] && [ -d "$_LTSP_DIR/common" ]; then _SOURCED=1 debug "$_LTSP_DIR/ltsp sourced by $0" # Ignore the caller command line parameters; they're not for us set -- break fi done test "$_SOURCED" = "1" || die "Could not locate the ltsp directory" fi locate_applet_scripts "ltsp" source_scripts "$_SCRIPTS" test "$_SOURCED" = "1" || ltsp_cmdline "$@" } # On abnormal termination, we run both the term and exit commands. # On normal termination, we only run the exit commands. # For example, in initrd-bottom we don't want to unmount on normal exit. at_exit() { # Don't stop on errors for the exit commands set +e # Stop trapping trap - 0 HUP INT QUIT SEGV PIPE TERM if [ "$1" = "-TERM" ] || [ "$_DIED" = "1" ]; then eval "$_TERM_COMMANDS" fi eval "$_EXIT_COMMANDS" # It's possible to manually call at_exit, run the commands, # then call exit_command again (e.g. for `ltsp kernel img1 img2`). unset _TERM_COMMANDS unset _EXIT_COMMANDS unset _HAVE_TRAP set -e } # For the external tools we need that are also provided by busybox, # if some tool doesn't exist, create a namesake function that calls busybox. # `/usr/lib/initramfs-tools/bin/busybox` shows the smallest list of tools. busybox_fallbacks() { local busybox tool busybox=$(command -v busybox) # Ubuntu chroots ship with a "busybox-initramfs" minimal package if [ -z "$busybox" ] && [ -x /usr/lib/initramfs-tools/bin/busybox ]; then busybox=/usr/lib/initramfs-tools/bin/busybox fi if [ -z "$busybox" ]; then warn "Busybox not found?!" return 0 fi for tool in awk blockdev cat chgrp chmod chown chroot chvt cp \ cpio cut date df env expr find getopt grep head hostname id \ insmod ionice ip kill killall ln logger losetup ls lsmod \ mkdir mktemp modprobe mount mv nc netstat pidof ping \ poweroff ps pwd readlink rm rmdir rmmod sed setsid sleep sort \ swapoff swapon switch_root sync tee touch tr truncate umount \ uname wc do # Periodically, prefix a "true" to the following line and test all # applets to see if we are indeed compatible with the busybox syntax ! is_command "$tool" || continue eval "$tool() { $busybox $tool \"\$@\" }" done } # Show a message to stderr and /run/ltsp/debug.log if $LTSP_DEBUG is # appropriately set debug() { case ",$LTSP_DEBUG," in *",$_APPLET,"*|,1,) ;; *) return 0; esac warn "$@" } # The debug shell is used to examine the internals of the ltsp scripts; # to get or set variables; so, use `eval`, not `bash`. debug_shell() { local cmd if [ "$$" != "$(sh -c 'echo $PPID')" ]; then warn "Not activating debug_shell while inside a subshell" exit 1 fi echo "${*:-"Emulating a shell, type \`return [0|1]\` to continue|exit:"}" printf "debug:%s\$ " "$(pwd)" while read -r cmd; do eval "$cmd" || true printf "debug:%s\$ " "$(pwd)" done } # Print a message to stderr and exit with an error code. # No need to pass a message if the failed command displays the error. die() { test $# -eq 0 && set "Aborting ltsp" # Debian defaults to SPLASH="true" and only disables it when # nosplash*|plymouth.enable=0 is passed in the cmdline if [ "$_APPLET" = "initrd-bottom" ] || [ "$_APPLET" = "init" ]; then if is_command plymouth && pidof plymouthd >/dev/null; then warn "Stopping plymouth" rw plymouth quit fi warn "$@" if [ "$DEBUG_SHELL" != "1" ]; then warn "LTSP boot error! Enable DEBUG_SHELL to troubleshoot!" while sleep 1000; do true done return 0 fi else warn "$@" fi test "$DEBUG_SHELL" = "1" && debug_shell # This notifies at_exit() to execute TERM_COMMANDS _DIED=1 # If called from subshells, this just exits the subshell # With `set -e` though, it'll still exit on commands like x=$(false) exit 1 } # POSIX recommends that printf is preferred over echo. # But do offer a simple wrapper to avoid "%s\n" all the time. echo() { printf "%s\n" "$*" } # You may use `exit_command "rw command"`, but not `exit_command "re command"` exit_command() { if [ "$_HAVE_TRAP" != "1" ]; then _HAVE_TRAP=1 trap "at_exit -TERM" HUP INT QUIT SEGV PIPE TERM trap "at_exit -EXIT" EXIT fi if [ "$_APPLET" = "initrd-bottom" ] || [ "$_APPLET" = "init" ]; then _TERM_COMMANDS="$* $_TERM_COMMANDS" else _EXIT_COMMANDS="$* $_EXIT_COMMANDS" fi } # Check if parameter is a command; `command -v` isn't allowed by POSIX is_command() { local fun if [ -z "$is_command" ]; then command -v is_command >/dev/null || die "Your shell doesn't support command -v" is_command=1 fi for fun in "$@"; do command -v "$fun" >/dev/null || return $? done } # Set _APPLET, _APPLET_DIR, _APPLET_FUNCTION and _SCRIPTS locate_applet_scripts() { local sub_dir script _APPLET=$1 shift for sub_dir in common server client; do _APPLET_DIR="$_LTSP_DIR/$sub_dir/$_APPLET" test -d "$_APPLET_DIR" && break done test -d "$_APPLET_DIR" || die "Could not locate LTSP applet: $_APPLET" if [ "$sub_dir" = client ] && [ ! -d /run/ltsp/client ] && [ "$$" != "1" ] then die "Refusing to run client applet $_APPLET in non-ltsp client" fi # All applets are required to have an entry function ${_APPLET}_cmdline _APPLET_FUNCTION=$(echo "${_APPLET}_cmdline" | sed 's/[^[:alnum:]]/_/g') # https://www.freedesktop.org/software/systemd/man/systemd.unit.html # Drop-in files in /etc take precedence over those in /run # which in turn take precedence over those in /usr. _SCRIPTS=$(run_parts_list "$_APPLET_DIR" \ "/run/ltsp/$sub_dir/$_APPLET" "/etc/ltsp/$sub_dir/$_APPLET") } # Run a command. Exit if it failed. re() { rwr "$@" || die } # Run a command and return 0. Silently. rs() { local _RWR_SILENCE # If _RWR_SILENCE isn't declared local, it might remain in the environment! _RWR_SILENCE=1 rwr "$@" || true } # Run a command silently and return $?. Used like `rsr cmd1 || cmd2`. # This is just a shortcut for `cmd1 >/dev/null 2>&1 || cmd2`. rsr() { local _RWR_SILENCE _RWR_SILENCE=1 rwr "$@" || return $? } # Run all the main_script() functions we already sourced run_main_functions() { local scripts script scripts=$1; shift # 55-initrd.sh should be called as: initrd_main # <&3 is to allow scripts to use stdin instead of using the HEREDOC while read -r script <&3; do is_command "${script}_main" || continue case ",$LTSP_SKIP_SCRIPTS," in *",$script,"*) debug "Skipping main of script: $script" ;; *) debug "Running main of script: $script" "${script}_main" "$@" ;; esac done 3</dev/null 2>&1 || got=$? else "$@" || got=$? fi # Failed if either of them is zero and the other non-zero # Use {} to avoid subshells and shellcheck's SC2166 if { [ "$want" = 0 ] && [ "$got" != 0 ]; } || { [ "$want" != 0 ] && [ "$got" = 0 ]; } then test "$_RWR_SILENCE" = "1" || warn "LTSP command failed: $*" fi return $got } source_scripts() { local scripts script scripts=$1 test -n "$scripts" || die "ltsp $_APPLET contains no scripts!" while read -r script <&3; do debug "Sourcing: $script" . "$script" done 3<&2 if [ -z "$DEBUG_LOG" ]; then DEBUG_LOG=0 # Only keep the last `ltsp command` log for non-ltsp boots test -d /run/ltsp/client || rs rm -f /run/ltsp/debug.log test -w /run && mkdir -p /run/ltsp && ( umask 0077 && touch /run/ltsp/debug.log ) && DEBUG_LOG=1 fi if [ "$DEBUG_LOG" = "1" ]; then echo "$(date +%H:%M:%S.%N) $*" >>/run/ltsp/debug.log fi } main "$@" ltsp-23.02/ltsp/server/000077500000000000000000000000001437727275600150165ustar00rootroot00000000000000ltsp-23.02/ltsp/server/dnsmasq/000077500000000000000000000000001437727275600164645ustar00rootroot00000000000000ltsp-23.02/ltsp/server/dnsmasq/55-dnsmasq.sh000066400000000000000000000120171437727275600207160ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Configure dnsmasq for LTSP DNS=${DNS:-0} PROXY_DHCP=${PROXY_DHCP:-1} REAL_DHCP=${REAL_DHCP:-1} TFTP=${TFTP:-1} dnsmasq_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "d:p:r:s:t:" -l \ "dns:,proxy-dhcp:,real-dhcp:,dns-server:,tftp:" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -d|--dns) shift; DNS=$1 ;; -p|--proxy-dhcp) shift; PROXY_DHCP=$1 ;; -r|--real-dhcp) shift; REAL_DHCP=$1 ;; -s|--dns-server) shift; DNS_SERVER=$1 ;; # Note that this is fine: ltsp -t... dnsmasq -t... -t|--tftp) shift; TFTP=$1 ;; --) shift; break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done run_main_functions "$_SCRIPTS" "$@" } dnsmasq_main() { test -f /etc/dnsmasq.d/ltsp-server-dnsmasq.conf && die "Found LTSP5 configuration: /etc/dnsmasq.d/ltsp-server-dnsmasq.conf Aborting, please remove the LTSP5 configuration first" mkdir -p "$TFTP_DIR" install_template "ltsp-dnsmasq.conf" "/etc/dnsmasq.d/ltsp-dnsmasq.conf" "\ s|^port=0|$(textifb "$DNS" "#&" "&")| s|^dhcp-range=set:proxy.*|$(textifb "$PROXY_DHCP" "$(proxy_dhcp)" "#&")| s|^dhcp-range=192.168.67.20.*|$(textifb "$REAL_DHCP" "&" "#&")| s|^\(dhcp-option=option:dns-server,\).*|\1$(dns_server)| s|^\(tftp-root=\).*|\1$TFTP_DIR| s|^enable-tftp|$(textifb "$TFTP" "&" "#&")| " restart_dnsmasq } dns_server() { local dns_server resolvectl if [ -n "$DNS_SERVER" ]; then echo "$DNS_SERVER" | tr " " "," return 0 fi dns_server= if is_command resolvectl; then resolvectl="resolvectl status" elif is_command systemd-resolve; then resolvectl="systemd-resolve --status" else resolvectl="" fi if [ -n "$resolvectl" ]; then dns_server=$(LANG=C.UTF-8 rw $resolvectl | sed -n '/DNS Servers:/,/:/s/.* \([0-9.]\{7,15\}\).*/\1/p' | grep -v '^127.0.' | tr '\n' ',') fi if [ -z "$dns_server" ]; then dns_server=$(rw awk '/^ *nameserver [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { if ($2 !~ "127\\.0\\..*" ) printf "%s,",$2 }' /etc/resolv.conf) fi dns_server=${dns_server%,} dns_server=${dns_server:-8.8.8.8,208.67.222.222} test "$DNS" = "1" && dns_server="0.0.0.0,$dns_server" echo "$dns_server" } length_to_netmask() { local nm nm=$((0xffffffff ^ ((1 << (32 - $1)) - 1))) printf "%d.%d.%d.%d\n" "$(((nm >> 24) & 0xff))" \ "$(((nm >> 16) & 0xff))" "$(((nm >> 8) & 0xff))" "$((nm & 0xff))" } proxy_dhcp() { local cidr _dummy subnet netmask separator ip route show | while read -r cidr _dummy; do subnet=${cidr%%/*} case "$subnet" in 127.0.0.1|169.254.0.0|192.168.67.0|*[!0-9.]*) continue ;; *) # Ignore single IP routes, like vbox NAT gateway test "$cidr" != "${cidr#*/}" || continue netmask=$(length_to_netmask "${cidr#*/}") # echo in dash translates "\n", use printf to keep it printf "%sdhcp-range=set:proxy,%s,proxy,%s" \ "${separator}" "$subnet" "$netmask" # Insert a separator only after the first line separator="\n" ;; esac done } restart_dnsmasq() { if [ "$DNS" = "1" ]; then # If systemd-resolved is running, disable it if grep -qws '3500007F:0035' /proc/net/tcp; then re mkdir -p /etc/systemd/resolved.conf.d re cat >/etc/systemd/resolved.conf.d/ltsp.conf </dev/null; then IONICE="$IONICE ionice -c3" fi fi re mkdir -pm 0700 "$BASE_DIR/images" ie=$(image_excludes) # image_excludes can't call exit_command because of the subshell test "${ie%.tmp}" != "$ie" && exit_command "rw rm '$ie'" # -regex might be nicer: https://stackoverflow.com/questions/57304278 re $IONICE mksquashfs "$_COW_DIR" "$BASE_DIR/images/$_IMG_NAME.img.tmp" \ -noappend -wildcards -ef "$ie" $MKSQUASHFS_PARAMS if [ "$BACKUP" != 0 ] && [ -f "$BASE_DIR/images/$_IMG_NAME.img" ]; then re mv "$BASE_DIR/images/$_IMG_NAME.img" "$BASE_DIR/images/$_IMG_NAME.img.old" fi re mv "$BASE_DIR/images/$_IMG_NAME.img.tmp" "$BASE_DIR/images/$_IMG_NAME.img" # Unmount everything and continue with the next image rw at_exit -EXIT echo "Running: ltsp kernel $BASE_DIR/images/$_IMG_NAME.img" re "$0" kernel ${KERNEL_INITRD:+-k "$KERNEL_INITRD"} "$BASE_DIR/images/$_IMG_NAME.img" } # Handle ADD_IMAGE_EXCLUDES and OMIT_IMAGE_EXCLUDES image_excludes() { local src dst inp out if [ -f /etc/ltsp/image.excludes ]; then src=/etc/ltsp/image.excludes else src="$_APPLET_DIR/image.excludes" fi if [ -z "$ADD_IMAGE_EXCLUDES$OMIT_IMAGE_EXCLUDES" ]; then echo "$src" return 0 fi dst=$(re readlink -f "$_COW_DIR/../image.excludes.tmp") # comm requires all input to be sorted in the current locale re sort "$src" > "$dst" if [ -f "$OMIT_IMAGE_EXCLUDES" ]; then inp=$(re sort "$OMIT_IMAGE_EXCLUDES") else inp=$(echo "$OMIT_IMAGE_EXCLUDES" | re sort) fi out=$(echo "$inp" | re comm - "$dst" -13) { if [ -f "$ADD_IMAGE_EXCLUDES" ]; then cat "$ADD_IMAGE_EXCLUDES" else echo "$ADD_IMAGE_EXCLUDES" fi echo "$out" } | grep -v '^#' | re sort -u > "$dst" echo "$dst" } ltsp-23.02/ltsp/server/image/image.excludes000066400000000000000000000016721437727275600207260ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2022 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # To customize, see ADD_IMAGE_EXCLUDES and OMIT_IMAGE_EXCLUDES in ltsp.conf(5) cdrom/.* cdrom/* dev/* etc/epoptes/server.key etc/.!(java) etc/mysql/debian.cnf etc/NetworkManager/system-connections/* etc/ssh/ssh_host_* etc/udev/rules.d/??-persistent-*.rules home/* lost+found/* media/* mnt/* opt/ltsp* proc/* root/.* root/* run/* srv/* swapfile sys/* tmp/.* tmp/* var/backups/* var/cache/apt/archives/*.deb var/cache/apt/archives/partial/* var/cache/apt-xapian-index/* var/cache/lightdm/dmrc/* var/cache/squid-deb-proxy/* var/crash/* var/lib/apt/lists/* var/lib/lightdm/.* var/lib/lightdm/* var/lib/mysql/* var/lib/sudo/* var/lib/systemd/linger/* var/log/*.[1-9] var/log/*.gz var/log/*.old var/log/*/* var/mail/* var/spool/cron/*/* var/spool/cups/* var/spool/squid/* var/spool/squid3/* var/swap var/tmp/.* var/tmp/* ltsp-23.02/ltsp/server/image/lockf000077500000000000000000000035221437727275600171260ustar00rootroot00000000000000#!/usr/bin/python3 # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later """ Usage: lockf [message] Try to lock . If it's locked by another process, display in stderr and wait until it's free. If the user interrupts the wait, return False. When locking succeeds, unlock and spawn a child process that will immediately re-acquire the lock and sleep until it's sent a TERM signal. Then echo the pid of that child process to stdout and exit. """ import fcntl import os import subprocess import sys import time def main(argv): """Run from the command line""" if len(argv) <= 1: print(__doc__) sys.exit(1) fname = argv[1] if len(argv) > 2: message = argv[2] else: message = ( "A package management process is running, waiting for it" " to finish...\n" "Press Ctrl+C to abort") # The child is spawned with this undocumented parameter if message == " ": with open(fname, "w") as file: # There's a slight race condition here; if it wasn't locked for # the parent and is now locked for the child, the child will # wait without the parent having displayed the message fcntl.lockf(file, fcntl.LOCK_EX) while True: time.sleep(1000) with open(fname, "w") as file: try: fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: print(message, file=sys.stderr) try: fcntl.lockf(file, fcntl.LOCK_EX) except KeyboardInterrupt: sys.exit(1) print(subprocess.Popen([argv[0], argv[1], " "], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).pid) if __name__ == "__main__": main(sys.argv) ltsp-23.02/ltsp/server/ipxe/000077500000000000000000000000001437727275600157635ustar00rootroot00000000000000ltsp-23.02/ltsp/server/ipxe/55-ipxe.sh000066400000000000000000000124431437727275600175170ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019-2023 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Install iPXE binaries and configuration in TFTP # @LTSP.CONF: DEFAULT_IMAGE KERNEL_PARAMETERS MENU_TIMEOUT ipxe_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "b::" -l \ "binaries::" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -b | --binaries) shift BINARIES=${1:-1} ;; --) shift break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done test "$#" = "0" || usage 1 run_main_functions "$_SCRIPTS" "$@" } ipxe_main() { local key items gotos r_items r_gotos img_name title client_sections # Prepare the menu text for all images and chroot key=0 items="" gotos=":images" img_name=$(re list_img_names -i) set -- $img_name for img_name in "$@"; do key=$((key + 1)) title=$(echo_values "$(ipxe_name "$img_name.img")") title=${title:-$img_name.img} items="${items:+"$items\n"}$(printf "item --key %d %-20s %s" "$((key % 10))" "$img_name" "$title")" gotos=":$img_name\n$gotos" done r_items="" r_gotos=":roots" img_name=$(re list_img_names -c) set -- $img_name for img_name in "$@"; do key=$((key + 1)) title=$(echo_values "$(ipxe_name "$img_name")") title=${title:-$img_name} r_items="${r_items:+"$r_items\n"}$(printf "item --key %d %-20s %s" "$((key % 10))" "r_$img_name" "$title")" r_gotos=":r_$img_name\nset img $img_name \&\& goto roots\n$r_gotos" done re mkdir -p "$TFTP_DIR/ltsp" if [ "$OVERWRITE" = "0" ] && [ -f "$TFTP_DIR/ltsp/ltsp.ipxe" ]; then warn "Configuration file already exists: $TFTP_DIR/ltsp/ltsp.ipxe To overwrite it, run: ltsp --overwrite $_APPLET ..." else client_sections=$(re client_sections) re install_template "ltsp.ipxe" "$TFTP_DIR/ltsp/ltsp.ipxe" "\ s|/srv/ltsp|$BASE_DIR|g s|^\(set cmdline_ltsp .*\)|$(textif "$KERNEL_PARAMETERS" "\1\nset cmdline_client $KERNEL_PARAMETERS" "&")| s|^\(set cmdline_ltsp .*\)|$(textif "$DEFAULT_IMAGE" "\1\nset img $DEFAULT_IMAGE" "&")| s/\(|| set menu-timeout \)5000/$(textif "$MENU_TIMEOUT" "\1$MENU_TIMEOUT" "&")/ s|^:61:6c:6b:69:73:67\$|$(textif "$client_sections" "$client_sections" "&")| s|^#.*item.*\bimages\b.*|$(textif "$items$r_items" "$items\n$r_items" "&")| s|^:images\$|$(textif "$items" "$gotos" "&")| s|^:roots\$|$(textif "$r_items" "$r_gotos" "&")| " fi if [ "$BINARIES" != "0" ]; then re copy_binary memtest.0 /boot/memtest86+*32.bin /boot/memtest86+.bin re copy_binary memtest.efi /boot/memtest86+x64.efi re copy_binary snponly.efi /usr/lib/ipxe/snponly.efi \ /usr/lib/ipxe/ipxe.efi re copy_binary undionly.kpxe /usr/lib/ipxe/undionly.kpxe fi } # Print the client sections list client_sections() { local section mac first # If ltsp.conf doesn't exist, there's no section_list is_command section_list || return 0 first=1 for section in $(section_list); do # We only care about mac address sections case "$section" in section_[0-9a-f][0-9a-f]_[0-9a-f][0-9a-f]_[0-9a-f][0-9a-f]_[0-9a-f][0-9a-f]_[0-9a-f][0-9a-f]_[0-9a-f][0-9a-f]) mac=$(echo "$section" | sed 's/section_//;s/_/:/g') # Use a subshell to avoid overriding useful variables ( unset DEFAULT_IMAGE KERNEL_PARAMETERS MENU_TIMEOUT unset HOSTNAME section_call "$mac" test -n "$DEFAULT_IMAGE$KERNEL_PARAMETERS$MENU_TIMEOUT" || return 1 # Print an empty line between sections test "$first" = "1" || printf '\\n\\n' printf ':%s' "$mac" test -n "$HOSTNAME" && printf '\\nset hostname %s' "$HOSTNAME" test -n "$DEFAULT_IMAGE" && printf '\\nset img %s' "$DEFAULT_IMAGE" test -n "$KERNEL_PARAMETERS" && printf '\\nset cmdline_client %s' "$KERNEL_PARAMETERS" test -n "$MENU_TIMEOUT" && printf '\\nset menu-timeout %s' "$MENU_TIMEOUT" printf '\\ngoto start' ) || continue unset first ;; esac done } # Search for binary $1 in locations $2+ and copy it to TFTP copy_binary() { local binary location binary=$1 shift if [ "$BINARIES" = "1" ] || [ ! -f "$TFTP_DIR/ltsp/$binary" ]; then for location in "/usr/share/ltsp/binaries/$binary" "$@"; do test -f "$location" && break done if [ -f "$location" ]; then re install -pm 644 "$location" "$TFTP_DIR/ltsp/$binary" echo "Installed $location in $TFTP_DIR/ltsp/$binary" elif [ "${binary%.*}" = "memtest" ]; then warn "$binary not found, that iPXE menu won't work" else die "Could not locate required iPXE binary: $binary" fi else echo "Skipped existing $TFTP_DIR/ltsp/$binary" fi } ipxe_name() { echo "$*" | awk '{ var=toupper($0); gsub("[^A-Z0-9]", "_", var); print "IPXE_" var }' } ltsp-23.02/ltsp/server/ipxe/ltsp.ipxe000066400000000000000000000056331437727275600176430ustar00rootroot00000000000000#!ipxe # This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Configure iPXE for LTSP # Documentation=man:ltsp-ipxe(8) # Set the default image (img) based on arch, or to root-path if it's not empty cpuid --ext 29 && set img x86_64 || set img x86_32 isset ${root-path} && set img ${root-path} || isset ${proxydhcp/dhcp-server} && set srv ${proxydhcp/dhcp-server} || set srv ${next-server} # Define cmdline parameters common to all LTSP clients set cmdline_ltsp BOOTIF=01-${mac:hexhyp} goto ${mac} || goto start # The following client-specific settings can be defined in ltsp.conf: # DEFAULT_IMAGE (img): default menu item for that client # KERNEL_PARAMETERS (cmdline_client): additional kernel parameters # MENU_TIMEOUT (menu-timeout): menu milliseconds, 0=forever, -1=hide menu # For those clients, :mac:address sections are generated below :61:6c:6b:69:73:67 :start # To completely hide the menu, set menu-timeout to -1 isset ${menu-timeout} || set menu-timeout 5000 iseq "${menu-timeout}" "-1" && goto ${img} || menu iPXE boot menu - ${hostname}:${srv}:${root-path} || goto ${img} item --gap Boot an image from the network in LTSP mode: # item --key 1 images x86_64 item item --gap Other options: item --key m memtest Memory test item --key c config Enter iPXE configuration item --key s shell Drop to iPXE shell item --key d disk Boot from the first local disk item item --key x exit Exit iPXE and continue BIOS boot choose --timeout ${menu-timeout} --default ${img} img || goto cancel goto ${img} :images # The "images" method can boot anything in /srv/ltsp/images set cmdline_method root=/dev/nfs nfsroot=${srv}:/srv/ltsp ltsp.image=images/${img}.img loop.max_part=9 goto ltsp :roots # The "roots" method can boot all /srv/ltsp/roots set cmdline_method root=/dev/nfs nfsroot=${srv}:/srv/ltsp/${img} goto ltsp :ltsp # :images and :roots jump here after setting cmdline_method set cmdline ${cmdline_method} ${cmdline_ltsp} ${cmdline_client} # In EFI mode, iPXE requires initrds to be specified in the cmdline kernel /ltsp/${img}/vmlinuz initrd=ltsp.img initrd=initrd.img ${cmdline} initrd /ltsp/ltsp.img initrd /ltsp/${img}/initrd.img boot || goto failed :memtest iseq ${platform} pcbios && kernel memtest.0 || kernel memtest.efi # Boot "fails" on normal memtest exit with Esc, so show the menu again boot || goto start :config config goto start :shell echo Type 'exit' to get the back to the menu shell goto start :disk # Boot the first local HDD sanboot --no-describe --drive 0x80 || goto failed :exit # Exit with error, to fall back to the next boot option # http://forum.ipxe.org/showthread.php?tid=6775 exit 1 :cancel echo You cancelled the menu, dropping to shell goto shell :failed echo Booting failed, dropping to shell goto shell ltsp-23.02/ltsp/server/kernel/000077500000000000000000000000001437727275600162765ustar00rootroot00000000000000ltsp-23.02/ltsp/server/kernel/55-kernel.sh000066400000000000000000000121131437727275600203370ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Copy vmlinuz and initrd.img from image to TFTP # @LTSP.CONF: RPI_IMAGE kernel_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "k:" -l \ "kernel-initrd:" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -k|--kernel-initrd) shift; KERNEL_INITRD=$1 ;; --) shift; break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done run_main_functions "$_SCRIPTS" "$@" } kernel_main() { local img_src img_name runipxe tmp if [ "$#" -eq 0 ]; then img_src=$(list_img_names) set -- $img_src if [ "$#" -gt 3 ] && [ "$ALL_IMAGES" != "1" ]; then die "Refusing to run ltsp $_APPLET for $# detected images! Please export ALL_IMAGES=1 if you want to allow this" fi fi runipxe=0 for img_src in "$@"; do img_path=$(add_path_to_src "${img_src%%,*}") img_name=$(img_path_to_name "$img_path") re test "kernel_main:$img_name" != "kernel_main:" if [ "$img_name" = "$RPI_IMAGE" ] && [ -f "$BASE_DIR/$img_name/boot/bootcode.bin" ] then re rpi_image "$img_name" continue fi tmp=$(re mktemp -d) exit_command "rw rmdir '$tmp'" # tmp has mode=0700; use a subdir to hide the mount from users re mkdir -p "$tmp/root" "$tmp/tmpfs" exit_command "rw rmdir '$tmp/root' '$tmp/tmpfs'" re mount_img_src "$img_src" "$tmp/root" "$tmp/tmpfs" tmp=$tmp/root re mkdir -p "$TFTP_DIR/ltsp/$img_name/" read -r vmlinuz initrd < initramfs-VER.img) vmlinuz-* s|vmlinuz-\(.*\)|initramfs-\1.img| # Tinycorelinux vmlinuz s|vmlinuz|core.gz| EOF } ltsp-23.02/ltsp/server/nfs/000077500000000000000000000000001437727275600156045ustar00rootroot00000000000000ltsp-23.02/ltsp/server/nfs/55-nfs.sh000066400000000000000000000021351437727275600171560ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright 2019 the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Configure NFS exports for LTSP NFS_HOME=${NFS_HOME:-0} NFS_TFTP=${NFS_TFTP:-1} nfs_cmdline() { local args args=$(getopt -n "ltsp $_APPLET" -o "h:t:" -l \ "nfs-home:,nfs-tftp:" -- "$@") || usage 1 eval "set -- $args" while true; do case "$1" in -h|--nfs-home) shift; NFS_HOME=$1 ;; -t|--nfs-tftp) shift; NFS_TFTP=$1 ;; --) shift; break ;; *) die "ltsp $_APPLET: error in cmdline: $*" ;; esac shift done test "$#" = "0" || usage 1 run_main_functions "$_SCRIPTS" "$@" } nfs_main() { re mkdir -p /etc/exports.d re install_template "ltsp-nfs.exports" "/etc/exports.d/ltsp-nfs.exports" "\ s|^/srv/ltsp|$BASE_DIR| s|^/srv/tftp/ltsp|$(textifb "$NFS_TFTP" "$TFTP_DIR/ltsp" "#&")| s|^#/home|$(textifb "$NFS_HOME" "$HOME_DIR" "&")| " re mkdir -p "$BASE_DIR" "$TFTP_DIR/ltsp" re systemctl restart nfs-kernel-server echo "Restarted nfs-kernel-server" } ltsp-23.02/ltsp/server/nfs/ltsp-nfs.exports000066400000000000000000000013101437727275600207730ustar00rootroot00000000000000# This file is part of LTSP, https://ltsp.org # Copyright (C) the LTSP team, see AUTHORS # SPDX-License-Identifier: GPL-3.0-or-later # Configure NFS exports for LTSP # Documentation=man:ltsp-nfs(8) # Export LTSP chroots and images /srv/ltsp *(ro,async,crossmnt,no_subtree_check,no_root_squash,insecure) # Export TFTP_DIR over NFS as well, for synching local kernels and ltsp.img /srv/tftp/ltsp *(ro,async,crossmnt,no_subtree_check,no_root_squash,insecure) # Export home over NFS3; note this is insecure # "no_root_squash" is for root epoptes to access /home/user/.Xauthority # "insecure" allows access over NAT, e.g. from vbox/kvm virtual clients #/home *(rw,async,no_subtree_check,no_root_squash,insecure)