commit 42a42d1921fb773a65abce5d2f8192ce817b8bde Author: Ward Date: Thu Feb 23 22:05:14 2023 +0200 first commit diff --git a/archive/.cz.toml b/archive/.cz.toml new file mode 100644 index 0000000..8b9c8ca --- /dev/null +++ b/archive/.cz.toml @@ -0,0 +1,7 @@ +[tool.commitizen] +name = "cz_conventional_commits" +version = "1.5.2" +tag_format = "$version" +version_files = [ + 'repolib/__version__.py:__version__' +] \ No newline at end of file diff --git a/archive/.readthedocs.yml b/archive/.readthedocs.yml new file mode 100644 index 0000000..07db85f --- /dev/null +++ b/archive/.readthedocs.yml @@ -0,0 +1,20 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + - epub + - htmlzip diff --git a/archive/.travis.yml b/archive/.travis.yml new file mode 100644 index 0000000..e098634 --- /dev/null +++ b/archive/.travis.yml @@ -0,0 +1,13 @@ +dist: bionic +addons: + apt: + packages: + - libdbus-1-dev + +language: python +python: "3.8" + +install: + - pip install -r requirements.txt +scripts: + - pytest --pylint diff --git a/archive/LICENSE b/archive/LICENSE new file mode 100644 index 0000000..01a7a80 --- /dev/null +++ b/archive/LICENSE @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . \ No newline at end of file diff --git a/archive/LICENSE.LESSER b/archive/LICENSE.LESSER new file mode 100644 index 0000000..13fc077 --- /dev/null +++ b/archive/LICENSE.LESSER @@ -0,0 +1,157 @@ +### GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the +terms and conditions of version 3 of the GNU General Public License, +supplemented by the additional permissions listed below. + +#### 0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the +GNU General Public License. + +"The Library" refers to a covered work governed by this License, other +than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +#### 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +#### 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +- a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or +- b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +#### 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a +header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +- a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the object code with a copy of the GNU GPL and this + license document. + +#### 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +- a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. +- c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. +- d) Do one of the following: + - 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + - 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. +- e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +#### 5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +- a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. +- b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +#### 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License "or any later version" applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, you +may choose any version of the GNU Lesser General Public License ever +published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/archive/README.rst b/archive/README.rst new file mode 100644 index 0000000..be9b897 --- /dev/null +++ b/archive/README.rst @@ -0,0 +1,167 @@ +======= +RepoLib +======= + +RepoLib is a Python library and CLI tool-set for managing your software +system software repositories. It's currently set up to handle APT repositories +on Debian-based linux distributions. + +RepoLib is intended to operate on DEB822-format sources. It aims to provide +feature parity with software-properties for most commonly used functions. + +Documentation +============= + +Documentation is available online at `Read The Docs `_. + + +Basic CLI Usage +--------------- + +RepoLib includes a CLI program for managing software repositories, +:code:`apt-manage` +. + +Usage is divided into subcommands for most tasks. Currently implemented commands +are: + + apt-manage add # Adds repositories to the system + apt-manage list # Lists configuration details of repositories + +Additional information is available with the built-in help: + + apt-manage --help + + +Add +^^^ + +Apt-manage allows entering a URL for a repository, a complete debian line, or a +Launchpad PPA shortcut (e.g. "ppa:user/repo"). It also adds signing keys for PPA +style repositories automatically. + + +List +^^^^ + +With no options, it outputs a list of the currently configured repositories on +the system (all those found in +:code:`/etc/apt/sources.list.d/` +. With a configured repository as an argument, it outputs the configuration +details of the specified repository. + +Remove +^^^^^^ + +Accepts one repository as an argument. Removes the specified repository. + +NOTE: The system repository (/etc/at/sources.list.d/system.sources) cannot be +removed. + +Source +^^^^^^ + +Allows enabling or disabling source code for the given repository. + +Modify +^^^^^^ + +Allows changing configuration details of a given repository + +Installation +============ + +From System Package Manager +--------------------------- + +If your operating system packages repolib, you can install it by running:: + + sudo apt install python3-repolib + + +Uninstall +^^^^^^^^^ + +To uninstall, simply do:: + + sudo apt remove python3-repolib + + +From PyPI +--------- + +Repolib is available on PyPI. You can install it for your current user with:: + + pip3 install repolib + +Alternatively, you can install it system-wide using:: + + sudo pip3 install repolib + +Uninstall +^^^^^^^^^ + +To uninstall, simply do:: + + sudo pip3 uninstall repolib + +From Git +-------- + +First, clone the git repository onto your local system:: + + git clone https://github.com/isantop/repolib + cd repolib + +Debian +------ + +On debian based distributions, you can build a .deb package locally and install +it onto your system. You will need the following build-dependencies: + + * debhelper (>=11) + * dh-python + * python3-all + * python3-setuptools + +You can use this command to install these all in one go:: + + sudo apt install debhelper dh-python python3-all python3-setuptools + +Then build and install the package:: + + debuild -us -uc + cd .. + sudo dpkg -i python3-repolib_*.deb + +Uninstall +^^^^^^^^^ + +To uninstall, simply do:: + + sudo apt remove python3-repolib + +setuptools setup.py +------------------- + +You can build and install the package using python3-setuptools. First, install +the dependencies:: + + sudo apt install python3-all python3-setuptools + +Then build and install the package:: + + sudo python3 ./setup.py install + +Uninstall +^^^^^^^^^ + +You can uninstall RepoLib by removing the following files/directories: + + * /usr/local/lib/python3.7/dist-packages/repolib/ + * /usr/local/lib/python3.7/dist-packages/repolib-\*.egg-info + * /usr/local/bin/apt-manage + +This command will remove all of these for you:: + + sudo rm -r /usr/local/lib/python3.7/dist-packages/repolib* /usr/local/bin/apt-manage diff --git a/archive/SECURITY.md b/archive/SECURITY.md new file mode 100644 index 0000000..d8f441d --- /dev/null +++ b/archive/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +The following versions of RepoLib currently receive updates for security: + +| Version | Supported | +| ------- | ------------------------ | +| 1.1.x | :heavy_check_mark: +| 1.0.x | :heavy_exclamation_mark: | + +Please note that only the current supported patch version of each release +is supported. + +## Reporting a Vulnerability + +When Filing an issue for a potential security vulnerability, please be sure +to include a `[SECURITY]` tag in the issue title. diff --git a/archive/TESTING.md b/archive/TESTING.md new file mode 100644 index 0000000..8979356 --- /dev/null +++ b/archive/TESTING.md @@ -0,0 +1,276 @@ +## Testing + +The following components of RepoLib should be tested with each revision: + +### CLI - apt-manage command + +The `apt-manage` command should be tested every single revision to ensure that +it is working correctly. This will also generally test that changes to the +underlying library have not affected anything. + +#### Adding repositories + +Adding repositories should be tested. + +1. Test Adding PPAs + +``` +sudo apt-manage -b add ppa:system76/proposed +``` +Verify that the information output and the resulting deb lines (at the end of +the output) appear correct for the given PPA. + +``` +sudo apt-manage add -s ppa:system76/proposed +``` +Verify that the `deb-src` is included in the `Types` field, then remove the +repository: `sudo apt-manage remove ppa-system76-proposed` + +``` +sudo apt-manage add -d ppa:system76/proposed +``` +Verify that the `Enabled` field is set to `no`, then remove the +repository: `sudo apt-manage remove ppa-system76-proposed` + +``` +sudo apt-manage add ppa:system76/proposed +``` +Verify that the command asks for verification before completing and displays +information about the PPA (similar to `add-apt-repository`). Verify that the +command fetches the signing key and adds it to the system. Verify that the +the correct `.list` file is added to `/etc/apt/sources.list.d`, then remove the +repository: `sudo apt-manage remove ppa-system76-proposed` + + +2. Test adding deb repositories + +``` +sudo apt-manage add --format list deb http://example.com/ubuntu focal main +``` +Verify that the added repository matches the given input, and that there is a +commented-out `deb-src` repository with it. Ensure that the added repository +file ends in `.list` and that the contents match legacy Deb format. + +Remove the repository with `sudo apt-manage remove + +3. Test adding URLs + +``` +sudo apt-manage add --format list http://example.com/ubuntu +``` +Verify that the repository is correctly expanded to include the `deb` at the +beginning, and the correct `{RELEASE} main` suites and components, where +{RELEASE} matches the current version codename. Verify that a matching `deb-src` +entry is added as well in the command output. + +4. Test adding Pop Development repositories + +``` +sudo apt-manage add popdev:master +``` + +Verify that the repository details are correct, that the `Signed-by` field +points to `/etc/apt/keyrings/popdev-archive-keyring.gpg`, that the key file +exists at that path. Then, delete the repostiory: +``` +sudo apt-manage remove popdev-master +``` + +#### Test Listing Details + +1. Test that all repos are listed + +``` +apt-manage list +``` +Verify that all configured repositories added to `/etc/apt/sources.list.d` are +printed in the command output. + +2. Test that details for all repositories are listed + +``` +apt-manage list -a +``` + +Verify that the specific configuration for each repository listed in step 1 is +presented in the output. + +3. Test that details for a specific repository are listed + +``` +apt-manage list system +``` + +Verify that the detailed configuration for only the specified repository is +listed in the output. + + +#### Removing repositories + +1. Test cancelling removal + +``` +sudo apt-manage remove example-com-ubuntu +sudo apt-manage remove example-com-ubuntu +sudo apt-manage remove example-com-ubuntu +``` + +On the first run, enter 'n' and press enter. Verify that the source is not +removed using `apt-manage list`. + +On the second run, simply press enter. Verify that the source is not removed by +using `apt-manage list`. + +On the third run, enter 'v' and press enter. Verify that the source is not removed by +using `apt-manage list`. + +2. Test removing sources + +``` +sudo apt-manage remove example-com-ubuntu +``` + +Enter 'y'. Verify that the source is removed using `apt-manage list`. + +3. Verify protection of system sources + +``` +sudo apt-manage remove system +``` + +Verify that the command returns an error and that no action is taken (even if a +system.sources file does not exist) using `apt-manage list` + + +#### Modifying a repository + +##### Setup + +Add a testing repository: + +``` +sudo apt-manage add popdev:master +apt-manage list popdev-master +``` +Verify that the details are correct in the output. + +1. Change Repository Name + +``` +sudo apt-manage modify popdev-master --name 'Testing Repo' +apt-manage list popdev-master +``` + +Verify that the name was updated in the final output to match the given input +`Testing Repo` + +2. Disable Repository + +``` +sudo apt-manage modify popdev-master --disable +apt-manage list popdev-master +``` +Ensure that the repository is now listed as `Enabled: no`. + +3. Add/Remove URI + +``` +sudo apt-manage modify popdev-master --add-uri http://example.com/ +apt-manage list popdev-master +``` +Ensure that the `http://example.com` URI was added to the source. + +``` +sudo apt-manage modify popdev-master --remove-uri http://example.com +apt-manage list popdev-master +``` +Ensure that the `http://example.com` URI was removed. + +4. Add/Remove Suite + +``` +sudo apt-manage modify popdev-master --add-suite xenial +apt-manage list popdev-master +``` +Ensure that the suite `xenial` was added to the source. + +``` +sudo apt-manage modify popdev-master --remove-suite xenial +apt-manage list popdev-master +``` + +Ensure that the suite `xenial` was removed from the source. + +5. Add/Remove Components + +``` +sudo apt-manage modify popdev-master --add-component 'universe multiverse' +apt-manage list popdev-master +``` +Ensure that the components `universe` and `multiverse` were added to the source. + +``` +sudo apt-manage modify popdev-master --remove-component 'main multiverse' +apt-manage list popdev-master +``` +Ensure that the components `main` and `multiverse` were removed from the source. + + +#### Enabling/Disabling Source Code + +##### Setup + +Add a testing repository: + +``` +apt-manage list popdev-master +``` +Verify that the details are correct in the output and that `Types:` is just +`deb`. + +1. Enable source code for one repo + +``` +sudo apt-manage modify --source-enable popdev-master +apt-manage list popdev-master +``` +Verify that the `Types:` is now `deb deb-src`. + +2. Disable source code for one repo + +``` +sudo apt-manage modify --source-disable popdev-master +apt-manage list popdev-master +``` +Verify that the `Types:` is now just `deb`. + +Finally, remove the testing repository: +``` +sudo apt-manage remove popdev-master +``` + +### Installation/upgrading (packaging tests) + +_This section is to test the installation behavior of the Debian package, +and doesn't need to be run for changes to repolib itself._ + +**Confirm add-apt-repository is installed when software-properties-common is not installed:** + +- [ ] Make sure `software-properties-common` is not installed. +- [ ] Remove `python3-repolib` with `sudo dpkg -r --force-all python3-repolib`. +- [ ] Ensure `/usr/bin/add-apt-repository` doesn't exist (if it does, remove it.) +- [ ] Install `python3-repolib` (can be done with `sudo apt install -f`). +- [ ] Ensure `/usr/bin/add-apt-repository` exists and is a link to `/usr/lib/repolib/add-apt-repository`. + +**Confirm add-apt-repository is not installed when software-properties-common is installed:** + +- [ ] Remove `python3-repolib` with `sudo dpkg -r --force-all python3-repolib`. +- [ ] Ensure `/usr/bin/add-apt-repository` doesn't exist (if it does, remove it.) +- [ ] Install `software-properties-common`, confirm `/usr/bin/add-apt-repository` now exists and is not a link. + - Can be done with `apt download software-properties-common python3-software-properties` + followed by `dpkg -i` on the downloaded files. +- [ ] Install `python3-repolib` again, confirm `/usr/bin/add-apt-repository` still isn't a link. + +**Confirm add-apt-repository is installed when software-properties-common is removed:** + +- [ ] Remove `software-properties-common` and confirm that `/usr/bin/add-apt-repository` still exists and is now a link. diff --git a/archive/apt-manage-spec.rst b/archive/apt-manage-spec.rst new file mode 100644 index 0000000..d8d31ea --- /dev/null +++ b/archive/apt-manage-spec.rst @@ -0,0 +1,172 @@ +=================== +Spec for apt-manage +=================== + +apt-manage is the CLI interface to manage software sources using RepoLib. It is +written in Python and shipped with RepoLib + +Commands +======== + +There are several commands provided by apt-manage:: + + add + remove + modify + list + source + +add +--- + +The add command is used for adding new software sources to the system. It +requires root. It has the following options:: + + --disable, -d + --source-code, -s + --expand, -e + +--disable +^^^^^^^^^ + +The --disable option (short form -d) will add the source and disable it +immediately + +--source-code +^^^^^^^^^^^^^ + +The --source-code option (short form -s) will enable source code for the +new source as well as binary packages. + +--expand +^^^^^^^^ + +The --expand options (short form -e) will show expanded details about the source +prior to adding it. + +remove +------ + +The remove command will remove the selected source. It has no options. Note that +the system sources cannot be removed. This requires root. + +modify +------ + +The modify command will allow making modifications to the specified repository. +It requires providing a repository to modify. It requires root. It accepts the +following options:: + + --enable, -e + --disable, -d + --add-suite + --remove-suite + --add-component + --remove-component + --add-uri + --remove-uri + --add-option + --remove-option + +--enable +^^^^^^^^ + +The enable option (short form -e) sets the source to be enabled. + +--disable +^^^^^^^^^ + +The disable option (short form -d) sets the source to be disabled. + +--add-suite +^^^^^^^^^^^ + +The --add-suite option attempts to add the specified suite or suites to the +source. If a given suite is already added to the source, it is ignored. Note +that legacy deb sources cannot have multiple suites. + +--remove-suite +^^^^^^^^^^^^^^ + +The --remove-suite option attempts to remove the specified suite or suites from +the source. If a given suite is not added to the source, it is ignored. The last +configured suite on a source cannot be removed. + +--add-component +^^^^^^^^^^^^^^^ + +The --add-component option attempts to add the specified component or components +to the source. If a given component is already added to the source, it is +ignored. + +--remove-component +^^^^^^^^^^^^^^^^^^ + +The --remove-component option attempts to remove the specified component or +components from the source. If a given component is not added to the source, it +is ignored. The last configured component on a source cannot be removed. + +--add-uri +^^^^^^^^^ + +The --add-uri option attempts to add the specified URI or URIs to the source. If +a given URI is already added to the source, it is ignored. Note that legacy deb +sources cannot have multiple URIs. + +--remove-uri +^^^^^^^^^^^^ + +The --remove-uri option attempts to remove the specified URI or URIs from the +source. If a given URI is not added to the source, it is ignored. The last +configured URI on a source cannot be removed. + +--add-option +^^^^^^^^^^^^ +The --add-option option attempts to add the given option or options as well as +values to the source. If a given option is already added to the source, it is +ignored. + +--remove-option +^^^^^^^^^^^^^^^ + +The --remove-option option attempts to remove the given option or options from +the source. If a given option isn't added to the source, it is ignored. + +list +---- + +The list command lists available software sources as well as details about +sources. With no further options, it lists all configured sources. With a +configured source, it lists details about the specified source. It has the +following option:: + + --verbose, -v + --legacy, -l + +--verbose +^^^^^^^^^ + +The --verbose option (short form -v) lists all details for all configured +software sources. It has no effect if a specific source is provided. + +source +------ + +The source command allows enabling or disabling source code in configured +sources. If a configured source is provided, this command will affect that +source. If no sources are provided, this command will affect all sources on the +system. Without options, it will list the status for source code packages. It +also accepts the following options, which require root:: + + --enable, -e + --disable, -d + +--enable +^^^^^^^^ + +The --enable option (short form -e) will enable source code packages. + +--disable +^^^^^^^^^ + +The --disable option (short form -d) will disable source code packages \ No newline at end of file diff --git a/archive/bin/add-apt-repository b/archive/bin/add-apt-repository new file mode 100755 index 0000000..5c2f26c --- /dev/null +++ b/archive/bin/add-apt-repository @@ -0,0 +1,194 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2020, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" +#pylint: disable=invalid-name +# Pylint will complain about our module name not being snake_case, however this +# is a command rather than a python module, and thus this is correct anyway. + +import argparse +import os +import subprocess + +import repolib + +system_codename = repolib.util.DISTRO_CODENAME + +system_components = [ + 'main', + 'universe', + 'multiverse', + 'restricted' +] + +system_suites = [ + system_codename, + f'{system_codename}-updates', + f'{system_codename}-security', + f'{system_codename}-backports', + f'{system_codename}-proposed', + 'updates', + 'security', + 'backports', + 'proposed', +] + +def get_args(): + parser = argparse.ArgumentParser( + prog='add-apt-repository', + description=( + 'add-apt-repository is a script for adding apt sources.list entries.' + '\nThis command has been deprecated in favor of `apt-manage`. See ' + '`apt-manage --help` for more information.' + ) + ) + + parser.add_argument( + 'sourceline', + metavar='' + ) + + group = parser.add_mutually_exclusive_group() + + group.add_argument( + '-m', + '--massive-debug', + dest='debug', + action='store_true', + help='Print a lot of debug information to the command line' + ) + + group.add_argument( + '-r', + '--remove', + action='store_true', + help='remove repository from sources.list.d directory' + ) + + group.add_argument( + '-s', + '--enable-source', + dest='source', + action='store_true', + help='Allow downloading of source packages from the repository' + ) + + parser.add_argument( + '-y', + '--yes', + action='store_true', + help='Assum yes to all queries' + ) + + parser.add_argument( + '-n', + '--no-update', + dest='noupdate', + action='store_true', + help='Do not update package cache after adding' + ) + + parser.add_argument( + '-u', + '--update', + action='store_true', + help='Update package cache after adding (legacy option)' + ) + + parser.add_argument( + '-k', + '--keyserver', + metavar='KEYSERVER', + help='Legacy option, unused.' + ) + + return parser + +parser = get_args() +args = parser.parse_args() + +command = ['apt-manage'] + +if args.debug: + command.append('-bb') + +sourceline = args.sourceline +run = True +remove = False + +if sourceline in system_components: + command.append('modify') + command.append('system') + if not args.remove: + command.append('--add-component') + else: + command.append('--remove-component') + +elif sourceline in system_suites: + command.append('modify') + command.append('system') + if not args.remove: + command.append('--add-suite') + else: + command.append('--remove-suite') + +else: + + if args.source: + command.append('source') + + elif args.remove: + remove = True + command.append('remove') + + else: + command.append('add') + if not args.yes: + command.append('--expand') + +if not remove: + command.append(sourceline) +else: + sources = repolib.get_all_sources() + comp_source = repolib.DebLine(sourceline) + for source in sources: + if comp_source.uris[0] in source.uris: + name = str(source.filename.name) + name = name.replace(".list", "") + name = name.replace(".sources", "") + command.append(name) + +run = True + +if os.geteuid() != 0: + print('Error: must run as root') + run = False + +if run: + subprocess.run(command) + + if not args.noupdate: + subprocess.run(['apt', 'update']) + +print('NOTE: add-apt-repository is deprecated in Pop!_OS. Use this instead:') +print_command = command.copy() +if '--expand' in print_command: + print_command.remove('--expand') +print(' '.join(print_command)) diff --git a/archive/bin/apt-manage b/archive/bin/apt-manage new file mode 100755 index 0000000..77ae812 --- /dev/null +++ b/archive/bin/apt-manage @@ -0,0 +1,85 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2020, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" +#pylint: disable=invalid-name +# Pylint will complain about our module name not being snake_case, however this +# is a command rather than a python module, and thus this is correct anyway. + +import argparse +import logging +import os +import sys + +import repolib +from repolib import command + +SOURCES_DIR = '/etc/apt/sources.list.d' + +def main(options=None): + """ Main function for apt-manage.""" + # Set up Argument Parsing. + parser = repolib.command.parser + + # Parse options + args = parser.parse_args() + if options: + args = parser.parse_args(options) + + if not args.debug: + args.debug = 0 + + if args.debug > 2: + args.debug = 2 + + verbosity = { + 0 : logging.WARN, + 1 : logging.INFO, + 2 : logging.DEBUG + } + + log = logging.getLogger('apt-manage') + handler = logging.StreamHandler() + formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s') + handler.setFormatter(formatter) + log.addHandler(handler) + log.setLevel(verbosity[args.debug]) + log.debug('Logging set up!') + repolib.set_logging_level(args.debug) + + if not args.action: + args = parser.parse_args(sys.argv[1:] + ['list']) + + log.debug('Arguments passed: %s', str(args)) + log.debug('Got command: %s', args.action) + + subcommand = args.action.capitalize() + + command = getattr(repolib.command, subcommand)(log, args, parser) + result = command.run() + if not result: + sys.exit(1) + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print('') + sys.exit(130) diff --git a/archive/data/bash-completion/apt-manage b/archive/data/bash-completion/apt-manage new file mode 100755 index 0000000..f881bc4 --- /dev/null +++ b/archive/data/bash-completion/apt-manage @@ -0,0 +1,42 @@ +# Debian apt-manage completion + +_apt_manage() +{ + local cur prev words cword package + _init_completion -n ':=' || return + + local special i + i=0 + for (( i=0; i < ${#words[@]}-1; i++ )); do + if [[ ${words[i]} == @(add|list|modify|remove|key) ]]; then + special=${words[i]} + fi + done + + if [[ -n $special ]]; then + case $special in + list|modify|remove|key) + COMPREPLY=( $( compgen -W '$(apt-manage list -n)' -- "$cur" ) ) + return + ;; + *) + ;; + esac + fi + + + if [[ "$cur" == -* ]]; then + return + # COMPREPLY=( $(compgen -W ' + # --help --disable --source-code --expand + # --verbose --legacy --no-names + # --enable --disable --name --add-suite --remove-suite + # --add-component --remove-component --add-uri --remove-uri + # ' -- "$cur") ) + else + COMPREPLY=( $(compgen -W 'add list modify remove key' \ + -- "$cur") ) + fi + +} && +complete -F _apt_manage apt-manage diff --git a/archive/data/org.pop_os.repolib.conf b/archive/data/org.pop_os.repolib.conf new file mode 100644 index 0000000..cadef8b --- /dev/null +++ b/archive/data/org.pop_os.repolib.conf @@ -0,0 +1,20 @@ + + + + system + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/archive/data/org.pop_os.repolib.policy b/archive/data/org.pop_os.repolib.policy new file mode 100644 index 0000000..7a6caef --- /dev/null +++ b/archive/data/org.pop_os.repolib.policy @@ -0,0 +1,20 @@ + + + + + Repoman + https://github.com/pop-os/repolib + x-system-software-sources + + + Modifies a Debian Repository in the system software sources. + Authentication is required to change software sources. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + \ No newline at end of file diff --git a/archive/data/org.pop_os.repolib.service b/archive/data/org.pop_os.repolib.service new file mode 100644 index 0000000..2f354e3 --- /dev/null +++ b/archive/data/org.pop_os.repolib.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.pop_os.repolib +Exec=/usr/bin/python3 /usr/lib/repolib/service.py +User=root diff --git a/archive/data/service.py b/archive/data/service.py new file mode 100755 index 0000000..b8d79dd --- /dev/null +++ b/archive/data/service.py @@ -0,0 +1,316 @@ +#!/usr/bin/python3 +''' + Copyright 2020 Ian Santopietro (ian@system76.com) + + This file is part of Repolib. + + Repolib 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. + + Repolib 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 Repolib. If not, see . +''' +#pylint: skip-file + +import shutil +import subprocess + +import gi +from gi.repository import GObject, GLib + +from pathlib import Path +import dbus +import dbus.service +import dbus.mainloop.glib +import time + +import repolib + +class RepolibException(dbus.DBusException): + _dbus_error_name = 'org.pop_os.repolib.RepolibException' + +class PermissionDeniedByPolicy(dbus.DBusException): + _dbus_error_name = 'org.pop_os.repolib.PermissionDeniedByPolicy' + +class AptException(Exception): + pass + +class Repo(dbus.service.Object): + def __init__(self, conn=None, object_path=None, bus_name=None): + dbus.service.Object.__init__(self, conn, object_path, bus_name) + + # These are used by PolKit to check privileges + self.dbus_info = None + self.polkit = None + self.enforce_polkit = True + + try: + self.system_repo = repolib.SystemSource() + except: + self.system_repo = None + + self.source = None + self.sources_dir = Path('/etc/apt/sources.list.d') + self.keys_dir = Path('/etc/apt/trusted.gpg.d') + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='as', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def add_apt_signing_key(self, cmd, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + print(cmd) + key_path = str(cmd.pop(-1)) + with open(key_path, mode='wb') as keyfile: + try: + subprocess.run(cmd, check=True, stdout=keyfile) + except subprocess.CalledProcessError as e: + raise e + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='ss', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def install_signing_key(self, src, dest, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + shutil.copy2(src, dest) + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='s', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def delete_signing_key(self, src, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + key_path = Path(src) + key_path.unlink() + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='s', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def delete_prefs_file(self, src, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + prefs_path = Path(src) + prefs_path.unlink() + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='ss', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def output_prefs_to_disk(self, path, contents, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + full_path = Path(path) + with open(full_path, mode='w') as output_file: + output_file.write(contents) + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='ss', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def output_file_to_disk(self, filename, source, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + full_path = self.sources_dir / filename + with open(full_path, mode='w') as output_file: + output_file.write(source) + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='ss', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def backup_alt_file(self, alt_file, save_file, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + alt_path = self.sources_dir / alt_file + save_path = self.sources_dir / save_file + if alt_path.exists(): + alt_path.rename(save_path) + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='s', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def delete_source_file(self, filename, sender=None, conn=None): + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + source_file = self.sources_dir / filename + source_file.unlink() + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='', out_signature='', + sender_keyword='sender', connection_keyword='conn' + ) + def exit(self, sender=None, conn=None): + mainloop.quit() + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='b', out_signature='b', + sender_keyword='sender', connection_keyword='conn' + ) + def set_system_source_code_enabled(self, enabled, sender=None, conn=None): + """ Enable or disable source code in the system source. + + Arguments: + enabled (bool): The new state to set, True = Enabled. + """ + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + if self.system_repo: + self.system_repo.load_from_file() + new_types = [repolib.util.AptSourceType.BINARY] + if enabled: + new_types.append(repolib.util.AptSourceType.SOURCE) + self.system_repo.types = new_types + self.system_repo.save_to_disk() + return enabled + return False + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='sb', out_signature='b', + sender_keyword='sender', connection_keyword='conn' + ) + def set_system_comp_enabled(self, comp, enable, sender=None, conn=None): + """ Enable or disable a component in the system source. + + Arguments: + comp (str): the component to set + enable (bool): The new state to set, True = Enabled. + """ + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + if self.system_repo: + self.system_repo.load_from_file() + self.system_repo.set_component_enabled(component=comp, enabled=enable) + self.system_repo.save_to_disk() + return True + return False + + @dbus.service.method( + "org.pop_os.repolib.Interface", + in_signature='sb', out_signature='b', + sender_keyword='sender', connection_keyword='conn' + ) + def set_system_suite_enabled(self, suite, enable, sender=None, conn=None): + """ Enable or disable a suite in the system source. + + Arguments: + suite (str): the suite to set + enable (bool): The new state to set, True = Enabled. + """ + self._check_polkit_privilege( + sender, conn, 'org.pop_os.repolib.modifysources' + ) + if self.system_repo: + self.system_repo.load_from_file() + self.system_repo.set_suite_enabled(suite=suite, enabled=enable) + self.system_repo.save_to_disk() + return True + return False + + @classmethod + def _log_in_file(klass, filename, string): + date = time.asctime(time.localtime()) + ff = open(filename, "a") + ff.write("%s : %s\n" %(date,str(string))) + ff.close() + + @classmethod + def _strip_source_line(self, source): + source = source.replace("#", "# ") + source = source.replace("[", "") + source = source.replace("]", "") + source = source.replace("'", "") + source = source.replace(" ", " ") + return source + + def _check_polkit_privilege(self, sender, conn, privilege): + # from jockey + '''Verify that sender has a given PolicyKit privilege. + sender is the sender's (private) D-BUS name, such as ":1:42" + (sender_keyword in @dbus.service.methods). conn is + the dbus.Connection object (connection_keyword in + @dbus.service.methods). privilege is the PolicyKit privilege string. + This method returns if the caller is privileged, and otherwise throws a + PermissionDeniedByPolicy exception. + ''' + if sender is None and conn is None: + # called locally, not through D-BUS + return + if not self.enforce_polkit: + # that happens for testing purposes when running on the session + # bus, and it does not make sense to restrict operations here + return + + # get peer PID + if self.dbus_info is None: + self.dbus_info = dbus.Interface(conn.get_object('org.freedesktop.DBus', + '/org/freedesktop/DBus/Bus', False), 'org.freedesktop.DBus') + pid = self.dbus_info.GetConnectionUnixProcessID(sender) + + # query PolicyKit + if self.polkit is None: + self.polkit = dbus.Interface(dbus.SystemBus().get_object( + 'org.freedesktop.PolicyKit1', + '/org/freedesktop/PolicyKit1/Authority', False), + 'org.freedesktop.PolicyKit1.Authority') + try: + # we don't need is_challenge return here, since we call with AllowUserInteraction + (is_auth, _, details) = self.polkit.CheckAuthorization( + ('unix-process', {'pid': dbus.UInt32(pid, variant_level=1), + 'start-time': dbus.UInt64(0, variant_level=1)}), + privilege, {'': ''}, dbus.UInt32(1), '', timeout=600) + except dbus.DBusException as e: + if e._dbus_error_name == 'org.freedesktop.DBus.Error.ServiceUnknown': + # polkitd timed out, connect again + self.polkit = None + return self._check_polkit_privilege(sender, conn, privilege) + else: + raise + + if not is_auth: + Repo._log_in_file('/tmp/repolib.log','_check_polkit_privilege: sender %s on connection %s pid %i is not authorized for %s: %s' % + (sender, conn, pid, privilege, str(details))) + raise PermissionDeniedByPolicy(privilege) + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + name = dbus.service.BusName("org.pop_os.repolib", bus) + object = Repo(bus, '/Repo') + + mainloop = GLib.MainLoop() + mainloop.run() \ No newline at end of file diff --git a/archive/data/zsh-completion/_apt-manage b/archive/data/zsh-completion/_apt-manage new file mode 100644 index 0000000..7ccf75b --- /dev/null +++ b/archive/data/zsh-completion/_apt-manage @@ -0,0 +1,70 @@ +#compdef apt-manage +typeset -A opt_args + +typeset -A opt_args + +_arguments -C \ + '1:cmd:->cmds' \ + '2:sources:->source_lists' \ + '*:: :->args' \ +&& ret=0 + +case "$state" in + (cmds) + local commands; commands=( + 'add' + 'list' + 'modify' + 'remove' + 'key' + ) + _describe -t commands 'command' commands && ret=0 + ;; + (source_lists) + local sources + sources=( $(apt-manage list -n)) + _describe -t sources 'source' sources && ret=0 + ;; + (args) + local arguments + arguments=( + # Main command + '--help' + # Add subcommand + '--disable' + '--source-code' + '--terse' + '--name' + '--identifier' + '--format' + '--skip-keys' + # Key Ssbcommand + '--path' + '--url' + '--ascii' + '--fingerprint' + '--keyserver' + '--remove' + # List subcommand + '--legacy' + '--verbose' + '--all' + '--file-names' + # Modify subcommand + '--enable' + '--source-enable' + '--source-disable' + '--add-uri' + '--add-suite' + '--add-component' + '--remove-uri' + '--remove-suite' + '--remove-component' + # Remove subcommand + '--assume-yes' + ) + _describe -t arguments 'argument' arguments && ret=0 + ;; +esac + +return 1 \ No newline at end of file diff --git a/archive/debian/README.source b/archive/debian/README.source new file mode 100644 index 0000000..752ec55 --- /dev/null +++ b/archive/debian/README.source @@ -0,0 +1,16 @@ +repolib for Debian +----------------- + +# RepoLib + +RepoLib is a Python library and CLI tool-set for managing your software +system software repositories. It's currently set up to handle APT repositories +on Debian-based linux distributions. + +RepoLib is intended to operate on DEB822-format sources. It aims to provide +feature parity with software-properties for most commonly used functions. + + + + -- Ian Santopietro Mon, 29 Apr 2019 14:53:49 -0600 + diff --git a/archive/debian/changelog b/archive/debian/changelog new file mode 100644 index 0000000..980b413 --- /dev/null +++ b/archive/debian/changelog @@ -0,0 +1,11 @@ +repolib (2.2.0~99pika2) kinetic; urgency=medium + + * Remove stupid post scripts + + -- Ward Nakchbandi Fri, 09 Oct 2022 21:38:00 +0300 + +repolib (2.2.0~99pika1) kinetic; urgency=medium + + * Initial Creation + + -- Ward Nakchbandi Fri, 09 Oct 2022 21:38:00 +0300 diff --git a/archive/debian/compat b/archive/debian/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/archive/debian/compat @@ -0,0 +1 @@ +11 diff --git a/archive/debian/control b/archive/debian/control new file mode 100644 index 0000000..e5b2775 --- /dev/null +++ b/archive/debian/control @@ -0,0 +1,43 @@ +Source: repolib +Section: python +Priority: optional +Maintainer: Ian Santopietro +Build-Depends: debhelper (>= 11), + dh-python, + python3-all, + python3-dbus, + python3-debian, + python3-distro, + python3-gnupg, + python3-lazr.restfulclient, + python3-launchpadlib, + python3-setuptools, + python3-distutils, + python3-pytest +Standards-Version: 4.1.3 +Homepage: https://github.com/isantop/repolib +XS-Python3-Version: >= 3.7 +Vcs-Browser: https://github.com/isantop/repolib +Vcs-Git: https://github.com/isantop/repolib.git + +Package: python3-repolib +Architecture: all +Depends: ${python3:Depends}, + ${misc:Depends}, + apt, + lsb-release, + python3-dbus, + python3-debian, + python3-gnupg +Recommends: python3-lazr.restfulclient, + python3-launchpadlib +Description: Repository Management for APT in Python (Python 3) + RepoLib is a Python library and CLI tool-set for managing your software + system software repositories. It's currently set up to handle APT repositories + on Debian-based linux distributions. + . + RepoLib is intended to operate on DEB822-format sources. It aims to provide + feature parity with software-properties for most commonly used functions. + . + This package installs the library for Python 3. + diff --git a/archive/debian/copyright b/archive/debian/copyright new file mode 100644 index 0000000..61e5672 --- /dev/null +++ b/archive/debian/copyright @@ -0,0 +1,68 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: repolib +Source: + +Files: * +Copyright: 2019 Ian Santopietro +License: BSD-2-Clause License + Copyright (c) 2019, Ian Santopietro + All rights reserved. + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + . + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: debian/* +Copyright: 2019 Ian Santopietro +License: GPL-2+ + This package 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 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +Files: repolib/ppa.py +Copyright: (c) 2004-2009 Canonical Ltd., (c) 2019 Ian Santopietro +License: GPL-2+ + This package 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 2 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". \ No newline at end of file diff --git a/archive/debian/rules b/archive/debian/rules new file mode 100755 index 0000000..af76d81 --- /dev/null +++ b/archive/debian/rules @@ -0,0 +1,24 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + +export PYBUILD_NAME=repolib +export PYBUILD_OPTION=--test-pytest + +%: + dh $@ --with python3 --buildsystem=pybuild + +## Uncomment to disable testing during package builds +## NOTE for QA or Engineering Review: This should not be uncommented in a +## PR. If it is, DO NOT APPROVE THE PR!!! +# override_dh_auto_test: +# true + +# If you need to rebuild the Sphinx documentation +# Add spinxdoc to the dh --with line +#override_dh_auto_build: +# dh_auto_build +# PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator +# PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator + diff --git a/archive/debian/source/format b/archive/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/archive/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/archive/debian/source/options b/archive/debian/source/options new file mode 100644 index 0000000..cb61fa5 --- /dev/null +++ b/archive/debian/source/options @@ -0,0 +1 @@ +extend-diff-ignore = "^[^/]*[.]egg-info/" diff --git a/archive/debian/triggers b/archive/debian/triggers new file mode 100644 index 0000000..c8cce33 --- /dev/null +++ b/archive/debian/triggers @@ -0,0 +1 @@ +interest /usr/bin/add-apt-repository diff --git a/archive/docs/Makefile b/archive/docs/Makefile new file mode 100644 index 0000000..298ea9e --- /dev/null +++ b/archive/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/archive/docs/aptmanage/add.rst b/archive/docs/aptmanage/add.rst new file mode 100644 index 0000000..d2e2121 --- /dev/null +++ b/archive/docs/aptmanage/add.rst @@ -0,0 +1,71 @@ +============== +Adding Sources +============== + +The ``add`` subcommand is used to add new repositories to the software sources. +You can specify a deb-line to configure into the system or a ``ppa:`` shortcut +to add the new source directly:: + + $ sudo apt-manage add deb http://apt.pop-os.org/ubuntu disco main + $ sudo apt-manage add ppa:system76/pop + +If an internet connection is available, ``apt-manage`` will additionally attempt +to install the signing key for any ``ppa:`` shortcuts added. + + +Options for adding sources +========================== + +Various options control adding sources to the system. + + +Source Code, Details, Disabling Sources +--------------------------------------- + +To enable source code for the added repository, use the ``--source-code`` flag:: + + $ apt-manage add --source-code ppa:system76/pop + +The new source can be disabled upon adding it using the ``--disable`` flag:: + + $ apt-manage add --disable ppa:system76/pop + +Details for the PPA are printed for review prior to adding the source by default. +This will print the generated configuration for the source as well as any +available details fetched for the source (typically only available for PPA +sources). To suppress this output, include ``--terse``. + + +Source File Format +------------------ + +The format which RepoLib saves the repository on disk in depends on the type of +repository being added, but regardless the ``--format`` flag can be used to +force either legacy ``.list`` format or modern ``.sources`` format:: + + apt-manage add popdev:master --format=list + apt-manage add ppa:system76/pop --format=sources + + +Names and Identifiers +--------------------- + +Names for PPA sources are automatically detected from Launchpad if an internet +connection is available. Otherwise they are automatically generated based on the +source type and details. Optionally, a name can be specified when the source is +added:: + + $ apt-manage add ppa:system76/pop --name "PPA for Pop_OS Software" + +System-identifiers determine how the source is subsequently located within RepoLib and +on the system. It matches the filename for the source's configuration file. It +is automatically generated based on the source type, or can be specified +manually upon creation using the ``--identifier`` flag:: + + $ apt-manage add ppa:system76/pop --identifier pop-ppa + +.. note:: + Even though ``apt-manage`` allows modifcation or management of DEB822-format + sources, it does not currently support adding them to the system directly. + DEB822 sources can be manually added or added using third-party tools, and + ``apt-manage`` will correctly operate on them subsequently. diff --git a/archive/docs/aptmanage/aptmanage.rst b/archive/docs/aptmanage/aptmanage.rst new file mode 100644 index 0000000..2e80cc4 --- /dev/null +++ b/archive/docs/aptmanage/aptmanage.rst @@ -0,0 +1,28 @@ +========== +Apt Manage +========== + +``apt-manage`` is a command line tool for managing your local software sources +using RepoLib. Run ``apt-manage`` standalone to get a listing of all of the +software repositories currently configured:: + + $ apt-manage + Configured sources: + system + pop-os-apps + ppa-system76-pop + + +``apt-manage`` operates on both DEB822-formated sources (located in the +``/etc/apt/sources.list.d/*.sources`` files) as well as using traditional +one-line format sources (in ``/etc/apt/sources.list.d/*.list`` files). + +.. toctree:: + :maxdepth 1 + :caption: apt-manage Documentation + + add + list + modify + remove + key diff --git a/archive/docs/aptmanage/key.rst b/archive/docs/aptmanage/key.rst new file mode 100644 index 0000000..88fe9a6 --- /dev/null +++ b/archive/docs/aptmanage/key.rst @@ -0,0 +1,78 @@ +===================== +Managing Signing Keys +===================== + +Signing keys are an important part of repository security and are generally +required to be used in repositories for all recent versions of APT. As previous +methods of handling Apt keys have been deprecated, Apt Manage provides easy +tools to use for managing signing keys for repositories in the ``key`` +subcommand. + +Most of the tools in the ``key`` subcommand are centered around adding a signing +key to a repository:: + + apt-manage key repo-id --fingerprint 63C46DF0140D738961429F4E204DD8AEC33A7AFF + +Apt Manage supports adding keys from a variety of sources: + + +Existing Keyring Files, --name, --path +====================================== + +``--name`` sets the :ref:`signed_by` value of the existing repository to the +name of a file within the system key configuration directory:: + + apt-manage key popdev-master --name popdev + +``--path`` sets the :ref:`signed_by` value of the existing repository to the +path of a file on disk:: + + apt-manage key popdev-master --path /etc/apt/keyrings/popdev-archive-keyring.gpg + + +Keyring Files Stored on the Internet, --url +=========================================== + +``--url`` will download a key file from the internet and install it into the +system, then set the repository to use that key:: + + apt-manage key popdev-master --url https://example.com/sigining-key.asc + + +Keys Stored on a Public Keyserver +================================= + +``--fingerprint`` will fetch the specified fingerprint from a public keyserver. +By default, keys will be fetched from ``keyserver.ubuntu.com``, but any SKS +keyserver can be specified using the ``--keyserver=`` argument:: + + apt-manage key ppa-system76-pop \ + --fingerprint=E6AC16572ED1AD6F96C7EBE01E5F8BBC5BEB10AE + + apt-manage key popdev-master \ + --fingerprint=63C46DF0140D738961429F4E204DD8AEC33A7AFF \ + --keyserver=https://keyserver.example.com/ + + +Adding ASCII-Armored Keys Directly, --ascii +=========================================== + +``--ascii`` Will take plain ascii data from the command line and add it to a new +keyring file, then set the repository to use that key:: + + apt-manage key popdev-master --ascii "$(/tmp/popdev-key.asc)" + + +Removing Keys +============= + +Generally, manually removing keys is not necessary because removing a source +automatically removes the key (if it is the only source using that key). However, +If there is a need to remove a key manually (e.g. the signing key has changed +and must be re-added), then removal is supported:: + + apt-manage key popdev-master --remove + +This will remove the key from the repository configuration and if no other +sources are using a particular key, it will also remove the keyring file from +disk. diff --git a/archive/docs/aptmanage/list.rst b/archive/docs/aptmanage/list.rst new file mode 100644 index 0000000..7eacea9 --- /dev/null +++ b/archive/docs/aptmanage/list.rst @@ -0,0 +1,79 @@ +============================= +Listing Configuration Details +============================= + +To get a list of all of the sources configured on the system, use the ``list`` +subcommand:: + + $ apt-manage list + Configured sources: + system - Pop_OS System Sources + pop-os-apps - Pop_OS Applications + ppa-system76-pop - Pop!_OS PPA + +The sources are listed with the system source (if detected/configured) first, +followed by all DEB822-format sources detected first, then by all one-line +format sources. The system-identifier (used to identify sources in the system) +is listed at the beginning of the line, and the name of the source is listed +after. + +Details of an individual source can be printed by specifying a source's +system-identifier:: + + $ apt-manage list ppa-system76-pop + Details for source ppa-system76-pop: + Name: Pop!_OS PPA + Enabled: yes + Types: deb deb-src + URIs: http://apt.pop-os.org/release + Suites: focal + Components: main + + +Listing all details of all sources at once +========================================== + +Details about all sources can be listed at once using the ``--verbose`` flag:: + + $ apt-manage list --verbose + Configured sources: + system - Pop_OS System Sources + Name: Pop_OS System Sources + Enabled: yes + Types: deb deb-src + URIs: http://us.archive.ubuntu.com/ubuntu/ + Suites: focal focal-security focal-updates focal-backports + Components: main restricted universe multiverse + + pop-os-apps - Pop_OS Applications + Name: Pop_OS Applications + Enabled: yes + Types: deb + URIs: http://apt.pop-os.org/proprietary + Suites: focal + Components: main + + ppa-system76-pop - Pop!_OS PPA + Name: Pop!_OS PPA + Enabled: yes + Types: deb deb-src + URIs: http://apt.pop-os.org/release + Suites: focal + Components: main + +Note +^^^^ + +Passing the ``--verbose`` flag only applies to listing all sources. It has no +effect if a source is specified using the system-identifier; in that case, only +the specified source is printed. Additionally, if there are sources files which +contain errors, the ``--verbose`` flag will print details about them, including +the contents of the files and the stack trace of the exception which caused the +error. + + +Legacy sources.list entries +=========================== + +The contents of the system ``sources.list`` file can be appended to the end of +the output using the ``--legacy`` flag. \ No newline at end of file diff --git a/archive/docs/aptmanage/modify.rst b/archive/docs/aptmanage/modify.rst new file mode 100644 index 0000000..98a6929 --- /dev/null +++ b/archive/docs/aptmanage/modify.rst @@ -0,0 +1,82 @@ +================= +Modifying Sources +================= + +Modifications can be made to various configured sources using the ``modify`` +subcommand. + +Enabling/Disabling Sources: --enable | --disable +================================================ + +Sources can be disabled, which prevents software/updates from being installed +from the source but keeps it present in the system configuration for later use +or records for later. To disable a source, use ``--disable``:: + + $ apt-manage modify ppa-system76-pop --disable + +To re-enable a source after it's been disabled, use ``--enable``:: + + $ apt-manage modify ppa-system76-pop --enable + + +Changing names of sources: --name +================================= + +RepoLib allows setting up human-readable names for use in GUIs or other +user-facing contexts. To set or change a name of a source, use ``--name``:: + + $ apt-manage modify ppa-system76-pop --name "Pop_OS PPA" + + +Suites: --add-suite | --remove-suite +==================================== + +Suites for sources can be added or removed from the configuration. In one-line +sources, these are added with multiple lines, since each one-line source can +have only one suite each. DEB822 sources can have multiple suites. + +To add a suite, use ``--add-suite``:: + + $ apt-manage modify ppa-system76-pop --add-suite groovy + +Use ``--remove-suite`` to remove a suite:: + + $ apt-manage modify ppa-system76-pop --remove-suite focal + + +Components: --add-component | --remove-component +================================================ + +Both types of source format can have multiple components for each source. Note +that all components for one-line format sources will share all of a source's +components. + +Components are managed similarly to suites:: + + $ apt-manage modify system --add-component universe + $ apt-manage modify system --remove-component restricted + + +URIs: --add-uri | --remove-uri +============================== + +DEB822 sources may contain an arbitrary number of URIs. One-line sources require +an additional line for each individual URI added. All suites on a source are all +applied to all of the URIs equally. + +URIs are managed similarly to both suites and components:: + + $ apt-manage modify system --add-uri http://apt.pop-os.org/ubuntu + $ apt-manage modify system --remove-uri http://us.archive.ubuntu.com/ubuntu + + +Notes +^^^^^ + +Multiple modifications may be applied on a single ``apt-manage modify`` calls:: + + $ apt-manage modify system --name "Pop_OS 20.10 System Sources" \ + --add-suite groovy \ + --remove-suite focal focal-proposed \ + --add-uri http://apt.pop-os.org/ubuntu \ + --remove-uri http://us.archive.ubuntu.com/ubuntu diff --git a/archive/docs/aptmanage/remove.rst b/archive/docs/aptmanage/remove.rst new file mode 100644 index 0000000..dfe1f03 --- /dev/null +++ b/archive/docs/aptmanage/remove.rst @@ -0,0 +1,9 @@ +================ +Removing Sources +================ + +To remove a source from the system configuration, use the ``remove`` +subcommand:: + + $ apt-manage remove ppa-system76-pop + \ No newline at end of file diff --git a/archive/docs/conf.py b/archive/docs/conf.py new file mode 100644 index 0000000..ee962e6 --- /dev/null +++ b/archive/docs/conf.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config +#pylint: skip-file + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import sphinx_rtd_theme + + +# -- Project information ----------------------------------------------------- + +project = 'RepoLib' +copyright = '2019-2020, Ian Santopietro' +author = 'Ian Santopietro' + +# The short X.Y version +version = '0.0.8' +# The full version, including alpha/beta/rc tags +release = '' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'RepoLibdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'RepoLib.tex', 'RepoLib Documentation', + 'Ian Santopietro', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'repolib', 'RepoLib Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'RepoLib', 'RepoLib Documentation', + author, 'RepoLib', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] diff --git a/archive/docs/deb822-format.rst b/archive/docs/deb822-format.rst new file mode 100644 index 0000000..b01e815 --- /dev/null +++ b/archive/docs/deb822-format.rst @@ -0,0 +1,276 @@ +.. _deb822-explanation: + +======================================= +Explanation of the DEB822 Source Format +======================================= + +The sources described in ``/etc/apt/sources.list.d/`` on a Debian-based OS are +designed to support any number of different active and inactive sources, as well +as a large variety of source media and transport protocols. The files describe +one or more sources each and contain multiline stanzas defining one or more +sources per stanza, with the most preferred source listed first. Information +about available packages and versions from each source is fetched using the +``apt update`` command, or with an equivalent command from a different frontend. + + +.. _sources-list-d: + +sources.list.d +============== + +APT source information is stored locally within the ``/etc/apt/sources.list.d`` +directory. In this directory are one or more files describing one or more +sources each. For :ref:`deb822-format` sources, each file needs to have the .sources +extension. The filenames may only contain letters, digits, underscore, hyphen, +and period characters. Files with other characters in their filenames will cause +APT to print a notice that it has ignore that file (unless the file matches a +pattern in the ``Dir::Ignore-Files-Silently`` configuration list, which will +force APT to silently ignore the file. + + +.. _one-line-format: + +One-Line-Style Format +===================== + +In order to understand some of the decisions behind using the :ref:`deb822-format` +sources, it is helpful to understand the older :ref:`one-line-format`. + +:ref:`one-line-format` sources occupy one line in a file ending in ``.list``. +The line begins with a type (i.e. ``deb`` or ``deb-src`` followed by options and +arguments for this type. Individual entries cannot be continued onto multiple +lines (hence the "one-line" portion of this format's name). Empty lines in +``.list`` files are ignored, and a ``#`` character anywhere on the line signifies +that the remainder of that line is a comment. Consequently an entry can be +disabled by commenting out that entire line (prefixing it with a ``#``). If +options are provided, they are space-separated and all together are enclosed +within square brackets ( ``[]`` ). Options allowing multiple values should +separate each value with a comma ( ``,`` ) and each option name is separated +from its values by an equals sign ( ``=`` ). + +This is the traditional format and is supported by all current APT versions. +It has the advantage of being relatively compact for single-sources and +relatively easy for humans to parse. + + +.. _one-line-disadvantages: + +Disadvantages +------------- + +Problems with the :ref:`one-line-format` begin when parsing entries via machine. +Traditional, optionless entries are relatively simple to parse, as each +different portion of the entry is separated with a space. With options, however, +this is no longer the case. The presence of options causes there to be no, 1, or +multiple segments of configuration between the type declaration and the URI. +Additionally, APT sources support a variety of URI schemas, with the capability +for extensions to add additional schemas on certain configurations. Thus, +supporting modern, optioned :ref:`one-line-format` source entries requires use +of either regular expressions or multi-level parsing in order to adequately +parse the entry. Further compounding this support is the fact that +:ref:`one-line-format` entries can have one or more components, preventing +parsing of sources backwards from the end towards the front. + +Consider the following examples:: + + deb [] http://example.com.ubuntu disco main restricted multiverse universe + deb [ arch=amd64 ] http://example.com/ubuntu disco main nonfree + deb [lang=en_US] http://example.com/ubuntu disco main restricted universe multiverse + deb [ arch=amd64,armel lang=en_US,en_CA ] http://example.com/ubuntu disco main + +Each of these entries are syntactically valid source entries, each cleanly +splits into eight segments when splitting on spaces. Depending on which entry +being parsed, the URI portion of the entry may be in index 2, 4, or 5 while +options (where present) may be in index 1, 2, or 3. If we want to work backwards, +then the URI is in either index -3, -4, or -6. The only segments guaranteed to +be present at any given index is the type. The situation is even more +complicated when considering that entries may have at a minimum 3 elements, and +at a maximum 12 or more elements. + +In addition to parsing difficulty, :ref:`one-line-format` entries may only +specify a single suite and URI per entry, meaning that having two active mirrors +for a given source requires doubling the number of entries configured. You must +also create an extra set of duplicated entries for each suite you want to +configure. This can make tracking down duplicated entries difficult for users +and leads to longer-than-necessary configuration. + + +.. _deb822-format: + +Deb822-style Format +=================== + +This format addresses the file-length, duplication, and machine-parsability +issues present in the :ref:`one-line-format`. Each source is configured in a +single stanza of configuration, with lines explicitly describing their +function for the source. They also allow for lists of values for most options, +meaning that mirrors or multiple suites can be defined within a single source. A +``#`` character at the beginning of a line marks the entire line as a comment. +Entries can again be disabled by commenting out each line within the stanza; +however, as a convenience this format also brings an ``Enabled:`` field which, +when set to ``no`` disables the entry as well. Removing this field or setting it +to ``yes`` re-enables it. Options have the same format as every other field, +thus a stanza may be parsed by checking the beginning of each line for a fixed +substring, and if the line doesn't match a known substring, it can be assumed +the line is an option and the line can be ignored. Unknown options are ignored +by all versions of APT. This has the unintentional side effect of adding +extensibility to the source; by selecting a carefully namespaced field name, +third-party applications and libraries can add their own fields to sources +without causing breakage by APT. This can include comments, version information, +and (as is the case with :ref:`repolib-module`, pretty, human-readable names. + +From the ``sources.list(5)`` manual page: + + This is a new format supported by apt itself since version 1.1. Previous + versions ignore such files with a notice message as described earlier. It is + intended to make this format gradually the default format, deprecating the + previously described one-line-style format, as it is easier to create, + extend and modify for humans and machines alike especially if a lot of + sources and/or options are involved. + +.. _deb822-technical: + +=================================== +DEB822 Source Format Specifications +=================================== + +Following is a description of each field in the deb822 source format. + + +.. _deb822-field-enabled: + +Enabled: +======== + +Enabled: (value: "yes" or "no", required: No, default: "yes") + Tells APT whether the source is enabled or not. Disabled sources are not + queried for package lists, effectively removing them from the system + sources while still allowing reference or re-enabling at any time. + +.. _deb822-field-types: + +Types: +====== + +Types: (value: "deb" or "deb-src", required: Yes) + Defines which types of packages to look for from a given source; either + binary: ``deb`` or source code: ``deb-src``. The ``deb`` type references a + typical two-level Debian archive providing packages containing pre-compiled + binary data intended for execution on user machines. The ``deb-src`` type + references a Debian distribution's source code in the same form as the ``deb`` + type. A ``deb-src`` line is required to fetch source pacakge indices. + + +.. _deb822-field-uris: + +URIs: +===== + +URIs: (value: string(s), required: Yes) + The URI must specify the base of the Debian distribution archive, from which + APT finds the information it needs. There must be a URI component present in + order for the source to be valid; multipls URIs can be configured + simultaneously by adding a space-separated list of URIs. + + A list of the current built-in URI Schemas supported by APT is available at + the `Debian sources.list manpage `_. + + +.. _deb822-field-suites: + +Suites: +======= + +Suites: (value: strings(s), required: Yes) + The Suite can specify an exact path in relation to the URI(s) provided, in + which case the :ref:`deb822-field-components` **must** be omitted and suite + **must** end with a slash ( ``/`` ). Alternatively, it may take the form of + a distribution version (e.g. a version codename like ``disco`` or ``artful`` + ). If the suite does not specify a path, at least one + :ref:`deb822-field-component` **must** be present. + + +.. _deb822-field-components: + +Components: +=========== + +Components: (value: string(s), required: see :ref:`deb822-field-suites`) + Components specify different sections of one distribution version present in + a Suite. If :ref:`deb822-field-suites` specifies an exact path, then no + Components may be specified. Otherwise, a component **must** be present. + + +.. _deb822-options: + +Options +======= + +Sources may specify a number of options. These options and their values will +generally narrow a set of software to be available from the source or in some +other way control what software is downloaded from it. An exhaustive list of +options can be found at the +`Debian sources.list manpage `_. + + +.. _deb822-fields-repolib: + +RepoLib-Specific Deb822 Fields +============================== + +RepoLib presently defines a single internal-use fields which it adds to deb822 +sources that it modifies. + +X-Repolib-Name: +--------------- + +X-Repolib-Name: (value: string, required: no, default: filename) + This field defines a formatted name for the source which is suitable for + inclusion within a graphical UI or other interface which presents source + information to an end-user. As a :ref:`repolib-module` specific field, this + is silently ignored by APT and other tools operating with deb822 sources and + is only intended to be utilized by :ref:`repolib-module` itself. + + +.. _deb822-examples: + +======== +Examples +======== + +The following specifies a binary and source-code source fetching from the +primary Ubuntu archive with multiple suites for updates as well as several components:: + + Enabled: yes + Types: deb deb-src + URIs: http://archive.ubuntu.com/ubuntu + Suites: disco disco-updates disco-security disco-backports + Components: main universe multiverse restricted + +This is a source for fetching Google's Chrome browser, which specifies a CPU +architecture option and a RepoLib Name:: + + X-Repolib-Name: Google Chrome + Enabled: yes + URIs: http://dl.google.com/linux/chrome/deb + Suites: stable + Components: main + Architectures: amd64 + +This specifies a source for downloading packages for System76's Pop!_OS:: + + X-Repolib-Name: Pop!_OS PPA + Enabled: yes + Types: deb + URIs: http://ppa.launchpad.net/system76/pop/ubuntu + Suites: disco + Components: main + +Following is a PPA source which has been disabled:: + + X-Repolib-Name: ZNC Stable + Enabled: no + Types: deb + URIs: http://ppa.launchpad.net/teward/znc/ubuntu + Suites: disco + Components: main diff --git a/archive/docs/index.rst b/archive/docs/index.rst new file mode 100644 index 0000000..264d01e --- /dev/null +++ b/archive/docs/index.rst @@ -0,0 +1,182 @@ +.. RepoLib documentation master file, created by + sphinx-quickstart on Wed May 1 13:49:01 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +=================================== +Welcome to RepoLib's documentation! +=================================== + +RepoLib is a Python library and CLI tool-set for managing your software +system software repositories. It's currently set up to handle APT repositories +on Debian-based linux distributions. + +RepoLib is intended to operate on DEB822-format sources as well as legacy +one-line format sources. It aims to provide feature parity with +software-properties for most commonly used functions. It also allows for +simple, automated conversion from legacy "one-line" style source to newer +DEB822 format sources. These sources will eventually deprecate the older +one-line sources and are much easier to parse for both human and machine. For a +detailed explanation of the DEB822 Source format, see :ref:`deb822-explanation`. + +RepoLib provides much faster access to a subset of ``SoftwareProperties`` +features than ``SoftwareProperties`` itself does. Its scope is somewhat more +limited because of this, but the performance improvements gained are substantial. +``SoftwareProperties`` also does not yet feature support for managing DEB822 +format sources, and instead only manages one-line sources. + +RepoLib is available under the GNU LGPL. + +.. contents:: Table of Contents + :local: + +.. toctree:: + :maxdepth: 4 + :caption: Developer Documentation + + library/developer + +.. toctree:: + :maxdepth: 1 + :caption: RepoLib Documentation + + deb822-format + +.. toctree:: + :maxdepth: 1 + :caption: apt-manage Documentation + + aptmanage/aptmanage + +============ +Installation +============ + +There are a variety of ways to install RepoLib + +From System Package Manager +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your operating system packages repolib, you can install it by running:: + + sudo apt install python3-repolib + + +Uninstall +""""""""" + +To uninstall, simply do:: + + sudo apt remove python3-repolib + + +From PyPI +^^^^^^^^^ + +Repolib is available on PyPI. You can install it for your current user with:: + + pip3 install repolib + +Alternatively, you can install it system-wide using:: + + sudo pip3 install repolib + +Uninstall +""""""""" + +To uninstall, simply do:: + + sudo pip3 uninstall repolib + +From Git +^^^^^^^^ + +First, clone the git repository onto your local system:: + + git clone https://github.com/isantop/repolib + cd repolib + +Debian +^^^^^^ + +On debian based distributions, you can build a .deb package locally and install +it onto your system. You will need the following build-dependencies: + + * debhelper (>= 11) + * dh-python + * lsb-release + * python3-all + * python3-dbus + * python3-debian + * python3-setuptools + * python3-distutils + * python3-pytest + * python3-gnupg + +You can use this command to install these all in one go:: + + sudo apt install debhelper dh-python python3-all python3-setuptools python3-gnupg + +Then build and install the package:: + + debuild -us -uc + cd .. + sudo dpkg -i python3-repolib_*.deb + +Uninstall +""""""""" + +To uninstall, simply do:: + + sudo apt remove python3-repolib + +setuptools setup.py +^^^^^^^^^^^^^^^^^^^ + +You can build and install the package using python3-setuptools. First, install +the dependencies:: + + sudo apt install python3-all python3-setuptools + +Then build and install the package:: + + sudo python3 ./setup.py install + +Uninstall +""""""""" + +You can uninstall RepoLib by removing the following files/directories: + + * /usr/local/lib/python3.7/dist-packages/repolib/ + * /usr/local/lib/python3.7/dist-packages/repolib-\*.egg-info + * /usr/local/bin/apt-manage + +This command will remove all of these for you:: + + sudo rm -r /usr/local/lib/python3.7/dist-packages/repolib* /usr/local/bin/apt-manage + + +| +| +| +| +| + +Copyright © 2019-2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . + diff --git a/archive/docs/installation.rst b/archive/docs/installation.rst new file mode 100644 index 0000000..a8279d1 --- /dev/null +++ b/archive/docs/installation.rst @@ -0,0 +1,106 @@ +============ +Installation +============ + +There are a variety of ways to install RepoLib + +From System Package Manager +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your operating system packages repolib, you can install it by running:: + + sudo apt install python3-repolib + + +Uninstall +""""""""" + +To uninstall, simply do:: + + sudo apt remove python3-repolib + + +From PyPI +^^^^^^^^^ + +Repolib is available on PyPI. You can install it for your current user with:: + + pip3 install repolib + +Alternatively, you can install it system-wide using:: + + sudo pip3 install repolib + +Uninstall +""""""""" + +To uninstall, simply do:: + + sudo pip3 uninstall repolib + +From Git +^^^^^^^^ + +First, clone the git repository onto your local system:: + + git clone https://github.com/isantop/repolib + cd repolib + +Debian +^^^^^^ + +On debian based distributions, you can build a .deb package locally and install +it onto your system. You will need the following build-dependencies: + + * debhelper (>= 11) + * dh-python + * lsb-release + * python3-all + * python3-dbus + * python3-debian + * python3-setuptools + * python3-distutils + * python3-pytest + * python3-gnupg + +You can use this command to install these all in one go:: + + sudo apt install debhelper dh-python python3-all python3-setuptools python3-gnupg + +Then build and install the package:: + + debuild -us -uc + cd .. + sudo dpkg -i python3-repolib_*.deb + +Uninstall +""""""""" + +To uninstall, simply do:: + + sudo apt remove python3-repolib + +setuptools setup.py +^^^^^^^^^^^^^^^^^^^ + +You can build and install the package using python3-setuptools. First, install +the dependencies:: + + sudo apt install python3-all python3-setuptools + +Then build and install the package:: + + sudo python3 ./setup.py install + +Uninstall +""""""""" + +You can uninstall RepoLib by removing the following files/directories: + + * /usr/local/lib/python3.7/dist-packages/repolib/ + * /usr/local/lib/python3.7/dist-packages/repolib-\*.egg-info + * /usr/local/bin/apt-manage + +This command will remove all of these for you:: + + sudo rm -r /usr/local/lib/python3.7/dist-packages/repolib* /usr/local/bin/apt-manage diff --git a/archive/docs/library/developer.rst b/archive/docs/library/developer.rst new file mode 100644 index 0000000..b7ba6b7 --- /dev/null +++ b/archive/docs/library/developer.rst @@ -0,0 +1,335 @@ +.. _repolib_module: + +======= +RepoLib +======= + +The :ref:`repolib-module` module simplifies working with APT sources, especially +those stored in the DEB822 format. It translates these sources into Python +Objects for easy reading, parsing, and manipulation of them within Python +programs. The program takes a user-defined sources filename and reads the +contents, parsing it into a Python object which can then be iterated upon and +which uses native data types where practicable. The :ref:`repolib-module` module +also allows easy creation of new DEB822-style sources from user-supplied data. + +.. toctree:: + :maxdepth: 1 + :caption: RepoLib + + source/source + ppa-module + enums + +================== +``repolib`` Module +================== + +The ``repolib`` Module is the main module for the package. It allows interfacing +with the various Classes, Subclasses, and Functions provided by RepoLib. + +Module-level Attributes +======================= + +There are a couple of module-level attributes and functions provided directly in +the module. + +.. _module-version + +``VERSION`` +----------- + +repolib.VERSION + Provides the current version of the library. + +.. _module_log_file_path + +``LOG_FILE_PATH`` +----------------- + +repolib.LOG_FILE_PATH + Stores the current path to the log file + +.. _module_log_level + +repolib.LOG_LEVEL + Stores the current logging level. Note: Change this level using the + :ref:`module_set_logging_level` function. + +.. _module_dirs + +Configuration directories +========================= + +repolib.KEYS_DIR +repolib.SOURCES_DIR + Stores the current :obj:`Pathlib.Path` pointing at the signing key and + sources directories, respectively. Used for building path names and reading + configuration. + +.. _module_distro_codename: + +DISTRO_CODENAME +=============== + +repolib.DISTRO_CODENAME + The current CODENAME field from LSB. If LSB is not available, it will + default to ``linux``. + + +.. _module_clean_chars + +CLEAN_CHARS +=========== + +repolib.CLEAN_CHARS + A ``dict`` which maps invalid characters for the :ref:`ident` attributes + which cannot be used and their replacements. These limitations are based on + invalid characters in unix-compatible filenames. + +.. _module_sources + +sources +======= + +repolib.sources + A :obj:`dict` storing all current sources configured on the system. To save + resources, this list is only loaded/parsed when + :ref:`module_load_all_sources` is called, since many simple operations don't + need the full list of currently-configured sources. + +.. _module_files + +files +===== + +repolib.files + A :obj:`dict` containing any source file objects present in the configured + sources dir (See :ref:`module_dirs`). This list is empty until + :ref:`module_load_all_sources` is called, since many simple operations don't + need the full list of currently-installed config files. + +.. _module_keys + +keys +==== + +repolib.keys + A :obj`dict` containing any installed repository signing keys on the system. + This list is empty until :ref:`module_load_all_sources` is called, since + many simple operations don'tneed the full list of + currently-installed keys. + +.. _module_compare_sources + +compare_sources() +----------------- + +repolib.compare_sources(source1, source2, excl_keys:list) -> bool + Compares two sources based on arbitrary criteria. + + This looks at a given list of keys, and if the given keys between the two + given sources are identical, returns True. + + Returns: :obj:`bool` + `True` if the sources are identical, otherwise `False`. + +source1, source2 +^^^^^^^^^^^^^^^^ +The two source objects to compare. + +excl_keys +^^^^^^^^^ +:obj:`list` A list of DEB822key names to ignore when comparing. Even if these +items don't match, this function still returns true if all other keys match. + +.. _module_combine_sources + +combine_sources() +----------------- + +repolib.combine_sources(source1, source2) -> None + Copies all of the data in `source2` and adds it to `source1`. + + This avoids duplicating data and ensures that all of both sources' data are + present in the unified source + +source1 +^^^^^^^ +The source into which both sources should be merged + +source2 +^^^^^^^ +The source from which to copy to `source1` + + +.. _module_url_validator + +url_validator() +--------------- + +repolib.url_validator(url: str) -> bool + Validates a given URL and attempts to see if it's a valid Debian respository + URL. Returns `True` if the URL appears to be valid and `False` if not. + +url +^^^ +:obj:`str`The url to validate + + +.. _module_validate_debline + +validate_debline() +================== + +repolib.util.validate_debline(line: str) -> bool + Validate if the provided debline ``line`` is valid or not. + + Returns ``True`` if ``line`` is valid, otherwise ``False``. + +line +^^^^ +:obj:`str` The line to validate + + +.. _module_strip_hashes + +strip_hashes() +-------------- + +repolib.strip_hashes(line: str) -> str + Strips leading hash (`#`) characters from a line and returns the result. + +line +^^^^ +:obj:`str` The line to strip + +.. _module_load_all_sources + +load_all_sources() +------------------ + +repolib.load_all_sources() -> None + + Loads all sources from the current system configuration. + + +.. _module-set_testing + +set_testing() +------------- + +repolib.set_testing(testing: bool = True) -> None + Enables or disables testing mode in Repolib + + When in testing mode, repolib will operate within a temporary directory + rather tan on your live system configuration. This can be used for testing + out changes to the program without worry about changes to the system config. + It's also used for unit testing. + + +testing +^^^^^^^ +:obj:`bool` - Wether testing mode should be enabled (`True`) or not (`False`) + + +Example +======= + +The following code as a Python program that creates in ``example.sources`` file +in `/etc/apt/sources.list.d` with some sample data, then modifies the suites +used by the source and prints it to the console, before finally saving the new, +modified source to disk:: + + import repolib + source = repolib.Source() + file = repolib.SourceFile(name='example') + + source.ident = 'example-source' + source.name = 'Example Source' + source.types = [repolib.SourceType.BINARY] + source.uris = ['http://example.com/software'] + source.suites = ['focal'] + source.components = ['main'] + file.add_source(source) + + print(source.ui) + + file.save() + +When run with the appropriate arguments, it prints the contents of the source to +console and then saves a new ``example.sources`` file:: + + $ + example-source: + Name: Example Source + Enabled: yes + Types: deb + URIs: http://example.com/software + Suites: focal + Components: main + + $ ls -la /etc/apt/sources.list.d/example.sources + -rw-r--r-- 1 root root 159 May 1 15:21 /etc/apt/sources.list.d/example.sources + +Below is a walkthrough of this example. + +Creating Source and File Objects +-------------------------------- + +The first step in using :ref:`repolib_module` is creating :ref:`source_object` +and :ref:`file_object`:: + + source = repolib.Source() + file = repolib.SourceFile(name='example') + +The :ref:`source_object` will hold all of the information for the source to be +created. The :ref:`file_object` represents the output file on disk, and allows +for advanced usage like multiple sources per file. + +Adding and Manipulating Data +---------------------------- + +The :ref:`source_object` contains attributes which describe the connfiguration +aspects of the source required to fetch and install software. Generally, these +attributes are lists of strings which describe different aspects of the source. +They can be set or retrieved like any other attributes:: + + source.uris = ['http://example.com/software'] + source.suites = ['focal'] + +This will add a ``focal`` suite to our source and add a URI from which to +download package lists. + +:ref:`source_object` also presents a dict-like interface for setting and getting +configuration data. Key names are case-insensitive and their order within the +object are preserved. + +Adding the Source to the File +----------------------------- + +Before the :ref:`source_object` can be saved, it needs to be added to a +:ref:`file_object`:: + + file.add_source(source) + +This will add `source` to the `file`'s lists of sources, as well as setting the +`source`'s file attribute to `file`. + +Saving Data to Disk +------------------- + +Once a source has the correct data and has been added to a file object, it can +be saved into the system configuration using :ref:`file-save`:: + + file.save() + +When called, this writes the sources stored in the file to disk. This does not +destroy the object, so that it may be further manipulated by the program. + +.. note:: + While data within the source or file objects can be manipulated after + calling :ref:`file-save`, any subsequent changes will not be automatically + written to disk as well. The :ref:`file-save` method must be called to + commit changes to disk. + +.. _source_object: diff --git a/archive/docs/library/enums.rst b/archive/docs/library/enums.rst new file mode 100644 index 0000000..b83031a --- /dev/null +++ b/archive/docs/library/enums.rst @@ -0,0 +1,45 @@ +.. _repolib-enums: + +============= +RepoLib Enums +============= + +RepoLib uses a variety of Enums to help ensure that data values are consistent +when they need to be set to specific string values for compatibility. + +.. _enum_sourceformat + +SourceFormat +============ + +repolib.SourceFormat() + Encodes the two formats of source files, either ``.list`` for legacy format + files or ``.sources`` for DEB822-formatted files. + + * ``DEFAULT`` - DEB822 formatting (value: ``"sources"``) + * ``LEGACY`` - Legacy, one-line formatting (value: ``"list"``) + + +.. _enum_sourcetype + +SourceType +========== + +repolib.SourceType() + Encodes the type of packages the repository provides (binary or source code). + + * ``BINARY`` - Binary package source type (value: ``"deb"``) + * ``SOURCECODE`` - Source code package type (value : ``"deb-src"``) + + +.. _enum_aptsourceenabled: + +AptSourceEnabled +================ + +repolib.AptSourceEnabled() + Used to encode whether the source is enabled or not. + + * ``TRUE`` - The source should be enabled (value: ``"yes"``). + * ``FALSE`` - The source should not be enabled (value: ``"no"``). + \ No newline at end of file diff --git a/archive/docs/library/source/exceptions.rst b/archive/docs/library/source/exceptions.rst new file mode 100644 index 0000000..bf5ddce --- /dev/null +++ b/archive/docs/library/source/exceptions.rst @@ -0,0 +1,50 @@ +.. _exceptions + +========== +Exceptions +========== + +RepoLib throws various forms of exceptions to halt execution of code when +incorrect input is detected or deviations from expected norms are encountered. + +.. _exc_repoerror + +RepoError() +----------- + +Repolib.RepoError() + This is base exception thrown by RepoLib. All other exceptions are + subclasses of this exception type. + +.. _exc_sourceerror + +SourceError() +------------- + +Repolib.Source.SourceError() + Raised when errors result from processing within the :obj:`source_object` or + one of it's subclasses. + +.. _exc_debparseerror + +DebParseError() +--------------- + +Repolib.parsedeb.DebParseError() + Raised due to problems parsing legacy Debian one-line repository lines. + +.. _exc_sourcefileerror + +SourceFileError() +----------------- + +Repolib.file.SourceFileError() + Raised due to errors handling :ref:`file_object` objects. + +.. _exc_keyfileerror + +KeyFileError() +-------------- + +Repolib.key.KeyFileError() + Raised due to errors loading/finding key files or :ref:`key_object` objects. diff --git a/archive/docs/library/source/file.rst b/archive/docs/library/source/file.rst new file mode 100644 index 0000000..c4f6f9f --- /dev/null +++ b/archive/docs/library/source/file.rst @@ -0,0 +1,172 @@ +.. _file_object: + +=========== +File object +=========== + +SourceFile objects are the interface between the OS storage and the repolib +:ref:`source_object` items. Files contain sources and comments and allow for +loading sources from and saving them to disk. They also assign idents to sources +in a way that ensures each source has a unique ident. + +Attributes +========== + +File objects contain the following attributes: + +.. _file-name + +name +---- + +SourceFile.name + The name of the file on disk. This does not include the file extension, as + that is stored in :ref:`file-format`. + +.. _file-path + +path +---- + +SourceFile.path + A ``Pathlib.Path`` object representing this file's actual path on disk. Note + that the file may not actually exist on disk yet. + +.. _file-format + +format +------ + +SourceFile.format + The format this file should be saved in. Saved as a :ref:`enum_sourceformat`. + +.. _file-contents + +contents +-------- + +SourceFile.contents + A :obj:`list` containing, in order, every comment line and source in this + file. + +.. _file-sources + +sources +------- + +SourceFile.sources + A :obj:`list` containing, in order, only the sources in this file. + + +Methods +======= + +.. _file-add_source + +add_source() +------------ + +SourceFile.add_source(source) -> None + Adds a given source to the file. This correctly appends the source to both + the :ref:`file-contents` and :ref:`file-sources` lists, and sets the + :ref:`source-file` attribute of the source to this file. + +source +^^^^^^ +The source to add to the file. + +.. _file-remove_source + +remove_source() +--------------- + +SourceFile.remove_source(ident: str) -> None + Removes the source with a specified ident from this file. + +ident +^^^^^ +The ident of the source to remove from the file. + +.. _file-get_source_by_ident + +get_source_by_ident() +--------------------- + +SourceFile.get_source_by_ident(ident: str) -> :obj:`Source` + Finds a source within this file given a specified ident and returns the + :ref:`source_object` matching that ident. If the file does not contain a + Source matching the given ident, raises a :ref:`exc_sourcefileerror`. + +ident +^^^^^ +The ident to look up. + +.. _file-reset_path + +SourceFile.reset_path() -> None + Attempts to detect the full path to the file given the :ref:`file-name` + attribute for this file. If the ``name.sources`` exists on disk, the path + will be set to that, otherwise if the ``name.list`` exists, it will be set + to that instead. Failing both, the path will fallback to ``name.sources`` as + a default. + +.. _file-load + +load() +------ + +SourceFile.load() -> None + Loads the file specified by :ref:`path` from disk, creating sources and + comments and appending them in order to the :ref:`file-contents` and + :ref:`file-sources` lists as appropriate. + +.. _file-save + +save() +------ + +SourceFile.save() -> None + Saves the file and any sources currently configured to disk. This method + must be called to commit changes to disk. If the file currently contains no + sources, then the file will instead be deleted. + + +Output +====== + +There are four attributes which contain the output of the files stored as +strings and which are ready for full output in the specified format. + +.. _file-deb822 + +deb822 +------ + +SourceFile.deb822 + Outputs the entire file as DEB822-formatted sources + +.. _file-legacy + +legacy +------ + +SourceFile.legacy + Outputs the entire file as one-line legacy-formatted deb lines + +.. _file-ui + +ui +-- + +SourceFile.ui + Outputs the file in a format for output through a UI (e.g. for preview or + external parsing.) + +.. _file-output + +output +------ + +SourceFile.output + Outputs the entire file in the format matching that configured in + :ref:`file-format`. diff --git a/archive/docs/library/source/key.rst b/archive/docs/library/source/key.rst new file mode 100644 index 0000000..684f71a --- /dev/null +++ b/archive/docs/library/source/key.rst @@ -0,0 +1,121 @@ +.. _key_object: + +========== +Key object +========== + +The SourceKey object is the representation for signing keys used to validate +software packages downloaded from repositories. + + +Attributes +========== + +.. _key-path + +path +---- + +SourceKey.path + A :obj:`Pathlib.Path` object pointing to this key's file location on disk. + +.. _key-gpg + +gpg +--- + +SourceKey.gpg + A :obj:`gnupg.GPG` object used for importing and manipulating GPG data. + +.. _key-data + +data +---- + +SourceKey.data + The binary data stored in this key, used to verify package signatures. + + +Methods +======= + +.. _key-reset_path + +reset_path() +------------ + +SourceKey.reset_path(name:str='', path:str='', suffix:str='archive-keyring') -> None + (Re)sets the path object for this key to the key file specified. If a ``name`` + is given, then the file is expected to be located within the system default + signing key directory ``/etc/apt/keyrings``. If a ``path`` is spefified, + then it is assumed to be the full path to the keyring file on disk. + +name +^^^^ +The name of a keyring file located within the system signing key directory, less +any suffix or file extensions. + +path +^^^^ +The absolute path to a keyring file located at any readable location on disk. + +suffix +^^^^^^ +A suffix to append to the file name before the file extension. (Default: +``'archive-keyring'``) + +.. _key-setup_gpg + +setup_gpg() +----------- + +SourceKey.setup_gpg() -> None + Sets up the :ref:`key-gpg` object for this key and loads key data from disk + (if present). + +.. _key-save_gpg + +save_gpg() +---------- + +SourceKey.save_gpg() -> None + Saves the GPG key data to disk. + +.. _key-delete_key + +delete_key() +------------ + +SourceKey.delete_key() -> None + Deletes the key file from disk. + +.. _key-load_key_data + +load_key_data() +--------------- + +SourceKey.load_key_data(raw=|ascii=|url=|fingerprint=) -> None + Fetches and loads a key from some remote source. Keys can be loaded from one + of: + * Raw internal data (:obj:`bytes`) + * ASCII-armored keys (:obj:`str`) + * Internet URL download (:obj:`str`) + * Public GPG Keyserver Fingerprint (:obj:`str`) + +raw=data (:obj:`bytes`) +^^^^^^^^^^^^^^^^^^^^^^^ +Load a key from raw binary data. This is ideal when importing a key which has +already been loaded from a binary data file or stream. + +ascii=armor (:obj:`str`) +^^^^^^^^^^^^^^^^^^^^^^^^ +Load a key from an ASCII-Armored string. + +url=url (:obj:`str`) +^^^^^^^^^^^^^^^^^^^^ +Download a key over an HTTPS connection. Note that keys should only be downloaded +from secure sources. + +fingerprint=fingerprint (:obj:`str`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Fetch the key specified by ``fingerprint`` from a public keyserver. diff --git a/archive/docs/library/source/shortcuts.rst b/archive/docs/library/source/shortcuts.rst new file mode 100644 index 0000000..9ba697c --- /dev/null +++ b/archive/docs/library/source/shortcuts.rst @@ -0,0 +1,56 @@ +.. _shortcuts + +==================== +Repository Shortcuts +==================== + +Repolib supports adding repositories via abbreviated shortcuts (such as +``ppa:system76/pop`` or ``popdev:master``) where the format of the shortcut is +given in the form of a prefix, and the data required to expand the shortcut into +a full repository is provided, with a colon character separating the two parts. +These function as subclasses of the :ref:`source_object` with specialized +functionality. Currently there is support for Launchpad PPAs and Pop_OS +Development branches automatically. + + +Launchpad PPAs +============== + +repolib.PPASource() + RepoLib has built-in support for adding Launchpad PPA shortcuts, similarly to + ``add-apt-repository``. Launchpad shortcuts take the general form:: + + ppa:owner-name/ppa-name + + If an internet connection is available, RepoLib can automatically fetch the + repository signing key as well as metadata like the description, human-readable + name, etc. + + +Pop_OS Development Branches +=========================== + +repolib.PopdevSource() + RepoLib can automatically add Pop_OS development branches for testing unstable + pre-release software before it is ready for general use. Development branch + shortcuts take the form:: + + popdev:branch-name + + If an internet connection is available, then RepoLib will also add the signing + key for the branch (if it is not already added). + + + +Adding Shortcut Repositories +============================ + +Shortcuts are generally added similarly to loading a different source from data, +the main difference being that in the call to :ref:`load_from_data` the only +element in the ``data`` list should be a string of the shortcut:: + + ppa_source = repolib.PPASource() + ppa_source.load_from_data(['ppa:system76/pop']) + + popdev_source = repolib.PopdevSource() + popdev_source.load_from_data(['popdev:master']) \ No newline at end of file diff --git a/archive/docs/library/source/source.rst b/archive/docs/library/source/source.rst new file mode 100644 index 0000000..bf6b3fe --- /dev/null +++ b/archive/docs/library/source/source.rst @@ -0,0 +1,281 @@ +.. _source_object: + +============= +Source object +============= + +The Source object is the base class for all of the sources used within RepoLib. +The Source class itself is a subclass of the deb822() class from the Python +Debian module, which provides a dict-like interface for setting data as well as +several methods for dumping data to files and other helpful functions. + +.. toctree:: + :maxdepth: 2 + :caption: Source + + deb + legacy-deb + ppa-object + system + +class repolib.Source (file=None) + Create a new :ref:`source_object`. + + + The Source object has the following attributes: + + * :ref:`ident` - The system-identifier to use for this source. + * :ref:`name` - The human-readable name of the source. (default: '') + * :ref:`enabled` - Whether the source is enabled or not at creation. + (default: True) + * :ref:`types` - A list of the types that the source should use. + (default: []) + * :ref:`uris` - A list of URIs from which to fetch software or check for + updates. (default: []) + * :ref:`suites` - Suites in which to narrow available software. (default: + []) + * :ref:`components` - Components of the source repository to enable. + (default: []) + * :ref:`signed_by` - The path to the signing key for this source + * :ref:`file` - The :ref:`file_object` for this source + * :ref:`key` - The :ref:`key_object` for this source. + * :ref:`twin_source`: - This source should be saved with both binary and + source code types enabled. + +The following decribe how each of these are used. + +.. _ident: + +ident +----- + +The ``ident`` is the system-identifier for this source. This determines the +filename the source will use on-disk as well as the way to specify a source to +load. + +.. _name: + +name +---- + +This is a human-readable and nicely-formatted name to help a user recognize +what this source is. Any unicode character is allowed in this field. If a +source is opened which doesn't have a name field, the filename will be used. + +:ref:`name` is a string value, set to ``''`` by default. If there is no name in +a loaded source, it will be set to the same as the filename (minus the +extension). + +This field maps to the ``X-Repolib-Name:`` field in the .sources file, which +is ignored by Apt and other standards-compliant sources parsers. + +.. _enabled: + +enabled +------- + +Apt sources can be disbaled without removing them entirely from the system. A +disabled source is excluded from updates and new software installs, but it can +be easily re-enabled at any time. It defaults to ``True``. + +This field maps to the ``Enabled:`` field in the .sources file. This is optional +per the DEB822 standard, however if set to anything other than ``no``, the +source is considered enabled. + +.. _types: + +types +----- + +Debian archives may contain either binary packages or source code packages, and +this value specifies which of those Apt should look for in the source. ``deb`` +is used to look for binary packages, while ``deb-src`` looks for source code +packages. RepoLib stores this value as a list of :ref:`aptsourcetype-enum`s, and +defaults to ``[AptSourceType.BINARY]``. + +This field maps to the ``Types:`` field in the sources file. + +.. _uris: + +uris +---- + +A list of string values describing the URIs from which to fetch package lists +and software for updates and installs. The currently recognized URI types are: + + * file + * cdrom + * http + * ftp + * copy + * rsh + * ssh + +DEB822 sources may directly contain an arbitrary number of URIs. Legacy sources +may also have multiple URIs; however, these require writing a new deb line for +each URI as the one-line format only allows a single URI per source. + +.. note:: + Although these are the currently-recognized official URI types, Apt can be + extended with additional URI schemes through extension packages. Thus it is + **not** recommended to parse URIs by type and instead rely on user input + being correct and to throw exceptions when that isn't the case. + +.. _suites: + +suites +------ + +The Suite, also known as the **distribution** specifies a subdirectory of the +main archive root in which to look for software. This is typically used to +differentiate versions for the same OS, e.g. ``disco`` or ``cosmic`` for Ubuntu, +or ``squeeze`` and ``unstable`` for Debian. + +DEB822 Sources allow specifying an arbitrary number of suites. One-line sources +also support multiple suites, but require an additional repo line for each as +the one-line format only allows a single suite for each source. + +This value maps to the ``Suites:`` field in the sources file. + +.. _components: + +components +---------- + +This value is a list of strings describing the enabled distro components to +download software from. Common values include ``main``, ``restricted``, +``nonfree``, etc. + +.. _signed_by + +signed_by +--------- +The path to the keyring containing the signing key used to verify packages +downloaded from this repository. This should generally match the +:ref:`key-path` attribute for this source's :ref:`key` object. + +.. _source-file + +file +---- +The :ref:`file_object` for the file which contains this source. + +.. _key + +key +--- +The :ref:`key_object` for this source. + +======= +Methods +======= + + +.. _get_description + +Source.get_description() -> str + Returns a :obj:`str` containing a UI-compatible description of the source. + +.. _reset_values: + +reset_values() +------------- + +Source.reset_values() + Initializes the Source's attributes with default data in-order. This is + recommended to ensure that the fields in the underlying deb822 Dict are + in order and correctly capitalized. + +.. _load_from_data + +load_from_data() +---------------- + +Source.load_from_data(data: list) -> None + Loads configuration information from a list of data, rather than using + manual entry. The data can either be a list of strings with DEB822 data, or + a single-element list containing a one-line legacy line. + +data +^^^^ +The data load load into the source. If this contains a legacy-format one-line +repository, it must be a single-element list. Otherwise, it should contain a +list of strings, each being a line from a DEB822 configuration. + +.. _generate_default_ident + +generate_default_ident() +------------------------ + +Source.generate_default_ident(prefix: str = '') -> str + Generates a suitable default ident, optionally with a prefix, and sets it. + The generated ident is also returned for processing convenience. + +prefix +^^^^^^ +The prefix to prepend to the ident. + +.. _generate_default_name + +generate_default_name() +_______________________ + +Source.generate_default_name() -> + Generates a default name for the source and sets it. The generated name is + also returned for convenience. + +.. _load_key + +load_key() +__________ + +Source.load_key(ignore_errors: bool = True) -> None + Finds the signing key for this source spefified in :ref:`signed_by` and + loads a :ref:`key_object` for it. + +ignore_errors +^^^^^^^^^^^^^ +If `False`, raise a :ref:`exc_sourceerror` if the key can't be loaded or doesn't +exist. + +.. _source_save + +save() +------ + +Source.save() + Proxy method for the :ref:`file-save` method for this source's + :ref:`file_object`. + +Output Properties +================= + +There are three output properties which contain the current source data for +output in a variety of formats. + +.. _source_deb822 + +deb822 +------ + +Source.deb822 + A representation of the source data as a DEB822-formatted string + +.. _source_legacy + +legacy +------ + +Source.legacy + A one-line formatted string of the source. It :ref:`twin_source` is ``True``, + then there will additionally be a ``deb-src`` line following the primary + line. + +.. _source_ui + +ui +-- + +Source.ui + A representation of the source with certain key names translated to better + represent their use in a UI for display to a user. diff --git a/archive/repolib/__init__.py b/archive/repolib/__init__.py new file mode 100644 index 0000000..4b4b7e5 --- /dev/null +++ b/archive/repolib/__init__.py @@ -0,0 +1,144 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging +import logging.handlers as handlers + +from pathlib import Path + +from . import __version__ + +VERSION = __version__.__version__ + +from .file import SourceFile, SourceFileError +from .source import Source, SourceError +from .shortcuts import PPASource, PopdevSource, shortcut_prefixes +from .key import SourceKey, KeyFileError +from . import util +from . import system + +LOG_FILE_PATH = '/var/log/repolib.log' +LOG_LEVEL = logging.WARNING +KEYS_DIR = util.KEYS_DIR +SOURCES_DIR = util.SOURCES_DIR +TESTING = util.TESTING +KEYSERVER_QUERY_URL = util.KEYSERVER_QUERY_URL +DISTRO_CODENAME = util.DISTRO_CODENAME +PRETTY_PRINT = util.PRETTY_PRINT +CLEAN_CHARS = util.CLEAN_CHARS + +try: + from systemd.journal import JournalHandler + systemd_support = True +except ImportError: + systemd_support = False + +## Setup logging +stream_fmt = logging.Formatter( + '%(name)-21s: %(levelname)-8s %(message)s' +) +file_fmt = logging.Formatter( + '%(asctime)s - %(name)-21s: %(levelname)-8s %(message)s' +) +log = logging.getLogger(__name__) + +console_log = logging.StreamHandler() +console_log.setFormatter(stream_fmt) +console_log.setLevel(LOG_LEVEL) + +# file_log = handlers.RotatingFileHandler( +# LOG_FILE_PATH, maxBytes=(1048576*5), backupCount=5 +# ) +# file_log.setFormatter(file_fmt) +# file_log.setLevel(LOG_LEVEL) + +log.addHandler(console_log) +# log.addHandler(file_log) + +log_level_map:dict = { + 0: logging.WARNING, + 1: logging.INFO, + 2: logging.DEBUG +} + +if systemd_support: + journald_log = JournalHandler() # type: ignore (this is handled by the wrapping if) + journald_log.setLevel(logging.INFO) + journald_log.setFormatter(stream_fmt) + log.addHandler(journald_log) + +log.setLevel(logging.DEBUG) + +def set_testing(testing:bool = True) -> None: + """Puts Repolib into testing mode""" + global KEYS_DIR + global SOURCES_DIR + + util.set_testing(testing=testing) + KEYS_DIR = util.KEYS_DIR + SOURCES_DIR = util.SOURCES_DIR + + +def set_logging_level(level:int) -> None: + """Set the logging level for this current repolib + + Accepts an integer between 0 and 2, with 0 being the default loglevel of + logging.WARNING, 1 being logging.INFO, and 2 being logging.DEBUG. + + Values greater than 2 are clamped to 2. Values less than 0 are clamped to 0. + + Note: This only affects console output. Log file output remains + at logging.INFO + + Arguments: + level(int): A logging level from 0-2 + """ + if level > 2: + level = 2 + if level < 0: + level = 0 + LOG_LEVEL = log_level_map[level] + console_log.setLevel(LOG_LEVEL) + +RepoError = util.RepoError +SourceFormat = util.SourceFormat +SourceType = util.SourceType +AptSourceEnabled = util.AptSourceEnabled + +scrub_filename = util.scrub_filename +url_validator = util.url_validator +prettyprint_enable = util.prettyprint_enable +validate_debline = util.validate_debline +strip_hashes = util.strip_hashes +compare_sources = util.compare_sources +combine_sources = util.combine_sources +sources = util.sources +files = util.files +keys = util.keys +errors = util.errors + +valid_keys = util.valid_keys +options_inmap = util.options_inmap +options_outmap = util.options_outmap +true_values = util.true_values + +load_all_sources = system.load_all_sources diff --git a/archive/repolib/__version__.py b/archive/repolib/__version__.py new file mode 100644 index 0000000..f28f7ec --- /dev/null +++ b/archive/repolib/__version__.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" +__version__ = "2.1.1" diff --git a/archive/repolib/command/__init__.py b/archive/repolib/command/__init__.py new file mode 100644 index 0000000..86f8e6c --- /dev/null +++ b/archive/repolib/command/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +from .argparser import get_argparser +from .add import Add +from .list import List +from .remove import Remove +from .modify import Modify +from .key import Key + +parser = get_argparser() diff --git a/archive/repolib/command/add.py b/archive/repolib/command/add.py new file mode 100644 index 0000000..5ba3f14 --- /dev/null +++ b/archive/repolib/command/add.py @@ -0,0 +1,267 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +from httplib2.error import ServerNotFoundError +from urllib.error import URLError + +from .. import util +from ..source import Source, SourceError +from ..file import SourceFile, SourceFileError +from ..shortcuts import ppa, popdev, shortcut_prefixes +from .command import Command, RepolibCommandError + +class Add(Command): + """Add subcommand + + Adds a new source into the system. Requests root, if not present. + + Options: + --disable, d + --source-code, -s + --terse, -t + --name, -n + --identifier, -i + --format, -f + --skip-keys, -k + """ + + @classmethod + def init_options(cls, subparsers): + """ Sets up the argument parser for this command. + + Returns: argparse.subparser + The subparser for this command + """ + + sub = subparsers.add_parser( + 'add', + help='Add a new repository to the system' + ) + + sub.add_argument( + 'deb_line', + nargs='*', + default=['822styledeb'], + help='The deb line of the repository to add' + ) + sub.add_argument( + '-d', + '--disable', + action='store_true', + help='Add the repository and then set it to disabled.' + ) + sub.add_argument( + '-s', + '--source-code', + action='store_true', + help='Also enable source code packages for the repository.' + ) + sub.add_argument( + '-t', + '--terse', + action='store_true', + help='Do not display expanded info about a repository before adding it.' + ) + sub.add_argument( + '-n', + '--name', + default='', + help='A name to set for the new repo' + ) + sub.add_argument( + '-i', + '--identifier', + default='', + help='The filename to use for the new source' + ) + sub.add_argument( + '-f', + '--format', + default='sources', + help='The source format to save as, `sources` or `list`' + ) + sub.add_argument( + '-k', + '--skip-keys', + action='store_true', + help='Skip adding signing keys (not recommended!)' + ) + + return sub + + def finalize_options(self, args): + super().finalize_options(args) + self.deb_line = ' '.join(args.deb_line) + self.terse = args.terse + self.source_code = args.source_code + self.disable = args.disable + self.log.debug(args) + + try: + name = args.name.split() + except AttributeError: + name = args.name + + try: + ident = args.identifier.split() + except AttributeError: + ident = args.identifier + + self.name = ' '.join(name) + pre_ident:str = '-'.join(ident) + self.ident = util.scrub_filename(pre_ident) + self.skip_keys = args.skip_keys + self.format = args.format.lower() + + def run(self) -> bool: + """Run the command, return `True` if successful; otherwise `False`""" + if self.deb_line == '822styledeb': + self.parser.print_usage() + self.log.error('A repository is required') + return False + + repo_is_url = self.deb_line.startswith('http') + repo_is_nospaced = len(self.deb_line.split()) == 1 + + if repo_is_url and repo_is_nospaced: + self.deb_line = f'deb {self.deb_line} {util.DISTRO_CODENAME} main' + + print('Fetching repository information...') + + self.log.debug('Adding line %s', self.deb_line) + + new_source: Source = Source() + + for prefix in shortcut_prefixes: + self.log.debug('Trying prefix %s', prefix) + if self.deb_line.startswith(prefix): + self.log.debug('Line is prefix: %s', prefix) + new_source = shortcut_prefixes[prefix]() + if not new_source.validator(self.deb_line): + self.log.error( + 'The shortcut "%s" is malformed', + self.deb_line + ) + + # Try and get a suggested correction for common errors + try: + if self.deb_line[len(prefix)] != ':': + fixed_debline: str = self.deb_line.replace( + self.deb_line[len(prefix)], + ":", + 1 + ) + print(f'Did you mean "{fixed_debline}"?') + except IndexError: + pass + return False + + try: + new_source.load_from_data([self.deb_line]) + except (URLError, ServerNotFoundError) as err: + import traceback + self.log.debug( + 'Exception info: %s \n %s \n %s', + type(err), + ''.join(traceback.format_exception(err)), + err.args + ) + self.log.error( + 'System is offline. A connection is required to add ' + 'PPA and Popdev sources.' + ) + return False + except Exception as err: + import traceback + self.log.debug( + 'Exception info: %s \n %s \n %s', + type(err), + err.__traceback__, + err + ) + self.log.error('An error ocurred: %s', err) + return False + break + + + if not new_source: + self.log.error( + f'Could not parse line "{self.deb_line}". Double-check the ' + 'spelling.' + ) + valid_shortcuts: str = '' + for shortcut in shortcut_prefixes: + if shortcut.startswith('deb'): + continue + valid_shortcuts += f'{shortcut}, ' + valid_shortcuts = valid_shortcuts.strip(', ') + print(f'Supported repository shortcuts:\n {valid_shortcuts}') + return False + + new_source.twin_source = True + new_source.sourcecode_enabled = self.source_code + + if self.name: + new_source.name = self.name + if self.ident: + new_source.ident = self.ident + if self.disable: + new_source.enabled = False + + if not new_source.ident: + new_source.ident = new_source.generate_default_ident() + + new_file = SourceFile(name=new_source.ident) + new_file.format = new_source.default_format + if self.format: + self.log.info('Setting new source format to %s', self.format) + for format in util.SourceFormat: + if self.format == format.value: + new_file.format = format + self.log.debug('New source format set to %s', format.value) + + new_file.add_source(new_source) + new_source.file = new_file + self.log.debug('File format: %s', new_file.format) + self.log.debug('File path: %s', new_file.path) + + self.log.debug('Sources in file %s:\n%s', new_file.path, new_file.sources) + + if not self.terse: + print( + 'Adding the following source: \n', + new_source.get_description(), + '\n\n', + new_source.ui + ) + try: + input( + 'Press ENTER to continue adding this source or Ctrl+C ' + 'to cancel' + ) + except KeyboardInterrupt: + # Handled here to avoid printing the exception to console + exit(0) + + new_file.save() + util.dbus_quit() + return True diff --git a/archive/repolib/command/argparser.py b/archive/repolib/command/argparser.py new file mode 100644 index 0000000..182ccd7 --- /dev/null +++ b/archive/repolib/command/argparser.py @@ -0,0 +1,65 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2020, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . + +Module for getting an argparser. Used by apt-manage +""" + +import argparse +import inspect + +from repolib import command as cmd + +from .. import __version__ + +def get_argparser(): + """ Get an argument parser with our arguments. + + Returns: + An argparse.ArgumentParser. + """ + parser = argparse.ArgumentParser( + prog='apt-manage', + description='Manage software sources and repositories', + epilog='apt-manage version: {}'.format(__version__.__version__) + ) + + parser.add_argument( + '-b', + '--debug', + action='count', + help=argparse.SUPPRESS + ) + + subparsers = parser.add_subparsers( + help='...', + dest='action', + metavar='COMMAND' + ) + + subcommands = [] + for i in inspect.getmembers(cmd, inspect.isclass): + obj = getattr(cmd, i[0]) + subcommands.append(obj) + + for i in subcommands: + i.init_options(subparsers) + + return parser diff --git a/archive/repolib/command/command.py b/archive/repolib/command/command.py new file mode 100644 index 0000000..b4afa41 --- /dev/null +++ b/archive/repolib/command/command.py @@ -0,0 +1,77 @@ +""" +Copyright (c) 2020, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . + +Command line application helper class. +""" + +from ..util import SOURCES_DIR + +class RepolibCommandError(Exception): + """ Exceptions generated by Repolib CLIs.""" + + def __init__(self, *args, code=1, **kwargs): + """Exception from a CLI + + Arguments: + code (:obj:`int`, optional, default=1): Exception error code. + """ + super().__init__(*args, **kwargs) + self.code = code + +class Command: + # pylint: disable=no-self-use,too-few-public-methods + # This is a base class for other things to inherit and give other programs + # a standardized interface for interacting with commands. + """ CLI helper class for developing command line applications.""" + + def __init__(self, log, args, parser): + self.log = log + self.parser = parser + self.sources_dir = SOURCES_DIR + self.finalize_options(args) + + def finalize_options(self, args): + """ Base options parsing class. + + Use this class in commands to set up the final options parsing and set + instance variables for later use. + """ + self.verbose = False + self.debug = False + if args.debug: + if args.debug > 1: + self.verbose = True + if args.debug != 0: + self.log.info('Debug mode set, not-saving any changes.') + self.debug = True + + def run(self): + """ The initial base for running the command. + + This needs to have a standardized format, argument list, and return + either True or False depending on whether the command was successful or + not. + + Returns: + True if the command succeeded, otherwise False. + """ + + # Since this is just the base, it should always return True (because + # you shouldn't fail at doing nothing). + return True diff --git a/archive/repolib/command/key.py b/archive/repolib/command/key.py new file mode 100644 index 0000000..f73c5b2 --- /dev/null +++ b/archive/repolib/command/key.py @@ -0,0 +1,325 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +from pathlib import Path + +from ..key import SourceKey +from .. import system, util + +from .command import Command + +KEYS_PATH = Path(util.KEYS_DIR) + +class Key(Command): + """Key subcommand. + + Manages signing keys for repositories, allowing adding, removal, and + fetching of remote keys. + + Options: + --name, -n + --path, -p + --url, -u + --ascii, -a + --fingerprint, -f + --keyserver, -s + --remove, -r + """ + + @classmethod + def init_options(cls, subparsers) -> None: + """Sets up this command's options parser""" + + sub = subparsers.add_parser( + 'key', + help='Manage repository signing keys', + epilog=( + 'Note that no verification of key validity is performed when ' + 'managing key using apt-mange. Ensure that keys are valid to ' + 'avoid errors with updates.' + ) + ) + + sub.add_argument( + 'repository', + default=['x-repolib-none'], + help='Which repository to manage keys for.' + ) + + sub_group = sub.add_mutually_exclusive_group( + required=True + ) + + sub_group.add_argument( + '-n', + '--name', + help='The name of an existing key file to set.' + ) + + sub_group.add_argument( + '-p', + '--path', + help='Sets a path on disk to a signing key.' + ) + + sub_group.add_argument( + '-u', + '--url', + help='Download a key over HTTPS' + ) + + sub_group.add_argument( + '-a', + '--ascii', + help='Add an ascii-armored key' + ) + + sub_group.add_argument( + '-f', + '--fingerprint', + help=( + 'Fetch a key via fingerprint from a keyserver. Use --keyserver ' + 'to specify the URL to the keyserver.' + ) + ) + + sub_group.add_argument( + '-r', + '--remove', + action='store_true', + help=( + 'Remove a signing key from the repo. If no other sources use ' + 'this key, its file will be deleted.' + ) + ) + + sub.add_argument( + '-s', + '--keyserver', + help=( + 'The keyserver from which to fetch the given fingerprint. ' + '(Default: keyserver.ubuntu.com)' + ) + ) + + def finalize_options(self, args): + super().finalize_options(args) + self.repo = args.repository + self.keyserver = args.keyserver + + self.actions:dict = {} + self.system_source = False + + for act in [ + 'name', + 'path', + 'url', + 'ascii', + 'fingerprint', + 'remove', + ]: + self.actions[act] = getattr(args, act) + + system.load_all_sources() + + def run(self): + """Run the command""" + self.log.info('Modifying signing key settings for %s', self.repo) + + if not self.repo: + self.log.error('No repository provided') + return False + + try: + self.source = util.sources[self.repo] + if self.repo == 'system': + self.system_source = True + except KeyError: + self.log.error( + 'The repository %s was not found. Check the spelling', + self.repo + ) + return False + + self.log.debug('Actions to take:\n%s', self.actions) + self.log.debug('Source before:\n%s', self.source) + + rets = [] + for action in self.actions: + if self.actions[action]: + self.log.debug('Running action: %s - (%s)', action, self.actions[action]) + ret = getattr(self, action)(self.actions[action]) + rets.append(ret) + + self.log.debug('Results: %s', rets) + self.log.debug('Source after: \n%s', self.source) + + if True in rets: + self.source.file.save() + return True + else: + self.log.warning('No valid changes specified, no actions taken.') + return False + + def name(self, value:str) -> bool: + """Sets the key file to a name in the key file directory""" + if not value: + return False + + key_path = None + + for ext in ['.gpg', '.asc']: + if value.endswith(ext): + path = KEYS_PATH / value + if path.exists(): + key_path = path + break + else: + if 'archive-keyring' not in value: + value += '-archive-keyring' + path = KEYS_PATH / f'{value}{ext}' + if path.exists(): + key_path = path + break + + if not key_path: + self.log.error('The key file %s could not be found', value) + return False + + return self.path(str(key_path)) + + def path(self, value:str) -> bool: + """Sets the key file to the given path""" + if not value: + return False + + key_path = Path(value) + + if not key_path.exists(): + self.log.error( + 'The path %s does not exist', value + ) + return False + + self.source.signed_by = str(key_path) + self.source.load_key() + return True + + def url(self, value:str) -> bool: + """Downloads a key to use from a provided URL.""" + if not value: + return False + + if not value.startswith('https:'): + response = 'n' + self.log.warning( + 'The key is not being downloaded over an encrypted connection, ' + 'and may be tampered with in-transit. Only add keys that you ' + 'trust, from sources which you trust.' + ) + response = input('Do you want to continue? (y/N): ') + if response not in util.true_values: + return False + + key = SourceKey(name=self.source.ident) + key.load_key_data(url=value) + self.source.key = key + self.source.signed_by = str(key.path) + self.source.load_key() + return True + + def ascii(self, value:str) -> bool: + """Loads the key from provided ASCII-armored data""" + if not value: + return False + + response = 'n' + print('Only add signing keys from data you trust.') + response = input('Do you want to continue? (y/N): ') + + if response not in util.true_values: + return False + + key = SourceKey(name=self.source.ident) + key.load_key_data(ascii=value) + self.source.key = key + self.source.signed_by = str(key.path) + self.source.load_key() + return True + + def fingerprint(self, value:str) -> bool: + """Downloads the key with the given fingerprint from a keyserver""" + if not value: + return False + + key = SourceKey(name=self.source.ident) + + if self.keyserver: + key.load_key_data(fingerprint=value, keyserver=self.keyserver) + else: + key.load_key_data(fingerprint=value) + + self.source.key = key + self.source.signed_by = str(key.path) + self.source.load_key() + return True + + def remove(self, value:str) -> bool: + """Removes the key from the source""" + + if not self.source.key: + self.log.error( + 'The source %s does not have a key configured.', + self.repo + ) + return False + + response = 'n' + print( + 'If you remove the key, there may no longer be any verification ' + 'of software packages from this repository, including for future ' + 'updates. This may cause errors with your updates.' + ) + response = input('Do you want to continue? (y/N): ') + if response not in util.true_values: + return False + + old_key = self.source.key + self.source.key = None + self.source.signed_by = '' + + for source in util.sources.values(): + if source.key == old_key: + self.log.info( + 'Key file %s in use with another key, not deleting', + old_key.path + ) + return True + + response = 'n' + print('No other sources were found which use this key.') + response = input('Do you want to remove it? (Y/n): ') + if response in util.true_values: + old_key.delete_key() + + return True + diff --git a/archive/repolib/command/list.py b/archive/repolib/command/list.py new file mode 100644 index 0000000..aad121b --- /dev/null +++ b/archive/repolib/command/list.py @@ -0,0 +1,216 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import argparse +import textwrap +import traceback + +from ..file import SourceFile, SourceFileError +from ..source import Source, SourceError +from .. import RepoError, util, system + +from .command import Command, RepolibCommandError + +class List(Command): + """List subcommand + + Lists information about currently-configured sources on the system. + + Options: + --legacy, -l + --verbose, -v + --all, -a + --no-names, -n + --file-names, -f + --no-indentation + """ + + @classmethod + def init_options(cls, subparsers): + """Sets up ths argument parser for this command. + + Returns: argparse.subparser: + This command's subparser + """ + + sub = subparsers.add_parser( + 'list', + help=( + 'List information for configured repostiories. If a repository ' + 'name is provided, details about that repository are printed.' + ) + ) + + sub.add_argument( + 'repository', + nargs='*', + default=['x-repolib-all-sources'], + help='The repository for which to list configuration' + ) + sub.add_argument( + '-v', + '--verbose', + action='store_true', + help='Show additional information, if available.' + ) + sub.add_argument( + '-a', + '--all', + action='store_true', + help='Display full configuration for all configured repositories.' + ) + sub.add_argument( + '-l', + '--legacy', + action='store_true', + help='Include repositories configured in legacy sources.list file.' + ) + sub.add_argument( + '-n', + '--no-names', + action='store_true', + dest='skip_names', + help=argparse.SUPPRESS + ) + sub.add_argument( + '-f', + '--file-names', + action='store_true', + dest='print_files', + help="Don't print names of files" + ) + sub.add_argument( + '--no-indentation', + action='store_true', + dest='no_indent', + help=argparse.SUPPRESS + ) + + def finalize_options(self, args): + super().finalize_options(args) + self.repo = ' '.join(args.repository) + self.verbose = args.verbose + self.all = args.all + self.legacy = args.legacy + self.skip_names = args.skip_names + self.print_files = args.print_files + self.no_indent = args.no_indent + + def list_legacy(self, indent) -> None: + """List the contents of the sources.list file. + + Arguments: + list_file(Path): The sources.list file to try and parse. + indent(str): An indentation to append to the output + """ + try: + sources_list_file = util.SOURCES_DIR.parent / 'sources.list' + except FileNotFoundError: + sources_list_file = None + + print('Legacy source.list sources:') + if sources_list_file: + with open(sources_list_file, mode='r') as file: + for line in file: + if 'cdrom' in line: + line = '' + + try: + source = Source() + source.load_from_data([line]) + print(textwrap.indent(source.ui, indent)) + except SourceError: + pass + + def list_all(self): + """List all sources presently configured in the system + + This may include the configuration data for each source as well + """ + + indent = ' ' + if self.no_indent: + indent = '' + + if self.print_files: + print('Configured source files:') + + for file in util.files: + print(f'{file.path.name}:') + + for source in file.sources: + print(textwrap.indent(source.ui, indent)) + + if self.legacy: + self.list_legacy(indent) + + else: + print('Configured Sources:') + for source in util.sources: + output = util.sources[source] + print(textwrap.indent(output.ui, indent)) + + if self.legacy: + self.list_legacy(indent) + + if util.errors: + print('\n\nThe following files contain formatting errors:') + for err in util.errors: + print(err) + if self.verbose or self.debug: + print('\nDetails about failing files:') + for err in util.errors: + print(f'{err}: {util.errors[err].args[0]}') + + return True + + def run(self): + """Run the command""" + system.load_all_sources() + self.log.debug("Current sources: %s", util.sources) + ret = False + + if self.all: + return self.list_all() + + if self.repo == 'x-repolib-all-sources' and not self.all: + if not self.skip_names: + print('Configured Sources:') + for source in util.sources: + line = source + if not self.skip_names: + line += f' - {util.sources[source].name}' + print(line) + + return True + + else: + try: + output = util.sources[self.repo] + print(f'Details for source {output.ui}') + return True + except KeyError: + self.log.error( + "Couldn't find the source file for %s, check the spelling", + self.repo + ) + return False diff --git a/archive/repolib/command/modify.py b/archive/repolib/command/modify.py new file mode 100644 index 0000000..2738684 --- /dev/null +++ b/archive/repolib/command/modify.py @@ -0,0 +1,502 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +from argparse import SUPPRESS + +from .command import Command, RepolibCommandError +from .. import util, system + +class Modify(Command): + """Modify Subcommand + + Makes modifications to the specified repository + + Options: + --enable, -e + --disable, -d + --default-mirror + --name + --add-suite + --remove-suite + --add-component + --remove-component + --add-uri + --remove-uri + + Hidden Options + --add-option + --remove-option + --default-mirror + """ + + @classmethod + def init_options(cls, subparsers): + """ Sets up this command's options parser""" + + sub = subparsers.add_parser( + 'modify', + help='Change a configured repository.' + ) + sub.add_argument( + 'repository', + nargs='*', + default=['system'], + help='The repository to modify. Default is the system repository.' + ) + + modify_enable = sub.add_mutually_exclusive_group( + required=False + ) + modify_enable.add_argument( + '-e', + '--enable', + action='store_true', + help='Enable the repository, if disabled.' + ) + modify_enable.add_argument( + '-d', + '--disable', + action='store_true', + help=( + 'Disable the repository, if enabled. The system repository cannot ' + 'be disabled.' + ) + ) + + modify_source = sub.add_mutually_exclusive_group( + required=False + ) + + modify_source.add_argument( + '--source-enable', + action='store_true', + help='Enable source code for the repository, if disabled.' + ) + modify_source.add_argument( + '--source-disable', + action='store_true', + help='Disable source code for the repository, if enabled.' + ) + + sub.add_argument( + '--default-mirror', + help=SUPPRESS + #help='Sets the default mirror for the system source.' + ) + + # Name + sub.add_argument( + '-n', + '--name', + help='Set the repository name to NAME' + ) + + # Suites + sub.add_argument( + '--add-suite', + metavar='SUITE[,SUITE]', + help=( + 'Add the specified suite(s) to the repository. Multiple ' + 'repositories should be separated with commas. NOTE: Legacy deb ' + 'repositories may only contain one suite.' + ) + ) + sub.add_argument( + '--remove-suite', + metavar='SUITE[,SUITE]', + help=( + 'Remove the specified suite(s) from the repository. Multiple ' + 'repositories should be separated with commas. NOTE: The last ' + 'suite in a repository cannot be removed.' + ) + ) + + # Components + sub.add_argument( + '--add-component', + metavar='COMPONENT[,COMPONENT]', + help=( + 'Add the specified component(s) to the repository. Multiple ' + 'repositories should be separated with commas.' + ) + ) + sub.add_argument( + '--remove-component', + metavar='COMPONENT[,COMPONENT]', + help=( + 'Remove the specified component(s) from the repository. Multiple ' + 'repositories should be separated with commas. NOTE: The last ' + 'component in a repository cannot be removed.' + ) + ) + + # URIs + sub.add_argument( + '--add-uri', + metavar='URI[,URI]', + help=( + 'Add the specified URI(s) to the repository. Multiple ' + 'repositories should be separated with commas. NOTE: Legacy deb ' + 'repositories may only contain one uri.' + ) + ) + sub.add_argument( + '--remove-uri', + metavar='URI[,URI]', + help=( + 'Remove the specified URI(s) from the repository. Multiple ' + 'repositories should be separated with commas. NOTE: The last ' + 'uri in a repository cannot be removed.' + ) + ) + + # Options + sub.add_argument( + '--add-option', + metavar='OPTION=VALUE[,OPTION=VALUE]', + help=SUPPRESS + ) + sub.add_argument( + '--remove-option', + metavar='OPTION=VALUE[,OPTION=VALUE]', + help=SUPPRESS + ) + + def finalize_options(self, args): + super().finalize_options(args) + self.count = 0 + self.repo = ' '.join(args.repository) + self.enable = args.enable + self.disable = args.disable + self.source_enable = args.source_enable + self.source_disable = args.source_disable + + self.actions:dict = {} + + self.actions['endisable'] = None + for i in ['enable', 'disable']: + if getattr(args, i): + self.actions['endisable'] = i + + self.actions['source_endisable'] = None + for i in ['source_enable', 'source_disable']: + if getattr(args, i): + self.actions['source_endisable'] = i + + for arg in [ + 'default_mirror', + 'name', + 'add_uri', + 'add_suite', + 'add_component', + 'remove_uri', + 'remove_suite', + 'remove_component', + ]: + self.actions[arg] = getattr(args, arg) + + system.load_all_sources() + + def run(self): + """Run the command""" + self.log.info('Modifying repository %s', self.repo) + + self.system_source = False + try: + self.source = util.sources[self.repo] + except KeyError: + self.log.error( + 'The source %s could not be found. Check the spelling', + self.repo + ) + return False + + if self.source.ident == 'system': + self.system_source = True + + self.log.debug('Actions to take:\n%s', self.actions) + self.log.debug('Source before:\n%s', self.source) + + rets = [] + for action in self.actions: + ret = getattr(self, action)(self.actions[action]) + rets.append(ret) + + self.log.debug('Results: %s', rets) + self.log.debug('Source after: \n%s', self.source) + + if True in rets: + self.source.file.save() + return True + else: + self.log.warning('No valid changes specified, no actions taken.') + return False + + def default_mirror(self, value:str) -> bool: + """Checks if this is the system source, then set the default mirror""" + if not value: + return False + + if self.system_source: + self.source['X-Repolib-Default-Mirror'] = value + return True + return False + + def name(self, value:str) -> bool: + """Sets the source name""" + if not value: + return False + + self.log.info('Setting name for %s to %s', self.repo, value) + self.source.name = value + return True + + def endisable(self, value:str) -> bool: + """Enable or disable the source""" + if not value: + return False + + self.log.info('%sing source %s', value[:-1], self.repo) + if value == 'disable': + self.source.enabled = False + return True + + self.source.enabled = True + return True + + def source_endisable(self, value:str) -> bool: + """Enable/disable source code for the repo""" + if not value: + return False + + self.log.info('%sing source code for source %s', value[7:-1], self.repo) + if value == 'source_disable': + self.source.sourcecode_enabled = False + return True + + self.source.sourcecode_enabled = True + return True + + def add_uri(self, values:str) -> bool: + """Adds URIs to the source, if not already present.""" + if not values: + return False + + self.log.info('Adding URIs: %s', values) + uris = self.source.uris + + for uri in values.split(): + if not util.url_validator(uri): + raise RepolibCommandError( + f'Cannot add URI {uri} to {self.repo}. The URI is ' + 'malformed' + ) + + if uri not in uris: + uris.append(uri) + self.log.debug('Added URI %s', uri) + + else: + self.log.warning( + 'The URI %s was already present in %s', + uri, + self.repo + ) + + if uris != self.source.uris: + self.source.uris = uris + return True + return False + + def remove_uri(self, values:str) -> bool: + """Remove URIs from the soruce, if they are added.""" + if not values: + return False + + self.log.info('Removing URIs %s from source %s', values, self.repo) + uris = self.source.uris + self.log.debug('Starting uris: %s', uris) + + for uri in values.split(): + try: + uris.remove(uri) + self.log.debug('Removed URI %s', uri) + + except ValueError: + self.log.warning( + 'The URI %s was not present in %s', + uri, + self.repo + ) + + if len(uris) == 0: + self.log.error( + 'Cannot remove the last URI from %s. If you meant to delete the source, try REMOVE instead.', + self.repo + ) + return False + + if uris != self.source.uris: + self.source.uris = uris + return True + + return False + + def add_suite(self, values:str) -> bool: + """Adds a suite to the source""" + if not values: + return False + + self.log.info('Adding suites: %s', values) + suites = self.source.suites + + for suite in values.split(): + if suite not in suites: + suites.append(suite) + self.log.debug('Added suite %s', suite) + + else: + self.log.warning( + 'The suite %s was already present in %s', + suite, + self.repo + ) + + if suites != self.source.suites: + self.source.suites = suites + return True + return False + + def remove_suite(self, values:str) -> bool: + """Remove a suite from the source""" + if not values: + return False + + self.log.info('Removing suites %s from source %s', values, self.repo) + suites = self.source.suites + self.log.debug('Starting suites: %s', suites) + + for suite in values.split(): + try: + suites.remove(suite) + self.log.debug('Removed suite %s', suite) + + except ValueError: + self.log.warning( + 'The suite %s was not present in %s', + suite, + self.repo + ) + + if len(suites) == 0: + self.log.error( + 'Cannot remove the last suite from %s. If you meant to delete the source, try REMOVE instead.', + self.repo + ) + return False + + if suites != self.source.suites: + self.source.suites = suites + return True + + return False + + def add_component(self, values:str) -> bool: + """Adds components to the source""" + if not values: + return False + + self.log.info('Adding components: %s', values) + components = self.source.components + + for component in values.split(): + if component not in components: + components.append(component) + self.log.debug('Added component %s', component) + + else: + self.log.warning( + 'The component %s was already present in %s', + component, + self.repo + ) + + if len(components) > 1: + if self.source.file.format == util.SourceFormat.LEGACY: + self.log.warning( + 'Adding multiple components to a legacy source is not ' + 'supported. Consider converting the source to DEB822 format.' + ) + + if components != self.source.components: + self.source.components = components + return True + return False + + def remove_component(self, values:str) -> bool: + """Removes components from the source""" + if not values: + return False + + self.log.info('Removing components %s from source %s', values, self.repo) + components = self.source.components + self.log.debug('Starting components: %s', components) + + for component in values.split(): + try: + components.remove(component) + self.log.debug('Removed component %s', component) + + except ValueError: + self.log.warning( + 'The component %s was not present in %s', + component, + self.repo + ) + + if len(components) == 0: + self.log.error( + 'Cannot remove the last component from %s. If you meant to delete the source, try REMOVE instead.', + self.repo + ) + return False + + if components != self.source.components: + self.source.components = components + return True + + return False + + def add_option(self, values) -> bool: + """TODO: Support options""" + raise NotImplementedError( + 'Options have not been implemented in this version of repolib yet. ' + f'Please edit the file {self.source.file.path} manually.' + ) + + + def remove_option(self, values) -> bool: + """TODO: Support options""" + raise NotImplementedError( + 'Options have not been implemented in this version of repolib yet. ' + f'Please edit the file {self.source.file.path} manually.' + ) diff --git a/archive/repolib/command/remove.py b/archive/repolib/command/remove.py new file mode 100644 index 0000000..851be03 --- /dev/null +++ b/archive/repolib/command/remove.py @@ -0,0 +1,128 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +from .. import util, system +from .command import Command + +class Remove(Command): + """Remove subcommand + + Removes configured sources from the system + + Options: + --assume-yes, -y + """ + + @classmethod + def init_options(cls, subparsers): + """Sets up the argument parser for this command + + Returns: argparse.subparser + This command's subparser + """ + + sub = subparsers.add_parser( + 'remove', + help='Remove a configured repository' + ) + + sub.add_argument( + 'repository', + help='The identifier of the repository to remove. See LIST' + ) + sub.add_argument( + '-y', + '--assume-yes', + action='store_true', + help='Remove without prompting for confirmation' + ) + + def finalize_options(self, args): + super().finalize_options(args) + system.load_all_sources() + self.source_name = args.repository + self.assume_yes = args.assume_yes + self.source = None + + def run(self): + """Run the command""" + + self.log.info('Looking up %s for removal', self.source_name) + + if self.source_name == 'system': + self.log.error('You cannot remove the system sources') + return False + + if self.source_name not in util.sources: + self.log.error( + 'Source %s was not found. Double-check the spelling', + self.source_name + ) + source_list:list = [] + for source in util.sources: + source_list.append(source) + suggested_source:str = self.source_name.replace(':', '-') + suggested_source = suggested_source.translate(util.CLEAN_CHARS) + if not suggested_source in source_list: + return False + + response:str = input(f'Did you mean "{suggested_source}"? (Y/n) ') + if not response: + response = 'y' + if response not in util.true_values: + return False + self.source_name = suggested_source + + self.source = util.sources[self.source_name] + self.key = self.source.key + self.file = self.source.file + + print(f'This will remove the source {self.source_name}') + print(self.source.ui) + response:str = 'n' + if self.assume_yes: + response = 'y' + else: + response = input('Are you sure you want to do this? (y/N) ') + + if response in util.true_values: + self.file.remove_source(self.source_name) + self.file.save() + + system.load_all_sources() + for source in util.sources.values(): + self.log.debug('Checking key for %s', source.ident) + try: + if source.key.path == self.key.path: + self.log.info('Source key in use with another source') + return True + except AttributeError: + pass + + self.log.info('No other sources found using key, deleting key') + if self.key: + self.key.delete_key() + return True + + else: + print('Canceled.') + return False diff --git a/archive/repolib/file.py b/archive/repolib/file.py new file mode 100644 index 0000000..5541a56 --- /dev/null +++ b/archive/repolib/file.py @@ -0,0 +1,544 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" +import logging + +from pathlib import Path + +import dbus + +from .source import Source, SourceError +from . import util + +FILE_COMMENT = "## Added/managed by repolib ##" + +class SourceFileError(util.RepoError): + """ Exception from a source file.""" + + def __init__(self, *args, code:int = 1, **kwargs): + """Exception with a source file + + Arguments: + :code int, optional, default=1: Exception error code. + """ + super().__init__(*args, **kwargs) + self.code: int = code + +class SourceFile: + """ A Source File on disk + + Attributes: + path(Pathlib.Path): the path for this file on disk + name(str): The name for this source (filename less the extension) + format(SourceFormat): The format used by this source file + contents(list): A list containing all of this file's contents + """ + + def __init__(self, name:str='') -> None: + """Initialize a source file + + Arguments: + name(str): The filename within the sources directory to load + """ + self.log = logging.getLogger(__name__) + self.name:str = '' + self.path:Path = Path() + self.alt_path:Path = Path() + self.format:util.SourceFormat = util.SourceFormat.DEFAULT + self.contents:list = [] + self.sources:list = [] + + self.contents.append(FILE_COMMENT) + self.contents.append('#') + + if name: + self.name = name + self.reset_path() + + if self.path.exists(): + self.contents = [] + self.load() + + def __str__(self): + return self.output + + def __repr__(self): + return f'SourceFile(name={self.name})' + + def add_source(self, source:Source) -> None: + """Adds a source to the file + + Arguments: + source(Source): The source to add + """ + if source not in self.sources: + self.contents.append(source) + self.sources.append(source) + source.file = self + + def remove_source(self, ident:str) -> None: + """Removes a source from the file + + Arguments: + ident(str): The ident of the source to remove + """ + source = self.get_source_by_ident(ident) + self.contents.remove(source) + self.sources.remove(source) + self.save() + + ## Remove sources prefs files/pin-priority + prefs_path = source.prefs + try: + if prefs_path.exists() and prefs_path.name: + prefs_path.unlink() + + except AttributeError: + # No prefs path + pass + + except PermissionError: + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.delete_prefs_file(str(prefs_path)) + + def get_source_by_ident(self, ident: str) -> Source: + """Find a source within this file by its ident + + Arguments: + ident(str): The ident to search for + + Returns: Source + The located source + """ + self.log.debug(f'Looking up ident {ident} in {self.name}') + for source in self.sources: + if source.ident == ident: + self.log.debug(f'{ident} found') + return source + raise SourceFileError( + f'The file {self.path} does not contain the source {ident}' + ) + + def reset_path(self) -> None: + """Attempt to detect the correct path for this File. + + We default to DEB822 .sources format files, but if that file doesn't + exist, fallback to legacy .list format. If this also doesn't exist, we + swap back to DEB822 format, as this is likely a new file.""" + self.log.debug('Resetting path') + + default_path = util.SOURCES_DIR / f'{self.name}.sources' + legacy_path = util.SOURCES_DIR / f'{self.name}.list' + + if default_path.exists(): + self.path = default_path + self.alt_path = legacy_path + self.format = util.SourceFormat.DEFAULT + + elif legacy_path.exists(): + self.path = legacy_path + self.alt_path = default_path + self.format = util.SourceFormat.LEGACY + + else: + self.path = default_path + self.alt_path = legacy_path + + return + + def find_unique_ident(self, source1:Source, source2:Source) -> bool: + """Takes two sources with identical idents, and finds a new, unique + idents for them. + + The rules for this are mildly complicated, and vary depending on the + situation: + + * (DEB822) If the sources are identical other than some portion of + data, then the two will be combined into a single source. + * (legacy) If the two sources are identical other than source type + (common with legacy-format PPAs with source code) then the second + source will be dropped until export. + * (legacy) If the sources differ by URIs, Components, or Suites, then + the differing data will be appended to the sources' idents. + * (Either) If no other rules can be determined, then the sources will + have a number appended to them + + Arguments: + source1(Source): The original source with the ident + source2(Source): The new colliding source with the ident + + Returns: bool + `True` if the two sources were successfully deduped, `False` if the + second source should be discarded. + """ + ident_src1:str = source1.ident + ident_src2:str = source2.ident + + self.log.debug(f'Idents {ident_src1} and {ident_src2} conflict') + + if self.format == util.SourceFormat.DEFAULT: + util.combine_sources(source1, source2) + ident_src2 = '' + + else: + excl_keys = [ + 'X-Repolib-Name', + 'X-Repolib-ID', + 'X-Repolib-Comment', + 'Enabled', + 'Types' + ] + if len(source1.types) == 1 and len(source2.types) == 1: + if util.compare_sources(source1, source2, excl_keys): + util.combine_sources(source1, source2) + source1.types = [ + util.SourceType.BINARY, util.SourceType.SOURCECODE + ] + source1.twin_source = True + source1.sourcecode_enabled = source2.enabled + ident_src2 = '' + diffs = util.find_differences_sources(source1, source2, excl_keys) + if diffs: + for key in diffs: + raw_diffs:tuple = diffs[key] + diff1_list = raw_diffs[0].strip().split() + diff2_list = raw_diffs[1].strip().split() + for i in diff1_list: + if i not in diff2_list: + ident_src1 += f'-{i}' + break + for i in diff2_list: + if i not in diff1_list: + ident_src2 += f'-{i}' + break + if ident_src1 != ident_src2: + break + if ident_src2 and ident_src1 != ident_src2: + source1.ident = ident_src1 + source2.ident = ident_src2 + return True + + elif ident_src2 and ident_src1 == ident_src2: + for source in self.sources: + src_index = self.sources.index(source) + source.ident = f'{self.name}-{src_index}' + return True + + elif not ident_src2: + return False + + return True + + + def load(self) -> None: + """Loads the sources from the file on disk""" + self.log.debug(f'Loading source file {self.path}') + self.contents = [] + self.sources = [] + + if not self.name: + raise SourceFileError('You must provide a filename to load.') + + if not self.path.exists(): + raise SourceFileError(f'The file {self.path} does not exist.') + + with open(self.path, 'r') as source_file: + srcfile_data = source_file.readlines() + + item:int = 0 + raw822:list = [] + parsing_deb822:bool = False + source_name:str = '' + commented:bool = False + idents:dict = {} + + # Main file parsing loop + for line in srcfile_data: + comment_found:str = '' + name_line:bool = 'X-Repolib-Name' in line + + if not parsing_deb822: + commented = line.startswith('#') + + # Find commented out lines + if commented: + # Exclude disabled legacy deblines + valid_legacy = util.validate_debline(line.strip()) + if not valid_legacy and not name_line: + # Found a standard comment + self.contents.append(line.strip()) + + elif valid_legacy: + if self.format != util.SourceFormat.LEGACY: + raise SourceFileError( + f'File {self.path.name} is an updated file, but ' + 'contains legacy-format sources. This is not ' + 'allowed. Please fix the file manually.' + ) + new_source = Source() + new_source.load_from_data([line]) + if source_name: + new_source.name = source_name + if not new_source.ident: + new_source.ident = self.name + to_add:bool = True + if new_source.ident in idents: + old_source = idents[new_source.ident] + idents.pop(old_source.ident) + to_add = self.find_unique_ident(old_source, new_source) + idents[old_source.ident] = old_source + idents[new_source.ident] = new_source + if to_add: + new_source.file = self + self.contents.append(new_source) + self.sources.append(new_source) + + elif name_line: + source_name = ':'.join(line.split(':')[1:]) + source_name = source_name.strip() + + # Active legacy line + elif not commented: + if util.validate_debline(line.strip()): + if self.format != util.SourceFormat.LEGACY: + raise SourceFileError( + f'File {self.path.name} is an updated file, but ' + 'contains legacy-format sources. This is not ' + 'allowed. Please fix the file manually.' + ) + new_source = Source() + new_source.load_from_data([line]) + if source_name: + new_source.name = source_name + if not new_source.ident: + new_source.ident = self.name + to_add:bool = True + if new_source.ident in idents: + old_source = idents[new_source.ident] + idents.pop(old_source.ident) + to_add = self.find_unique_ident(old_source, new_source) + idents[old_source.ident] = old_source + idents[new_source.ident] = new_source + if to_add: + new_source.file = self + self.contents.append(new_source) + self.sources.append(new_source) + + # Empty lines are treated as comments + if line.strip() == '': + self.contents.append('') + + # Find 822 sources + # Valid sources can begin with any key: + for key in util.valid_keys: + if line.startswith(key): + if self.format == util.SourceFormat.LEGACY: + raise SourceFileError( + f'File {self.path.name} is a DEB822-format file, but ' + 'contains legacy sources. This is not allowed. ' + 'Please fix the file manually.' + ) + parsing_deb822 = True + raw822.append(line.strip()) + + item += 1 + + elif parsing_deb822: + # Deb822 sources are terminated with an empty line + if line.strip() == '': + parsing_deb822 = False + new_source = Source() + new_source.load_from_data(raw822) + new_source.file = self + if source_name: + new_source.name = source_name + if not new_source.ident: + new_source.ident = self.name + if new_source.ident in idents: + old_source = idents[new_source.ident] + idents.pop(old_source.ident) + self.find_unique_ident(old_source, new_source) + idents[old_source.ident] = old_source + idents[new_source.ident] = new_source + new_source.file = self + self.contents.append(new_source) + self.sources.append(new_source) + raw822 = [] + item += 1 + self.contents.append('') + else: + raw822.append(line.strip()) + + if raw822: + parsing_deb822 = False + new_source = Source() + new_source.load_from_data(raw822) + new_source.file = self + if source_name: + new_source.name = source_name + if not new_source.ident: + new_source.ident = self.name + if new_source.ident in idents: + old_source = idents[new_source.ident] + idents.pop(old_source.ident) + self.find_unique_ident(old_source, new_source) + idents[old_source.ident] = old_source + idents[new_source.ident] = new_source + new_source.file = self + self.contents.append(new_source) + self.sources.append(new_source) + raw822 = [] + item += 1 + self.contents.append('') + + for source in self.sources: + if not source.has_required_parts: + raise SourceFileError( + f'The file {self.path.name} is malformed and contains ' + 'errors. Maybe it has some extra new-lines?' + ) + + self.log.debug('File %s loaded', self.path) + + def save(self) -> None: + """Saves the source file to disk using the current format""" + self.log.debug(f'Saving source file to {self.path}') + + for source in self.sources: + self.log.debug('New Source %s: \n%s', source.ident, source) + + save_path = util.SOURCES_DIR / f'{self.name}.save' + + for source in self.sources: + if source.key: + source.key.save_gpg() + source.tasks_save() + + if not self.name or not self.format: + raise SourceFileError('There was not a complete filename to save') + + if not util.SOURCES_DIR.exists(): + try: + util.SOURCES_DIR.mkdir(parents=True) + except PermissionError: + self.log.error( + 'Source destination path does not exist and cannot be created ' + 'Failures expected now.' + ) + + if len(self.sources) > 0: + self.log.debug('Saving, Main path %s; Alt path: %s', self.path, self.alt_path) + try: + with open(self.path, mode='w') as output_file: + output_file.write(self.output) + if self.alt_path.exists(): + self.alt_path.rename(save_path) + + except PermissionError: + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.output_file_to_disk(self.path.name, self.output) + self.log.debug('File %s saved', self.path) + else: + try: + self.path.unlink(missing_ok=True) + self.alt_path.unlink(missing_ok=True) + save_path.unlink(missing_ok=True) + except PermissionError: + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.delete_source_file(self.path.name) + self.log.debug('File %s removed', self.path) + + + ## Attribute properties + @property + def format(self) -> util.SourceFormat: # type: ignore (We don't use str.format) + """The format of the file on disk""" + return self._format + + @format.setter + def format(self, format:util.SourceFormat) -> None: # type: ignore (We don't use str.format) + """The path needs to be updated when the format changes""" + alt_format:util.SourceFormat = util.SourceFormat.LEGACY + self._format = format + self.path = util.SOURCES_DIR / f'{self.name}.{self._format.value}' + for format_ in util.SourceFormat: + if format != format_: + alt_format = format_ + self.alt_path = util.SOURCES_DIR / f'{self.name}.{alt_format.value}' + + ## Output properties + @property + def legacy(self) -> str: + """Outputs the file in the output_legacy format""" + legacy_output:str = '' + for item in self.contents: + try: + legacy_output += item.legacy + except AttributeError: + legacy_output += item + legacy_output += '\n' + return legacy_output + + @property + def deb822(self) -> str: + """Outputs the file in the output_822 format""" + deb822_output:str = '' + for item in self.contents: + try: + deb822_output += item.deb822 + except AttributeError: + deb822_output += item + deb822_output += '\n' + return deb822_output + + + @property + def ui(self) -> str: + """Outputs the file in the output_ui format""" + ui_output:str = '' + for item in self.contents: + try: + ui_output += item.ui + except AttributeError: + pass # Skip file comments in UI mode + ui_output += '\n' + return ui_output + + + @property + def output(self) -> str: + """Outputs the file in the output format""" + default_output:str = '' + for item in self.contents: + try: + if self.format == util.SourceFormat.DEFAULT: + default_output += item.deb822 + elif self.format == util.SourceFormat.LEGACY: + default_output += item.legacy + default_output += '\n' + except AttributeError: + default_output += item + default_output += '\n' + return default_output + diff --git a/archive/repolib/key.py b/archive/repolib/key.py new file mode 100644 index 0000000..766881b --- /dev/null +++ b/archive/repolib/key.py @@ -0,0 +1,204 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging +import shutil + +import dbus +import gnupg +from pathlib import Path +from urllib import request + +from . import util + +SKS_KEYSERVER = 'https://keyserver.ubuntu.com/' +SKS_KEYLOOKUP_PATH = 'pks/lookup?op=get&options=mr&exact=on&search=0x' + +class KeyFileError(util.RepoError): + """ Exceptions related to apt key files.""" + + def __init__(self, *args, code=1, **kwargs): + """Exceptions related to apt key files. + + Arguments: + code (:obj:`int`, optional, default=1): Exception error code. + """ + super().__init__(*args, **kwargs) + self.code = code + +class SourceKey: + """A signing key for an apt source.""" + + def __init__(self, name:str = '') -> None: + self.log = logging.getLogger(__name__) + self.tmp_path = Path() + self.path = Path() + self.gpg = gnupg.GPG() + self.data = b'' + + if name: + self.reset_path(name=name) + self.setup_gpg() + + def reset_path(self, name: str = '', path:str = '', suffix: str = 'archive-keyring') -> None: + """Set the path for this key + + Arguments: + suffix(str): The suffix to append to the end of the name to get the + file name (default: 'archive-keyring') + name(str): The name of the source + path(str): The entire path to the key + """ + self.log.info('Setting path') + if not name and not path: + raise KeyFileError('A name is required to set the path for this key') + + if name: + file_name = f'{name}-{suffix}.gpg' + self.tmp_path = util.TEMP_DIR / file_name + self.path = util.KEYS_DIR / file_name + elif path: + self.path = Path(path) + self.tmp_path = util.TEMP_DIR / self.path.name + + self.setup_gpg() + + self.log.debug('Key Path: %s', self.path) + self.log.debug('Temp Path: %s', self.tmp_path) + + def setup_gpg(self) -> None: + """Set up the GPG object for this key.""" + self.log.info('Setting up GPG') + self.log.debug('Copying %s to %s', self.path, self.tmp_path) + try: + shutil.copy2(self.path, self.tmp_path) + + except FileNotFoundError: + pass + + self.gpg = gnupg.GPG(keyring=str(self.tmp_path)) + self.log.debug('GPG Setup: %s', self.gpg.keyring) + + def save_gpg(self) -> None: + """Saves the key to disk.""" + self.log.info('Saving key file %s from %s', self.path, self.tmp_path) + self.log.debug('Key contents: %s', self.gpg.list_keys()) + self.log.debug('Temp key exists? %s', self.tmp_path.exists()) + if not util.KEYS_DIR.exists(): + try: + util.KEYS_DIR.mkdir(parents=True) + except PermissionError: + self.log.error( + 'Key destination path does not exist and cannot be created ' + 'Failures expected now.' + ) + try: + shutil.copy(self.tmp_path, self.path) + + except PermissionError: + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.install_signing_key( + str(self.tmp_path), + str(self.path) + ) + + def delete_key(self) -> None: + """Deletes the key file from disk.""" + try: + self.tmp_path.unlink() + self.path.unlink() + + except PermissionError: + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.delete_signing_key(str(self.path)) + + except FileNotFoundError: + pass + + def load_key_data(self, **kwargs) -> None: + """Loads the key data from disk into the object for processing. + + Each of the keyword options specifies one place to look to import key + data. Once one is successfully imported, the method returns, so passing + multiple won't import multiple keys. + + Keyword Arguments: + raw(bytes): Raw data to import to the keyring + ascii(str): ASCII-armored key data to import directly + url(str): A URL to download key data from + fingerprint(str): A key fingerprint to download from `keyserver` + keyserver(str): A keyserver to download from. + keypath(str): The path on the keyserver from which to download. + + NOTE: The keyserver and keypath args only affect the operation of the + `fingerprint` keyword. + """ + + if self.path.exists(): + with open(self.path, mode='rb') as keyfile: + self.data = keyfile.read() + return + + self.tmp_path.touch() + + if 'raw' in kwargs: + self.data = kwargs['raw'] + self.gpg.import_keys(self.data) + return + + if 'ascii' in kwargs: + self.gpg.import_keys(kwargs['ascii']) + if self.tmp_path.exists(): + with open(self.tmp_path, mode='rb') as keyfile: + self.data = keyfile.read() + return + + if 'url' in kwargs: + req = request.Request(kwargs['url']) + with request.urlopen(req) as response: + self.data = response.read().decode('UTF-8') + self.gpg.import_keys(self.data) + return + + if 'fingerprint' in kwargs: + if not 'keyserver' in kwargs: + kwargs['keyserver'] = SKS_KEYSERVER + + if not 'keypath' in kwargs: + kwargs['keypath'] = SKS_KEYLOOKUP_PATH + + key_url = kwargs['keyserver'] + kwargs['keypath'] + kwargs['fingerprint'] + req = request.Request(key_url) + with request.urlopen(req) as response: + self.data = response.read().decode('UTF-8') + self.gpg.import_keys(self.data) + return + + raise TypeError( + f'load_key_data() got an unexpected keyword argument "{kwargs.keys()}', + ' Expected keyword arguments are: [raw, ascii, url, fingerprint]' + ) + + + diff --git a/archive/repolib/parsedeb.py b/archive/repolib/parsedeb.py new file mode 100644 index 0000000..1e0d992 --- /dev/null +++ b/archive/repolib/parsedeb.py @@ -0,0 +1,371 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging + +from . import util + +log = logging.getLogger(__name__) + +class DebParseError(util.RepoError): + """ Exceptions related to parsing deb lines.""" + + def __init__(self, *args, code=1, **kwargs): + """Exceptions related to parsing deb lines. + + Arguments: + code (:obj:`int`, optional, default=1): Exception error code. + """ + super().__init__(*args, **kwargs) + self.code = code + +def debsplit(line:str) -> list: + """ Improved string.split() with support for things like [] options. + + Adapted from python-apt + + Arguments: + line(str): The line to split up. + """ + line = line.strip() + line_list = line.split() + for i in line_list: + if util.url_validator(i): + line_list[line_list.index(i)] = decode_brackets(i) + line = ' '.join(line_list) + pieces:list = [] + tmp:str = "" + # we are inside a [..] block + p_found = False + for char in line: + if char == '[': + p_found = True + tmp += char + elif char == ']': + p_found = False + tmp += char + elif char.isspace() and not p_found: + pieces.append(tmp) + tmp = '' + continue + else: + tmp += char + # append last piece + if len(tmp) > 0: + pieces.append(tmp) + return pieces + +def encode_brackets(word:str) -> str: + """ Encodes any [ and ] brackets into URL-safe form + + Technically we should never be recieving these, and there are other things + which should technically be encoded as well. However, square brackets + actively break the URL parsing, and must be strictly avoided. + + Arguments: + word (str): the string to encode brackets in. + + Returns: + `str`: the encoded string. + """ + word = word.replace('[', '%5B') + word = word.replace(']', '%5D') + return word + +def decode_brackets(word:str) -> str: + """ Un-encodes [ and ] from the input + + Since our downstream libraries should also be encoding these correctly, it + is better to retain these as the user entered, as that ensures they can + recognize it properly. + + Arguments: + word (str): The string to decode. + + Returns: + `str`: the decoded string. + """ + word = word.replace('%5B', '[') + word = word.replace('%5D', ']') + return word + +def parse_name_ident(tail:str) -> tuple: + """ Find a Repolib name within the given comment string. + + The name should be headed with "X-Repolib-Name:" and is not space terminated. + The ident should be headed with "X-Repolib-ID:" and is space terminated. + + Either field ends at the end of a line, or at a subsequent definition of a + different field, or at a subsequent ' #' substring. Additionally, the ident + field ends with a subsequent space. + + Arguments: + tail (str): The comment to search within. + + Returns: tuple(name, ident, comment): + name (str): The detected name, or None + ident (str): The detected ident, or None + comment (str): The string with the name and ident removed + """ + tail = util.strip_hashes(tail) + + # Used for sanity checking later + has_name = 'X-Repolib-Name' in tail + log.debug('Line name found: %s', has_name) + has_ident = 'X-Repolib-ID' in tail + log.debug('Line ident found: %s', has_ident) + + parts: list = tail.split() + name_found = False + ident_found = False + name:str = '' + ident:str = '' + comment:str = '' + for item in parts: + log.debug("Checking line item: %s", item) + item_is_name = item.strip('#').strip().startswith('X-Repolib-Name') + item_is_ident = item.strip('#').strip().startswith('X-Repolib-ID') + + if '#' in item and not item_is_name and not item_is_ident: + name_found = False + ident_found = False + + elif item_is_name: + name_found = True + ident_found = False + continue + + elif item_is_ident: + name_found = False + ident_found = True + continue + + if name_found and not item_is_name: + name += f'{item} ' + continue + + elif ident_found and not item_is_ident: + ident += f'{item}' + ident_found = False + continue + + elif not name_found and not ident_found: + c = item.strip('#') + comment += f'{c} ' + + name = name.strip() + ident = ident.strip() + comment = comment.strip() + + if not name: + if ident: + name = ident + + # Final sanity checking + if has_name and not name: + raise DebParseError( + f'Could not parse repository name from comment {comment}. Make sure ' + 'you have a space between the colon and the Name' + ) + if has_ident and not ident: + raise DebParseError( + f'Could not parse repository ident from comment {comment}. Make sure ' + 'you have a space between the colon and the Ident' + ) + + return name, ident, comment + + +class ParseDeb: + """ Parsing for source entries. + + Contains parsing helpers for one-line format sources. + """ + + def __init__(self, debug:bool = False) -> None: + """ + Arguments: + debug (bool): In debug mode, the structured data is always returned + at the end, instead of checking for sanity (default: `False`) + """ + self.debug = debug + self.last_line: str = '' + self.last_line_valid: bool = False + self.curr_line: str = '' + self.curr_line_valid: bool = False + + def parse_options(self, opt:str) -> dict: + """ Parses a string of options into a dictionary that repolib can use. + + Arguments: + opt(str): The string with options returned from the line parser. + + Returns: + `dict`: The dictionary of options with key:val pairs (may be {}) + """ + opt = opt.strip() + opt = opt[1:-1].strip() # Remove enclosing brackets + options = opt.split() + + parsed_options:dict = {} + + for opt in options: + pre_key, values = opt.split('=') + values = values.split(',') + value:str = ' '.join(values) + try: + key:str = util.options_inmap[pre_key] + except KeyError: + raise DebParseError( + f'Could not parse line {self.curr_line}: option {opt} is ' + 'not a valid debian repository option or is unsupported.' + ) + parsed_options[key] = value + + return parsed_options + + + def parse_line(self, line:str) -> dict: + """ Parse a deb line into its individual parts. + + Adapted from python-apt + + Arguments: + line (str): The line input to parse + + Returns: + (dict): a dict containing the requisite data. + """ + self.last_line = self.curr_line + self.last_line_valid = self.curr_line_valid + self.curr_line = line.strip() + parts:list = [] + + line_is_comment = self.curr_line == '#' + line_is_empty = self.curr_line == '' + if line_is_comment or line_is_empty: + raise DebParseError(f'Current line "{self.curr_line}" is empty') + + line_parsed: dict = {} + line_parsed['enabled'] = True + line_parsed['name'] = '' + line_parsed['ident'] = '' + line_parsed['comments'] = [] + line_parsed['repo_type'] = '' + line_parsed['uri'] = '' + line_parsed['suite'] = '' + line_parsed['components'] = [] + line_parsed['options'] = {} + + if line.startswith('#'): + line_parsed['enabled'] = False + line = util.strip_hashes(line) + parts = line.split() + if not parts[0] in ('deb', 'deb-src'): + raise DebParseError(f'Current line "{self.curr_line}" is invalid') + + comments_index = line.find('#') + if comments_index > 0: + raw_comments:str = line[comments_index + 1:].strip() + ( + line_parsed['name'], + line_parsed['ident'], + comments + ) = parse_name_ident(raw_comments) + line_parsed['comments'].append(comments) + line = line[:comments_index] + + parts = debsplit(line) + if len(parts) < 3: # We need at least a type, a URL, and a component + raise DebParseError( + f'The line "{self.curr_line}" does not have enough pieces to be' + 'valid' + ) + # Determine the type of the repo + repo_type:str = parts.pop(0) + if repo_type in ['deb', 'deb-src']: + line_parsed['repo_type'] = util.SourceType(repo_type) + else: + raise DebParseError(f'The line "{self.curr_line}" is of invalid type.') + + # Determine the properties of our repo line + uri_index:int = 0 + is_cdrom: bool = False + ## The URI index is the vital piece of information we need to parse the + ## deb line, as it's position determines what other components are + ## present and where they are. This determines the location of the URI + ## regardless of where it's at. + for part in parts: + if part.startswith('['): + if 'cdrom' in part: + is_cdrom = True + uri_index = parts.index(part) + else: + uri_index = 1 + + if is_cdrom: + # This could maybe change if the parser now differentiates between + # CDROM URIs and option lists + raise DebParseError('Repolib cannot currently accept CDROM Sources') + + if uri_index != 0: + line_parsed['options'] = self.parse_options(parts.pop(0)) + + if len(line_parsed) < 2: # Should have at minimum a URI and a suite/path + raise DebParseError( + f'The line "{self.curr_line}" does not have enough pieces to be' + 'valid' + ) + + line_uri = parts.pop(0) + if util.url_validator(line_uri): + line_parsed['uri'] = line_uri + + else: + raise DebParseError( + f'The line "{self.curr_line}" has invalid URI: {line_uri}' + ) + + line_parsed['suite'] = parts.pop(0) + + line_components:list = [] + for comp in parts: + line_parsed['components'].append(comp) + + + has_type = line_parsed['repo_type'] + has_uri = line_parsed['uri'] + has_suite = line_parsed['suite'] + + if has_type and has_uri and has_suite: + # if we have these three minimum components, we can proceed and the + # line is valid. Otherwise, error out. + return line_parsed.copy() + + if self.debug: + return line_parsed.copy() + + raise DebParseError( + f'The line {self.curr_line} could not be parsed due to an ' + 'unknown error (Probably missing the repo type, URI, or a ' + 'suite/path).' + ) diff --git a/archive/repolib/shortcuts/__init__.py b/archive/repolib/shortcuts/__init__.py new file mode 100644 index 0000000..43c1503 --- /dev/null +++ b/archive/repolib/shortcuts/__init__.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +from .popdev import PopdevSource +from .ppa import PPASource +from ..source import Source + +shortcut_prefixes = { + 'deb': Source, + 'deb-src': Source, + ppa.prefix: ppa.PPASource, + popdev.prefix: popdev.PopdevSource +} \ No newline at end of file diff --git a/archive/repolib/shortcuts/popdev.py b/archive/repolib/shortcuts/popdev.py new file mode 100644 index 0000000..f4efc6e --- /dev/null +++ b/archive/repolib/shortcuts/popdev.py @@ -0,0 +1,171 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging +from pathlib import Path + +import dbus + +from repolib.key import SourceKey + +from ..source import Source, SourceError +from ..file import SourceFile +from .. import util + +BASE_FORMAT = util.SourceFormat.DEFAULT +BASE_URL = 'http://apt.pop-os.org/staging' +BASE_COMPS = 'main' +BASE_KEYURL = 'https://raw.githubusercontent.com/pop-os/pop/master/scripts/.iso.asc' + +DEFAULT_FORMAT = util.SourceFormat.DEFAULT + +prefix = 'popdev' +delineator = ':' + +class PopdevSource(Source): + """ PopDev Source shortcut + + These are given in the format popdev:branchname. + + Arguments: + shortcut (str): The ppa: shortcut to process + """ + prefs_dir = Path('/etc/apt/preferences.d') + default_format = BASE_FORMAT + + @staticmethod + def validator(shortcut:str) -> bool: + """Determine whether a PPA shortcut is valid. + + Arguments: + shortcut(str): The shortcut to validate + + Returns: bool + `True` if the PPA is valid, otherwise False + """ + if '/' in shortcut: + return False + + shortcut_split = shortcut.split(':') + try: + if not shortcut_split[1]: + return False + except IndexError: + return False + + if shortcut.startswith(f'{prefix}:'): + shortlist = shortcut.split(':') + if len(shortlist) > 0: + return True + + return False + + def __init__(self, *args, line='', fetch_data=True, **kwargs): + if line: + if not line.startswith('ppa:'): + raise SourceError(f'The PPA shortcut {line} is malformed') + super().__init__(args, kwargs) + self.log = logging.getLogger(__name__) + self.line = line + self.twin_source:bool = True + self.prefs_path = None + self.branch_name:str = '' + self.branch_url:str = '' + if line: + self.load_from_shortcut(line) + + def tasks_save(self, *args, **kwargs) -> None: + super().tasks_save(*args, **kwargs) + self.log.info('Saving prefs file for %s', self.ident) + prefs_contents = 'Package: *\n' + prefs_contents += f'Pin: release o=pop-os-staging-{self.branch_url}\n' + prefs_contents += 'Pin-Priority: 1002\n' + + self.log.debug('%s prefs for pin priority:\n%s', self.ident, prefs_contents) + + try: + with open(self.prefs, mode='w') as prefs_file: + prefs_file.write(prefs_contents) + except PermissionError: + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.output_prefs_to_disk(str(self.prefs), prefs_contents) + + self.log.debug('Pin priority saved for %s', self.ident) + + + def get_description(self) -> str: + return f'Pop Development Staging branch' + + + def load_from_data(self, data: list) -> None: + self.log.debug('Loading line %s', data[0]) + self.load_from_shortcut(shortcut=data[0]) + + def load_from_shortcut(self, shortcut:str='', meta:bool=True, get_key:bool=True) -> None: + """Translates the shortcut line into a full repo. + + Arguments: + shortcut(str): The shortcut to load, if one hasn't been loaded yet. + """ + self.reset_values() + if shortcut: + self.line = shortcut + + if not self.line: + raise SourceError('No PPA shortcut provided') + + if not self.validator(self.line): + raise SourceError(f'The line {self.line} is malformed') + + self.log.debug('Loading shortcut %s', self.line) + + self.info_parts = shortcut.split(delineator) + self.branch_url = ':'.join(self.info_parts[1:]) + self.branch_name = util.scrub_filename(name=self.branch_url) + self.log.debug('Popdev branch name: %s', self.branch_name) + + self.ident = f'{prefix}-{self.branch_name}' + if f'{self.ident}.{BASE_FORMAT.value}' not in util.files: + new_file = SourceFile(name=self.ident) + new_file.format = BASE_FORMAT + self.file = new_file + util.files[str(self.file.path)] = self.file + else: + self.file = util.files[str(self.file.path)] + + self.file.add_source(self) + + self.name = f'Pop Development Branch {self.branch_name}' + self.uris = [f'{BASE_URL}/{self.branch_url}'] + self.suites = [util.DISTRO_CODENAME] + self.components = [BASE_COMPS] + + key = SourceKey(name='popdev') + key.load_key_data(url=BASE_KEYURL) + self.key = key + self.signed_by = str(self.key.path) + + self.prefs_path = self.prefs_dir / f'pop-os-staging-{self.branch_name}' + self.prefs = self.prefs_path + + self.enabled = True diff --git a/archive/repolib/shortcuts/ppa.py b/archive/repolib/shortcuts/ppa.py new file mode 100644 index 0000000..624ac4e --- /dev/null +++ b/archive/repolib/shortcuts/ppa.py @@ -0,0 +1,274 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging + +from repolib.key import SourceKey + +from ..source import Source, SourceError +from ..file import SourceFile +from .. import util + +try: + from launchpadlib.launchpad import Launchpad + from lazr.restfulclient.errors import BadRequest, NotFound, Unauthorized +except ImportError: + raise SourceError( + 'Missing optional dependency "launchpadlib". Try `sudo apt install ' + 'python3-launchpadlib` to install it.' + ) + +BASE_FORMAT = util.SourceFormat.LEGACY +BASE_URL = 'http://ppa.launchpad.net' +BASE_DIST = 'ubuntu' +BASE_COMPS = 'main' + +DEFAULT_FORMAT = util.SourceFormat.LEGACY + +prefix = 'ppa' +delineator = ':' + +class PPASource(Source): + """ PPA Source shortcut + + These are given in the format ppa:owner/name. Much of this code is adapted + from SoftwareProperties. + + Arguments: + shortcut (str): The ppa: shortcut to process + fetch_data (bool): Whether to try and fetch metadata from Launchpad. + """ + + default_format = BASE_FORMAT + + @staticmethod + def validator(shortcut:str) -> bool: + """Determine whether a PPA shortcut is valid. + + Arguments: + shortcut(str): The shortcut to validate + + Returns: bool + `True` if the PPA is valid, otherwise False + """ + + if shortcut.startswith(f'{prefix}:'): + shortlist = shortcut.split('/') + if len(shortlist) > 1: + return True + return False + + def __init__(self, *args, line='', fetch_data=True, **kwargs): + if line: + if not line.startswith('ppa:'): + raise SourceError(f'The PPA shortcut {line} is malformed') + super().__init__(args, kwargs) + self.log = logging.getLogger(__name__) + self.line = line + self.ppa = None + self.twin_source = True + self._displayname = '' + self._description = '' + if line: + self.load_from_shortcut(self.line) + + def get_description(self) -> str: + output:str = '' + output += self.displayname + output += '\n\n' + output += self.description + return output + + def load_from_data(self, data: list) -> None: + self.load_from_shortcut(shortcut=data[0]) + + def load_from_shortcut(self, shortcut:str='', meta:bool=True, key:bool=True) -> None: + """Translates the shortcut line into a full repo. + + Arguments: + shortcut(str): The shortcut to load, if one hasn't been loaded yet. + meta(bool): Whether to fetch repo metadata from Launchpad + key(bool): Whether to fetch and install a signing key + """ + self.reset_values() + if shortcut: + self.line = shortcut + + if not self.line: + raise SourceError('No PPA shortcut provided') + + if not self.validator(self.line): + raise SourceError(f'The line {self.line} is malformed') + + line = self.line.replace(prefix + delineator, '') + self.info_parts = line.split('/') + ppa_owner = self.info_parts[0] + ppa_name = self.info_parts[1] + + self.ident = f'{prefix}-{ppa_owner}-{ppa_name}' + if f'{self.ident}.{BASE_FORMAT.value}' not in util.files: + new_file = SourceFile(name=self.ident) + new_file.format = BASE_FORMAT + self.file = new_file + util.files[str(self.file.path)] = self.file + else: + self.file = util.files[str(self.file.path)] + + self.file.add_source(self) + + self.name = self.ident + self.uris = [f'{BASE_URL}/{ppa_owner}/{ppa_name}/{BASE_DIST}'] + self.suites = [util.DISTRO_CODENAME] + self.components = [BASE_COMPS] + + if meta or key: + self.ppa = get_info_from_lp(ppa_owner, ppa_name) + self.displayname = self.ppa.displayname + self.description = self.ppa.description + + if self.ppa and meta: + self.name = self.ppa.displayname + + if self.ppa and key: + repo_key = SourceKey(name=self.ident) + if str(repo_key.path) not in util.keys: + repo_key.load_key_data(fingerprint=self.ppa.fingerprint) + util.keys[str(repo_key.path)] = repo_key + self.key:SourceKey = repo_key + else: + self.key = util.keys[repo_key.path] + self.signed_by = str(self.key.path) + + self.enabled = True + + @property + def displayname(self) -> str: + """The name of the PPA provided by launchpad""" + if self._displayname: + return self._displayname + if self.ppa: + self._displayname = self.ppa.displayname + return self._displayname + + @displayname.setter + def displayname(self, displayname) -> None: + """Cache this for use without hitting LP""" + self._displayname = displayname + + @property + def description(self) -> str: + """The description of the PPA provided by Launchpad""" + if self._description: + return self._description + if self.ppa: + self._description = self.ppa.description + return self._description + + @description.setter + def description(self, desc) -> None: + """Cache this for use without hitting LP""" + self._description = desc + +class PPA: + """ An object to fetch data from PPAs. + + Portions of this class were adapted from Software Properties + """ + + def __init__(self, teamname, ppaname): + self.teamname = teamname + self.ppaname = ppaname + self._lap = None + self._lpteam = None + self._lpppa = None + self._signing_key_data = None + self._fingerprint = None + + @property + def lap(self): + """ The Launchpad Object.""" + if not self._lap: + self._lap = Launchpad.login_anonymously( + f'{self.__module__}.{self.__class__.__name__}', + service_root='production', + version='devel' + ) + return self._lap + + @property + def lpteam(self): + """ The Launchpad object for the PPA's owner.""" + if not self._lpteam: + try: + self._lpteam = self.lap.people(self.teamname) # type: ignore (This won't actually be unbound because of the property) + except NotFound as err: + msg = f'User/Team "{self.teamname}" not found' + raise SourceError(msg) from err + except Unauthorized as err: + msg = f'Invalid user/team name "{self.teamname}"' + raise SourceError(msg) from err + return self._lpteam + + @property + def lpppa(self): + """ The Launchpad object for the PPA.""" + if not self._lpppa: + try: + self._lpppa = self.lpteam.getPPAByName(name=self.ppaname) + except NotFound as err: + msg = f'PPA "{self.teamname}/{self.ppaname}"" not found' + raise SourceError(msg) from err + except BadRequest as err: + msg = f'Invalid PPA name "{self.ppaname}"' + raise SourceError(msg) from err + return self._lpppa + + @property + def description(self) -> str: + """str: The description of the PPA.""" + return self.lpppa.description or '' + + @property + def displayname(self) -> str: + """ str: the fancy name of the PPA.""" + return self.lpppa.displayname or '' + + @property + def fingerprint(self): + """ str: the fingerprint of the signing key.""" + if not self._fingerprint: + self._fingerprint = self.lpppa.signing_key_fingerprint + return self._fingerprint + + +def get_info_from_lp(owner_name, ppa): + """ Attempt to get information on a PPA from launchpad over the internet. + + Arguments: + owner_name (str): The Launchpad user owning the PPA. + ppa (str): The name of the PPA + + Returns: + json: The PPA information as a JSON object. + """ + ppa = PPA(owner_name, ppa) + return ppa diff --git a/archive/repolib/source.py b/archive/repolib/source.py new file mode 100644 index 0000000..889221b --- /dev/null +++ b/archive/repolib/source.py @@ -0,0 +1,920 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging +from pathlib import Path + +from debian import deb822 + +from .parsedeb import ParseDeb +from .key import SourceKey +from . import util + +DEFAULT_FORMAT = util.SourceFormat.LEGACY + +class SourceError(util.RepoError): + """ Exception from a source object.""" + + def __init__(self, *args, code=1, **kwargs): + """Exception with a source object + + Arguments: + code (:obj:`int`, optional, default=1): Exception error code. + """ + super().__init__(*args, **kwargs) + self.code = code + +class Source(deb822.Deb822): + """A DEB822 object representing a single software source. + + Attributes: + ident(str): The unique id for this source + name(str): The user-readable name for this source + enabled(bool): Whether or not the source is enabled + types([SourceType]): A list of repository types for this source + uris([str]): A list of possible URIs for this source + suites([str]): A list of enabled suites for this source + components([str]): A list of enabled components for this source + comments(str): Comments for this source + signed_by(Path): The path to this source's key file + file(SourceFile): The file this source belongs to + key(SourceKey): The key which signs this source + """ + + default_format = DEFAULT_FORMAT + + @staticmethod + def validator(shortcut:str) -> bool: + """Determine whether a deb line is valid. + + Arguments: + shortcut(str): The shortcut to validate + + Returns: bool + `True` if the PPA is valid, otherwise False + """ + shortcut_list:list = shortcut.split() + + if not shortcut.startswith('deb'): + return False + + if not len(shortcut_list) > 3: + return False + + if not util.validate_debline: + return False + + if len(shortcut_list) == 3 and '/' not in shortcut_list[-1]: + return False + + return True + + def __init__(self, *args, file=None, **kwargs) -> None: + """Initialize this source object""" + self.log = logging.getLogger(__name__) + super().__init__(*args, **kwargs) + self.reset_values() + self.file = file + self.twin_source = False + self.twin_enabled = False + + def __repr__(self): + """type: () -> str""" + # Append comments to the item + # if self.options: + + if self.comments: + self['Comments'] = '# ' + self['Comments'] += ' # '.join(self.comments) + + rep:str = '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + + rep:str = '{' + for key in self: + rep += f"{util.PRETTY_PRINT}'{key}': '{self[key]}', " + + rep = rep[:-2] + rep += f"{util.PRETTY_PRINT.replace(' ', '')}" + rep += '}' + + if self.comments: + self.pop('Comments') + + return rep + + def __bool__(self) -> bool: + has_uri:bool = len(self.uris) > 0 + has_suite:bool = len(self.suites) > 0 + has_component:bool = len(self.components) > 0 + + if has_uri and has_suite and has_component: + return True + return False + + + def get_description(self) -> str: + """Get a UI-compatible description for a source. + + Returns: (str) + The formatted description. + """ + return self.name + + def reset_values(self) -> None: + """Reset the default values for all attributes""" + self.log.info('Resetting source info') + self.ident = '' + self.name = '' + self.enabled = True + self.types = [util.SourceType.BINARY] + self.uris = [] + self.suites = [] + self.components = [] + self.comments = [] + self.signed_by = None + self.architectures = '' + self.languages = '' + self.targets = '' + self.pdiffs = '' + self.by_hash = '' + self.allow_insecure = '' + self.allow_weak = '' + self.allow_downgrade_to_insecure = '' + self.trusted = '' + self.signed_by = '' + self.check_valid_until = '' + self.valid_until_min = '' + self.valid_until_max = '' + self.prefs = '' + self._update_legacy_options() + self.file = None + self.key = None + + def load_from_data(self, data:list) -> None: + """Loads source information from the provided data + + Should correctly load either a lecagy Deb line (optionally with + preceeding comment) or a DEB822 source. + + Arguments: + data(list): the data to load into the source. + """ + self.log.info('Loading source from data') + self.reset_values() + + if util.validate_debline(data[0]): # Legacy Source + if len(data) > 1: + raise SourceError( + f'The source is a legacy source but contains {len(data)} entries. ' + 'It may only contain one entry.' + ) + deb_parser = ParseDeb() + parsed_debline = deb_parser.parse_line(data[0]) + self.ident = parsed_debline['ident'] + self.name = parsed_debline['name'] + self.enabled = parsed_debline['enabled'] + self.types = [parsed_debline['repo_type']] + self.uris = [parsed_debline['uri']] + self.suites = [parsed_debline['suite']] + self.components = parsed_debline['components'] + for key in parsed_debline['options']: + self[key] = parsed_debline['options'][key] + self._update_legacy_options() + for comment in parsed_debline['comments']: + self.comments.append(comment) + if self.comments == ['']: + self.comments = [] + + if not self.name: + self.name = self.generate_default_name() + + if self.signed_by: + self.load_key() + return + + # DEB822 Source + super().__init__(sequence=data) + if self.signed_by: + self.load_key() + return + + @property + def sourcecode_enabled(self) -> bool: + """`True` if this source also provides source code, otherwise `False`""" + if util.SourceType.SOURCECODE in self.types: + return True + return False + + @sourcecode_enabled.setter + def sourcecode_enabled(self, enabled) -> None: + """Accept a variety of input values""" + types = [util.SourceType.BINARY] + if enabled in util.true_values: + types.append(util.SourceType.SOURCECODE) + self.types = types + + + def generate_default_ident(self, prefix='') -> str: + """Generates a suitable ID for the source + + Returns: str + A sane default-id + """ + ident:str = '' + if len(self.uris) > 0: + uri:str = self.uris[0].replace('/', ' ') + uri_list:list = uri.split() + uri_str:str = '-'.join(uri_list[1:]) + branch_name:str = util.scrub_filename(uri_str) + ident = f'{prefix}{branch_name}' + ident += f'-{self.types[0].ident()}' + try: + if not self['X-Repolib-ID']: + self['X-Repolib-ID'] = ident + except KeyError: + self['X-Repolib-ID'] = ident + return ident + + def generate_default_name(self) -> str: + """Generate a default name based on the ident + + Returns: str + A name based on the ident + """ + name:str = self.ident + try: + name = self['X-Repolib-Name'] + except KeyError: + self['X-Repolib-Name'] = self.ident + return self['X-Repolib-Name'] + + if not name: + self['X-Repolib-Name'] = self.ident + + return self['X-Repolib-Name'] + + def load_key(self, ignore_errors:bool = True) -> None: + """Finds and loads the signing key from the system + + Arguments: + ignore_errors(bool): If `False`, throw a SourceError exception if + the key can't be found/doesn't exist (Default: `True`) + """ + self.log.info('Finding key for source %s', self.ident) + if not self.signed_by: + if ignore_errors: + self.log.warning('No key configured for source %s', self.ident) + else: + raise SourceError('No key configured for source {self.ident}') + + if self.signed_by not in util.keys: + new_key = SourceKey() + new_key.reset_path(path=self.signed_by) + self.key = new_key + util.keys[str(new_key.path)] = new_key + else: + self.key = util.keys[self.signed_by] + + def save(self) -> None: + """Proxy method to save the source""" + if not self.file == None: + self.file.save() + + def output_legacy(self) -> str: + """Outputs a legacy representation of this source + + Note: this is expected to fail if there is more than one type, URI, or + Suite; the one-line format does not support having multiple of these + properties. + + Returns: str + The source output formatted as Legacy + """ + return self.legacy + + def output_822(self) -> str: + """Outputs a DEB822 representation of this source + + Returns: str + The source output formatted as Deb822 + """ + return self.deb822 + + def output_ui(self) -> str: + """Outputs a string representation of this source for use in UIs + + Returns: str + The source output string + """ + return self.ui + + def prop_append(self, prop:list, item:str) -> None: + """Appends an item to a list property of this source. + NOTE: List properties are `types`, `uris`, `suites`, and `components`. + + Arguments: + prop(list): The property on which to append the item. + item(str): The item to append to the propery + """ + _list = prop + _list.append(item) + prop = _list + + def tasks_save(self, *args, **kwargs) -> None: + """Extra tasks to perform when saving a source""" + return + + ## Properties are stored/retrieved from the underlying Deb822 dict + @property + def has_required_parts(self) -> bool: + """(RO) True if all required attributes are set, otherwise false.""" + required_parts = ['uris', 'suites', 'ident'] + + for attr in required_parts: + if len(getattr(self, attr)) < 1: + return False + + return True + + + @property + def ident(self) -> str: + """The ident for this source within the file""" + try: + return self['X-Repolib-ID'] + except KeyError: + return '' + + + @ident.setter + def ident(self, ident: str) -> None: + ident = util.scrub_filename(ident) + self['X-Repolib-ID'] = ident + + + @property + def name(self) -> str: + """The human-friendly name for this source""" + try: + _name = self['X-Repolib-Name'] + except KeyError: + _name = '' + + if not _name: + self.generate_default_name() + return self['X-Repolib-Name'] + + @name.setter + def name(self, name: str) -> None: + self['X-Repolib-Name'] = name + + + @property + def enabled(self) -> util.AptSourceEnabled: + """Whether or not the source is enabled/active""" + try: + enabled = self['Enabled'] in util.true_values + except KeyError: + return util.AptSourceEnabled.FALSE + + if enabled and self.has_required_parts: + return util.AptSourceEnabled.TRUE + return util.AptSourceEnabled.FALSE + + @enabled.setter + def enabled(self, enabled) -> None: + """For convenience, accept a wide varietry of input value types""" + self['Enabled'] = 'no' + if enabled in util.true_values: + self['Enabled'] = 'yes' + + + @property + def types(self) -> list: + """The list of source types for this source""" + _types:list = [] + try: + for sourcetype in self['types'].split(): + _types.append(util.SourceType(sourcetype)) + except KeyError: + pass + return _types + + @types.setter + def types(self, types: list) -> None: + """Turn this list into a string of values for storage""" + self['Types'] = '' + _types:list = [] + for sourcetype in types: + if sourcetype not in _types: + _types.append(sourcetype) + for sourcetype in _types: + self['Types'] += f'{sourcetype.value} ' + self['Types'] = self['Types'].strip() + + + @property + def uris(self) -> list: + """The list of URIs for this source""" + try: + return self['URIs'].split() + except KeyError: + return [] + + @uris.setter + def uris(self, uris: list) -> None: + self['URIs'] = ' '.join(uris).strip() + + + @property + def suites(self) -> list: + """The list of URIs for this source""" + try: + return self['Suites'].split() + except KeyError: + return [] + + @suites.setter + def suites(self, suites: list) -> None: + self['Suites'] = ' '.join(suites).strip() + + + @property + def components(self) -> list: + """The list of URIs for this source""" + try: + return self['Components'].split() + except KeyError: + return [] + + @components.setter + def components(self, components: list) -> None: + self['Components'] = ' '.join(components).strip() + + + @property + def options(self) -> dict: + """The options for this source""" + return self._options + + @options.setter + def options(self, options:dict) -> None: + if 'Signed-By' in options: + self.signed_by = options['Signed-By'] + if self.signed_by: + options.pop('Signed-By') + self._options = options + + @property + def prefs(self): + """The path to any apt preferences files for this source.""" + try: + prefs = self['X-Repolib-Prefs'] + except KeyError: + prefs = '' + + if prefs: + return Path(prefs) + return Path() + + @prefs.setter + def prefs(self, prefs): + """Accept a str or a Path-like object""" + try: + del self['X-Repolib-Prefs'] + except KeyError: + pass + + if prefs: + prefs_str = str(prefs) + self['X-Repolib-Prefs'] = prefs_str + + + ## Option properties + + @property + def architectures (self) -> str: + """architectures option""" + try: + return self['Architectures'] + except KeyError: + return '' + + @architectures.setter + def architectures(self, data) -> None: + try: + self.pop('Architectures') + except KeyError: + pass + + if data: + self['Architectures'] = data + self._update_legacy_options() + + + @property + def languages (self) -> str: + """languages option""" + try: + return self['Languages'] + except KeyError: + return '' + + @languages.setter + def languages(self, data) -> None: + try: + self.pop('Languages') + except KeyError: + pass + + if data: + self['Languages'] = data + self._update_legacy_options() + + + @property + def targets (self) -> str: + """targets option""" + try: + return self['Targets'] + except KeyError: + return '' + + @targets.setter + def targets(self, data) -> None: + try: + self.pop('Targets') + except KeyError: + pass + + if data: + self['Targets'] = data + self._update_legacy_options() + + + @property + def pdiffs (self) -> str: + """pdiffs option""" + try: + return self['Pdiffs'] + except KeyError: + return '' + + @pdiffs.setter + def pdiffs(self, data) -> None: + try: + self.pop('Pdiffs') + except KeyError: + pass + + if data: + self['Pdiffs'] = data + self._update_legacy_options() + + + @property + def by_hash (self) -> str: + """by_hash option""" + try: + return self['By-Hash'] + except KeyError: + return '' + + @by_hash.setter + def by_hash(self, data) -> None: + try: + self.pop('By-Hash') + except KeyError: + pass + + if data: + self['By-Hash'] = data + self._update_legacy_options() + + + @property + def allow_insecure (self) -> str: + """allow_insecure option""" + try: + return self['Allow-Insecure'] + except KeyError: + return '' + + @allow_insecure.setter + def allow_insecure(self, data) -> None: + try: + self.pop('Allow-Insecure') + except KeyError: + pass + + if data: + self['Allow-Insecure'] = data + self._update_legacy_options() + + + @property + def allow_weak (self) -> str: + """allow_weak option""" + try: + return self['Allow-Weak'] + except KeyError: + return '' + + @allow_weak.setter + def allow_weak(self, data) -> None: + try: + self.pop('Allow-Weak') + except KeyError: + pass + + if data: + self['Allow-Weak'] = data + self._update_legacy_options() + + + @property + def allow_downgrade_to_insecure (self) -> str: + """allow_downgrade_to_insecure option""" + try: + return self['Allow-Downgrade-To-Insecure'] + except KeyError: + return '' + + @allow_downgrade_to_insecure.setter + def allow_downgrade_to_insecure(self, data) -> None: + try: + self.pop('Allow-Downgrade-To-Insecure') + except KeyError: + pass + + if data: + self['Allow-Downgrade-To-Insecure'] = data + self._update_legacy_options() + + + @property + def trusted (self) -> str: + """trusted option""" + try: + return self['Trusted'] + except KeyError: + return '' + + @trusted.setter + def trusted(self, data) -> None: + try: + self.pop('Trusted') + except KeyError: + pass + + if data: + self['Trusted'] = data + self._update_legacy_options() + + + @property + def signed_by (self) -> str: + """signed_by option""" + try: + return self['Signed-By'] + except KeyError: + return '' + + @signed_by.setter + def signed_by(self, data) -> None: + try: + self.pop('Signed-By') + except KeyError: + pass + + if data: + self['Signed-By'] = data + self._update_legacy_options() + + + @property + def check_valid_until (self) -> str: + """check_valid_until option""" + try: + return self['Check-Valid-Until'] + except KeyError: + return '' + + @check_valid_until.setter + def check_valid_until(self, data) -> None: + try: + self.pop('Check-Valid-Until') + except KeyError: + pass + + if data: + self['Check-Valid-Until'] = data + self._update_legacy_options() + + + @property + def valid_until_min (self) -> str: + """valid_until_min option""" + try: + return self['Valid-Until-Min'] + except KeyError: + return '' + + @valid_until_min.setter + def valid_until_min(self, data) -> None: + try: + self.pop('Valid-Until-Min') + except KeyError: + pass + + if data: + self['Valid-Until-Min'] = data + self._update_legacy_options() + + + @property + def valid_until_max (self) -> str: + """valid_until_max option""" + try: + return self['Valid-Until-Max'] + except KeyError: + return '' + + @valid_until_max.setter + def valid_until_max(self, data) -> None: + try: + self.pop('Valid-Until-Max') + except KeyError: + pass + + if data: + self['Valid-Until-Max'] = data + self._update_legacy_options() + + @property + def default_mirror(self) -> str: + """The default mirror/URI for the source""" + try: + return self['X-Repolib-Default-Mirror'] + except KeyError: + return '' + + @default_mirror.setter + def default_mirror(self, mirror) -> None: + if mirror: + self['X-Repolib-Default-Mirror'] = mirror + else: + self['X-Repolib-Default-Mirror'] = '' + + + ## Output Properties + @property + def deb822(self) -> str: + """The DEB822 representation of this source""" + self._update_legacy_options() + # comments get handled separately because they're a list, and list + # properties don't support .append() + if self.comments: + self['X-Repolib-Comments'] = '# ' + self['X-Repolib-Comments'] += ' # '.join(self.comments) + _deb822 = self.dump() + if self.comments: + self.pop('X-Repolib-Comments') + if _deb822: + return _deb822 + return '' + + @property + def ui(self) -> str: + """The UI-friendly representation of this source""" + self._update_legacy_options() + _ui_list:list = self.deb822.split('\n') + ui_output: str = f'{self.ident}:\n' + for line in _ui_list: + key = line.split(':')[0] + if key not in util.output_skip_keys: + if line: + ui_output += f'{line}\n' + for key in util.keys_map: + ui_output = ui_output.replace(key, util.keys_map[key]) + return ui_output + + @property + def legacy(self) -> str: + """The legacy/one-line format representation of this source""" + self._update_legacy_options() + + if str(self.prefs) != '.': + raise SourceError( + 'Apt Preferences files can only be used with DEB822-format sources.' + ) + + sourcecode = self.sourcecode_enabled + if len(self.types) > 1: + self.twin_source = True + self.types = [util.SourceType.BINARY] + sourcecode = True + + legacy = '' + + legacy += self._generate_legacy_output() + if self.twin_source: + legacy += '\n' + legacy += self._generate_legacy_output(sourcecode=True, enabled=sourcecode) + + return legacy + + def _generate_legacy_output(self, sourcecode=False, enabled=True) -> str: + """Generate a string of the current source in legacy format""" + legacy = '' + + if len(self.types) > 1: + self.twin_source = True + self.types = [util.SourceType.BINARY] + for attr in ['types', 'uris', 'suites']: + if len(getattr(self, attr)) > 1: + msg = f'The source has too many {attr}.' + msg += f'Legacy-format sources support one {attr[:-1]} only.' + raise SourceError(msg) + + if not self.enabled.get_bool() and not sourcecode: + legacy += '# ' + + if sourcecode and not enabled: + legacy += '# ' + + if sourcecode: + legacy += 'deb-src ' + else: + legacy += self.types[0].value + legacy += ' ' + + options_string = self._legacy_options() + if options_string: + legacy += '[' + legacy += options_string + legacy = legacy.strip() + legacy += '] ' + + legacy += f'{self.uris[0]} ' + legacy += f'{self.suites[0]} ' + + for component in self.components: + legacy += f'{component} ' + + legacy += f' ## X-Repolib-Name: {self.name}' + legacy += f' # X-Repolib-ID: {self.ident}' + if self.comments: + for comment in self.comments: + legacy += f' # {comment}' + + return legacy + + def _legacy_options(self) -> str: + """Turn the current options into a oneline-style string + + Returns: str + The one-line-format options string + """ + options_str = '' + for key in self.options: + if self.options[key] != '': + options_str += f'{key}={self.options[key].replace(" ", ",")} ' + return options_str + + def _update_legacy_options(self) -> None: + """Updates the current set of legacy options""" + self.options = { + 'arch': self.architectures, + 'lang': self.languages, + 'target': self.targets, + 'pdiffs': self.pdiffs, + 'by-hash': self.by_hash, + 'allow-insecure': self.allow_insecure, + 'allow-weak': self.allow_weak, + 'allow-downgrade-to-insecure': self.allow_downgrade_to_insecure, + 'trusted': self.trusted, + 'signed-by': self.signed_by, + 'check-valid-until': self.check_valid_until, + 'valid-until-min': self.valid_until_min, + 'valid-until-max': self.valid_until_max, + } \ No newline at end of file diff --git a/archive/repolib/system.py b/archive/repolib/system.py new file mode 100644 index 0000000..b22ef09 --- /dev/null +++ b/archive/repolib/system.py @@ -0,0 +1,73 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import logging + +from pathlib import Path + +from . import util +from .file import SourceFile +from .source import Source +from .shortcuts import popdev, ppa + + +log = logging.getLogger(__name__) + +def load_all_sources() -> None: + """Loads all of the sources present on the system.""" + log.info('Loading all sources') + + util.sources.clear() + util.files.clear() + util.keys.clear() + util.errors.clear() + + sources_path = Path(util.SOURCES_DIR) + sources_files = sources_path.glob('*.sources') + legacy_files = sources_path.glob('*.list') + + for file in sources_files: + try: + sourcefile = SourceFile(name=file.stem) + log.debug('Loading %s', file) + sourcefile.load() + if file.name not in util.files: + util.files[file.name] = sourcefile + + except Exception as err: + util.errors[file.name] = err + + for file in legacy_files: + try: + sourcefile = SourceFile(name=file.stem) + sourcefile.load() + util.files[file.name] = sourcefile + except Exception as err: + util.errors[file.name] = err + + for f in util.files: + file = util.files[f] + for source in file.sources: + if source.ident in util.sources: + source.ident = f'{file.name}-{source.ident}' + source.file.save() + util.sources[source.ident] = source diff --git a/archive/repolib/unittest/__init__.py b/archive/repolib/unittest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/archive/repolib/unittest/test_key.py b/archive/repolib/unittest/test_key.py new file mode 100644 index 0000000..977324b --- /dev/null +++ b/archive/repolib/unittest/test_key.py @@ -0,0 +1,142 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import unittest + +from ..key import SourceKey +from .. import util, system +from .. import set_testing + +# System76 Signing PubKey, for test import +KEY_DATA = ( + '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFlL+3MBEADdNM9Xy2t3EtKU1i3R1o' + '1OCgJqLiDm8OZZq47InYID8oAPKRjd\n0UDVJTrvfsB4oJH97VRi2hGv2xmc19OaFE/NsQBZW/' + '7/3ypLr8eyaNgvscsmG/WN\ncM1cbMZtwd1b0JOr3bNTzp6WKRI3jo9uRw7duM8FwPjKm76Lbo' + 'DQbAR+4Szm3O8x\n/om8Gs1MRPUkY2dVz5KzednFLHwy7qnUXR3WRB5K1L9EBZkFDDNqnyViUI' + 'rE4bTm\nBC9mTg/Xfw/QXUFYz3t/YTYduAU0o1q2yei+8tVAJKh7H9t3PrQ95l3RUUcaAvba\n' + 'A9zlCrI8fonpxu7eSpkqzT4uCkfxdLVwittl1DumKTEkSXDQ5txY21igbSZZQwBA\nZf9MnFhJ' + 'fPsEIq2YHRc1FBcQxiAIpnGizv7FgYY5FxmZQ7592dMQOZ00h+lDSQug\nNMxloHCogaXR038u' + 'IKGTQnQEVcT46FtTRkLMSvbigy+RVSchdu9MEBBPgD3vSv53\nNEobXsLiZ9hF6Hk7XI2WxP5j' + '1zWTPmzxvf9NDOWz2Sw9Z+ilf252LXoxZQaMngp8\nXL32uvw7q+mjB6F1W/qpe3b32uu7eGNr' + 'DWJ5veE808hpXXj803TllmRUfMGUrtY9\nk7uUTQQWtrJ5uZ0QmsTk1oJHCPIUjjuiNtQfq28+' + 'bfg8FEJ/F1N1mB0IvwARAQAB\ntCxQb3AgT1MgKElTTyBTaWduaW5nIEtleSkgPGluZm9Ac3lz' + 'dGVtNzYuY29tPokC\nNwQTAQIAIgUCWUv7cwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA' + 'CgkQIE3Y\nrsM6ev8kXw/4p/8fOH8wM59ZoU0t1+fv3O8dYaDdTVTVIEno9snrsx5A5tbMu59r' + '\nHoBaxGenv/PB0l8yANhRX+HVmU/l0Sj0lTlEkYzgH/IT2Ne60s1ETgI7DlgSuYyP\nH8wq61' + '85+2DyE2+R/XcXGq0I++QUq1Y6rS+B4KIyYcgpJotcVNFaZiwuJZE31uLg\nkVMZrm1oObHear' + '7P2JQTbgsENMZDJEhQBCGKVdnAfVdKUeUrd07syr0cDe3kwY9o\ncNc00bhIh23cLTJE2omok9' + 'yCsXoeFJlPMyZw8WvEa5oaYWzP4Yw7nF8/27JTzZ70\nDjK2D2xoTkr0cP87LtZulS6FC3lxLu' + 'Z6hSaxsqoBH8Dd1uyYVEzLDsIRMtSHsXk+\n3kLrr1p7/7/vjGShlYkbLtP4jWnlHc6vSxIzm/' + 'MQmQMCfjeo3QH7GGw88mYtXngQ\n/Zna6wz0oL6pGM/4t90SCxTxRqCnoxMxzkcpt9n42bj79g' + 'rESOMH4wm3ExfuPk7I\nDtY+SqzIq0QvoPbC3XJLusWVgwUsRF2FpTTRTHEiWEMjWDKDVEyT4K' + '1k1k3f/gi2\n6LdtXwqDwzUvJJU5HYwVFywt+0jt5F0ZlTlPizz3iHw4gMLOielRShl+gZrU2U' + '0O\naj1Hyts9LymEKMUvRQGwMqCZcXo6sGjs59tTsfeGX16PTOyBri8eoLkCDQRZS/tz\nARAA' + 'pD9IWm4zS1AuBcOTuvh1E/ciKHGIUtW3JftD9ah8loEeckakgG5Xn9he1X6J\nyxPULpsptcCC' + 'cKXlw853ZQK9PLQJX6moWLH+qf2Zo3UAn/YEsWk+KsHoxPXHNUds\nu/j6UKkqEk8c7H92hUo8' + 'aWghO3p4HDVJ9KmGtueQ3jOv8Qun7Eh9cIo0A59cKmMv\njKUiYHLIJw8bkveQ8rVPul1ZHn56' + 'ORiBi58vm3tzjI4UWHQMjiKxXT6H5eG/f5K6\nuaK8lljh6n6jhdnQCpBcdtSIbhE/6YRv2+Ig' + 'L+BRssvprBtx4/sBwKjNNqzWPeGy\nUDHMiF88ETYqZ8DfukQ/e5XuaxjU41g/F8cw8BeVTBMv' + 'eb1YTyOoWcWvTL+hoBfS\nqYc/lvDHmmJ7/IgeMvUE6KoByP4ub5wX52mJTqgMC4GMhA04BC60' + 'B+NfVAXLh2pa\nTRJAHoWTDswOxbR6q9zPEFGZzV04B9Y96EavwMpT5IzG2fOPBwvdT0EDnt+v' + 'Q/iB\nc9O7CvkRTROAV+RoNCLY2XU8yNc/XxuI66PCE4Q96jW4uDzHvi6sPW/glsfRi2NT\nRW' + 'CO15KMVf0aypXeBpSbHIXIYGdXRQRpw980IW6PrElPpqZ5/DGbkXei5CuruF2R\nmltuu3MqYQ' + 'jcUvP9T7s0e5GAFgQFrR/8q29nVULq8IF4vzUAEQEAAYkCHwQYAQIA\nCQUCWUv7cwIbDAAKCR' + 'AgTdiuwzp6/wTGD/9Co4gEmTTOW++FneMMJo5K4WqeWVRg\ng1q5+yoVqgWq3k6lLsEC5kxR30' + '5BAAcvXo9XPKdo62ySYmhIFOpIz/TkeTUxDZaw\nsLtcBxXUME2L5j/1od1V9lxecUvLAgA11o' + '5Kb8TMKn5ZcmGhadtTLslWQcYsKqhw\nLaYQRlcxLDHYT8DXFkHgDhUMMbpt07dU5v5lIjgtGN' + 'HRhdS7/lCmSWOBtYapwpAH\nGYSmahN0zO36VHzOB5uwFue0tSoQiBEvLrCV/8ZtT2S5NkXaSm' + 'isz6B5Vr6DRtWI\nOamW5pMbSL8WQNQ99Kik05ctERjv2NgxI4JQo/a4KKthRrT4JlixXmrfJD' + 'uPyDPp\nRuTu1Elo6snoqWKQNf1sEPKvcv7EviNxBOhbTKivWrJXMnbOme7+UlNLcq7VAFp3\n' + 'x5hxk/ap0WqH/hs7+8jMBC8nS402MoM7EyLS0++kbOuEL/Prf3+JxFRqIu5Df77J\n+bUmTtKI' + 'CV43ikiVWmnP5OuJj2JPSOTR+rLxAQYpyHmo7HKXE63FbH1FVLgsT88+\nEW6VtI01I7EYmKQX' + 'EqQo52yfeHKDrQjGNVBWMKcXj0SVU+QQ1Ue/4yLwA+74VD2d\nfOyJI22NfTI+3SMAsMQ8L+WV' + 'QI+58bu7+iEqoEfHCXikE8BtTbJAN4Oob1lrjfOe\n5utH/lMP9suRWw==\n=NL3f\n-----EN' + 'D PGP PUBLIC KEY BLOCK-----\n' +) + +class KeyTestCase(unittest.TestCase): + def setUp(self): + set_testing() + self.key_data = KEY_DATA + self.keys_dir = util.KEYS_DIR + self.key_id = '204DD8AEC33A7AFF' + self.key_uids = ['Pop OS (ISO Signing Key) '] + self.key_length = '4096' + self.key_date = '1498151795' + + def test_import_ascii(self): + key = SourceKey(name='popdev') + key.load_key_data(ascii=self.key_data) + key_dict = key.gpg.list_keys()[0] + key_path = self.keys_dir / 'popdev-archive-keyring.gpg' + + self.assertEqual(key.path, key_path) + self.assertEqual(len(key.gpg.list_keys()), 1) + self.assertEqual(key_dict['keyid'], self.key_id) + self.assertEqual(key_dict['uids'], self.key_uids) + self.assertEqual(key_dict['length'], self.key_length) + self.assertEqual(key_dict['date'], self.key_date) + + def test_key_save_load(self): + print(self.keys_dir) + key_path = self.keys_dir / 'popdev-archive-keyring.gpg' + if key_path.exists(): + key_path.unlink() + + self.assertFalse(key_path.exists()) + key_save = SourceKey(name='popdev') + key_save.load_key_data(ascii=self.key_data) + key_save.save_gpg() + + self.assertTrue(key_save.path.exists()) + + key_load = SourceKey() + key_load.reset_path(name='popdev') + key_dict = key_load.gpg.list_keys()[0] + + self.assertEqual(key_load.path, key_path) + self.assertEqual(len(key_load.gpg.list_keys()), 1) + self.assertEqual(key_dict['keyid'], self.key_id) + self.assertEqual(key_dict['uids'], self.key_uids) + self.assertEqual(key_dict['length'], self.key_length) + self.assertEqual(key_dict['date'], self.key_date) + + def test_delete_key(self): + key_path = self.keys_dir / 'popdev-archive-keyring.gpg' + if key_path.exists(): + key_path.unlink() + + self.assertFalse(key_path.exists()) + + self.assertFalse(key_path.exists()) + key_save = SourceKey(name='popdev') + key_save.load_key_data(ascii=self.key_data) + + key_save.save_gpg() + + self.assertTrue(key_save.path.exists()) + + key_load = SourceKey() + key_load.reset_path(name='popdev') + key_load.delete_key() + + self.assertFalse(key_load.path.exists()) diff --git a/archive/repolib/unittest/test_parsedeb.py b/archive/repolib/unittest/test_parsedeb.py new file mode 100644 index 0000000..0a975a0 --- /dev/null +++ b/archive/repolib/unittest/test_parsedeb.py @@ -0,0 +1,188 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . + +This is a library for parsing deb lines into deb822-format data. +""" + +import unittest + +from ..source import Source +from .. import util + +class DebTestCase(unittest.TestCase): + def test_normal_source(self): + source = Source() + source.load_from_data([ + 'deb http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.types, [util.SourceType.BINARY]) + self.assertTrue(source.enabled.get_bool()) + self.assertEqual(source.uris, ['http://example.com/']) + self.assertEqual(source.suites, ['suite']) + self.assertEqual(source.components, ['main']) + self.assertEqual(source.ident, 'example-com-binary') + + def test_source_with_multiple_components(self): + source = Source() + source.load_from_data([ + 'deb http://example.com/ suite main nonfree' + ]) + source.generate_default_ident() + self.assertEqual(source.suites, ['suite']) + self.assertEqual(source.components, ['main', 'nonfree']) + + def test_source_with_option(self): + source = Source() + source.load_from_data([ + 'deb [ arch=amd64 ] http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example.com/']) + self.assertEqual(source.architectures, 'amd64') + + def test_source_uri_with_brackets(self): + source = Source() + source.load_from_data([ + 'deb http://example.com/[release]/ubuntu suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example.com/[release]/ubuntu']) + + def test_source_options_with_colons(self): + source = Source() + source.load_from_data([ + 'deb [ arch=arm:2 ] http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example.com/']) + self.assertEqual(source.architectures, 'arm:2') + + def test_source_with_multiple_option_values(self): + source = Source() + source.load_from_data([ + 'deb [ arch=armel,amd64 ] http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example.com/']) + self.assertEqual(source.architectures, 'armel amd64') + + def test_source_with_multiple_options(self): + source = Source() + source.load_from_data([ + 'deb [ arch=amd64 lang=en_US ] http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example.com/']) + self.assertEqual(source.architectures, 'amd64') + self.assertEqual(source.languages, 'en_US') + + def test_source_with_multiple_options_with_multiple_values(self): + source = Source() + source.load_from_data([ + 'deb [ arch=amd64,armel lang=en_US,en_CA ] ' + 'http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example.com/']) + self.assertEqual(source.architectures, 'amd64 armel') + self.assertEqual(source.languages, 'en_US en_CA') + + def test_source_uri_with_brackets_and_options(self): + source = Source() + source.load_from_data([ + 'deb [ arch=amd64 lang=en_US,en_CA ] ' + 'http://example][.com/[release]/ubuntu suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example][.com/[release]/ubuntu']) + self.assertEqual(source.architectures, 'amd64') + self.assertEqual(source.languages, 'en_US en_CA') + + def test_source_uri_with_brackets_and_options_with_colons(self): + source = Source() + source.load_from_data([ + 'deb [ arch=amd64,arm:2 lang=en_US,en_CA ] ' + 'http://example][.com/[release]/ubuntu suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example][.com/[release]/ubuntu']) + self.assertEqual(source.architectures, 'amd64 arm:2') + self.assertEqual(source.languages, 'en_US en_CA') + + def test_worst_case_sourcenario(self): + source = Source() + source.load_from_data([ + 'deb [ arch=amd64,arm:2,arm][ lang=en_US,en_CA ] ' + 'http://example][.com/[release:good]/ubuntu suite main restricted ' + 'nonfree not-a-component' + ]) + source.generate_default_ident() + self.assertEqual(source.uris, ['http://example][.com/[release:good]/ubuntu']) + self.assertEqual(source.suites, ['suite']) + self.assertEqual(source.components, [ + 'main', 'restricted', 'nonfree', 'not-a-component' + ]) + source.generate_default_ident() + self.assertEqual(source.architectures, 'amd64 arm:2 arm][') + self.assertEqual(source.languages, 'en_US en_CA') + + def test_source_code_source(self): + source = Source() + source.load_from_data([ + 'deb-src http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertEqual(source.types, [util.SourceType.SOURCECODE]) + + def test_disabled_source(self): + source = Source() + source.load_from_data([ + '# deb http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertFalse(source.enabled.get_bool()) + + def test_disabled_source_without_space(self): + source = Source() + source.load_from_data([ + '#deb http://example.com/ suite main' + ]) + source.generate_default_ident() + self.assertFalse(source.enabled.get_bool()) + + def test_source_with_trailing_comment(self): + source = Source() + source.load_from_data([ + 'deb http://example.com/ suite main # This is a comment' + ]) + source.generate_default_ident() + self.assertEqual(source.suites, ['suite']) + self.assertEqual(source.components, ['main']) + + def test_disabled_source_with_trailing_comment(self): + source = Source() + source.load_from_data([ + '# deb http://example.com/ suite main # This is a comment' + ]) + source.generate_default_ident() + self.assertEqual(source.suites, ['suite']) + self.assertEqual(source.components, ['main']) diff --git a/archive/repolib/unittest/test_popdev.py b/archive/repolib/unittest/test_popdev.py new file mode 100644 index 0000000..8207b09 --- /dev/null +++ b/archive/repolib/unittest/test_popdev.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import unittest + +from ..shortcuts import popdev +from .. import util + +class PopdevTestCase(unittest.TestCase): + + def test_ppa(self): + source = popdev.PopdevSource() + + # Verification data + uris_test = ['http://apt.pop-os.org/staging/master'] + signed_test = '/usr/share/keyrings/popdev-archive-keyring.gpg' + source.load_from_shortcut(shortcut='popdev:master') + + self.assertEqual(source.uris, uris_test) + self.assertEqual(source.ident, 'popdev-master') + self.assertEqual(source.suites, [util.DISTRO_CODENAME]) + self.assertEqual(source.components, ['main']) + self.assertEqual(source.types, [util.SourceType.BINARY]) + self.assertTrue(source.signed_by.endswith(signed_test)) \ No newline at end of file diff --git a/archive/repolib/unittest/test_ppa.py b/archive/repolib/unittest/test_ppa.py new file mode 100644 index 0000000..9b346cf --- /dev/null +++ b/archive/repolib/unittest/test_ppa.py @@ -0,0 +1,42 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import unittest + +from ..shortcuts import ppa +from .. import util + +class PPATestCase(unittest.TestCase): + + def test_ppa(self): + source = ppa.PPASource() + + # Verification data + uris_test = ['http://ppa.launchpad.net/system76/pop/ubuntu'] + signed_test = '/usr/share/keyrings/ppa-system76-pop-archive-keyring.gpg' + source.load_from_shortcut(shortcut='ppa:system76/pop', meta=False, key=False) + + self.assertEqual(source.uris, uris_test) + self.assertEqual(source.ident, 'ppa-system76-pop') + self.assertEqual(source.suites, [util.DISTRO_CODENAME]) + self.assertEqual(source.components, ['main']) + self.assertEqual(source.types, [util.SourceType.BINARY]) diff --git a/archive/repolib/unittest/test_source.py b/archive/repolib/unittest/test_source.py new file mode 100644 index 0000000..ae7f38e --- /dev/null +++ b/archive/repolib/unittest/test_source.py @@ -0,0 +1,245 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import unittest + +from .. import file, util, source + +class SourceTestCase(unittest.TestCase): + def setUp(self): + util.set_testing() + self.source = source.Source() + self.source.ident = 'test' + self.source.name = 'Test Source' + self.source.enabled = True + self.source.types = [util.SourceType.BINARY, util.SourceType.SOURCECODE] + self.source.uris = ['http://example.com/ubuntu', 'http://example.com/mirror'] + self.source.suites = ['suite', 'suite-updates'] + self.source.components = ['main', 'contrib', 'nonfree'] + self.source.architectures = 'amd64 armel' + self.source.languages = 'en_US en_CA' + self.file = file.SourceFile(name=self.source.ident) + self.file.add_source(self.source) + self.source.file = self.file + + self.source_legacy = source.Source() + self.source_legacy.ident = 'test-legacy' + self.source_legacy.name = 'Test Legacy Source' + self.source_legacy.enabled = True + self.source_legacy.types = [util.SourceType.BINARY] + self.source_legacy.uris = ['http://example.com/ubuntu'] + self.source_legacy.suites = ['suite'] + self.source_legacy.components = ['main', 'contrib', 'nonfree'] + self.source_legacy.architectures = 'amd64 armel' + self.source_legacy.languages = 'en_US en_CA' + self.source_legacy.file = file.SourceFile(name=self.source_legacy.ident) + self.source_legacy.file.format = util.SourceFormat.LEGACY + + + def test_default_source_data(self): + self.assertEqual(self.source.name, 'Test Source') + self.assertTrue(self.source.enabled.get_bool()) + self.assertEqual( + self.source.types, + [util.SourceType.BINARY, util.SourceType.SOURCECODE] + ) + self.assertTrue(self.source.sourcecode_enabled) + self.assertEqual( + self.source.uris, + ['http://example.com/ubuntu', 'http://example.com/mirror'] + ) + self.assertEqual( + self.source.suites, + ['suite', 'suite-updates'] + ) + self.assertEqual( + self.source.components, + ['main', 'contrib', 'nonfree'] + ) + self.assertEqual(self.source.architectures, 'amd64 armel') + self.assertEqual(self.source.languages, 'en_US en_CA') + self.assertEqual(self.source.file.path.name, 'test.sources') + + def test_output_822(self): + source_string = ( + 'X-Repolib-ID: test\n' + 'X-Repolib-Name: Test Source\n' + 'Enabled: yes\n' + 'Types: deb deb-src\n' + 'URIs: http://example.com/ubuntu http://example.com/mirror\n' + 'Suites: suite suite-updates\n' + 'Components: main contrib nonfree\n' + 'Architectures: amd64 armel\n' + 'Languages: en_US en_CA\n' + ) + legacy_source_string = ( + 'X-Repolib-ID: test-legacy\n' + 'X-Repolib-Name: Test Legacy Source\n' + 'Enabled: yes\n' + 'Types: deb\n' + 'URIs: http://example.com/ubuntu\n' + 'Suites: suite\n' + 'Components: main contrib nonfree\n' + 'Architectures: amd64 armel\n' + 'Languages: en_US en_CA\n' + ) + self.assertEqual(self.source.deb822, source_string) + self.assertEqual(self.source_legacy.deb822, legacy_source_string) + + def test_output_ui(self): + source_string = ( + 'test:\n' + 'Name: Test Source\n' + 'Enabled: yes\n' + 'Types: deb deb-src\n' + 'URIs: http://example.com/ubuntu http://example.com/mirror\n' + 'Suites: suite suite-updates\n' + 'Components: main contrib nonfree\n' + 'Architectures: amd64 armel\n' + 'Languages: en_US en_CA\n' + '' + ) + legacy_source_string = ( + 'test-legacy:\n' + 'Name: Test Legacy Source\n' + 'Enabled: yes\n' + 'Types: deb\n' + 'URIs: http://example.com/ubuntu\n' + 'Suites: suite\n' + 'Components: main contrib nonfree\n' + 'Architectures: amd64 armel\n' + 'Languages: en_US en_CA\n' + ) + self.assertEqual(self.source.ui, source_string) + self.assertEqual(self.source_legacy.ui, legacy_source_string) + + def test_output_legacy(self): + source_string = ( + 'deb [arch=amd64,armel lang=en_US,en_CA] http://example.com/ubuntu suite main contrib nonfree ## X-Repolib-Name: Test Legacy Source # X-Repolib-ID: test-legacy' + ) + self.assertEqual(self.source_legacy.legacy, source_string) + + def test_enabled(self): + self.source.enabled = False + self.assertFalse(self.source.enabled.get_bool()) + + def test_sourcecode_enabled(self): + self.source.sourcecode_enabled = False + self.assertEqual(self.source.types, [util.SourceType.BINARY]) + + def test_dict_access(self): + self.assertEqual(self.source['X-Repolib-ID'], 'test') + self.assertEqual(self.source['X-Repolib-Name'], 'Test Source') + self.assertEqual(self.source['Enabled'], 'yes') + self.assertEqual(self.source['Enabled'], 'yes') + self.assertEqual(self.source['Types'], 'deb deb-src') + self.assertEqual(self.source['URIs'], 'http://example.com/ubuntu http://example.com/mirror') + self.assertEqual(self.source['Suites'], 'suite suite-updates') + self.assertEqual(self.source['Components'], 'main contrib nonfree') + self.assertEqual(self.source['Architectures'], 'amd64 armel') + self.assertEqual(self.source['Languages'], 'en_US en_CA') + + def test_load(self): + load_source = source.Source() + load_source.load_from_data([ + 'X-Repolib-ID: load-test', + 'X-Repolib-Name: Test Source Loading', + 'Enabled: yes', + 'Types: deb', + 'URIs: http://example.com/ubuntu http://example.com/mirror', + 'Suites: suite suite-updates', + 'Components: main contrib nonfree', + 'Architectures: amd64 armel', + 'Languages: en_US en_CA', + ]) + + self.assertEqual(load_source.ident, 'load-test') + self.assertEqual(load_source.name, 'Test Source Loading') + self.assertTrue(load_source.enabled.get_bool()) + self.assertEqual( + load_source.types, + [util.SourceType.BINARY] + ) + self.assertEqual( + load_source.uris, + ['http://example.com/ubuntu', 'http://example.com/mirror'] + ) + self.assertEqual( + load_source.suites, + ['suite', 'suite-updates'] + ) + self.assertEqual( + load_source.components, + ['main', 'contrib', 'nonfree'] + ) + self.assertEqual(load_source.architectures, 'amd64 armel') + self.assertEqual(load_source.languages, 'en_US en_CA') + + load_legacy_source = source.Source() + load_legacy_source.load_from_data( + ['deb [arch=amd64,armel lang=en_US,en_CA] http://example.com/ubuntu suite main contrib nonfree ## X-Repolib-Name: Test Legacy Source Loading # X-Repolib-ID: test-load-legacy'] + ) + + self.assertEqual(load_legacy_source.ident, 'test-load-legacy') + self.assertEqual(load_legacy_source.name, 'Test Legacy Source Loading') + self.assertTrue(load_legacy_source.enabled.get_bool()) + self.assertEqual( + load_legacy_source.types, + [util.SourceType.BINARY] + ) + self.assertEqual( + load_legacy_source.uris, + ['http://example.com/ubuntu'] + ) + self.assertEqual( + load_legacy_source.suites, + ['suite'] + ) + self.assertEqual( + load_legacy_source.components, + ['main', 'contrib', 'nonfree'] + ) + self.assertEqual(load_legacy_source.architectures, 'amd64 armel') + self.assertEqual(load_legacy_source.languages, 'en_US en_CA') + + def test_save_load(self): + self.source.file.save() + load_source_file = file.SourceFile(name='test') + load_source_file.load() + self.assertGreater(len(load_source_file.sources), 0) + self.assertGreater( + len(load_source_file.contents), len(load_source_file.sources) + ) + load_source = load_source_file.sources[0] + + self.assertEqual(load_source.ident, self.source.ident) + self.assertEqual(load_source.name, self.source.name) + self.assertEqual(load_source.enabled, self.source.enabled) + self.assertEqual(load_source.types, self.source.types) + self.assertEqual(load_source.sourcecode_enabled, self.source.sourcecode_enabled) + self.assertEqual(load_source.uris, self.source.uris) + self.assertEqual(load_source.suites, self.source.suites) + self.assertEqual(load_source.components, self.source.components) + self.assertEqual(load_source.architectures, self.source.architectures) + self.assertEqual(load_source.languages, self.source.languages) + self.assertEqual(load_source.file.name, self.source.file.name) + \ No newline at end of file diff --git a/archive/repolib/util.py b/archive/repolib/util.py new file mode 100644 index 0000000..cb303a9 --- /dev/null +++ b/archive/repolib/util.py @@ -0,0 +1,455 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2022, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" + +import atexit +import logging +import re +import tempfile + +from enum import Enum +from pathlib import Path +from urllib.parse import urlparse +from urllib import request, error + +import dbus + +SOURCES_DIR = Path('/etc/apt/sources.list.d') +KEYS_DIR = Path('/etc/apt/keyrings/') +TESTING = False +KEYSERVER_QUERY_URL = 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x' + +log = logging.getLogger(__name__) + +class RepoError(Exception): + """ Exception from this module.""" + + def __init__(self, *args, code=1, **kwargs): + """Exception with a source object + + Arguments: + code (:obj:`int`, optional, default=1): Exception error code. + """ + super().__init__(*args, **kwargs) + self.code = code + +try: + import distro + DISTRO_CODENAME = distro.codename() +except ImportError: + DISTRO_CODENAME = 'linux' + +class SourceFormat(Enum): + """Enum of SourceFile Formats""" + DEFAULT = "sources" + LEGACY = "list" + +class SourceType(Enum): + """Enum of repository types""" + BINARY = 'deb' + SOURCECODE = 'deb-src' + + def ident(self) -> str: + """Used for getting a version of the format for idents""" + ident = f'{self.value}' + ident = ident.replace('deb-src', 'source') + ident = ident.replace('deb', 'binary') + return ident + +class AptSourceEnabled(Enum): + """ Helper Enum to translate between bool data and the Deb822 format. """ + TRUE = 'yes' + FALSE = 'no' + + def get_bool(self): + """ Return a bool based on the value. """ + # pylint: disable=comparison-with-callable + # This doesnt seem to actually be a callable in this case. + if self.value == "yes": + return True + + return False + +valid_keys = [ + 'X-Repolib-Name:', + 'X-Repolib-ID:', + 'X-Repolib-Default-Mirror:', + 'X-Repolib-Comment', + 'X-Repolib-Prefs', + 'Enabled:', + 'Types:', + 'URIs:', + 'Suites:', + 'Components:', + 'Architectures:', + 'Languages:', + 'Targets:', + 'PDiffs:', + 'By-Hash:', + 'Allow-Insecure:', + 'Allow-Weak:', + 'Allow-Downgrade-To-Insecure:', + 'Trusted:', + 'Signed-By:', + 'Check-Valid-Until:', + 'Valid-Until-Min:', + 'Valid-Until-Max:', +] + +output_skip_keys = [ + 'X-Repolib-Prefs', + 'X-Repolib-ID', +] + +options_inmap = { + 'arch': 'Architectures', + 'lang': 'Languages', + 'target': 'Targets', + 'pdiffs': 'PDiffs', + 'by-hash': 'By-Hash', + 'allow-insecure': 'Allow-Insecure', + 'allow-weak': 'Allow-Weak', + 'allow-downgrade-to-insecure': 'Allow-Downgrade-To-Insecure', + 'trusted': 'Trusted', + 'signed-by': 'Signed-By', + 'check-valid-until': 'Check-Valid-Until', + 'valid-until-min': 'Valid-Until-Min', + 'valid-until-max': 'Valid-Until-Max' +} + +options_outmap = { + 'Architectures': 'arch', + 'Languages': 'lang', + 'Targets': 'target', + 'PDiffs': 'pdiffs', + 'By-Hash': 'by-hash', + 'Allow-Insecure': 'allow-insecure', + 'Allow-Weak': 'allow-weak', + 'Allow-Downgrade-To-Insecure': 'allow-downgrade-to-insecure', + 'Trusted': 'trusted', + 'Signed-By': 'signed-by', + 'Check-Valid-Until': 'check-valid-until', + 'Valid-Until-Min': 'valid-until-min', + 'Valid-Until-Max': 'valid-until-max' +} + +true_values = [ + True, + 'True', + 'true', + 'Yes', + 'yes', + 'YES', + 'y', + 'Y', + AptSourceEnabled.TRUE, + 1 +] + +keys_map = { + 'X-Repolib-Name: ': 'Name: ', + 'X-Repolib-ID: ': 'Ident: ', + 'X-Repolib-Comments: ': 'Comments: ', + 'X-Repolib-Default-Mirror: ': 'Default Mirror: ', +} + +PRETTY_PRINT = '\n ' + +_KEYS_TEMPDIR = tempfile.TemporaryDirectory() +TEMP_DIR = Path(_KEYS_TEMPDIR.name) + +options_re = re.compile(r'[^@.+]\[([^[]+.+)\]\ ') +uri_re = re.compile(r'\w+:(\/?\/?)[^\s]+') + +CLEAN_CHARS = { + 33: None, + 64: 45, + 35: 45, + 36: 45, + 37: 45, + 94: 45, + 38: 45, + 42: 45, + 41: None, + 40: None, + 43: 45, + 61: 45, + 91: None, + 92: None, + 93: None, + 123: None, + 125: None, + 124: 95, + 63: None, + 47: 95, + 46: 45, + 60: 95, + 62: 95, + 44: 95, + 96: None, + 126: None, + 32: 95, + 58: None, + 59: None, +} + +sources:dict = {} +files:dict = {} +keys:dict = {} +errors:dict = {} + + +def scrub_filename(name: str = '') -> str: + """ Clean up a string intended for a filename. + + Arguments: + name (str): The prospective name to scrub. + + Returns: str + The cleaned-up name. + """ + return name.translate(CLEAN_CHARS) + +def set_testing(testing:bool=True) -> None: + """Sets Repolib in testing mode where changes will not be saved. + + Arguments: + testing(bool): Whether testing mode should be enabled or disabled + (Defaul: True) + """ + global KEYS_DIR + global SOURCES_DIR + + testing_tempdir = tempfile.TemporaryDirectory() + + if not testing: + KEYS_DIR = '/usr/share/keyrings' + SOURCES_DIR = '/etc/apt/sources.list.d' + return + + testing_root = Path(testing_tempdir.name) + KEYS_DIR = testing_root / 'usr' / 'share' / 'keyrings' + SOURCES_DIR = testing_root / 'etc' / 'apt' / 'sources.list.d' + + +def _cleanup_temsps() -> None: + """Clean up our tempdir""" + _KEYS_TEMPDIR.cleanup() + # _TESTING_TEMPDIR.cleanup() + +atexit.register(_cleanup_temsps) + +def dbus_quit(): + bus = dbus.SystemBus() + privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') + privileged_object.exit() + +def compare_sources(source1, source2, excl_keys:list) -> bool: + """Compare two sources based on arbitrary criteria. + + This looks at a given list of keys, and if the given keys between the two + given sources are identical, returns True. + + Arguments: + source1, source2(Source): The two sources to compare + excl_keys([str]): Any keys to exclude from the comparison + + Returns: bool + `True` if the sources are identical, otherwise `False`. + """ + for key in source1: + if key in excl_keys: + continue + if key in source2: + if source1[key] != source2[key]: + return False + else: + continue + else: + return False + for key in source2: + if key in excl_keys: + continue + if key in source1: + if source1[key] != source2[key]: + return False + else: + continue + else: + return False + return True + +def find_differences_sources(source1, source2, excl_keys:list) -> dict: + """Find key-value pairs which differ between two sources. + + Arguments: + source1, source2(Source): The two sources to compare + excl_keys([str]): Any keys to exclude from the comparison + + Returns: dict{'key': ('source1[key]','source2[key]')} + The dictionary of different keys, with the key values from each source. + """ + differing_keys:dict = {} + + for key in source1: + if key in excl_keys: + continue + if key in source2: + if source1[key] == source2[key]: + continue + differing_keys[key] = (source1[key], source2[key]) + differing_keys[key] = (source1[key], '') + for key in source2: + if key in excl_keys: + continue + if key in source1: + if source1[key] == source2[key]: + continue + differing_keys[key] = ('', source2[key]) + + return differing_keys + +def combine_sources(source1, source2) -> None: + """Combine the data in two sources into one. + + Arguments: + source1(Source): The source to be merged into + source2(Source): The source to merge from + """ + for key in source1: + if key in ('X-Repolib-Name', 'X-Repolib-ID', 'Enabled', 'Types'): + continue + if key in source2: + source1[key] += f' {source2[key]}' + for key in source2: + if key in ('X-Repolib-Name', 'X-Repolib-ID', 'Enabled', 'Types'): + continue + if key in source1: + source1[key] += f' {source2[key]}' + + # Need to deduplicate the list + for key in source1: + vals = source1[key].strip().split() + newvals = [] + for val in vals: + if val not in newvals: + newvals.append(val) + source1[key] = ' '.join(newvals) + for key in source2: + vals = source2[key].strip().split() + newvals = [] + for val in vals: + if val not in newvals: + newvals.append(val) + source2[key] = ' '.join(newvals) + + +def prettyprint_enable(enabled: bool = True) -> None: + """Easy helper to enable/disable pretty-printing for object reprs. + + Can also be used as an easy way to reset to defaults. + + Arguments: + enabled(bool): Whether or not Pretty Printing should be enabled + """ + global PRETTY_PRINT + if enabled: + PRETTY_PRINT = '\n ' + else: + PRETTY_PRINT = '' + +def url_validator(url): + """ Validate a url and tell if it's good or not. + + Arguments: + url (str): The URL to validate. + + Returns: + `True` if `url` is not malformed, otherwise `False`. + """ + try: + # pylint: disable=no-else-return,bare-except + # A) We want to return false if the URL doesn't contain those parts + # B) We need this to not throw any exceptions, regardless what they are + result = urlparse(url) + if not result.scheme: + return False + if result.scheme == 'x-repolib-name': + return False + if result.netloc: + # We need at least a scheme and a netlocation/hostname or... + return all([result.scheme, result.netloc]) + elif result.path: + # ...a scheme and a path (this allows file:/// URIs which are valid) + return all([result.scheme, result.path]) + return False + except: + return False + +def validate_debline(valid): + """ Basic checks to see if a given debline is valid or not. + + Arguments: + valid (str): The line to validate. + + Returns: + True if the line is valid, False otherwise. + """ + comment:bool = False + if valid.startswith('#'): + comment = True + valid = valid.replace('#', '') + valid = valid.strip() + + if valid.startswith("deb"): + words = valid.split() + for word in words: + if url_validator(word): + return True + + elif valid.startswith("ppa:"): + if "/" in valid: + return True + + else: + if valid.endswith('.flatpakrepo'): + return False + if len(valid.split()) == 1 and not comment: + return url_validator(valid) + return False + +def strip_hashes(line:str) -> str: + """ Strips the leading #'s from the given line. + + Arguments: + line (str): The line to strip. + + Returns: + (str): The input line without any leading/trailing hashes or + leading/trailing whitespace. + """ + while True: + line = line.strip('#') + line = line.strip() + if not line.startswith('#'): + break + + return line diff --git a/archive/requirements.txt b/archive/requirements.txt new file mode 100644 index 0000000..204fdde --- /dev/null +++ b/archive/requirements.txt @@ -0,0 +1,4 @@ +dbus-python +distro +pytest-pylint +python-debian \ No newline at end of file diff --git a/archive/setup.py b/archive/setup.py new file mode 100755 index 0000000..9a78ce1 --- /dev/null +++ b/archive/setup.py @@ -0,0 +1,198 @@ +#!/usr/bin/python3 + +""" +Copyright (c) 2019-2020, Ian Santopietro +All rights reserved. + +This file is part of RepoLib. + +RepoLib is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RepoLib 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with RepoLib. If not, see . +""" +#pylint: skip-file +# We don't need to check these in setup + +import os +import subprocess +from setuptools import setup, find_packages, Command + +def get_version(): + """ Get the program version. """ + #pylint: disable=exec-used + # Just getting the version. + version = {} + with open(os.path.join('repolib', '__version__.py')) as fp: + exec(fp.read(), version) + return version['__version__'] + +with open("README.rst", "r") as fh: + long_description = fh.read() + +classifiers = [ + 'Environment :: Console', + 'Intended Audience :: System Administrators', + 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System' +] + +class Release(Command): + """ Generate a release and push it to git.""" + description = "Generate a release and push it to git." + + user_options = [ + ('dry-run', None, 'Skip the actual release and do a dry run instead.'), + ('skip-deb', None, 'Skip doing a debian update for this release.'), + ('skip-git', None, 'Skip committing to git at the end.'), + ('prerelease=', None, 'Release a pre-release version (alpha,beta,rc)'), + ('increment=', None, 'Manually specify the desired increment (MAJOR, MINOR, PATCH)') + ] + + def initialize_options(self): + self.dry_run = False + self.skip_deb = False + self.skip_git = False + self.prerelease = None + self.increment = None + + def finalize_options(self): + pass + + def run(self): + cz_command = ['cz', 'bump', '--yes'] + ch_command = ['dch'] + git_command = ['git', 'add', '.'] + + def capture_version(sp_complete): + output = sp_complete.stdout.decode('UTF-8').split('\n') + print('\n'.join(output)) + for line in output: + if 'tag to create' in line: + version_line = line + + try: + return version_line.split()[-1].replace('v', '') + except UnboundLocalError: + stderr = sp_complete.stderr.decode('UTF-8') + print("WARNING: Couldn't get updated version! Using current.") + print(stderr) + return get_version() + + if self.dry_run: + print('Dry run: Not making actual changes') + cz_command.append('--dry-run') + + if self.prerelease: + if self.prerelease.lower() not in ['alpha', 'beta', 'rc']: + raise Exception( + f'{self.prerelease} is not a valid prerelease type. Please ' + 'use one of "alpha", "beta", or "rc".' + ) + cz_command.append('--prerelease') + cz_command.append(self.prerelease.lower()) + + if self.increment: + if self.increment.upper() not in ['MAJOR', 'MINOR', 'PATCH']: + raise Exception( + f'{self.increment} is not a valid increments. Please use ' + 'one of MAJOR, MINOR, or PATCH.' + ) + cz_command.append('--increment') + cz_command.append(self.increment.upper()) + + # We need to get the new version from CZ, as the file hasn't been + # updated yet. + version_command = cz_command.copy() + version_command.append('--dry-run') + version_complete = subprocess.run(version_command, capture_output=True) + + version = capture_version(version_complete) + print(f'Old Version: {get_version()}') + print(f'New version: {version}') + + ch_command.append(f'-v{version}') + if not self.skip_deb: + print(ch_command) + if not self.dry_run: + subprocess.run(ch_command) + subprocess.run(['dch', '-r', '""']) + + if not self.skip_git: + print(git_command) + if not self.dry_run: + subprocess.run(git_command) + + print(' '.join(cz_command)) + if not self.dry_run: + subprocess.run(cz_command) + +class Test(Command): + """ Run pyflakes and pytest""" + description = 'Run pyflakes, pytest, and pylint' + + user_options = [ + ('run-flakes', None, 'Run pyflakes'), + ('skip-test', None, 'Skip running pytest'), + ('skip-lint', None, 'Skip running pylint') + ] + + def initialize_options(self): + self.run_flakes = True + self.skip_test = False + self.skip_lint = False + + def finalize_options(self): + pass + + def run(self): + pytest_command = ['pytest-3'] + flakes_command = ['pyflakes3', 'repolib'] + lint_command = ['pylint', 'repolib'] + + if not self.skip_test: + subprocess.run(pytest_command) + + if not self.run_flakes: + subprocess.run(flakes_command) + + if not self.skip_lint: + subprocess.run(lint_command) + +setup( + name='repolib', + version=get_version(), + author='Ian Santopietro', + author_email='ian@system76.com', + url='https://github.com/pop-os/repolib', + description='Easily manage software sources', + download_url='https://github.com/pop-os/repolib/releases', + long_description=long_description, + tests_require=['pytest'], + license='LGPLv3', + packages=['repolib', 'repolib/command', 'repolib/shortcuts', 'repolib/unittest'], + cmdclass={'release': Release, 'test': Test}, + scripts=['bin/apt-manage'], + data_files=[ + ('share/bash-completion/completions', ['data/bash-completion/apt-manage']), + ('share/zsh/vendor-completions', ['data/zsh-completion/_apt-manage']), + ('/usr/share/dbus-1/system-services', ['data/org.pop_os.repolib.service']), + ('/usr/share/polkit-1/actions', ['data/org.pop_os.repolib.policy']), + ('/etc/dbus-1/system.d/', ['data/org.pop_os.repolib.conf']), + ('/usr/lib/repolib', ['data/service.py', 'bin/add-apt-repository']) + ] +) diff --git a/orig.source.txt b/orig.source.txt new file mode 100644 index 0000000..ca94e91 --- /dev/null +++ b/orig.source.txt @@ -0,0 +1 @@ +Source is this git's root