first commit
This commit is contained in:
parent
42a42d1921
commit
ed107083f5
40
.github/workflows/release.yml
vendored
Normal file
40
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: PikaOS Package Release
|
||||
|
||||
on:
|
||||
workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: self-hosted
|
||||
container:
|
||||
image: ubuntu:22.10
|
||||
volumes:
|
||||
- /proc:/proc
|
||||
options: --privileged -it
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install needed packages
|
||||
run: apt update && apt install software-properties-common sudo git bc gpg gpg-agent bison build-essential ccache cpio fakeroot flex git kmod libelf-dev libncurses5-dev libssl-dev lz4 qtbase5-dev rsync schedtool wget zstd tar reprepro dpkg-sig devscripts -y
|
||||
|
||||
- name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v5
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
|
||||
- name: Install SSH key
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
name: id_rsa
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||
if_key_exists: replace
|
||||
|
||||
- name: Build Package
|
||||
run: ./main.sh
|
||||
|
||||
- name: Release Package
|
||||
run: ./release.sh
|
@ -1,7 +0,0 @@
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
version = "1.5.2"
|
||||
tag_format = "$version"
|
||||
version_files = [
|
||||
'repolib/__version__.py:__version__'
|
||||
]
|
@ -1,20 +0,0 @@
|
||||
# .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
|
@ -1,13 +0,0 @@
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libdbus-1-dev
|
||||
|
||||
language: python
|
||||
python: "3.8"
|
||||
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
scripts:
|
||||
- pytest --pylint
|
675
archive/LICENSE
675
archive/LICENSE
@ -1,675 +0,0 @@
|
||||
### GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
### Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom
|
||||
to share and change all versions of a program--to make sure it remains
|
||||
free software for all its users. We, the Free Software Foundation, use
|
||||
the GNU General Public License for most of our software; it applies
|
||||
also to any other work released this way by its authors. You can apply
|
||||
it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you
|
||||
have certain responsibilities if you distribute copies of the
|
||||
software, or if you modify it: responsibilities to respect the freedom
|
||||
of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the
|
||||
manufacturer can do so. This is fundamentally incompatible with the
|
||||
aim of protecting users' freedom to change the software. The
|
||||
systematic pattern of such abuse occurs in the area of products for
|
||||
individuals to use, which is precisely where it is most unacceptable.
|
||||
Therefore, we have designed this version of the GPL to prohibit the
|
||||
practice for those products. If such problems arise substantially in
|
||||
other domains, we stand ready to extend this provision to those
|
||||
domains in future versions of the GPL, as needed to protect the
|
||||
freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish
|
||||
to avoid the special danger that patents applied to a free program
|
||||
could make it effectively proprietary. To prevent this, the GPL
|
||||
assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in
|
||||
detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU General Public
|
||||
License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that numbered version or
|
||||
of any later version published by the Free Software Foundation. If the
|
||||
Program does not specify a version number of the GNU General Public
|
||||
License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU General Public License can be used, that proxy's public
|
||||
statement of acceptance of a version permanently authorizes you to
|
||||
choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
### How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these
|
||||
terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to
|
||||
attach them to the start of each source file to most effectively state
|
||||
the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper
|
||||
mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands \`show w' and \`show c' should show the
|
||||
appropriate parts of the General Public License. Of course, your
|
||||
program's commands might be different; for a GUI interface, you would
|
||||
use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. For more information on this, and how to apply and follow
|
||||
the GNU GPL, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your
|
||||
program into proprietary programs. If your program is a subroutine
|
||||
library, you may consider it more useful to permit linking proprietary
|
||||
applications with the library. If this is what you want to do, use the
|
||||
GNU Lesser General Public License instead of this License. But first,
|
||||
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
|
@ -1,157 +0,0 @@
|
||||
### GNU LESSER GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
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.
|
@ -1,167 +0,0 @@
|
||||
=======
|
||||
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 <https://repolib.rtfd.io/>`_.
|
||||
|
||||
|
||||
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
|
@ -1,18 +0,0 @@
|
||||
# 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.
|
@ -1,276 +0,0 @@
|
||||
## 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.
|
@ -1,172 +0,0 @@
|
||||
===================
|
||||
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
|
@ -1,194 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
#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='<sourceline>'
|
||||
)
|
||||
|
||||
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))
|
@ -1,85 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
#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)
|
@ -1,42 +0,0 @@
|
||||
# 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
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
<type>system</type>
|
||||
<!-- Only root can own the service -->
|
||||
<policy user="root">
|
||||
<allow own="org.pop_os.repolib"/>
|
||||
<allow send_destination="org.pop_os.repolib"/>
|
||||
<allow receive_sender="org.pop_os.repolib"/>
|
||||
</policy>
|
||||
<policy group="adm">
|
||||
<allow send_destination="org.pop_os.repolib"/>
|
||||
<allow receive_sender="org.pop_os.repolib"/>
|
||||
</policy>
|
||||
<policy group="sudo">
|
||||
<allow send_destination="org.pop_os.repolib"/>
|
||||
<allow receive_sender="org.pop_os.repolib"/>
|
||||
</policy>
|
||||
</busconfig>
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<vendor>Repoman</vendor>
|
||||
<vendor_url>https://github.com/pop-os/repolib</vendor_url>
|
||||
<icon_name>x-system-software-sources</icon_name>
|
||||
|
||||
<action id="org.pop_os.repolib.modifysources">
|
||||
<description>Modifies a Debian Repository in the system software sources.</description>
|
||||
<message>Authentication is required to change software sources.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
@ -1,4 +0,0 @@
|
||||
[D-BUS Service]
|
||||
Name=org.pop_os.repolib
|
||||
Exec=/usr/bin/python3 /usr/lib/repolib/service.py
|
||||
User=root
|
@ -1,316 +0,0 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
#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()
|
@ -1,70 +0,0 @@
|
||||
#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
|
@ -1,11 +0,0 @@
|
||||
repolib (2.2.0~99pika2) kinetic; urgency=medium
|
||||
|
||||
* Remove stupid post scripts
|
||||
|
||||
-- Ward Nakchbandi <hotrod.master@hotmail.com> Fri, 09 Oct 2022 21:38:00 +0300
|
||||
|
||||
repolib (2.2.0~99pika1) kinetic; urgency=medium
|
||||
|
||||
* Initial Creation
|
||||
|
||||
-- Ward Nakchbandi <hotrod.master@hotmail.com> Fri, 09 Oct 2022 21:38:00 +0300
|
@ -1,19 +0,0 @@
|
||||
# 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)
|
@ -1,71 +0,0 @@
|
||||
==============
|
||||
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.
|
@ -1,28 +0,0 @@
|
||||
==========
|
||||
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
|
@ -1,78 +0,0 @@
|
||||
=====================
|
||||
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.
|
@ -1,79 +0,0 @@
|
||||
=============================
|
||||
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.
|
@ -1,82 +0,0 @@
|
||||
=================
|
||||
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
|
@ -1,9 +0,0 @@
|
||||
================
|
||||
Removing Sources
|
||||
================
|
||||
|
||||
To remove a source from the system configuration, use the ``remove``
|
||||
subcommand::
|
||||
|
||||
$ apt-manage remove ppa-system76-pop
|
||||
|
@ -1,177 +0,0 @@
|
||||
# -*- 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']
|
@ -1,276 +0,0 @@
|
||||
.. _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 <https://manpages.debian.org/stretch/apt/sources.list.5.en.html#URI_SPECIFICATION>`_.
|
||||
|
||||
|
||||
.. _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 <https://manpages.debian.org/stretch/apt/sources.list.5.en.html#THE_DEB_AND_DEB-SRC_TYPES:_OPTIONS>`_.
|
||||
|
||||
|
||||
.. _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
|
@ -1,182 +0,0 @@
|
||||
.. 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 <https://www.gnu.org/licenses/>.
|
||||
|
@ -1,106 +0,0 @@
|
||||
============
|
||||
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
|
@ -1,335 +0,0 @@
|
||||
.. _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:
|
@ -1,45 +0,0 @@
|
||||
.. _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"``).
|
||||
|
@ -1,50 +0,0 @@
|
||||
.. _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.
|
@ -1,172 +0,0 @@
|
||||
.. _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`.
|
@ -1,121 +0,0 @@
|
||||
.. _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.
|
@ -1,56 +0,0 @@
|
||||
.. _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'])
|
@ -1,281 +0,0 @@
|
||||
.. _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.
|
@ -1,144 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,22 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
__version__ = "2.1.1"
|
@ -1,30 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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()
|
@ -1,267 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,65 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
@ -1,77 +0,0 @@
|
||||
"""
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
@ -1,325 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
||||
|
@ -1,216 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,502 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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.'
|
||||
)
|
@ -1,128 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,544 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
@ -1,204 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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]'
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,371 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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).'
|
||||
)
|
@ -1,32 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,274 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,920 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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,
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,142 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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) <info@system76.com>']
|
||||
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())
|
@ -1,188 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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'])
|
@ -1,43 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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))
|
@ -1,42 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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])
|
@ -1,245 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
@ -1,455 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
@ -1,4 +0,0 @@
|
||||
dbus-python
|
||||
distro
|
||||
pytest-pylint
|
||||
python-debian
|
198
archive/setup.py
198
archive/setup.py
@ -1,198 +0,0 @@
|
||||
#!/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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
#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'])
|
||||
]
|
||||
)
|
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
repolib (2.2.0~99pika4) kinetic; urgency=medium
|
||||
|
||||
* Initial Creation
|
||||
|
||||
-- Ward Nakchbandi <hotrod.master@hotmail.com> Fri, 09 Oct 2022 21:38:00 +0300
|
0
archive/debian/compat → debian/compat
vendored
0
archive/debian/compat → debian/compat
vendored
0
archive/debian/control → debian/control
vendored
0
archive/debian/control → debian/control
vendored
0
archive/debian/rules → debian/rules
vendored
0
archive/debian/rules → debian/rules
vendored
21
main.sh
Executable file
21
main.sh
Executable file
@ -0,0 +1,21 @@
|
||||
# Add dependent repositories
|
||||
wget -q -O - https://ppa.pika-os.com/key.gpg | sudo apt-key add -
|
||||
add-apt-repository https://ppa.pika-os.com
|
||||
add-apt-repository ppa:pikaos/pika
|
||||
add-apt-repository ppa:kubuntu-ppa/backports
|
||||
# Clone Upstream
|
||||
git clone https://github.com/pop-os/repolib
|
||||
rm -rvf ./repolib/debian
|
||||
cp -rvf ./debian ./repolib
|
||||
cd ./repolib
|
||||
|
||||
# Get build deps
|
||||
apt-get build-dep ./ -y
|
||||
|
||||
# Build package
|
||||
dpkg-buildpackage
|
||||
|
||||
# Move the debs to output
|
||||
cd ../
|
||||
mkdir -p ./output
|
||||
mv ./*.deb ./output/
|
11
release.sh
Executable file
11
release.sh
Executable file
@ -0,0 +1,11 @@
|
||||
# Sign the packages
|
||||
dpkg-sig --sign builder ./output/*.deb
|
||||
|
||||
# Pull down existing ppa repo db files etc
|
||||
rsync -azP --exclude '*.deb' ferreo@direct.pika-os.com:/srv/www/pikappa/ ./output/repo
|
||||
|
||||
# Add the new package to the repo
|
||||
reprepro -V --basedir ./output/repo/ includedeb kinetic ./output/*.deb
|
||||
|
||||
# Push the updated ppa repo to the server
|
||||
rsync -azP ./output/repo/ ferreo@direct.pika-os.com:/srv/www/pikappa/
|
Loading…
Reference in New Issue
Block a user