diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/Cargo.lock b/Cargo.lock index 0c779e1..b91b936 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,60 +97,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -197,6 +143,7 @@ dependencies = [ name = "blimp" version = "0.1.0" dependencies = [ + "clap", "common", ] @@ -210,18 +157,7 @@ dependencies = [ "file-lock", "serde", "sha2", -] - -[[package]] -name = "blimp-server" -version = "0.1.0" -dependencies = [ - "axum", - "common", - "envy", - "serde", - "tracing", - "tracing-subscriber", + "toml", ] [[package]] @@ -298,9 +234,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.39" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -308,9 +244,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -320,9 +256,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck", "proc-macro2", @@ -349,7 +285,6 @@ dependencies = [ "anyhow", "bytes", "bzip2", - "clap", "flate2", "futures", "futures-util", @@ -358,10 +293,10 @@ dependencies = [ "rand", "reqwest", "serde", - "serde_json", "tar", "tokio", "tokio-util", + "toml", "utils", "xz2", ] @@ -460,15 +395,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "envy" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" -dependencies = [ - "serde", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -752,12 +678,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.6.0" @@ -771,7 +691,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -998,12 +917,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.172" @@ -1050,12 +963,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "memchr" version = "2.7.4" @@ -1105,16 +1012,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -1186,12 +1083,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "password-hash" version = "0.5.0" @@ -1520,12 +1411,11 @@ dependencies = [ ] [[package]] -name = "serde_path_to_error" -version = "0.1.17" +name = "serde_spanned" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ - "itoa", "serde", ] @@ -1552,15 +1442,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1686,16 +1567,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tinystr" version = "0.8.1" @@ -1766,6 +1637,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tower" version = "0.5.2" @@ -1779,7 +1689,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1800,23 +1709,10 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.33" @@ -1824,32 +1720,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", ] [[package]] @@ -1925,12 +1795,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "vcpkg" version = "0.2.15" @@ -2071,28 +1935,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-link" version = "0.1.1" @@ -2274,6 +2116,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index a71beba..830497e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "builder", "client", "common", - "server", ] [profile.release] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c84b14c..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Luc Lenôtre - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 572da1a..bc3b8bc 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,25 @@

- - logo + + logo

-[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge&logo=book)](./LICENSE) -![Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fllenotre%2Fblimp%2Fmaster%2Fclient%2FCargo.toml&query=%24.package.version&style=for-the-badge&label=version) -![Continuous integration](https://img.shields.io/github/actions/workflow/status/llenotre/blimp/check.yml?style=for-the-badge&logo=github) +[![AGPL-3.0 license](https://img.shields.io/badge/license-AGPL--3.0-blue.svg?style=for-the-badge&logo=book)](./COPYING) +![Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmaestro-os%2Fblimp%2Fmaster%2Fclient%2FCargo.toml&query=%24.package.version&style=for-the-badge&label=version) +![Continuous integration](https://img.shields.io/github/actions/workflow/status/maestro-os/blimp/check.yml?style=for-the-badge&logo=github) # About -Blimp is a simple package manager for Unix-like operating systems, more specifically for [Maestro](https://github.com/llenotre/maestro). +Blimp is a simple package manager for Unix-like operating systems, more specifically for [Maestro](https://github.com/maestro-os/maestro). This repository contains the following components: - `blimp`: The package manager itself - `blimp-builder`: An utility to build packages -- `blimp-server`: The package manager's server The `common` crate is a library with utilities shared across components. - - # Build Build the package manager using: @@ -33,17 +30,13 @@ cargo build # Debug mode cargo build --release # Release mode ``` -Building with network support required the `network` feature: - -```sh -cargo build --features network # Debug mode -cargo build --features network --release # Release mode -``` - - +Features: +- `network` (default): Enable network support. Disabling this feature is necessary when the SSL library is not available. # Usage +Man pages are shipped with this repository, and are available in `man/`. + ## Blimp Synchronize packages information with remotes: @@ -76,8 +69,6 @@ Show the whole usage of the command: blimp ``` - - ## Package builder The general usage of the command is: @@ -92,8 +83,6 @@ The `--package` flag can be used to write the resulting package into an archive > **Note**: the structure of package descriptors and output packages are not yet documented as they are unstable - - ### Bootstrapping When building packages for a new system on a different target triplet than the current system, **bootstrapping** is required. diff --git a/bootstrap/README.md b/bootstrap/README.md index 754559f..9e7e67b 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -2,11 +2,9 @@ Bootstrapping is the process of creating an environment which allows the cross compilation of packages. +`./init.sh` builds a cross-compilation toolchain in `sysroot/`. - -## Overview - -The following build steps are required for bootstrapping: +The following packages are built: | Package | Host triplet | Target triplet | Notes | |---------------------------------------------|--------------|----------------|-------------------------------------------------| @@ -21,18 +19,4 @@ The following build steps are required for bootstrapping: > **Note**: one last compilation of gcc (stage 3) will be necessary for a final system, but it is treated as a casual package and not discussed here. -## Building - -First, the `sysroot` directory and a basic file hierarchy must be created. Use `init.sh`: -```sh -./init.sh -``` - -Then, each package has to be built, in the order of the table above. - -The command to use for building a package is: -```sh -PATH="$(pwd)/sysroot/tools:$PATH" HOST= TARGET= blimp-builder --from desc// --to sysroot/ -``` - -Once this is done, the second **gcc** can be used to cross compile packages (autoconf, make, etc...) on the target. +Once built, the second **gcc** can be used to cross compile packages on the target. diff --git a/bootstrap/desc/binutils1/metadata.toml b/bootstrap/desc/binutils1/metadata.toml new file mode 100644 index 0000000..b8f72ec --- /dev/null +++ b/bootstrap/desc/binutils1/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "bootstrap-binutils" +version = "2.44" +description = "Stage 1 binutils for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" diff --git a/bootstrap/desc/binutils1/package.json b/bootstrap/desc/binutils1/package.json deleted file mode 100644 index 950abb6..0000000 --- a/bootstrap/desc/binutils1/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" - } - ], - "package": { - "name": "bootstrap-binutils", - "version": "2.44", - - "description": "Stage 1 binutils for bootstrapping", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/desc/binutils2/metadata.toml b/bootstrap/desc/binutils2/metadata.toml new file mode 100644 index 0000000..aa04442 --- /dev/null +++ b/bootstrap/desc/binutils2/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "bootstrap-binutils" +version = "2.44" +description = "Stage 2 binutils for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" \ No newline at end of file diff --git a/bootstrap/desc/binutils2/package.json b/bootstrap/desc/binutils2/package.json deleted file mode 100644 index 86c5fed..0000000 --- a/bootstrap/desc/binutils2/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" - } - ], - "package": { - "name": "bootstrap-binutils", - "version": "2.44", - - "description": "Stage 2 binutils for bootstrapping", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/desc/gcc1/metadata.toml b/bootstrap/desc/gcc1/metadata.toml new file mode 100644 index 0000000..f34374c --- /dev/null +++ b/bootstrap/desc/gcc1/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-gcc1" +version = "15.1.0" +description = "Stage 1 gcc for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" + +[[package.build_dep]] +name = "bootstrap-binutils" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/gcc1/package.json b/bootstrap/desc/gcc1/package.json deleted file mode 100644 index e181749..0000000 --- a/bootstrap/desc/gcc1/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" - } - ], - "package": { - "name": "bootstrap-gcc1", - "version": "15.1.0", - - "description": "Stage 1 gcc for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-binutils", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/gcc2/metadata.toml b/bootstrap/desc/gcc2/metadata.toml new file mode 100644 index 0000000..b979362 --- /dev/null +++ b/bootstrap/desc/gcc2/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-gcc2" +version = "15.1.0" +description = "Stage 2 gcc for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" + +[[package.build_dep]] +name = "bootstrap-libstdc++" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/gcc2/package.json b/bootstrap/desc/gcc2/package.json deleted file mode 100644 index c633408..0000000 --- a/bootstrap/desc/gcc2/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" - } - ], - "package": { - "name": "bootstrap-gcc2", - "version": "15.1.0", - - "description": "Stage 2 gcc for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-libstdc++", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/libstdc++/metadata.toml b/bootstrap/desc/libstdc++/metadata.toml new file mode 100644 index 0000000..1d1eb7d --- /dev/null +++ b/bootstrap/desc/libstdc++/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-libstdc++" +version = "15.1.0" +description = "libstdc++ for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" + +[[package.build_dep]] +name = "bootstrap-musl" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/libstdc++/package.json b/bootstrap/desc/libstdc++/package.json deleted file mode 100644 index ecf0593..0000000 --- a/bootstrap/desc/libstdc++/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" - } - ], - "package": { - "name": "bootstrap-libstdc++", - "version": "15.1.0", - - "description": "libstdc++ for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-musl", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/linux-headers/metadata.toml b/bootstrap/desc/linux-headers/metadata.toml new file mode 100644 index 0000000..169632a --- /dev/null +++ b/bootstrap/desc/linux-headers/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "bootstrap-linux-headers" +version = "6.15" +description = "Linux kernel headers for bootstrapping" + +[[source]] +location = "/" +url = "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.tar.xz" \ No newline at end of file diff --git a/bootstrap/desc/linux-headers/package.json b/bootstrap/desc/linux-headers/package.json deleted file mode 100644 index 7d7b022..0000000 --- a/bootstrap/desc/linux-headers/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.tar.xz" - } - ], - "package": { - "name": "bootstrap-linux-headers", - "version": "6.15", - - "description": "Linux kernel headers for bootstrapping", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/desc/musl/build-hook b/bootstrap/desc/musl/build-hook index 4b3ba59..633fce3 100755 --- a/bootstrap/desc/musl/build-hook +++ b/bootstrap/desc/musl/build-hook @@ -6,7 +6,7 @@ cd * ./configure \ --build="$BUILD" \ --host="$HOST" \ - --target="$TARGET" \ + --target="$HOST" \ --prefix="/usr" \ --enable-optimize \ --enable-shared diff --git a/bootstrap/desc/musl/metadata.toml b/bootstrap/desc/musl/metadata.toml new file mode 100644 index 0000000..d8008f3 --- /dev/null +++ b/bootstrap/desc/musl/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-musl" +version = "1.2.5" +description = "musl for bootstrapping" + +[[source]] +location = "/" +url = "https://musl.libc.org/releases/musl-1.2.5.tar.gz" + +[[package.build_dep]] +name = "bootstrap-gcc1" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/musl/package.json b/bootstrap/desc/musl/package.json deleted file mode 100644 index f944fb7..0000000 --- a/bootstrap/desc/musl/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://musl.libc.org/releases/musl-1.2.5.tar.gz" - } - ], - "package": { - "name": "bootstrap-musl", - "version": "1.2.5", - - "description": "musl for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-gcc1", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/zlib/metadata.toml b/bootstrap/desc/zlib/metadata.toml new file mode 100644 index 0000000..3df87ea --- /dev/null +++ b/bootstrap/desc/zlib/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "zlib" +version = "1.3.1" +description = "Bootstrapping zlib" + +[[source]] +location = "/" +url = "https://zlib.net/zlib-1.3.1.tar.gz" \ No newline at end of file diff --git a/bootstrap/desc/zlib/package.json b/bootstrap/desc/zlib/package.json deleted file mode 100644 index 779cb99..0000000 --- a/bootstrap/desc/zlib/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://zlib.net/zlib-1.3.1.tar.gz" - } - ], - "package": { - "name": "zlib", - "version": "1.3.1", - - "description": "Bootstrapping zlib", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/init.sh b/bootstrap/init.sh index 926ece9..c6cda79 100755 --- a/bootstrap/init.sh +++ b/bootstrap/init.sh @@ -2,15 +2,35 @@ set -e -mkdir -pv sysroot/tools sysroot/{etc,var} sysroot/usr/{bin,lib,lib32,sbin} +A="$(cc -dumpmachine)" +if [ -z "$HOST" ]; then + echo "Missing HOST environment variable" + exit 1 +fi +B="$HOST" +unset BUILD HOST TARGET +# Create base directories +mkdir -pv sysroot/tools sysroot/{etc,var} sysroot/usr/{bin,lib,lib32,sbin} for i in bin lib sbin; do ln -sv "usr/$i" "sysroot/$i" done - -case ${HOST%%-*} in +case ${B%%-*} in x86_64) ln -sv usr/lib sysroot/lib64 ln -sv lib sysroot/usr/lib64 ;; -esac \ No newline at end of file +esac + +# Build packages +# TODO use release mode builder instead? +OLD_PATH="$PATH" +export PATH="$(pwd)/sysroot/tools/bin:$(pwd)/../target/debug:$PATH" +HOST="$A" TARGET="$B" blimp-builder --from desc/binutils1/ --to sysroot/ +HOST="$A" TARGET="$B" blimp-builder --from desc/gcc1/ --to sysroot/ +blimp-builder --from desc/linux-headers/ --to sysroot/ +HOST="$B" blimp-builder --from desc/musl/ --to sysroot/ +HOST="$B" blimp-builder --from desc/zlib/ --to sysroot/ +HOST="$B" blimp-builder --from desc/libstdc++/ --to sysroot/ +HOST="$B" TARGET="$B" blimp-builder --from desc/binutils2/ --to sysroot/ +HOST="$B" TARGET="$B" blimp-builder --from desc/gcc2/ --to sysroot/ diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 1c4f0d4..a62d5f1 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -10,7 +10,8 @@ file-lock = "2.1.11" serde = { version = "1.0.219", features = ["derive"] } sha2 = "0.10.9" clap = { version = "4.5.39", features = ["derive"] } +toml = "0.9.5" [features] -default = [] +default = ["network"] network = ["common/network"] diff --git a/builder/src/build.rs b/builder/src/build.rs index 1fe696b..282f432 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -1,10 +1,29 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Implementation of the package building procedure. use crate::{desc::BuildDescriptor, WORK_DIR}; use common::{ anyhow::Result, flate2::{write::GzEncoder, Compression}, - serde_json, tar, tokio, + repository::Repository, + tar, tokio, }; use std::{ fs, @@ -19,7 +38,7 @@ use std::{ /// A build process is the operation of converting source code into an installable package. /// /// To build a package, the following files are required: -/// - `package.json`: The file describing the package +/// - `build.toml`: Information to prepare for building the package /// - `build-hook`: The script to build the package /// /// The package is build and then installed to a fake system root, which is then compressed. @@ -28,6 +47,7 @@ pub struct BuildProcess { input_path: PathBuf, /// The build descriptor. build_desc: BuildDescriptor, + /// The path to the build directory. build_dir: PathBuf, /// The path to the system root in which the package is installed. @@ -40,13 +60,15 @@ impl BuildProcess { /// Arguments: /// - `input_path` is the path to the directory containing information to build the package. /// - `sysroot` is the path to the system root. If `None`, a directory is created. - pub fn new(input_path: PathBuf, sysroot: Option) -> io::Result { - let build_desc_path = input_path.join("package.json"); + pub fn new(input_path: PathBuf, sysroot: Option) -> Result { + let build_desc_path = input_path.join("metadata.toml"); let build_desc = fs::read_to_string(build_desc_path)?; - let build_desc = serde_json::from_str(&build_desc)?; + let build_desc = toml::from_str::(&build_desc)?; + build_desc.package.validate()?; Ok(Self { input_path, build_desc, + build_dir: common::util::create_tmp_dir(WORK_DIR)?, sysroot: sysroot .map(fs::canonicalize) @@ -54,11 +76,6 @@ impl BuildProcess { }) } - /// Returns the build descriptor of the package to be built. - pub fn get_build_desc(&self) -> &BuildDescriptor { - &self.build_desc - } - /// Returns the path to the build directory. pub fn get_build_dir(&self) -> &Path { &self.build_dir @@ -74,7 +91,7 @@ impl BuildProcess { let build_dir = Arc::new(self.build_dir.clone()); let futures = self .build_desc - .sources + .source .iter() .cloned() .map(move |s| { @@ -114,16 +131,34 @@ impl BuildProcess { .map(|s| s.success()) } + /// Writes the package's metadata to the repository + pub fn write_metadata(&self, repo: &Repository, arch: &str) -> Result<()> { + let path = repo.get_metadata_path( + arch, + &self.build_desc.package.name, + &self.build_desc.package.version, + ); + // Make sure the parent directory exists + fs::create_dir_all(path.parent().unwrap())?; + // Create metadata + let metadata = toml::to_string(&self.build_desc.package)?; + fs::write(path, metadata)?; + Ok(()) + } + /// Creates the archive of the package after being build. - /// - /// `output_path` is the path at which the package's archive will be created. - pub fn create_archive(&self, output_path: &Path) -> io::Result<()> { - let build_desc_path = self.input_path.join("package.json"); - let tar_gz = File::create(output_path)?; - let enc = GzEncoder::new(tar_gz, Compression::default()); + pub fn create_archive(&self, repo: &Repository, arch: &str) -> io::Result<()> { + let output_path = repo.get_archive_path( + arch, + &self.build_desc.package.name, + &self.build_desc.package.version, + ); + let build_desc_path = self.input_path.join("metadata.toml"); + let archive = File::create(output_path)?; + let enc = GzEncoder::new(archive, Compression::default()); let mut tar = tar::Builder::new(enc); tar.follow_symlinks(false); - tar.append_path_with_name(build_desc_path, "package.json")?; + tar.append_path_with_name(build_desc_path, "metadata.toml")?; tar.append_dir_all("data", &self.sysroot)?; // TODO add install/update/remove hooks tar.finish() diff --git a/builder/src/cache.rs b/builder/src/cache.rs index beab31a..8842eba 100644 --- a/builder/src/cache.rs +++ b/builder/src/cache.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Packages sources cache. use base64::{prelude::BASE64_STANDARD, Engine}; @@ -77,7 +95,6 @@ impl CacheEntry { /// Flushes the entry to the cache by computing and storing its checksum. pub fn flush(&mut self) -> io::Result<()> { let dir_path = cache_directory()?; - let path = dir_path.join(&self.encoded_key); // `.` is not part of the base64 character set let checksum_path = dir_path.join(format!("{}.checksum", self.encoded_key)); // Compute checksum @@ -96,7 +113,8 @@ impl CacheEntry { /// before. pub fn get_or_insert(key: &[u8]) -> io::Result { let dir_path = cache_directory()?; - let encoded_key = BASE64_STANDARD.encode(key); + // `/` causes issues with paths. Replace it by `-` which is not in the base64 characters set + let encoded_key = BASE64_STANDARD.encode(key).replace('/', "-"); let path = dir_path.join(&encoded_key); // Open file let opt = FileOptions::new().read(true).write(true).create(true); diff --git a/builder/src/desc.rs b/builder/src/desc.rs index 351af09..f54998a 100644 --- a/builder/src/desc.rs +++ b/builder/src/desc.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! A build descriptor defines how to build a package. //! //! A build descriptor contains general information about the package, but also sources for files @@ -126,8 +144,9 @@ impl Source { /// Description of how to build a package. #[derive(Deserialize, Serialize)] pub struct BuildDescriptor { - /// The list of sources for the package. - pub sources: Vec, - /// The package's descriptor. + /// The package's descriptor pub package: Package, + /// The list of sources for the package + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub source: Vec, } diff --git a/builder/src/main.rs b/builder/src/main.rs index 1342746..fcb3ec3 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Utility allowing to build packages. mod build; @@ -10,14 +28,13 @@ use crate::{ build::BuildProcess, util::{get_build_triplet, get_jobs_count}, }; +use clap::Parser; use common::{ anyhow::{anyhow, bail, Result}, - clap::Parser, repository::Repository, - serde_json, tokio::runtime::Runtime, }; -use std::{env, fs, io, path::PathBuf, process::exit, str}; +use std::{env, fs, path::PathBuf, process::exit, str}; /// The path to the work directory. const WORK_DIR: &str = "work/"; @@ -31,39 +48,30 @@ const WORK_DIR: &str = "work/"; \tTARGET: Target triplet for which the package builds (this is useful when cross-compiling compilers) \tBLIMP_DEBUG: If set to `true`, build files are kept for troubleshooting purpose -All environment variable are optional")] +All environment variables are optional")] #[command(version, about, long_about = None)] struct Args { - /// Path to the directory containing the package to build. + /// Path to the directory containing the package to build #[arg(long)] from: PathBuf, - /// Output directory path. + /// Output directory path #[arg(long)] to: PathBuf, /// If set, the package is packed into an archive, written to this directory. /// Else, the package is directly *installed* in this directory (which acts as the system - /// root). + /// root) #[arg(long)] package: bool, } -/// Prepares the repository's directory for the package. -/// -/// On success, the function returns the output archive path. -fn prepare(build_process: &BuildProcess, to: PathBuf) -> io::Result { - // Create directory - let build_desc = build_process.get_build_desc(); - let name = &build_desc.package.name; - let version = &build_desc.package.version; - let package_path = to.join(name).join(version.to_string()); - fs::create_dir_all(&package_path)?; - // Create descriptor - let desc_path = package_path.join("desc"); - let desc = serde_json::to_string(&build_desc.package)?; - fs::write(desc_path, desc)?; - // Get archive path - let repo = Repository::load(to)?; - Ok(repo.get_archive_path(name, version)) +/// Returns the architecture directory name for the given `host` +fn get_arch(host: &str) -> &str { + let arch = host.split_once('-').map(|(a, _)| a); + match arch { + Some("i386" | "i486" | "i586" | "i686") => "x86", + Some(a) => a, + None => host, + } } fn main_impl(args: Args) -> Result<()> { @@ -72,31 +80,37 @@ fn main_impl(args: Args) -> Result<()> { let build = get_build_triplet()?; let host = env::var("HOST"); let host = host.as_deref().unwrap_or(build.as_str()); + let arch = get_arch(host); let target = env::var("TARGET"); let target = target.as_deref().unwrap_or(host); let debug = env::var("BLIMP_DEBUG") .map(|s| s == "true") .unwrap_or(false); + fs::create_dir_all(&args.to) + .map_err(|e| anyhow!("failed to create destination directory: {e}"))?; println!("[INFO] Jobs: {jobs}; Build: {build}; Host: {host}; Target: {target}"); - let build_process = BuildProcess::new(args.from, (!args.package).then(|| args.to.clone()))?; + let sysroot = (!args.package).then(|| args.to.clone()); + let build_process = BuildProcess::new(args.from, sysroot)?; let rt = Runtime::new()?; rt.block_on(build_process.fetch_sources()) - .map_err(|e| anyhow!("Cannot fetch sources: {e}"))?; + .map_err(|e| anyhow!("cannot fetch sources: {e}"))?; println!("[INFO] Compilation..."); let success = build_process .build(jobs, &build, host, target) - .map_err(|e| anyhow!("Cannot build package: {e}"))?; + .map_err(|e| anyhow!("cannot build package: {e}"))?; if !success { - bail!("Package build failed!"); + bail!("package build failed"); } if args.package { println!("[INFO] Prepare repository at `{}`...", args.to.display()); - let archive_path = prepare(&build_process, args.to) - .map_err(|e| anyhow!("Failed to prepare directory for package: {e}"))?; + let repo = Repository::load(args.to.clone()); + build_process + .write_metadata(&repo, arch) + .map_err(|e| anyhow!("failed to write package metadata: {e}"))?; println!("[INFO] Create archive..."); build_process - .create_archive(&archive_path) - .map_err(|e| anyhow!("Cannot create archive: {e}"))?; + .create_archive(&repo, arch) + .map_err(|e| anyhow!("failed to create package archive: {e}"))?; } if debug { eprintln!( diff --git a/builder/src/util.rs b/builder/src/util.rs index 9ac0c94..351ea73 100644 --- a/builder/src/util.rs +++ b/builder/src/util.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Utilities. use common::anyhow::{anyhow, Result}; diff --git a/client/Cargo.toml b/client/Cargo.toml index 8a70b31..88906a7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,8 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.39", features = ["derive"] } common = { path = "../common" } [features] -default = [] +default = ["network"] network = ["common/network"] diff --git a/client/src/confirm.rs b/client/src/confirm.rs index e34947c..3662185 100644 --- a/client/src/confirm.rs +++ b/client/src/confirm.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Confirmation prompt. use common::maestro_utils; diff --git a/client/src/install.rs b/client/src/install.rs index ad2f96c..fda9a58 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! This module handles package installation. use crate::confirm; @@ -8,7 +26,7 @@ use common::{ repository::Repository, Environment, }; -use std::{collections::HashMap, io, path::PathBuf}; +use std::collections::HashMap; // TODO Clean /// Installs the given list of packages. @@ -16,23 +34,23 @@ use std::{collections::HashMap, io, path::PathBuf}; /// Arguments: /// - `names` is the list of packages to install. /// - `env` is the blimp environment. -/// - `local_repos` is the list of paths to local package repositories. -pub async fn install( - names: &[String], - env: &mut Environment, - local_repos: Vec, -) -> Result<()> { +pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { + if names.is_empty() { + bail!("must specify at least one package"); + } // The list of repositories - let repos = local_repos - .into_iter() + let repos = env + .local_repos() + .iter() + .cloned() .map(Repository::load) - .collect::>>()?; + .collect::>(); // Tells whether the operation failed let mut failed = false; // The list of packages to install with their respective repository let mut packages = HashMap::::new(); for name in names { - let pkg = repository::get_package_with_constraint(&repos, name, None)?; + let pkg = repository::get_package_with_constraint(&repos, env.arch(), name, None)?; let Some((repo, pkg)) = pkg else { eprintln!("Package `{name}` not found!"); failed = true; @@ -40,8 +58,8 @@ pub async fn install( }; packages.insert(pkg, repo); // If already installed, print message - if let Some(version) = env.get_installed_version(name) { - println!("Package `{name}` version `{version}` is already installed. Reinstalling",); + if let Some(version) = env.get_installed_version(name)? { + println!("Package `{name}` version `{version}` is already installed. Reinstalling"); } } if failed { @@ -58,6 +76,7 @@ pub async fn install( &mut |name, version_constraint| { let res = repository::get_package_with_constraint( &repos, + env.arch(), name, Some(version_constraint), ); @@ -95,12 +114,12 @@ pub async fn install( for (pkg, repo) in &total_packages { let name = &pkg.name; let version = &pkg.version; - match repo.get_package(name, version)? { + match repo.get_package(env.arch(), name, version)? { Some(_) => println!("\t- {name} ({version}) - cached"), None => { // Get package size from remote let remote = repo.get_remote().unwrap(); - let size = remote.get_size(pkg).await?; + let size = remote.get_size(env, pkg).await?; total_size += size; println!("\t- {name} ({version}) - download size: {size}"); } @@ -127,7 +146,7 @@ pub async fn install( let mut futures = Vec::new(); // TODO download biggest packages first (sort_unstable by decreasing size) for (pkg, repo) in &total_packages { - if repo.is_in_cache(&pkg.name, &pkg.version) { + if repo.is_in_cache(env.arch(), &pkg.name, &pkg.version) { println!("`{}` is in cache.", &pkg.name); continue; } @@ -141,13 +160,13 @@ pub async fn install( use common::download::DownloadTask; use std::fs::OpenOptions; - let path = repo.get_archive_path(&pkg.name, &pkg.version); + let path = repo.get_archive_path(env.arch(), &pkg.name, &pkg.version); let file = OpenOptions::new() .create(true) .write(true) .truncate(true) .open(path)?; - let url = remote.download_url(pkg); + let url = remote.download_url(env, pkg); let mut task = DownloadTask::new(&url, &file).await?; while task.next().await? > 0 {} Ok::<(), common::anyhow::Error>(()) @@ -172,7 +191,7 @@ pub async fn install( // Install all packages for (pkg, repo) in total_packages { println!("Installing `{}`...", pkg.name); - let archive_path = repo.get_archive_path(&pkg.name, &pkg.version); + let archive_path = repo.get_archive_path(env.arch(), &pkg.name, &pkg.version); if let Err(e) = env.install(&pkg, &archive_path) { eprintln!("Failed to install `{}`: {e}", &pkg.name); failed = true; diff --git a/client/src/main.rs b/client/src/main.rs index 321079c..b0bb1f8 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Blimp is a simple package manager for Unix systems. mod confirm; @@ -8,185 +26,101 @@ mod remove; #[cfg(feature = "network")] mod update; +use clap::{Args, Parser, Subcommand}; use common::{ anyhow::{anyhow, Result}, tokio, Environment, }; use install::install; use remove::remove; -use std::{ - env, - path::{Path, PathBuf}, - process::exit, -}; +use std::{env, path::PathBuf, process::exit}; + +#[derive(Args, Clone, Debug)] +struct PkgList { + /// Packages + packages: Vec, +} -/// Prints command line usage. -fn print_usage() { - eprintln!( - "blimp package manager version {}", - env!("CARGO_PKG_VERSION") - ); - eprintln!(); - eprintln!("USAGE:"); - eprintln!("\tblimp [OPTIONS]"); - eprintln!(); - eprintln!("COMMAND:"); - eprintln!("\tinfo : Prints information about the given package(s)"); - eprintln!("\tinstall : Installs the given package(s)"); +#[derive(Clone, Debug, Subcommand)] +enum Action { + /// Synchronizes packages information from remotes #[cfg(feature = "network")] - { - eprintln!("\tupdate: Synchronizes packages information from remote"); - } - eprintln!( - "\tupgrade [package...]: Upgrades the given package(s). If no package is specified, \ -the package manager updates every package that is not up to date" - ); - eprintln!("\tremove : Removes the given package(s)"); - eprintln!("\tclean: Clean the cache"); + Update, + /// Prints information about the given package(s) + Info(PkgList), + /// Installs the given package(s) + Install(PkgList), + /// Upgrades the given package(s). If no package is specified, the package manager updates + /// every package that is not up-to-date + Upgrade(PkgList), + /// Removes the given package(s) + Remove(PkgList), + /// Cleans the cache + Clean, + /// Lists remote servers #[cfg(feature = "network")] - { - eprintln!("\tremote-list: Lists remote servers"); - eprintln!("\tremote-add : Adds a remote server"); - eprintln!("\tremote-remove : Removes a remote server"); - } - eprintln!(); - eprintln!("OPTIONS:"); - eprintln!("\t--verbose: Enables verbose mode"); - eprintln!( - "\t--version : When installing or upgrading, this option allows to \ -specify a version" - ); - eprintln!(); - eprintln!("ENVIRONMENT VARIABLES:"); - eprintln!("\tSYSROOT: Specifies the path to the system's root"); - eprintln!( - "\tLOCAL_REPO: Specifies paths separated by `:` at which packages are \ -stored locally (the SYSROOT variable does not apply to these paths)" - ); + RemoteList, + /// Adds a remote server + #[cfg(feature = "network")] + RemoteAdd { remote: String }, + /// Removes a remote server + #[cfg(feature = "network")] + RemoteRemove { remote: String }, } -/// Returns an environment for the given sysroot. -/// -/// If the environment's lockfile cannot be acquired, the function returns an error. -fn get_env(sysroot: &Path) -> Result { - Environment::with_root(sysroot)?.ok_or(anyhow!("failed to acquire lockfile")) +#[derive(Parser, Debug)] +#[command(version, about, long_about = None, after_long_help = "Environment variables: +\tSYSROOT: Specifies the path to the system's root +\tLOCAL_REPO: Specifies paths separated by `:` at which packages are stored locally (the SYSROOT variable does not apply to these paths) + +All environment variables are optional")] +struct Cli { + #[command(subcommand)] + action: Action, + /// The branch to use on package repositories, defaults to `stable` + #[arg(short, long)] + branch: Option, + /// The architecture to install for, defaults to the current + #[arg(short, long)] + arch: Option, } -async fn main_impl(sysroot: &Path, local_repos: Vec) -> Result { - // If no argument is specified, print usage - let args: Vec = env::args().collect(); - if args.len() <= 1 { - print_usage(); - return Ok(false); - } - // Match command - match args[1].as_str() { - "info" => { - let packages = &args[2..]; - if packages.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - todo!() - } +async fn main_impl() -> Result<()> { + let args = Cli::parse(); + let sysroot = env::var_os("SYSROOT") + .map(PathBuf::from) + .unwrap_or(PathBuf::from("/")); + let local_repos = env::var("LOCAL_REPO") // TODO var_os + .map(|s| s.split(':').map(PathBuf::from).collect()) + .unwrap_or_default(); + let mut env = Environment::acquire(&sysroot, local_repos, args.arch)? + .ok_or_else(|| anyhow!("failed to acquire lockfile"))?; + match args.action { #[cfg(feature = "network")] - "update" => { - let mut env = get_env(sysroot)?; - update::update(&mut env).await?; - Ok(true) - } - "install" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - install(names, &mut env, local_repos).await?; - Ok(true) - } - "upgrade" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - let _env = get_env(sysroot)?; - todo!() - } - "remove" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - remove(names, &mut env)?; - Ok(true) - } - "clean" => { - let _env = get_env(sysroot)?; - todo!() - } + Action::Update => update::update(&mut env).await?, + Action::Info(_names) => todo!(), + Action::Install(names) => install(&names.packages, &mut env).await?, + Action::Upgrade(_names) => todo!(), + Action::Remove(names) => remove(&names.packages, &mut env)?, + Action::Clean => todo!(), #[cfg(feature = "network")] - "remote-list" => { - let env = get_env(sysroot)?; - remote::list(&env).await?; - Ok(true) - } + Action::RemoteList => remote::list(&env).await?, #[cfg(feature = "network")] - "remote-add" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several remotes to add"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - remote::add(&mut env, names)?; - Ok(true) - } + Action::RemoteAdd { + remote, + } => remote::add(&mut env, remote)?, #[cfg(feature = "network")] - "remote-remove" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several remotes to remove"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - remote::remove(&mut env, names)?; - Ok(true) - } - #[cfg(not(feature = "network"))] - "update" | "remote-list" | "remote-add" | "remote-remove" => { - eprintln!( - "This feature is not enabled. To use it, recompile the package manager with the feature `network`" - ); - Ok(false) - } - cmd => { - eprintln!("Command `{cmd}` does not exist"); - eprintln!(); - print_usage(); - Ok(false) - } + Action::RemoteRemove { + remote, + } => remote::remove(&mut env, remote)?, } + Ok(()) } #[tokio::main] async fn main() { - let sysroot = env::var_os("SYSROOT") - .map(PathBuf::from) - .unwrap_or(PathBuf::from("/")); - let local_repos = env::var("LOCAL_REPO") // TODO var_os - .map(|s| s.split(':').map(PathBuf::from).collect()) - .unwrap_or_default(); - let res = main_impl(&sysroot, local_repos).await; - match res { - Ok(false) => exit(1), - Err(e) => { - eprintln!("error: {e}"); - exit(1); - } - _ => {} + if let Err(e) = main_impl().await { + eprintln!("error: {e}"); + exit(1); } } diff --git a/client/src/remote.rs b/client/src/remote.rs index 2752ca6..65a2bfd 100644 --- a/client/src/remote.rs +++ b/client/src/remote.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! Remotes management. use common::{repository::remote::Remote, Environment}; @@ -9,46 +27,43 @@ pub async fn list(env: &Environment) -> std::io::Result<()> { for remote in remotes { let host = &remote.host; match remote.fetch_motd().await { - Ok(motd) => println!("- {host} (status: UP): {motd}"), + Ok(Some(motd)) => println!("- {host} (status: UP): {motd}"), + Ok(None) => println!("- {host} (status: UP)"), Err(_) => println!("- {host} (status: DOWN)"), } } Ok(()) } -/// Adds one or several remotes. +/// Adds a remote. /// /// Arguments: -/// - `env` is the environment. -/// - `remotes` is the list of remotes to add. -pub fn add(env: &mut Environment, new_remotes: &[String]) -> std::io::Result<()> { +/// - `env` is the environment +/// - `remote` is the remote to add +pub fn add(env: &mut Environment, remote: String) -> std::io::Result<()> { let mut remotes = Remote::load_list(env)?; - for remote in new_remotes { - if remotes.contains(remote.as_str()) { - eprintln!("Remote `{remote}` already exists"); - } else { - println!("Add remote `{remote}`"); - remotes.insert(Remote { - host: remote.clone(), - }); - } + if remotes.contains(remote.as_str()) { + eprintln!("Remote `{remote}` already exists"); + } else { + println!("Add remote `{remote}`"); + remotes.insert(Remote { + host: remote, + }); } Remote::save_list(env, remotes.into_iter())?; Ok(()) } -/// Removes one or several remotes. +/// Removes a remote. /// /// Arguments: -/// - `env` is the environment. -/// - `remotes` is the list of remotes to remove. -pub fn remove(env: &mut Environment, new_remotes: &[String]) -> std::io::Result<()> { +/// - `env` is the environment +/// - `remote` is the remote to remove +pub fn remove(env: &mut Environment, remote: String) -> std::io::Result<()> { let mut remotes = Remote::load_list(env)?; - for remote in new_remotes { - let existed = remotes.remove(remote.as_str()); - if !existed { - eprintln!("Remote `{remote}` not found"); - } + let existed = remotes.remove(remote.as_str()); + if !existed { + eprintln!("Remote `{remote}` not found"); } Remote::save_list(env, remotes.into_iter())?; Ok(()) diff --git a/client/src/remove.rs b/client/src/remove.rs index 51295ea..50b7400 100644 --- a/client/src/remove.rs +++ b/client/src/remove.rs @@ -1,14 +1,36 @@ -//! TODO doc +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ -use common::{anyhow::Result, Environment}; +//! TODO doc -// TODO ask for confirm before remove +use common::{ + anyhow::{bail, Result}, + Environment, +}; /// Removes the given list of packages. /// /// Arguments: /// - `names` is the list of packages to remove. /// - `env` is the blimp environment. -pub fn remove(_names: &[String], _env: &mut Environment) -> Result<()> { +pub fn remove(names: &[String], _env: &mut Environment) -> Result<()> { + if names.is_empty() { + bail!("must specify at least one package"); + } todo!() } diff --git a/client/src/update.rs b/client/src/update.rs index 2afed2a..2d2e4c9 100644 --- a/client/src/update.rs +++ b/client/src/update.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! This module handles packages list updating. use common::{ @@ -9,11 +27,11 @@ use common::{ /// Updates the packages list. pub async fn update(env: &mut Environment) -> Result<()> { let remotes = Remote::load_list(env) - .map_err(|error| anyhow!("Could not update packages list: {error}"))?; - println!("Updating from remotes..."); + .map_err(|error| anyhow!("could not update packages list: {error}"))?; + println!("Update from remotes..."); let mut futures = Vec::new(); for r in &remotes { - futures.push((&r.host, r.fetch_list())); + futures.push((&r.host, r.fetch_index())); } let mut failed = false; for (host, f) in futures { diff --git a/common/Cargo.toml b/common/Cargo.toml index 364fedc..5c3a78d 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -10,19 +10,18 @@ path = "src/lib.rs" anyhow = "1.0.97" bytes = "1.10.1" bzip2 = "0.5.2" -clap = "4.5.39" flate2 = "1.1.1" futures = "0.3.31" futures-util = "0.3.31" indicatif = "0.17.11" infer = "0.19.0" rand = "0.9.0" -reqwest = { version = "0.12.15", features = ["json", "stream"], optional = true } +reqwest = { version = "0.12.15", features = ["stream"], optional = true } serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" tar = "0.4.44" tokio = { version = "1.44.2", features = ["fs", "macros", "rt", "rt-multi-thread"] } tokio-util = { version = "0.7.15", features = ["io"] } +toml = { version = "0.9.5" } utils = { git = "https://github.com/maestro-os/maestro-utils" } xz2 = "0.1.7" diff --git a/common/src/download.rs b/common/src/download.rs index 12620ca..dc4e45f 100644 --- a/common/src/download.rs +++ b/common/src/download.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! This module handles files download. use crate::USER_AGENT; diff --git a/common/src/lib.rs b/common/src/lib.rs index fc991f3..7803953 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,11 +1,25 @@ -//! The blimp library is the core of the Blimp package manager. +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ -#![feature(io_error_more)] +//! The blimp library is the core of the Blimp package manager. pub use anyhow; -pub use clap; pub use flate2; -pub use serde_json; pub use tar; pub use tokio; pub use tokio_util; @@ -31,9 +45,8 @@ use std::{ /// The directory containing cached packages. const LOCKFILE_PATH: &str = "var/lib/blimp/.lock"; -// TODO -/*/// The path to directory storing information about installed packages. -const INSTALLED_DB: &str = "var/lib/blimp/installed";*/ +/// The path to directory storing information about installed packages. +const INSTALLED_DB: &str = "var/lib/blimp/installed"; /// The user agent for HTTP requests. pub const USER_AGENT: &str = concat!("blimp/", env!("CARGO_PKG_VERSION")); @@ -45,21 +58,40 @@ pub const USER_AGENT: &str = concat!("blimp/", env!("CARGO_PKG_VERSION")); /// /// The lockfile is destroyed when the environment is dropped. pub struct Environment { - /// The path to the sysroot of the environment. + /// The path to the sysroot of the environment sysroot: PathBuf, + /// Local repositories, if any + local_repos: Vec, + /// The architecture to install for + arch: String, } impl Environment { - /// Returns an instance for the environment with the given sysroot. + /// Tries to lock the environment at `sysroot` so that no other instance can access it at the + /// same time. + /// + /// Arguments: + /// - `sysroot` is the root directory of the system to lock + /// - `local_repos` is the list of local repositories, if any + /// - `arch` is the architecture to use. Defaults to the current /// - /// The function tries to lock the environment so that no other instance can access it at the - /// same time. If already locked, the function returns `None`. - pub fn with_root(sysroot: &Path) -> io::Result> { + /// If the environment is already locked, the function returns `None`. + pub fn acquire( + sysroot: &Path, + local_repos: Vec, + arch: Option, + ) -> io::Result> { let sysroot = sysroot.canonicalize()?; let path = sysroot.join(LOCKFILE_PATH); let acquired = lockfile::lock(&path)?; + #[cfg(target_arch = "x86")] + let default_arch = "x86"; + #[cfg(target_arch = "x86_64")] + let default_arch = "x86_64"; Ok(acquired.then_some(Self { sysroot, + local_repos, + arch: arch.unwrap_or_else(|| default_arch.to_owned()), })) } @@ -68,20 +100,56 @@ impl Environment { &self.sysroot } - /// Returns the installed version for the package with the given `name`. - pub fn get_installed_version(&self, _name: &str) -> Option { - todo!() + /// Returns the local repositories list + #[inline] + pub fn local_repos(&self) -> &[PathBuf] { + &self.local_repos + } + + /// Returns the repository architecture to use + #[inline] + pub fn arch(&self) -> &str { + &self.arch + } + + /// If installed, returns the version of the package with the given `name` + pub fn get_installed_version(&self, name: &str) -> Result> { + // Ensure the parent directory exists + let path = self.sysroot.join(INSTALLED_DB); + fs::create_dir_all(&path)?; + // Read file and get version + let path = path.join(name); + let res = fs::read_to_string(path); + let installed = match res { + Ok(i) => i, + Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None), + Err(e) => return Err(e.into()), + }; + let installed: InstalledPackage = toml::from_str(&installed)?; + Ok(Some(installed.desc.version)) + } + + /// Writes installed package information + fn write_installed_version(&self, pkg: &InstalledPackage) -> Result<()> { + // Ensure the parent directory exists + let path = self.sysroot.join(INSTALLED_DB); + fs::create_dir_all(&path)?; + // Write + let path = path.join(&pkg.desc.name); + let content = toml::to_string(pkg)?; + fs::write(path, content)?; + Ok(()) } /// Installs the given package. /// /// Arguments: - /// - `pkg` is the package to be installed. - /// - `archive_path` is the path to the archive of the package. + /// - `pkg` is the package to be installed + /// - `archive_path` is the path to the archive of the package /// /// The function does not resolve dependencies. It is the caller's responsibility to install /// them beforehand. - pub fn install(&self, _pkg: &Package, archive_path: &Path) -> Result<(), Box> { + pub fn install(&self, pkg: &Package, archive_path: &Path) -> Result<(), Box> { // Read archive let mut archive = util::read_package_archive(archive_path)?; // TODO Get hooks (pre-install-hook and post-install-hook) @@ -106,16 +174,19 @@ impl Environment { files.push(path); } // TODO Execute post-install-hook - // TODO add package to installed db + self.write_installed_version(&InstalledPackage { + desc: pkg.clone(), + files, + })?; Ok(()) } /// Installs a new version of the package, removing the previous. /// /// Arguments: - /// - `pkg` is the package to be updated. - /// - `archive_path` is the path to the archive of the new version of the package. - pub fn update(&self, _pkg: &Package, archive_path: &Path) -> Result<()> { + /// - `pkg` is the package to be updated + /// - `archive_path` is the path to the archive of the new version of the package + pub fn upgrade(&self, _pkg: &Package, archive_path: &Path) -> Result<()> { // Read archive let _archive = util::read_package_archive(archive_path)?; // TODO Get hooks (pre-update-hook and post-update-hook) diff --git a/common/src/lockfile.rs b/common/src/lockfile.rs index 762a771..0d1fbb5 100644 --- a/common/src/lockfile.rs +++ b/common/src/lockfile.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! The lock file allows to prevent several instances of the package manager from running at the //! same time. diff --git a/common/src/package.rs b/common/src/package.rs index fc618ad..ef65808 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! A package is a software that can be installed using the package manager. //! //! Packages are usually downloaded from a remote host. @@ -6,12 +24,27 @@ use crate::{ repository::Repository, version::{Version, VersionConstraint}, }; +use anyhow::{bail, Result}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt, fs, io, io::ErrorKind, path::PathBuf}; +use std::{ + collections::HashMap, + fmt, fs, io, + io::ErrorKind, + path::{Path, PathBuf}, +}; /// Tells whether the given package name is valid. pub fn is_valid_name(name: &str) -> bool { - name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') + if name.len() < 2 { + return false; + } + name.chars().enumerate().all(|(i, c)| { + if i == 0 { + c.is_ascii_lowercase() + } else { + c.is_ascii_lowercase() || c.is_ascii_digit() || "+-.".contains(c) + } + }) } /// Enumeration of package dependency resolution errors. @@ -83,29 +116,49 @@ impl fmt::Display for Dependency { /// A package's description. #[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct Package { - /// The package's name. + /// The package's name pub name: String, - /// The package's version. + /// The package's version pub version: Version, - /// The package's description. + /// The package's description pub description: String, - /// Dependencies required to build the package. - pub build_deps: Vec, - /// Dependencies required to run the package. - pub run_deps: Vec, + /// Dependencies required to build the package + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub build_dep: Vec, + /// Dependencies required to run the package + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub run_dep: Vec, } impl Package { - /// Loads a package from the given path. + /// Loads a package from the metadata file. /// - /// If the package does not exist, the function returns None. - pub fn load(path: PathBuf) -> io::Result> { - match fs::read_to_string(path.join("desc")) { - Ok(content) => Ok(Some(serde_json::from_str(&content)?)), + /// If the package does not exist, the function returns `None`. + pub fn load(metadata_path: &Path) -> Result> { + match fs::read_to_string(metadata_path) { + Ok(content) => Ok(Some(toml::from_str(&content)?)), Err(e) if e.kind() == ErrorKind::NotFound => Ok(None), - Err(e) => Err(e), + Err(e) => Err(e.into()), + } + } + + /// Validates the package's metadata + pub fn validate(&self) -> Result<()> { + if !is_valid_name(&self.name) { + bail!("invalid package name: {}", self.name); + } + for d in &self.build_dep { + if !is_valid_name(&d.name) { + bail!("invalid dependency name: {}", d.name); + } } + for d in &self.run_dep { + if !is_valid_name(&d.name) { + bail!("invalid dependency name: {}", d.name); + } + } + Ok(()) } /// Resolves the dependencies of the package and inserts them into the given `HashMap`. @@ -129,7 +182,7 @@ impl Package { let mut errors = vec![]; // TODO Add support for build dependencies - for d in &self.run_deps { + for d in &self.run_dep { // TODO check already installed packages // Get package in the installation list let pkg = packages.keys().find(|p| p.name == d.name); @@ -190,7 +243,7 @@ pub fn list_unmatched_dependencies( pkgs.iter() .flat_map(|(_, pkg)| { pkg.desc - .run_deps + .run_dep .iter() .filter(|dep| { pkgs.get(&dep.name) diff --git a/common/src/repository/mod.rs b/common/src/repository/mod.rs index cbe9ebc..8550c52 100644 --- a/common/src/repository/mod.rs +++ b/common/src/repository/mod.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! A repository contains packages that can be installed. //! //! A repository can be linked to a remote, from which packages can be fetched. @@ -6,14 +24,19 @@ pub mod remote; use crate::{ + package, package::Package, version::{Version, VersionConstraint}, }; +use anyhow::{bail, Result}; #[cfg(feature = "network")] use remote::Remote; -use std::{fs, io, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; -/// Structure representing a local repository. +/// A local repository. pub struct Repository { /// The path to the repository. path: PathBuf, @@ -27,13 +50,19 @@ impl Repository { /// Loads the repository at the given path. /// /// If the repository is invalid, the function returns an error. - pub fn load(path: PathBuf) -> io::Result { - Ok(Self { + pub fn load(path: PathBuf) -> Self { + Self { path, #[cfg(feature = "network")] remote: None, // TODO read from file - }) + } + } + + /// Returns the repository's path + #[inline] + pub fn get_path(&self) -> &Path { + &self.path } /// Returns the remote associated with the repository. @@ -42,39 +71,45 @@ impl Repository { self.remote.as_ref() } - /// Returns the path to the descriptor of the package with the given name `name` and version - /// `version`. - pub fn get_desc_path(&self, name: &str, version: &Version) -> PathBuf { - self.path.join(name).join(version.to_string()).join("desc") + /// Returns the path to a package's metadata + pub fn get_metadata_path(&self, arch: &str, name: &str, version: &Version) -> PathBuf { + self.path + .join("dist") + .join(arch) + .join(format!("{name}_{version}.meta")) } - /// Returns the path to the archive of the package with the given name `name` and version - /// `version`. - pub fn get_archive_path(&self, name: &str, version: &Version) -> PathBuf { + /// Returns the path to a package's archive + pub fn get_archive_path(&self, arch: &str, name: &str, version: &Version) -> PathBuf { self.path - .join(name) - .join(version.to_string()) - .join("archive") + .join("dist") + .join(arch) + .join(format!("{name}_{version}.tar.gz")) } - /// Tells whether the **archive** of the package with name `name` and version `version` is - /// present in the repository. - /// - /// Note: A package can be present in a repository with its archive. - pub fn is_in_cache(&self, name: &str, version: &Version) -> bool { - self.get_archive_path(name, version).exists() + /// Tells whether the **archive** of a package is present in the repository. + pub fn is_in_cache(&self, arch: &str, name: &str, version: &Version) -> bool { + self.get_archive_path(arch, name, version).exists() } - /// Returns the package with name `name` and version `version`. + /// Returns a package in the repository /// - /// If the package doesn't exist, the function returns None. - pub fn get_package(&self, name: &str, version: &Version) -> io::Result> { - let path = self.path.join(name).join(version.to_string()); - Package::load(path) + /// If the package does not exist, the function returns `None`. + pub fn get_package( + &self, + arch: &str, + name: &str, + version: &Version, + ) -> Result> { + if !package::is_valid_name(name) { + bail!("invalid package name: {name}"); + } + let path = self.get_metadata_path(arch, name, version); + Package::load(&path) } /// Returns the list of packages with each versions in the repository. - pub fn list_packages(&self) -> io::Result> { + pub fn list_packages(&self) -> Result> { fs::read_dir(&self.path)? .filter_map(|ent| { let ent = ent.ok()?; @@ -95,7 +130,7 @@ impl Repository { let version = Version::try_from(ent_name.as_ref()).ok()?; let ent_path = ent_path.join(version.to_string()); - Package::load(ent_path).transpose() + Package::load(&ent_path).transpose() }); Some(iter) }) @@ -106,26 +141,34 @@ impl Repository { /// Returns the package with the given name. /// /// Arguments: - /// - `name` is the name of the package. + /// - `arch` is the required architecture + /// - `name` is the name of the package /// - `version_constraint` is the version constraint to match. If no constraint is specified, - /// the latest version is selected. + /// the latest version is selected /// /// If the package doesn't exist, the function returns `None`. pub fn get_package_with_constraint( &self, + arch: &str, name: &str, version_constraint: Option<&VersionConstraint>, - ) -> io::Result> { - let version = fs::read_dir(self.path.join(name))? + ) -> Result> { + let base_path = self.path.join("dist").join(arch); + fs::read_dir(base_path)? .filter_map(|ent| { let ent = ent.ok()?; - - if ent.file_type().ok()?.is_dir() { - let name = ent.file_name(); - Version::try_from(name.to_str()?).ok() - } else { - None + if !ent.file_type().ok()?.is_file() { + return None; + } + let n = ent.file_name(); + let n = n.to_str()?; + // Retrieve package name and version + let name_version = n.strip_suffix(".meta")?; + let (n, version) = name_version.split_once('_')?; + if n != name { + return None; } + Version::try_from(version).ok() }) .filter(|version| { if let Some(c) = version_constraint { @@ -134,30 +177,31 @@ impl Repository { true } }) - .max(); - - match version { - Some(version) => self.get_package(name, &version), - None => Ok(None), - } + .max() + .and_then(|version| self.get_package(arch, name, &version).transpose()) + .transpose() } } // TODO Handle error reporting -/// Returns the package with name `name` and version `version` along with its associated +/// Returns the package with the given `arch`, `name` and `version` along with its associated /// repository. /// /// `repos` is the list of repositories to check on. /// -/// If the package doesn't exist, the function returns None. +/// If the package does not exist, the function returns `None`. pub fn get_package<'a>( repos: &'a [Repository], + arch: &str, name: &str, version: &Version, -) -> io::Result> { +) -> Result> { + if !package::is_valid_name(name) { + bail!("invalid package name: {name}"); + } Ok(repos .iter() - .filter_map(|repo| match repo.get_package(name, version) { + .filter_map(|repo| match repo.get_package(arch, name, version) { Ok(Some(pack)) => Some((repo, pack)), _ => None, }) @@ -165,26 +209,31 @@ pub fn get_package<'a>( } // TODO Handle error reporting -/// Returns the package with the given name along with its associated repository. +/// Returns the package with the given constraints along with its associated repository. /// /// Arguments: -/// - `name` is the name of the package. +/// - `arch` is the required architecture +/// - `name` is the name of the package /// - `version_constraint` is the version constraint to match. If no constraint is specified, the -/// latest version is selected. +/// latest version is selected /// -/// If the package doesn't exist, the function returns `None`. +/// If the package does not exist, the function returns `None`. pub fn get_package_with_constraint<'a>( repos: &'a [Repository], + arch: &str, name: &str, version_constraint: Option<&VersionConstraint>, -) -> io::Result> { +) -> Result> { + if !package::is_valid_name(name) { + bail!("invalid package name: {name}"); + } Ok(repos .iter() - .filter_map( - |repo| match repo.get_package_with_constraint(name, version_constraint) { + .filter_map(|repo| { + match repo.get_package_with_constraint(arch, name, version_constraint) { Ok(Some(pack)) => Some((repo, pack)), _ => None, - }, - ) + } + }) .max_by(|(_, p0), (_, p1)| p0.version.cmp(&p1.version))) } diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index 05341bb..b92d66d 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! A remote is a remote host from which packages can be downloaded. use crate::{package::Package, Environment, USER_AGENT}; @@ -12,7 +30,7 @@ use std::{ }; /// The file which contains the list of remotes. -const REMOTES_FILE: &str = "var/lib/blimp/remotes_list"; +const REMOTES_FILE: &str = "var/lib/blimp/remotes"; /// A remote host. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -60,8 +78,8 @@ impl Remote { Ok(()) } - /// Returns the remote's motd. - pub async fn fetch_motd(&self) -> Result { + /// Fetches the remote's motd + pub async fn fetch_motd(&self) -> Result> { let client = reqwest::Client::new(); let url = format!("https://{}/motd", &self.host); let response = client @@ -71,15 +89,20 @@ impl Remote { .await?; let status = response.status(); match status { - StatusCode::OK => Ok(response.text().await?), - _ => bail!("Failed to retrieve motd (status {status})"), + StatusCode::OK => { + let s = response.text().await?; + let metadata = toml::from_str(&s)?; + Ok(Some(metadata)) + } + StatusCode::NOT_FOUND => Ok(None), + _ => bail!("failed to retrieve remote metadata (status {status})"), } } - /// Fetches the list of all the packages from the remote. - pub async fn fetch_list(&self) -> Result> { + /// Fetches the remote's index + pub async fn fetch_index(&self) -> Result> { let client = reqwest::Client::new(); - let url = format!("https://{}/package", self.host); + let url = format!("https://{}/index", self.host); let response = client .get(url) .header("User-Agent", USER_AGENT) @@ -87,24 +110,26 @@ impl Remote { .await?; let status = response.status(); match status { - StatusCode::OK => Ok(response.json().await?), + StatusCode::OK => { + todo!() + } _ => bail!("Failed to retrieve packages list from remote (status {status})"), } } /// Returns the download URL for the given `package`. - pub fn download_url(&self, package: &Package) -> String { + pub fn download_url(&self, env: &Environment, package: &Package) -> String { format!( - "https://{}/package/{}/version/{}/archive", - self.host, package.name, package.version + "https://{}/dist/{}/{}_{}.tar.gz", + self.host, env.arch, package.name, package.version ) } /// Returns the download size of the package `package` in bytes. - pub async fn get_size(&self, package: &Package) -> Result { + pub async fn get_size(&self, env: &Environment, package: &Package) -> Result { let client = reqwest::Client::new(); client - .head(self.download_url(package)) + .head(self.download_url(env, package)) .header("User-Agent", USER_AGENT) .send() .await? diff --git a/common/src/util.rs b/common/src/util.rs index 7f552d6..3e917e0 100644 --- a/common/src/util.rs +++ b/common/src/util.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! This module implements utility functions. use bzip2::read::BzDecoder; diff --git a/common/src/version.rs b/common/src/version.rs index 9e04cdb..4e732c3 100644 --- a/common/src/version.rs +++ b/common/src/version.rs @@ -1,7 +1,29 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro 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. + * + * Maestro 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 + * Maestro. If not, see . + */ + //! The version structure represents the version of a package. use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; -use std::{cmp::Ordering, fmt, num::ParseIntError}; +use std::{ + cmp::{max, Ordering}, + fmt, + num::ParseIntError, +}; /// A package version. #[derive(Clone, Eq, Hash, PartialEq)] @@ -23,8 +45,9 @@ impl<'de> Deserialize<'de> for Version { where D: Deserializer<'de>, { - let s: &str = Deserialize::deserialize(deserializer)?; - s.try_into().map_err(Error::custom) + // toml does not like &str + let s: String = Deserialize::deserialize(deserializer)?; + s.as_str().try_into().map_err(Error::custom) } } @@ -45,7 +68,10 @@ impl TryFrom<&str> for Version { impl Ord for Version { fn cmp(&self, other: &Self) -> Ordering { - for (left, right) in self.components.iter().zip(other.components.iter()) { + let len = max(self.components.len(), other.components.len()); + for i in 0..len { + let left = self.components.get(i).unwrap_or(&0); + let right = self.components.get(i).unwrap_or(&0); let cmp = left.cmp(right); if cmp != Ordering::Equal { return cmp; @@ -104,8 +130,9 @@ impl<'de> Deserialize<'de> for VersionConstraint { where D: Deserializer<'de>, { - let s: &str = Deserialize::deserialize(deserializer)?; - s.try_into().map_err(Error::custom) + // toml does not like &str + let s: String = Deserialize::deserialize(deserializer)?; + s.as_str().try_into().map_err(Error::custom) } } diff --git a/man/man7/blimp.7 b/man/man7/blimp.7 new file mode 100644 index 0000000..148780e --- /dev/null +++ b/man/man7/blimp.7 @@ -0,0 +1,61 @@ +.TH BLIMP 7 +.SH NAME +blimp - package manager internals +.SH DESCRIPTION +.TP +.BR "Server-side files hierarchy" +.RS +Blimp downloads packages from a HTTP server. The server-side files hierarchy is designed in such a way to allow hosting packages without requiring a specific server program. + +Example: +.nf +.B - motd +.B - index +.B - dist/ +.B " "- x86_64/ +.B " "- _.meta +.B " "- _.tar.gz + ... +.B " "- x86/ + ... + ... +.B - i18n/ +.B " "- en/ +.B " "- _/ + ... + ... +.B " "- fr/ + ... + ... +.B - src/ +.B " "- _/ + ... + ... +.fi + +.B motd +the server's MOTD + +.B index +contains the list of all packages and their supported architecture, in TOML format. It is used by the client to determine the list of packages present on the server + +.B dist/ +contains packages (*.tar.gz) and their metadata (*.meta), sorted by CPU architecture + +.B i18n/ +contains internationalization files, sorted by locale + +.B src/ +contains package sources + +When updating the packages list, the client fetches +.B index +containing the list of packages. + +To download a package, the client looks up the required version in its local copy of the server's index. Then it downloads the +.B .meta +file, to look at the required dependencies. Dependencies are looked-up recursively until they are all found. Then, the client downloads all packages and installs them. +.RE +.SH "SEE ALSO" +.sp +\fBblimp\fP(1), \fBblimp-builder\fP(1) diff --git a/man/man8/blimp.8 b/man/man8/blimp.8 new file mode 100644 index 0000000..798a116 --- /dev/null +++ b/man/man8/blimp.8 @@ -0,0 +1,75 @@ +.TH BLIMP 8 +.SH NAME +blimp \- Maestro's package manager +.SH SYNOPSIS +.B blimp +update +.br +.B blimp +info +.br +.B blimp +install +.br +.B blimp +upgrade [package...] +.br +.B blimp +remove +.br +.B blimp +clean +.br +.B blimp +remote-list +.br +.B blimp +remote-add +.br +.B blimp +remote-remove +.SH DESCRIPTION +.PP +Maestro's package manager installs and upgrades packages on the system. It is able to download packages from remote servers, manages dependencies, and bootstrap new systems. +.TP +Each command has to specify an action to perform: +.TP +.B update +synchronizes packages information from remotes +.TP +.B info +prints information about the given package(s) +.TP +.B install +installs the given package(s) +.TP +.B upgrade +upgrades the given package(s). If no package is specified, the package manager updates every package that is not up-to-date +.TP +.B remove +removes the given package(s) +.TP +.B clean +cleans the cache +.TP +.B remote-list +lists remote servers +.TP +.B remote-add +adds a remote server +.TP +.B remote-remove +removes a remote server +.SH "ENVIRONMENT VARIABLES" +The following environment variables are relevant to blimp: +.TP +.B SYSROOT +path treated as the root directory of the system on which packages are installed, upgraded or removed. This is useful when bootstrapping a new system +.TP +.B LOCAL_REPO +paths to local package repositories, separated by `:`. +.B SYSROOT +does not apply to these paths +.SH "SEE ALSO" +.sp +\fBblimp\fP(7), \fBblimp-builder\fP(1) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index da5ad19..0750be7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2025-05-10" +channel = "nightly-2025-09-01" components = ["rustfmt", "rustc-dev", "rust-src", "clippy"] profile = "minimal" diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index d344ba6..0000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -config.json diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index 9168b6a..0000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "blimp-server" -version = "0.1.0" -edition = "2021" - -[dependencies] -axum = "0.8.3" -common = { path = "../common" } -envy = "0.4.2" -serde = { version = "1.0.219", features = ["derive"] } -tracing = "0.1.40" -tracing-subscriber = "0.3.18" diff --git a/server/default_config.json b/server/default_config.json deleted file mode 100644 index a9455ab..0000000 --- a/server/default_config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "port": 80, - - "motd": "This is a dummy blimp server", - - "repo_path": "path/to/public/repository" -} diff --git a/server/src/main.rs b/server/src/main.rs deleted file mode 100644 index c1e0bd7..0000000 --- a/server/src/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! The blimp server serves packages to be installed by the package manager. - -mod route; - -use axum::{routing::get, Router}; -use common::{repository::Repository, tokio}; -use serde::Deserialize; -use std::{io, path::PathBuf, sync::Arc}; - -/// The server's configuration. -#[derive(Deserialize)] -pub struct Config { - /// The server's port. - pub port: u16, - /// The server's motd. - pub motd: String, - /// The path to the repository containing the server's packages. - pub repo_path: String, -} - -/// The server's global context. -pub struct Context { - /// The server's motd. - motd: String, - /// The server's repository. - repo: Repository, -} - -#[tokio::main] -async fn main() -> io::Result<()> { - tracing_subscriber::fmt::init(); - let config: Config = envy::from_env().expect("configuration"); - let ctx = Arc::new(Context { - motd: config.motd, - repo: Repository::load(PathBuf::from(config.repo_path))?, - }); - let router = Router::new() - .route("/", get(route::root)) - .route("/motd", get(route::motd)) - .route("/package", get(route::package::list)) - .route("/package/:name/version/:version", get(route::package::info)) - .route( - "/package/:name/version/:version/archive", - get(route::package::archive), - ) - // TODO logging layer - .with_state(ctx); - let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.port)).await?; - axum::serve(listener, router).await -} diff --git a/server/src/route/mod.rs b/server/src/route/mod.rs deleted file mode 100644 index 8861ce2..0000000 --- a/server/src/route/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Endpoints implementations. - -pub mod package; - -use crate::Context; -use axum::extract::State; -use std::sync::Arc; - -pub async fn root() -> &'static str { - concat!("Blimp server version ", env!("CARGO_PKG_VERSION")) -} - -pub async fn motd(State(ctx): State>) -> String { - ctx.motd.clone() -} diff --git a/server/src/route/package.rs b/server/src/route/package.rs deleted file mode 100644 index 5f3f552..0000000 --- a/server/src/route/package.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Package endpoints. - -use crate::Context; -use axum::{ - body::Body, - extract::{Path, State}, - http::{header::CONTENT_TYPE, StatusCode}, - response::{IntoResponse, Response}, - Json, -}; -use common::{package, tokio::fs::File, tokio_util::io::ReaderStream, version::Version}; -use std::sync::Arc; -use tracing::error; - -/// Endpoint to list packages. -pub async fn list(State(ctx): State>) -> Response { - let res = ctx.repo.list_packages(); - match res { - Ok(packages) => Json(packages).into_response(), - Err(error) => { - error!(%error, "could not list packages"); - (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response() - } - } -} - -/// Endpoint to get information about a package. -pub async fn info( - Path((name, version)): Path<(String, String)>, - State(ctx): State>, -) -> Response { - if !package::is_valid_name(&name) { - return (StatusCode::BAD_REQUEST, "invalid package name").into_response(); - } - let Ok(version) = Version::try_from(version.as_str()) else { - return (StatusCode::BAD_REQUEST, "invalid package version").into_response(); - }; - let res = ctx.repo.get_package(&name, &version); - match res { - Ok(Some(pkg)) => Json(pkg).into_response(), - Ok(None) => (StatusCode::NOT_FOUND, "package or version not found").into_response(), - Err(error) => { - error!(%error, name, %version, "could read package"); - (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response() - } - } -} - -/// Endpoint to get the package's archive. -pub async fn archive( - Path((name, version)): Path<(String, String)>, - State(ctx): State>, -) -> Response { - if !package::is_valid_name(&name) { - return (StatusCode::BAD_REQUEST, "invalid package name").into_response(); - } - let Ok(version) = Version::try_from(version.as_str()) else { - return (StatusCode::BAD_REQUEST, "invalid package version").into_response(); - }; - // Check package exists - let res = ctx.repo.get_package(&name, &version); - match res { - Ok(Some(_)) => {} - Ok(None) => { - return (StatusCode::NOT_FOUND, "package or version not found").into_response() - } - Err(error) => { - error!(%error, name, %version, "could read package"); - return (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response(); - } - } - // Read archive - let archive_path = ctx.repo.get_archive_path(&name, &version); - let res = File::open(&archive_path).await; - let file = match res { - Ok(f) => f, - Err(error) => { - error!(%error, name, %version, path = %archive_path.display(), "could not read package archive"); - return (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response(); - } - }; - let body = Body::from_stream(ReaderStream::new(file)); - ([(CONTENT_TYPE, "application/x-gzip-compressed")], body).into_response() -}