diff --git a/.github/workflows/build-docker-container.yml b/.github/workflows/build-docker-container.yml
index 52f4967..2ae91d6 100644
--- a/.github/workflows/build-docker-container.yml
+++ b/.github/workflows/build-docker-container.yml
@@ -20,5 +20,6 @@ jobs:
with:
push: true
tags: hilkopterbob/packagelock:latest
+ file: tools/Dockerfile
build-args: |
APP_VERSION=${{ github.event.release.tag_name }}
diff --git a/.github/workflows/nightly_release.yml b/.github/workflows/nightly_release.yml
index 956e281..743e28c 100644
--- a/.github/workflows/nightly_release.yml
+++ b/.github/workflows/nightly_release.yml
@@ -55,4 +55,4 @@ jobs:
gh release upload nightly \
release/packagelock-${{ env.APP_VERSION }}-linux-amd64-nightly.tar.gz \
release/packagelock-${{ env.APP_VERSION }}-linux-amd64-nightly.tar.gz.md5 \
- --clobber
+ --clobber
\ No newline at end of file
diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml
new file mode 100644
index 0000000..5129f24
--- /dev/null
+++ b/.github/workflows/publish-wiki.yml
@@ -0,0 +1,18 @@
+name: Publish wiki
+on:
+ push:
+ branches: [devel]
+ paths:
+ - wiki/**
+ - .github/workflows/publish-wiki.yml
+concurrency:
+ group: publish-wiki
+ cancel-in-progress: true
+permissions:
+ contents: write
+jobs:
+ publish-wiki:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: Andrew-Chen-Wang/github-wiki-action@v4
diff --git a/.github/workflows/unstable-build-docker-container.yml b/.github/workflows/unstable-build-docker-container.yml
index 368beb2..d57d0d2 100644
--- a/.github/workflows/unstable-build-docker-container.yml
+++ b/.github/workflows/unstable-build-docker-container.yml
@@ -18,4 +18,5 @@ jobs:
uses: docker/build-push-action@v6
with:
push: true
+ file: tools/Dockerfile-tracing
tags: hilkopterbob/packagelock:unstable-${{ github.head_ref || github.ref_name }}
diff --git a/LICENSE b/LICENSE
index 1341823..f288702 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,674 @@
-MIT License
-
-Copyright (c) 2024 Nick von Podewils and contributors.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
index d6d4a91..f14413f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/HilkopterBob/PackageLock/.github%2Fworkflows%2Frun-tests.yml)
-![GitHub Actions Workflow Status](https://github.com/hilkopterbob/packagelock/actions/workflows/test-build.yml/badge.svg)
+![GitHub Actions Workflow Status](https://github.com/hilkopterbob/packagelock/actions/workflows/release_tag.yml/badge.svg)
![GitHub Actions Workflow Status](https://github.com/hilkopterbob/packagelock/actions/workflows/golangci-lint.yml/badge.svg)
![GitHub Actions Workflow Status](https://github.com/hilkopterbob/packagelock/actions/workflows/build-docker-container.yml/badge.svg)
![GitHub Actions Workflow Status](https://github.com/hilkopterbob/packagelock/actions/workflows/unstable-build-docker-container.yml/badge.svg)
diff --git a/certs/generate-certs.go b/certs/generate-certs.go
index bbd6db9..c0b4e13 100644
--- a/certs/generate-certs.go
+++ b/certs/generate-certs.go
@@ -8,16 +8,34 @@ import (
"encoding/pem"
"math/big"
"os"
- "packagelock/logger"
"time"
+
+ "go.uber.org/fx"
+ "go.uber.org/zap"
)
-// CreateSelfSignedCert generates a self-signed RSA certificate and private key
-func CreateSelfSignedCert(certFile, keyFile string) error {
+type CertGeneratorParams struct {
+ fx.In
+
+ Logger *zap.Logger
+}
+
+type CertGenerator struct {
+ logger *zap.Logger
+}
+
+func NewCertGenerator(params CertGeneratorParams) *CertGenerator {
+ return &CertGenerator{
+ logger: params.Logger,
+ }
+}
+
+// CreateSelfSignedCert generates a self-signed RSA certificate and private key.
+func (cg *CertGenerator) CreateSelfSignedCert(certFile, keyFile string) error {
// Generate a private key using RSA (2048-bit key size)
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
- logger.Logger.Warnf("failed to generate private key: %v", err)
+ cg.logger.Warn("Failed to generate private key", zap.Error(err))
return err
}
@@ -27,7 +45,7 @@ func CreateSelfSignedCert(certFile, keyFile string) error {
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
- logger.Logger.Warnf("failed to generate serial number: %v", err)
+ cg.logger.Warn("Failed to generate serial number", zap.Error(err))
return err
}
@@ -43,58 +61,64 @@ func CreateSelfSignedCert(certFile, keyFile string) error {
BasicConstraintsValid: true,
}
+ // Ensure the "certs" directory exists
+ err = os.MkdirAll("certs/", os.ModePerm)
+ if err != nil {
+ cg.logger.Fatal("Cannot create 'certs/' directory", zap.Error(err))
+ return err
+ }
+
// Generate a self-signed certificate using the RSA private key
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
- logger.Logger.Warnf("failed to create certificate: %v", err)
+ cg.logger.Warn("Failed to create certificate", zap.Error(err))
return err
}
// Save the certificate to certFile
certOut, err := os.Create(certFile)
if err != nil {
- logger.Logger.Warnf("failed to open cert.pem for writing: %v", err)
+ cg.logger.Warn("Failed to open cert.pem for writing", zap.Error(err))
+ return err
}
-
- // INFO: If the parrent throws an err and this defer is called
- // and fileOut.Close() throws an error to, the original error will be overwritten.
defer func() {
- deferredErr := certOut.Close()
- if deferredErr != nil {
- logger.Logger.Warnf("Cannot close Cert File, got: %s", deferredErr)
- return
+ if err := certOut.Close(); err != nil {
+ cg.logger.Warn("Cannot close certificate file", zap.Error(err))
}
}()
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
- logger.Logger.Warnf("failed to write certificate to cert.pem: %v", err)
+ cg.logger.Warn("Failed to write certificate to file", zap.Error(err))
return err
}
// Save the RSA private key to keyFile
keyOut, err := os.Create(keyFile)
if err != nil {
- logger.Logger.Warnf("failed to open key.pem for writing: %v", err)
+ cg.logger.Warn("Failed to open key.pem for writing", zap.Error(err))
return err
}
-
- // INFO: If the parrent throws an err and this defer is called
- // and fileOut.Close() throws an error to, the original error will be overwritten.
defer func() {
- deferredErr := keyOut.Close()
- if deferredErr != nil {
- logger.Logger.Warnf("Cannot close Cert File, got: %s", deferredErr)
- return
+ if err := keyOut.Close(); err != nil {
+ cg.logger.Warn("Cannot close key file", zap.Error(err))
}
}()
// Marshal the RSA private key
privBytes := x509.MarshalPKCS1PrivateKey(priv)
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes}); err != nil {
- logger.Logger.Warnf("failed to write private key to key.pem: %v", err)
+ cg.logger.Warn("Failed to write private key to file", zap.Error(err))
return err
}
- logger.Logger.Infof("Successfully created self-signed RSA certificate and private key.\n%s \n%s", certFile, keyFile)
+ cg.logger.Info("Successfully created self-signed RSA certificate and private key",
+ zap.String("certFile", certFile),
+ zap.String("keyFile", keyFile),
+ )
return nil
}
+
+// Module exports the certs module.
+var Module = fx.Options(
+ fx.Provide(NewCertGenerator),
+)
diff --git a/cmd/generate.go b/cmd/generate.go
new file mode 100644
index 0000000..ec55329
--- /dev/null
+++ b/cmd/generate.go
@@ -0,0 +1,171 @@
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "packagelock/certs"
+ "packagelock/config"
+ "packagelock/db"
+ "packagelock/logger"
+ "packagelock/structs"
+ "packagelock/tracing"
+ "time"
+
+ configPkg "packagelock/config"
+
+ "github.com/google/uuid"
+ "github.com/k0kubun/pp"
+ "github.com/sethvargo/go-password/password"
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+ "github.com/surrealdb/surrealdb.go"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+ "golang.org/x/crypto/bcrypt"
+)
+
+func NewGenerateCmd() *cobra.Command {
+ generateCmd := &cobra.Command{
+ Use: "generate [certs|config|admin]",
+ Short: "Generate certificates, configuration files, or an admin",
+ Long: "Generate certificates, configuration files, or an admin user required by the application.",
+ Args: cobra.ExactValidArgs(1),
+ ValidArgs: []string{"certs", "config", "admin"},
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) == 0 {
+ fmt.Println("Please specify an argument: certs, config, or admin.")
+ return
+ }
+ switch args[0] {
+ case "certs":
+ app := fx.New(
+ fx.Provide(func() string { return "Command Runner" }),
+ logger.Module,
+ configPkg.Module,
+ certs.Module,
+ tracing.Module,
+ fx.Invoke(runGenerateCerts),
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start application for certificate generation:", err)
+ os.Exit(1)
+ }
+
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop application after certificate generation:", err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ case "config":
+ app := fx.New(
+ fx.Provide(func() string { return "Command Runner" }),
+ certs.Module,
+ logger.Module,
+ configPkg.Module,
+ tracing.Module,
+ fx.Invoke(runGenerateConfig),
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start application for config generation:", err)
+ os.Exit(1)
+ }
+
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop application after config generation:", err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ case "admin":
+ app := fx.New(
+ fx.Provide(func() string { return "Command Runner" }),
+ certs.Module,
+ config.Module,
+ logger.Module,
+ db.Module,
+ tracing.Module,
+ fx.Invoke(runGenerateAdmin),
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start application for admin generation:", err)
+ os.Exit(1)
+ }
+
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop application after admin generation:", err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ default:
+ fmt.Println("Invalid argument. Use 'certs', 'config', or 'admin'.")
+ }
+ },
+ }
+
+ return generateCmd
+}
+
+func runGenerateCerts(certGen *certs.CertGenerator, logger *zap.Logger, config *viper.Viper) {
+ err := certGen.CreateSelfSignedCert(
+ config.GetString("network.ssl-config.certificatepath"),
+ config.GetString("network.ssl-config.privatekeypath"),
+ )
+ if err != nil {
+ fmt.Printf("Error generating self-signed certs: %v\n", err)
+ logger.Warn("Error generating self-signed certs", zap.Error(err))
+ } else {
+ logger.Info("Successfully generated self-signed certificates.")
+ fmt.Println("Certificates generated successfully.")
+ }
+}
+
+func runGenerateConfig(config *viper.Viper, logger *zap.Logger) {
+ configPkg.CreateDefaultConfig(config, logger)
+ logger.Info("Default configuration file created.")
+ fmt.Println("Configuration file generated successfully.")
+}
+
+func runGenerateAdmin(db *db.Database, logger *zap.Logger) {
+ adminPw, err := password.Generate(64, 10, 10, false, false)
+ if err != nil {
+ logger.Fatal("Error generating admin password", zap.Error(err))
+ }
+
+ // Hash the password for security
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPw), bcrypt.DefaultCost)
+ if err != nil {
+ logger.Fatal("Error hashing admin password", zap.Error(err))
+ }
+
+ // Admin data
+ temporalAdmin := structs.User{
+ UserID: uuid.New(),
+ Username: "admin",
+ Password: string(hashedPassword),
+ Groups: []string{"Admin", "StorageAdmin", "Audit"},
+ CreationTime: time.Now(),
+ UpdateTime: time.Now(),
+ ApiKeys: nil,
+ }
+
+ // Insert admin
+ adminInsertionData, err := db.DB.Create("user", temporalAdmin)
+ if err != nil {
+ logger.Fatal("Error inserting default admin into DB", zap.Error(err))
+ }
+
+ // Unmarshal data
+ var createdUser structs.User
+ err = surrealdb.Unmarshal(adminInsertionData, &createdUser)
+ if err != nil {
+ logger.Fatal("Error querying default admin", zap.Error(err))
+ }
+
+ pp.Println("Admin Username:", createdUser.Username)
+ pp.Println("Admin Password:", adminPw) // Display the original password
+
+ logger.Info("Admin user created successfully.")
+}
diff --git a/cmd/print_routes.go b/cmd/print_routes.go
new file mode 100644
index 0000000..2d4295f
--- /dev/null
+++ b/cmd/print_routes.go
@@ -0,0 +1,61 @@
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "packagelock/certs"
+ "packagelock/config"
+ "packagelock/db"
+ "packagelock/handler"
+ "packagelock/logger"
+ "packagelock/server"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/spf13/cobra"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+)
+
+func NewPrintRoutesCmd() *cobra.Command {
+ printRoutesCmd := &cobra.Command{
+ Use: "print-routes",
+ Short: "Prints out all registered routes",
+ Run: func(cmd *cobra.Command, args []string) {
+ app := fx.New(
+ fx.Provide(func() string { return "Command Runner" }),
+ logger.Module,
+ server.Module,
+ handler.Module,
+ config.Module,
+ certs.Module,
+ db.Module,
+ fx.Invoke(runPrintRoutes),
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start application for printing routes:", err)
+ os.Exit(1)
+ }
+
+ // Since runPrintRoutes runs synchronously, we can stop the app immediately
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop application after printing routes:", err)
+ os.Exit(1)
+ }
+ },
+ }
+
+ return printRoutesCmd
+}
+
+func runPrintRoutes(app *fiber.App, logger *zap.Logger) {
+ routes := app.Stack() // Get all registered routes
+ for _, route := range routes {
+ for _, r := range route {
+ fmt.Printf("%s %s\n", r.Method, r.Path)
+ }
+ }
+ logger.Info("Printed all routes.")
+ os.Exit(0)
+}
diff --git a/cmd/restart.go b/cmd/restart.go
new file mode 100644
index 0000000..08052db
--- /dev/null
+++ b/cmd/restart.go
@@ -0,0 +1,98 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strconv"
+ "syscall"
+ "time"
+
+ "github.com/spf13/cobra"
+)
+
+func NewRestartCmd() *cobra.Command {
+ restartCmd := &cobra.Command{
+ Use: "restart",
+ Short: "Restart the running server",
+ Run: func(cmd *cobra.Command, args []string) {
+ // Stop the running server
+ fmt.Println("Stopping the server...")
+ if err := stopServer(); err != nil {
+ fmt.Println("Failed to stop the server:", err)
+ os.Exit(1)
+ }
+
+ // Wait before restarting
+ time.Sleep(5 * time.Second)
+
+ // Start the server
+ fmt.Println("Starting the server...")
+ if err := startServer(); err != nil {
+ fmt.Println("Failed to start the server:", err)
+ os.Exit(1)
+ }
+ },
+ }
+
+ return restartCmd
+}
+
+// stopServer stops the running server by reading the PID file and sending SIGTERM.
+func stopServer() error {
+ // Read the PID from the file
+ data, err := os.ReadFile("packagelock.pid")
+ if err != nil {
+ return fmt.Errorf("could not read PID file: %w", err)
+ }
+
+ pid, err := strconv.Atoi(string(data))
+ if err != nil {
+ return fmt.Errorf("invalid PID found in file: %w", err)
+ }
+
+ // Send SIGTERM to the process
+ fmt.Printf("Stopping the server with PID: %d\n", pid)
+
+ process, err := os.FindProcess(pid)
+ if err != nil {
+ return fmt.Errorf("failed to find the process: %w", err)
+ }
+
+ err = process.Signal(syscall.SIGTERM)
+ if err != nil {
+ return fmt.Errorf("failed to stop the server: %w", err)
+ }
+
+ fmt.Println("Server stopped.")
+
+ // Remove the PID file
+ err = os.Remove("packagelock.pid")
+ if err != nil {
+ fmt.Println("Failed to remove PID file:", err)
+ } else {
+ fmt.Println("PID file removed successfully.")
+ }
+
+ return nil
+}
+
+// startServer starts the server by creating a new Fx application and running it in a separate process.
+func startServer() error {
+ // Execute the start command as a new process
+ executable, err := os.Executable()
+ if err != nil {
+ return fmt.Errorf("failed to get executable path: %w", err)
+ }
+
+ cmd := exec.Command(executable, "start")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("failed to start server process: %w", err)
+ }
+
+ fmt.Printf("Server started with PID: %d\n", cmd.Process.Pid)
+ return nil
+}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..a7589bf
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,65 @@
+package cmd
+
+import (
+ "context"
+ "os"
+
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+)
+
+// RootParams holds the dependencies for the root command.
+type RootParams struct {
+ fx.In
+
+ Logger *zap.Logger
+ Config *viper.Viper
+}
+
+// NewRootCmd creates the root command with injected dependencies.
+func NewRootCmd() *cobra.Command {
+ rootCmd := &cobra.Command{
+ Use: "packagelock",
+ Short: "Packagelock CLI tool",
+ Long: `Packagelock CLI manages the server and other operations.`,
+ }
+
+ // Add subcommands to the root command
+ rootCmd.AddCommand(NewStartCmd())
+ rootCmd.AddCommand(NewStopCmd())
+ rootCmd.AddCommand(NewRestartCmd())
+ rootCmd.AddCommand(NewSetupCmd())
+ rootCmd.AddCommand(NewGenerateCmd())
+ rootCmd.AddCommand(NewPrintRoutesCmd())
+
+ return rootCmd
+}
+
+// Execute runs the root command.
+func Execute(rootCmd *cobra.Command) error {
+ return rootCmd.Execute()
+}
+
+// Module exports the commands as an Fx module.
+var Module = fx.Options(
+ fx.Provide(NewRootCmd),
+ fx.Invoke(func(lc fx.Lifecycle, rootCmd *cobra.Command, logger *zap.Logger) {
+ lc.Append(fx.Hook{
+ OnStart: func(ctx context.Context) error {
+ go func() {
+ if err := rootCmd.Execute(); err != nil {
+ logger.Fatal("Failed to execute root command", zap.Error(err))
+ }
+ os.Exit(0)
+ }()
+ return nil
+ },
+ OnStop: func(ctx context.Context) error {
+ // Handle any cleanup if necessary
+ return nil
+ },
+ })
+ }),
+)
diff --git a/cmd/setup.go b/cmd/setup.go
new file mode 100644
index 0000000..21f70e0
--- /dev/null
+++ b/cmd/setup.go
@@ -0,0 +1,119 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "packagelock/logger"
+ "path/filepath"
+ "text/template"
+
+ "github.com/spf13/cobra"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+)
+
+func NewSetupCmd() *cobra.Command {
+ setupCmd := &cobra.Command{
+ Use: "setup",
+ Short: "Setup PackageLock",
+ Run: func(cmd *cobra.Command, args []string) {
+ app := fx.New(
+ logger.Module,
+ fx.Invoke(runSetup),
+ )
+
+ if err := app.Start(cmd.Context()); err != nil {
+ fmt.Println("Failed to start application for setup command:", err)
+ os.Exit(1)
+ }
+
+ // Since runSetup runs synchronously, we can stop the app immediately
+ if err := app.Stop(cmd.Context()); err != nil {
+ fmt.Println("Failed to stop application after setup command:", err)
+ os.Exit(1)
+ }
+ },
+ }
+
+ return setupCmd
+}
+
+func runSetup(logger *zap.Logger) {
+ fmt.Println("Starting the PackageLock setup!")
+
+ err := os.MkdirAll("logs/", os.ModePerm)
+ if err != nil {
+ logger.Fatal("Couldn't create 'logs' directory", zap.Error(err))
+ }
+ fmt.Println("Generated logs directory")
+
+ err = os.MkdirAll("certs/", os.ModePerm)
+ if err != nil {
+ logger.Fatal("Couldn't create 'certs' directory", zap.Error(err))
+ }
+ fmt.Println("Generated certs directory")
+
+ generateUnitFile(logger)
+ fmt.Println("Generated systemd unit file")
+
+ fmt.Println("Setup finished successfully!")
+ os.Exit(0)
+}
+
+func generateUnitFile(logger *zap.Logger) {
+ const systemdTemplate = `[Unit]
+Description=PackageLock Management Server
+After=network.target
+
+[Service]
+ExecStart={{.ExecStart}} start
+Restart=always
+User={{.User}}
+Group={{.Group}}
+
+[Install]
+WantedBy=multi-user.target
+`
+
+ type unitFileData struct {
+ ExecStart string
+ User string
+ Group string
+ }
+
+ execPath, err := os.Executable()
+ if err != nil {
+ logger.Fatal("Failed to get executable path", zap.Error(err))
+ }
+ execPath, err = filepath.Abs(execPath)
+ if err != nil {
+ logger.Fatal("Failed to get absolute executable path", zap.Error(err))
+ }
+
+ data := unitFileData{
+ ExecStart: execPath,
+ User: "your-user", // Replace with actual user
+ Group: "your-group", // Replace with actual group
+ }
+
+ filePath := "/etc/systemd/system/packagelock.service"
+ file, err := os.Create(filePath)
+ if err != nil {
+ fmt.Println("Seems like you can't generate the unit file...")
+ fmt.Println("Did you run this with 'sudo'? π")
+ logger.Fatal("Failed to create systemd unit file", zap.Error(err))
+ }
+ defer file.Close()
+
+ tmpl, err := template.New("systemd").Parse(systemdTemplate)
+ if err != nil {
+ logger.Fatal("Failed to parse systemd template", zap.Error(err))
+ }
+
+ err = tmpl.Execute(file, data)
+ if err != nil {
+ logger.Fatal("Failed to execute template", zap.Error(err))
+ }
+
+ fmt.Printf("Systemd unit file created at %s\n", filePath)
+}
diff --git a/cmd/start.go b/cmd/start.go
new file mode 100644
index 0000000..c9da0ec
--- /dev/null
+++ b/cmd/start.go
@@ -0,0 +1,62 @@
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/signal"
+ "packagelock/certs"
+ "packagelock/config"
+ "packagelock/db"
+ "packagelock/handler"
+ "packagelock/logger"
+ "packagelock/server"
+ "packagelock/tracing"
+ "syscall"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/spf13/cobra"
+ "go.uber.org/fx"
+)
+
+func NewStartCmd() *cobra.Command {
+ startCmd := &cobra.Command{
+ Use: "start",
+ Short: "Start the server",
+ }
+
+ startCmd.Run = func(cmd *cobra.Command, args []string) {
+ app := fx.New(
+ fx.Provide(
+ func() string {
+ return "1.0.0" // Replace with actual version
+ },
+ logger.NewLogger,
+ config.NewConfig,
+ ),
+ certs.Module,
+ db.Module,
+ handler.Module,
+ server.Module,
+ tracing.Module,
+ fx.Invoke(func(*fiber.App) {}),
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start server application:", err)
+ os.Exit(1)
+ }
+
+ // Wait for interrupt signal to gracefully shutdown the server
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+ <-c
+
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop server application:", err)
+ os.Exit(1)
+ }
+ }
+
+ return startCmd
+}
diff --git a/cmd/stop.go b/cmd/stop.go
new file mode 100644
index 0000000..fb3247c
--- /dev/null
+++ b/cmd/stop.go
@@ -0,0 +1,85 @@
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "packagelock/logger"
+ "strconv"
+ "syscall"
+
+ "github.com/spf13/cobra"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+)
+
+// NewStopCmd creates the stop command.
+func NewStopCmd() *cobra.Command {
+ stopCmd := &cobra.Command{
+ Use: "stop",
+ Short: "Stop the running server",
+ Run: func(cmd *cobra.Command, args []string) {
+ app := fx.New(
+ logger.Module,
+ fx.Invoke(runStop),
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start application for stop command:", err)
+ os.Exit(1)
+ }
+
+ // Since runStop runs synchronously, we can stop the app immediately
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop application after stop command:", err)
+ os.Exit(1)
+ }
+ },
+ }
+
+ return stopCmd
+}
+
+// runStop performs the stop operation using the injected logger.
+func runStop(logger *zap.Logger) {
+ // Read the PID from the file
+ data, err := os.ReadFile("packagelock.pid")
+ if err != nil {
+ logger.Fatal("Could not read PID file", zap.Error(err))
+ }
+
+ pid, err := strconv.Atoi(string(data))
+ if err != nil {
+ logger.Fatal("Invalid PID found in file", zap.Error(err))
+ }
+
+ // Send SIGTERM to the process
+ fmt.Printf("Stopping the server with PID: %d\n", pid)
+ logger.Info("Stopping the server", zap.Int("PID", pid))
+
+ process, err := os.FindProcess(pid)
+ if err != nil {
+ logger.Warn("Failed to find the process", zap.Error(err))
+ return
+ }
+
+ err = process.Signal(syscall.SIGTERM)
+ if err != nil {
+ logger.Warn("Failed to stop the server", zap.Error(err))
+ return
+ }
+
+ fmt.Println("Server stopped.")
+ logger.Info("Server stopped.")
+
+ // Remove the PID file
+ err = os.Remove("packagelock.pid")
+ if err != nil {
+ logger.Warn("Failed to remove PID file", zap.Error(err))
+ } else {
+ fmt.Println("PID file removed successfully.")
+ logger.Info("PID file removed successfully.")
+ }
+
+ os.Exit(0)
+}
diff --git a/config/conf-init.go b/config/conf-init.go
index c10c813..c316e56 100644
--- a/config/conf-init.go
+++ b/config/conf-init.go
@@ -2,63 +2,133 @@ package config
import (
"bytes"
- "io"
- "packagelock/logger"
+ "context"
+ "os"
+ "packagelock/certs"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
+ "go.opentelemetry.io/otel/codes" // Import for setting span status
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
)
-var Config ConfigProvider
-
-type ConfigProvider interface {
- SetConfigName(name string)
- SetConfigType(fileext string)
- AddConfigPath(path string)
- ReadInConfig() error
- OnConfigChange(run func(e fsnotify.Event))
- WatchConfig()
- WriteConfigAs(path string) error
- ReadConfig(in io.Reader) error
- AllSettings() map[string]any
- GetString(string string) string
- SetDefault(key string, value any)
- Get(key string) any
- GetBool(key string) bool
+type ConfigParams struct {
+ fx.In
+
+ Lifecycle fx.Lifecycle
+ Logger *zap.Logger
+ AppVersion string
+ CertGenerator *certs.CertGenerator
+ Tracer trace.Tracer // Injected Tracer from OpenTelemetry
}
-// TODO: How to test?
-func StartViper(config ConfigProvider) ConfigProvider {
- config.SetConfigName("config") // name of config file (without extension)
+func NewConfig(params ConfigParams) (*viper.Viper, error) {
+ // Start a new span for the configuration initialization
+ _, span := params.Tracer.Start(context.Background(), "Configuration Initialization")
+ defer span.End()
+
+ config := viper.New()
+ config.SetDefault("general.app-version", params.AppVersion)
+ config.SetConfigName("config") // Name of config file (without extension)
config.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
config.AddConfigPath("/app/data")
- config.AddConfigPath("/etc/packagelock/") // path to look for the config file in etc/
- config.AddConfigPath(".") // optionally look for config in the working directory
+ config.AddConfigPath("/etc/packagelock/") // Path to look for the config file in etc/
+ config.AddConfigPath(".") // Optionally look for config in the working directory
+
+ // Add attributes to the span
+ span.SetAttributes()
- // if no config file found a default file will be Created
- // than a rescan. new_config is the same as config, but needs a different name
- // as it cont be argument return-store
- // if there is a different error -> panic & exit
+ // Read the configuration file
if err := config.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
- CreateDefaultConfig(config)
- newConfig := StartViper(config)
- logger.Logger.Info("No Config found, created default Config.")
- return newConfig
+ // Create a default config if none is found
+ params.Logger.Info("No config file found. Creating default configuration.")
+ span.AddEvent("Config file not found. Creating default configuration.")
+
+ CreateDefaultConfig(config, params.Logger)
+
+ // Attempt to read the config again
+ if err := config.ReadInConfig(); err != nil {
+ params.Logger.Panic("Cannot read config after creating default config", zap.Error(err))
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "Failed to read config after creating default")
+ return nil, err
+ }
+ params.Logger.Info("Default config created and loaded successfully.")
+ span.AddEvent("Default config created and loaded successfully.")
} else {
- logger.Logger.Panicf("Cannot create default config, got: %s", err)
+ params.Logger.Panic("Cannot read config", zap.Error(err))
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "Failed to read config")
+ return nil, err
}
}
- logger.Logger.Info("Successfully Created Config Manager.")
- return config
+ params.Logger.Info("Successfully created Config Manager.")
+ span.AddEvent("Config Manager initialized successfully.")
+
+ // Check and create self-signed certificates if missing
+ certPath := config.GetString("network.ssl-config.certificatepath")
+ keyPath := config.GetString("network.ssl-config.privatekeypath")
+ if _, err := os.Stat(certPath); os.IsNotExist(err) {
+ params.Logger.Info("Certificate files missing. Creating new self-signed certificates.")
+ span.AddEvent("Certificate files missing. Creating new self-signed certificates.")
+
+ err := params.CertGenerator.CreateSelfSignedCert(certPath, keyPath)
+ if err != nil {
+ params.Logger.Panic("Error creating self-signed certificate", zap.Error(err))
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "Failed to create self-signed certificate")
+ return nil, err
+ }
+
+ params.Logger.Info("Self-signed certificates created successfully.")
+ span.AddEvent("Self-signed certificates created successfully.")
+ }
+
+ // Set up configuration change watching
+ params.Lifecycle.Append(fx.Hook{
+ OnStart: func(ctx context.Context) error {
+ // Start a new span for setting up config change watcher
+ _, watchSpan := params.Tracer.Start(ctx, "Config Watcher Setup")
+ defer watchSpan.End()
+
+ // Watch for configuration changes
+ config.OnConfigChange(func(e fsnotify.Event) {
+ params.Logger.Info("Config file changed", zap.String("file", e.Name))
+ watchSpan.AddEvent("Configuration file changed")
+ // Handle configuration change if necessary
+ })
+ config.WatchConfig()
+ params.Logger.Info("Started watching configuration changes.")
+ watchSpan.AddEvent("Started watching configuration changes.")
+
+ return nil
+ },
+ OnStop: func(ctx context.Context) error {
+ // Handle any cleanup if necessary
+ params.Logger.Info("Stopping configuration watcher.")
+ span.AddEvent("Configuration watcher stopped.")
+ return nil
+ },
+ })
+
+ params.Logger.Info("Configuration initialized successfully.")
+ span.SetStatus(codes.Ok, "Configuration initialized successfully.")
+ span.AddEvent("Configuration initialized successfully.")
+
+ return config, nil
}
-func CreateDefaultConfig(config ConfigProvider) {
+// CreateDefaultConfig generates a default configuration file.
+func CreateDefaultConfig(config *viper.Viper, logger *zap.Logger) {
yamlExample := []byte(`
general:
debug: true
production: false
+ monitoring: true
database:
address: 127.0.0.1
port: 8000
@@ -72,15 +142,23 @@ network:
allowselfsigned: true
certificatepath: ./certs/testing.crt
privatekeypath: ./certs/testing.key
- redirecthttp: true `)
+ redirecthttp: true
+`)
+ // Read the default configuration from the YAML example
err := config.ReadConfig(bytes.NewBuffer(yamlExample))
if err != nil {
- logger.Logger.Panicf("Incompatible Default Config! Got: %s", err)
+ logger.Panic("Incompatible default config", zap.Error(err))
}
+ // Write the default configuration to a file
errWrite := config.WriteConfigAs("./config.yaml")
if errWrite != nil {
- logger.Logger.Panicf("Cannot write config file, got: %s", errWrite)
+ logger.Panic("Cannot write config file", zap.Error(errWrite))
}
}
+
+// Module exports the config module.
+var Module = fx.Options(
+ fx.Provide(NewConfig),
+)
diff --git a/db/db.go b/db/db.go
index 20e3d86..aa74b4f 100644
--- a/db/db.go
+++ b/db/db.go
@@ -1,69 +1,108 @@
package db
import (
- "packagelock/config"
- "packagelock/logger"
+ "context"
+ "fmt"
+ "github.com/spf13/viper"
"github.com/surrealdb/surrealdb.go"
+ "go.opentelemetry.io/otel/codes" // Import for setting span status
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
)
-var DB *surrealdb.DB
+type DatabaseParams struct {
+ fx.In
-func InitDB() error {
- dbAddress := config.Config.GetString("database.address")
- dbPort := config.Config.GetString("database.port")
- dbUsername := config.Config.GetString("database.username")
- dbPasswd := config.Config.GetString("database.password")
+ Lifecycle fx.Lifecycle
+ Logger *zap.Logger
+ Config *viper.Viper
+ Tracer trace.Tracer // Injected Tracer from OpenTelemetry
+}
+
+type Database struct {
+ DB *surrealdb.DB
+ Logger *zap.Logger
+ Tracer trace.Tracer
+}
+
+// Module exports the database module.
+var Module = fx.Options(
+ fx.Provide(NewDatabase),
+)
+
+// NewDatabase initializes the database connection using the provided configuration and logger.
+func NewDatabase(params DatabaseParams) (*Database, error) {
+ // Start a new span for the database initialization
+ _, span := params.Tracer.Start(context.Background(), "Database Initialization")
+ defer span.End()
+
+ dbAddress := params.Config.GetString("database.address")
+ dbPort := params.Config.GetString("database.port")
+ dbUsername := params.Config.GetString("database.username")
+ dbPasswd := params.Config.GetString("database.password")
- db, err := surrealdb.New("ws://" + dbAddress + ":" + dbPort + "/rpc")
+ connString := fmt.Sprintf("ws://%s:%s/rpc", dbAddress, dbPort)
+
+ db, err := surrealdb.New(connString)
if err != nil {
- logger.Logger.Errorf(` Couldn't connect to DB! Got: '%s'.
- 1. Check the config for a wrong Address/Port (Currently: %s:%s)
- 2. Check if the DB is reachable (eg. a Ping). Check the Firewalls if there.
- 3. Consult the PackageLock Doc's! π
- Golang Trace Logs:
- `, err.Error(), dbAddress, dbPort)
+ params.Logger.Error("Couldn't connect to DB!",
+ zap.Error(err),
+ zap.String("address", dbAddress),
+ zap.String("port", dbPort),
+ zap.String("connString", connString),
+ )
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "Failed to connect to DB")
+ return nil, err
}
+ // Sign in to the database
if _, err = db.Signin(map[string]interface{}{
"user": dbUsername,
"pass": dbPasswd,
}); err != nil {
- logger.Logger.Errorf(` Couldn't connect to DB! Got: '%s'.
- 1. Check the config for a wrong DB-Username/Password (Currently: %s/)
- 3. Consult the PackageLock Doc's! π
- Golang Trace Logs:
- `, err.Error(), dbUsername)
+ params.Logger.Error("Couldn't sign in to DB!",
+ zap.Error(err),
+ zap.String("username", dbUsername),
+ )
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "Failed to sign in to DB")
+ return nil, err
}
+ // Use the specified namespace and database
if _, err = db.Use("PackageLock", "db1.0"); err != nil {
- // No error handling possible, as we need to use this db
- logger.Logger.Panicf("Couldn't Use 'PackageLock' Namespace and 'db1.0' Database. Got: %s", err)
+ params.Logger.Panic("Couldn't use 'PackageLock' Namespace and 'db1.0' Database.",
+ zap.Error(err),
+ )
+ span.RecordError(err)
+ span.SetStatus(codes.Error, "Failed to use Namespace and Database")
+ return nil, err
}
- DB = db
-
- logger.Logger.Infof("Successfully Connected to DB, at: %s:%s", dbAddress, dbPort)
- return nil
-}
+ params.Logger.Info("Successfully connected to DB.",
+ zap.String("address", dbAddress),
+ zap.String("port", dbPort),
+ )
+ span.AddEvent("Successfully connected and authenticated to DB")
-// INFO: If you use this, fix it!
-// INFO: And add logging/error handling
-func Select(tablename string, SliceOfType interface{}) error {
- transaction, err := DB.Select(tablename)
- if err != nil {
- // FIXME: logging?
- // Error handling
- panic(err)
+ database := &Database{
+ DB: db,
+ Logger: params.Logger,
+ Tracer: params.Tracer,
}
- err = surrealdb.Unmarshal(transaction, &SliceOfType)
- if err != nil {
- // FIXME: Logging?
- // Error Handling?
- panic(err)
- }
+ // Use Lifecycle to manage the database connection
+ params.Lifecycle.Append(fx.Hook{
+ OnStop: func(ctx context.Context) error {
+ params.Logger.Info("Closing database connection.")
+ span.AddEvent("Closing database connection")
+ db.Close()
+ return nil
+ },
+ })
- // FIXME: Add Success msg in Log!
- return nil
+ return database, nil
}
diff --git a/go.mod b/go.mod
index 8ba75bf..d1081b2 100644
--- a/go.mod
+++ b/go.mod
@@ -3,37 +3,58 @@ module packagelock
go 1.22.6
require (
+ github.com/ansrivas/fiberprometheus v0.3.2
github.com/fsnotify/fsnotify v1.7.0
+ github.com/gofiber/contrib/fiberzap v1.0.2
github.com/gofiber/contrib/jwt v1.0.10
+ github.com/gofiber/contrib/otelfiber v1.0.10
github.com/gofiber/fiber/v2 v2.52.5
github.com/gofiber/template/html/v2 v2.1.2
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/k0kubun/pp v3.0.1+incompatible
- github.com/k0kubun/pp/v3 v3.2.0
github.com/sethvargo/go-password v0.3.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/surrealdb/surrealdb.go v0.2.1
+ go.opentelemetry.io/otel v1.31.0
+ go.opentelemetry.io/otel/exporters/jaeger v1.17.0
+ go.opentelemetry.io/otel/sdk v1.31.0
+ go.opentelemetry.io/otel/trace v1.31.0
+ go.uber.org/fx v1.23.0
+ go.uber.org/zap v1.27.0
+ golang.org/x/crypto v0.28.0
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
- github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/andybalholm/brotli v1.1.1 // indirect
+ github.com/ansrivas/fiberprometheus/v2 v2.7.0 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gofiber/fiber v1.14.4 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
- github.com/google/go-cmp v0.6.0 // indirect
+ github.com/gorilla/schema v1.1.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
- github.com/klauspost/compress v1.17.9 // indirect
+ github.com/klauspost/compress v1.17.11 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+ github.com/prometheus/client_golang v1.20.5 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.60.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -43,15 +64,18 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
- github.com/valyala/fasthttp v1.55.0 // indirect
+ github.com/valyala/fasthttp v1.56.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
- go.uber.org/atomic v1.9.0 // indirect
+ go.opentelemetry.io/contrib v1.31.0 // indirect
+ go.opentelemetry.io/contrib/propagators/b3 v1.20.0 // indirect
+ go.opentelemetry.io/otel/metric v1.31.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.41.0 // indirect
+ go.uber.org/dig v1.18.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
- go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
- golang.org/x/sys v0.24.0 // indirect
- golang.org/x/text v0.17.0 // indirect
- gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ golang.org/x/sys v0.26.0 // indirect
+ golang.org/x/text v0.19.0 // indirect
+ google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index fb85c3e..6bd3e7b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,74 +1,173 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
-github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
-github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
+github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
+github.com/ansrivas/fiberprometheus v0.3.2 h1:2pqHjRQqQhveNo+1DmxjZdyG/jcqN1NEP57WyConr58=
+github.com/ansrivas/fiberprometheus v0.3.2/go.mod h1:NB+BT3NTXlz0oHVJby5FJWvQPSS93QAIUbggW5YsxsI=
+github.com/ansrivas/fiberprometheus/v2 v2.7.0 h1:09XiSzG0J7aZp7RviklngdWdDbSybKjhuWAstp003Gg=
+github.com/ansrivas/fiberprometheus/v2 v2.7.0/go.mod h1:hSJdO65lfnWW70Qn9uGdXXsUUSkckbhuw5r/KesygpU=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gofiber/contrib/fiberzap v1.0.2 h1:EQwhggtszVfIdBeXxN9Xrmld71es34Ufs+ef8VMqZxc=
+github.com/gofiber/contrib/fiberzap v1.0.2/go.mod h1:jGO8BHU4gRI9U0JtM6zj2CIhYfgVmW5JxziN8NTgVwE=
github.com/gofiber/contrib/jwt v1.0.10 h1:/ilGepl6i0Bntl0Zcd+lAzagY8BiS1+fEiAj32HMApk=
github.com/gofiber/contrib/jwt v1.0.10/go.mod h1:1qBENE6sZ6PPT4xIpBzx1VxeyROQO7sj48OlM1I9qdU=
+github.com/gofiber/contrib/otelfiber v1.0.10 h1:Bu28Pi4pfYmGfIc/9+sNaBbFwTHGY/zpSIK5jBxuRtM=
+github.com/gofiber/contrib/otelfiber v1.0.10/go.mod h1:jN6AvS1HolDHTQHFURsV+7jSX96FpXYeKH6nmkq8AIw=
+github.com/gofiber/fiber v1.14.4 h1:i80OW4tYZCPUBHywHfQlHXIzpBrl0sRaMHYjFDn6pz8=
+github.com/gofiber/fiber v1.14.4/go.mod h1:Yw2ekF1YDPreO9V6TMYjynu94xRxZBdaa8X5HhHsjCM=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/html/v2 v2.1.2 h1:wkK/mYJ3nIhongTkG3t0QgV4ADdgOYJYVSAF2AHnh8Y=
github.com/gofiber/template/html/v2 v2.1.2/go.mod h1:E98Z/FzvpaSib06aWEgYk6GXNf3ctoyaJH8yW5ay5ak=
+github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
+github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
-github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
-github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
-github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
-github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
+github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
+github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
+github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@@ -76,6 +175,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@@ -89,10 +190,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -104,31 +209,122 @@ github.com/surrealdb/surrealdb.go v0.2.1 h1:E4rCnD75Ftq8/wTgbQ9kJgMACi3xMziXtMlR
github.com/surrealdb/surrealdb.go v0.2.1/go.mod h1:CloW70O49xyVO/rGO9cAZ62FEbl0/hreRHEJuamnndQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
-github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
+github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
+github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
+github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
-go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
-go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
+go.opentelemetry.io/contrib v1.31.0 h1:GkjBOSwjro1dRWw64sDgsx3MAUa0puW4NLwLO4QNRCc=
+go.opentelemetry.io/contrib v1.31.0/go.mod h1:10IRYpeyXrTiOz6iJGXlLWoFWrnIzYRE/1EdC3GSHjg=
+go.opentelemetry.io/contrib/propagators/b3 v1.20.0 h1:Yty9Vs4F3D6/liF1o6FNt0PvN85h/BJJ6DQKJ3nrcM0=
+go.opentelemetry.io/contrib/propagators/b3 v1.20.0/go.mod h1:On4VgbkqYL18kbJlWsa18+cMNe6rYpBnPi1ARI/BrsU=
+go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
+go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
+go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
+go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
+go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
+go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC3 h1:MjaeegZTaX0Bv9uB9CrdVjOFM/8slRjReoWoV9xDCpY=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC3/go.mod h1:xpzajI9JBRr7gX63nO6kAmImmYIAtuQblZ36Z+LfCjE=
+go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
+go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
+go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk=
+go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w=
+go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
+go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
+go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
+go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
+go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
+go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
-golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
+google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/handler/UserGroup.go b/handler/UserGroup.go
deleted file mode 100644
index 47ce4bd..0000000
--- a/handler/UserGroup.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package handler
-
-import (
- "os"
- "packagelock/config"
- "packagelock/db"
- "packagelock/logger"
- "packagelock/structs"
- "time"
-
- "github.com/gofiber/fiber/v2"
- "github.com/golang-jwt/jwt/v5"
- "github.com/surrealdb/surrealdb.go"
-)
-
-func LoginHandler(c *fiber.Ctx) error {
- // Data Sheme
- type LoginRequest struct {
- Username string `json:"username"`
- Password string `json:"password"`
- }
-
- // Cast POST
- var loginReq LoginRequest
- if err := c.BodyParser(&loginReq); err != nil {
- logger.Logger.Debugf("Got invalid Login Request: %s", err)
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
- "error": "Failed to parse request",
- })
- }
-
- data, err := db.DB.Select("user")
- if err != nil {
- logger.Logger.Warnf("Got error while 'db.DB.Select('user')': %s \nSending 'StatusInternalServerError'", err)
- return c.Status(fiber.StatusInternalServerError).JSON(nil)
- }
-
- var UserTable []structs.User
- err = surrealdb.Unmarshal(data, &UserTable)
- if err != nil {
- logger.Logger.Warnf("Got error while surrealdb.Unmarshal: %s \nSending 'StatusInternalServerError'", err)
- return c.Status(fiber.StatusInternalServerError).JSON(nil)
- }
-
- var authenticatedUser structs.User
- for _, possibleUser := range UserTable {
- // TODO: implement password hashing
- if possibleUser.Username == loginReq.Username && possibleUser.Password == loginReq.Password {
- authenticatedUser = possibleUser
- }
- }
-
- // create JWT
- token := jwt.New(jwt.SigningMethodRS256)
- claims := token.Claims.(jwt.MapClaims)
- claims["username"] = authenticatedUser.Username
- claims["userID"] = authenticatedUser.UserID
- claims["exp"] = time.Now().Add(time.Hour * 72).Unix() // 3 days expiry
-
- // Sign and get the encoded token
- keyData, err := os.ReadFile(config.Config.GetString("network.ssl-config.privatekeypath"))
- if err != nil {
- logger.Logger.Warnf("Can't read from Private Key File, got: %s\nPath: %s", err, config.Config.GetString("network.ssl-config.privatekeypath"))
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to generate token",
- })
- }
-
- privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
- if err != nil {
- logger.Logger.Warnf("Can't sign with Private Key File, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to generate token",
- })
- }
-
- tokenString, err := token.SignedString(privateKey)
- if err != nil {
- logger.Logger.Warnf("Can't generate JWT, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to generate token",
- })
- }
-
- // Create && Populate new JWT-Onject
- // for appending to User && storing in DB
- newJWT := structs.ApiKey{
- KeyValue: tokenString,
- Description: "User Generated JWT",
- AccessSeperation: false,
- AccessRights: make([]string, 0),
- CreationTime: time.Now(),
- UpdateTime: time.Now(),
- }
-
- // Add the token to the user's APIToken slice
- authenticatedUser.ApiKeys = append(authenticatedUser.ApiKeys, newJWT)
- authenticatedUser.UpdateTime = time.Now()
-
- _, err = db.DB.Update(authenticatedUser.ID, authenticatedUser)
- if err != nil {
- logger.Logger.Warnf("Can't update User entry in DB to append new JWT, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to generate token",
- })
- }
-
- logger.Logger.Infof("Successfully authenticated and authorized following User: %s", authenticatedUser.Username)
- return c.JSON(newJWT)
-}
diff --git a/handler/agentgroup.go b/handler/agentgroup.go
deleted file mode 100644
index f1b479c..0000000
--- a/handler/agentgroup.go
+++ /dev/null
@@ -1,153 +0,0 @@
-package handler
-
-import (
- "encoding/base64"
- "packagelock/db"
- "packagelock/logger"
- "packagelock/structs"
-
- "github.com/gofiber/fiber/v2"
- "github.com/surrealdb/surrealdb.go"
-)
-
-// GetAgentByID filters a slice of Agents for a matching Agent.Agent_ID.
-// It returns a JSON response with fiber.StatusOK or fiber.StatusNotFound.
-func GetAgentByID(c *fiber.Ctx) error {
- // ID is an URL slice. Its a URL-Save base64 encoded UUID
- urlIDBytes, err := base64.RawURLEncoding.DecodeString(c.Query("AgentID"))
- if err != nil {
- logger.Logger.Warnf("Can't parse AgentID from URL, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to parse AgentID.",
- })
- }
-
- urlIDString := string(urlIDBytes)
-
- agents, err := db.DB.Select("agents")
- if err != nil {
- logger.Logger.Warnf("Failed to fetch 'agents' from db, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to fetch Agents.",
- })
- }
-
- var agentsSlice []structs.Agent
- err = surrealdb.Unmarshal(agents, &agentsSlice)
- if err != nil {
- logger.Logger.Warnf("Failed to unmarshal agents, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed Unmarshal.",
- })
- }
-
- var requestedAgentByID structs.Agent
- for _, agent := range agentsSlice {
- if agent.AgentID.String() == urlIDString {
- requestedAgentByID = agent
- return c.Status(fiber.StatusOK).JSON(requestedAgentByID)
- }
- }
-
- logger.Logger.Warnf("Got Request for agent with id: %s, which dosn't exist!", urlIDString)
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
- "error": "Agent not found",
- })
-}
-
-// RegisterAgent handles POST requests to register a new agent.
-func RegisterAgent(c *fiber.Ctx) error {
- var newAgent structs.Agent
-
- // Parse the JSON request body into newAgent
- if err := c.BodyParser(&newAgent); err != nil {
- logger.Logger.Warnf("Cannot parse JSON into new Agent! Got: %s", err)
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
- "error": "Cannot parse JSON",
- })
- }
-
- newAgentInsertionData, err := db.DB.Create("agents", newAgent)
- if err != nil {
- logger.Logger.Warnf("Can't insert new Agent into DB, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(nil)
- }
- // Respond with the newly created agent
- logger.Logger.Infof("Successfully Created new Agent with ID: %s", newAgent.AgentID)
- return c.Status(fiber.StatusCreated).JSON(newAgentInsertionData)
-}
-
-// GetHostByAgentID finds the host for a given agent ID.
-func GetHostByAgentID(c *fiber.Ctx) error {
- urlIDBytes, err := base64.RawURLEncoding.DecodeString(c.Query("AgentID"))
- if err != nil {
- logger.Logger.Warnf("Can't parse AgentID from URL, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to parse AgentID.",
- })
- }
-
- urlIDString := string(urlIDBytes)
-
- agents, err := db.DB.Select("agents")
- if err != nil {
- logger.Logger.Warnf("Failed to fetch 'agents' from db, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to fetch Agents.",
- })
- }
-
- var agentsSlice []structs.Agent
- err = surrealdb.Unmarshal(agents, &agentsSlice)
- if err != nil {
- logger.Logger.Warnf("Failed to unmarshal agents, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed Unmarshal.",
- })
- }
-
- var requestedAgentByID structs.Agent
- for _, agent := range agentsSlice {
- if agent.AgentID.String() == urlIDString {
- requestedAgentByID = agent
- }
- }
-
- // if no matching agent is found, the ID is empty
- if requestedAgentByID.ID == "" {
- logger.Logger.Warnf("Got Request for agent with id: %s, which dosn't exist!", urlIDString)
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
- "error": "Agent not found",
- })
- }
-
- hosts, err := db.DB.Select("hosts")
- if err != nil {
- logger.Logger.Warnf("Failed to fetch 'hosts' from db, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to fetch Hosts.",
- })
- }
-
- var hostsSlice []structs.Host
- err = surrealdb.Unmarshal(hosts, &hostsSlice)
- if err != nil {
- logger.Logger.Warnf("Failed to unmarshal hosts, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed Unmarshal.",
- })
- }
-
- var requestedHostByAgentID structs.Host
- for _, host := range hostsSlice {
- if host.HostID == requestedAgentByID.HostID {
- requestedHostByAgentID = host
- return c.Status(fiber.StatusOK).JSON(requestedHostByAgentID)
- }
- }
-
- logger.Logger.Warnf("Agent Found, but no Host is associated... Thats Weird. AgentID: %s", requestedAgentByID.AgentID)
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
- "error": "Agent Found, but no Host is associated... Thats Weird.",
- })
-}
diff --git a/handler/generalgroup.go b/handler/generalgroup.go
deleted file mode 100644
index 68727d7..0000000
--- a/handler/generalgroup.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package handler
-
-import (
- "packagelock/db"
- "packagelock/logger"
- "packagelock/structs"
-
- "github.com/gofiber/fiber/v2"
- "github.com/surrealdb/surrealdb.go"
-)
-
-// GetHosts responds with a list of all hosts.
-func GetHosts(c *fiber.Ctx) error {
- hosts, err := db.DB.Select("hosts")
- if err != nil {
- logger.Logger.Warnf("Failed to fetch 'hosts' from db, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to fetch hosts.",
- })
- }
-
- var hostsSlice []structs.Host
- err = surrealdb.Unmarshal(hosts, &hostsSlice)
- if err != nil {
- logger.Logger.Warnf("Failed to unmarshal hosts, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed Unmarshal.",
- })
- }
-
- return c.Status(fiber.StatusOK).JSON(hostsSlice)
-}
-
-// GetAgents responds with a list of all agents.
-func GetAgents(c *fiber.Ctx) error {
- agents, err := db.DB.Select("agents")
- if err != nil {
- logger.Logger.Warnf("Failed to fetch 'agents' from db, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to fetch agents.",
- })
- }
-
- var agentsSlice []structs.Host
- err = surrealdb.Unmarshal(agents, &agentsSlice)
- if err != nil {
- logger.Logger.Warnf("Failed to fetch 'agents' from db, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to fetch agents.",
- })
- }
-
- return c.Status(fiber.StatusOK).JSON(agentsSlice)
-}
diff --git a/handler/handler.go b/handler/handler.go
new file mode 100644
index 0000000..d2e38ca
--- /dev/null
+++ b/handler/handler.go
@@ -0,0 +1,380 @@
+package handler
+
+import (
+ "encoding/base64"
+ "os"
+ "packagelock/db"
+ "packagelock/structs"
+ "time"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/spf13/viper"
+ "github.com/surrealdb/surrealdb.go"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+)
+
+type Handlers struct {
+ // UserGroup handlers
+ LoginHandler fiber.Handler
+
+ // AgentGroup handlers
+ GetAgentByID fiber.Handler
+ RegisterAgent fiber.Handler
+ GetHostByAgentID fiber.Handler
+
+ // GeneralGroup handlers
+ GetHosts fiber.Handler
+ GetAgents fiber.Handler
+
+ // HostGroup handlers
+ RegisterHost fiber.Handler
+}
+
+type HandlerParams struct {
+ fx.In
+
+ Logger *zap.Logger
+ Config *viper.Viper
+ DB *db.Database
+}
+
+// NewHandlers constructs all handler functions with injected dependencies.
+func NewHandlers(params HandlerParams) *Handlers {
+ return &Handlers{
+ LoginHandler: NewLoginHandler(params),
+ GetAgentByID: NewGetAgentByIDHandler(params),
+ RegisterAgent: NewRegisterAgentHandler(params),
+ GetHostByAgentID: NewGetHostByAgentIDHandler(params),
+ GetHosts: NewGetHostsHandler(params),
+ GetAgents: NewGetAgentsHandler(params),
+ RegisterHost: NewRegisterHostHandler(params),
+ }
+}
+
+func NewLoginHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ type LoginRequest struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ }
+
+ var loginReq LoginRequest
+ if err := c.BodyParser(&loginReq); err != nil {
+ params.Logger.Debug("Invalid login request", zap.Error(err))
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": "Failed to parse request",
+ })
+ }
+
+ data, err := params.DB.DB.Select("user")
+ if err != nil {
+ params.Logger.Warn("Error selecting 'user'", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(nil)
+ }
+
+ var userTable []structs.User
+ err = surrealdb.Unmarshal(data, &userTable)
+ if err != nil {
+ params.Logger.Warn("Error unmarshalling users", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(nil)
+ }
+
+ var authenticatedUser *structs.User
+ for _, possibleUser := range userTable {
+ // TODO: Implement password hashing
+ if possibleUser.Username == loginReq.Username && possibleUser.Password == loginReq.Password {
+ authenticatedUser = &possibleUser
+ break
+ }
+ }
+
+ if authenticatedUser == nil {
+ // User not found or password incorrect
+ return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
+ "error": "Invalid username or password",
+ })
+ }
+
+ // Create JWT
+ token := jwt.New(jwt.SigningMethodRS256)
+ claims := token.Claims.(jwt.MapClaims)
+ claims["username"] = authenticatedUser.Username
+ claims["userID"] = authenticatedUser.UserID
+ claims["exp"] = time.Now().Add(72 * time.Hour).Unix() // 3 days expiry
+
+ // Sign and get the encoded token
+ keyData, err := os.ReadFile(params.Config.GetString("network.ssl-config.privatekeypath"))
+ if err != nil {
+ params.Logger.Warn("Cannot read private key file", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to generate token",
+ })
+ }
+
+ privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
+ if err != nil {
+ params.Logger.Warn("Cannot parse private key", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to generate token",
+ })
+ }
+
+ tokenString, err := token.SignedString(privateKey)
+ if err != nil {
+ params.Logger.Warn("Cannot generate JWT", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to generate token",
+ })
+ }
+
+ // Create and append new JWT object
+ newJWT := structs.ApiKey{
+ KeyValue: tokenString,
+ Description: "User Generated JWT",
+ AccessSeperation: false,
+ AccessRights: []string{},
+ CreationTime: time.Now(),
+ UpdateTime: time.Now(),
+ }
+
+ // Update user with new API key
+ authenticatedUser.ApiKeys = append(authenticatedUser.ApiKeys, newJWT)
+ authenticatedUser.UpdateTime = time.Now()
+
+ _, err = params.DB.DB.Update(authenticatedUser.ID, authenticatedUser)
+ if err != nil {
+ params.Logger.Warn("Cannot update user in DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to generate token",
+ })
+ }
+
+ params.Logger.Info("User authenticated", zap.String("username", authenticatedUser.Username))
+ return c.JSON(newJWT)
+ }
+}
+
+func NewGetAgentByIDHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ urlIDBytes, err := base64.RawURLEncoding.DecodeString(c.Query("AgentID"))
+ if err != nil {
+ params.Logger.Warn("Cannot parse AgentID from URL", zap.Error(err))
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": "Failed to parse AgentID",
+ })
+ }
+
+ urlIDString := string(urlIDBytes)
+
+ agents, err := params.DB.DB.Select("agents")
+ if err != nil {
+ params.Logger.Warn("Failed to fetch 'agents' from DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to fetch agents",
+ })
+ }
+
+ var agentsSlice []structs.Agent
+ err = surrealdb.Unmarshal(agents, &agentsSlice)
+ if err != nil {
+ params.Logger.Warn("Failed to unmarshal agents", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to unmarshal agents",
+ })
+ }
+
+ var requestedAgent *structs.Agent
+ for _, agent := range agentsSlice {
+ if agent.AgentID.String() == urlIDString {
+ requestedAgent = &agent
+ break
+ }
+ }
+
+ if requestedAgent == nil {
+ params.Logger.Warn("Agent not found", zap.String("AgentID", urlIDString))
+ return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
+ "error": "Agent not found",
+ })
+ }
+
+ return c.Status(fiber.StatusOK).JSON(requestedAgent)
+ }
+}
+
+func NewRegisterAgentHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ var newAgent structs.Agent
+
+ if err := c.BodyParser(&newAgent); err != nil {
+ params.Logger.Warn("Cannot parse JSON into new Agent", zap.Error(err))
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": "Cannot parse JSON",
+ })
+ }
+
+ newAgentInsertionData, err := params.DB.DB.Create("agents", newAgent)
+ if err != nil {
+ params.Logger.Warn("Cannot insert new Agent into DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(nil)
+ }
+
+ params.Logger.Info("Created new Agent", zap.String("AgentID", newAgent.AgentID.String()))
+ return c.Status(fiber.StatusCreated).JSON(newAgentInsertionData)
+ }
+}
+
+func NewGetHostByAgentIDHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ urlIDBytes, err := base64.RawURLEncoding.DecodeString(c.Query("AgentID"))
+ if err != nil {
+ params.Logger.Warn("Cannot parse AgentID from URL", zap.Error(err))
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": "Failed to parse AgentID",
+ })
+ }
+
+ urlIDString := string(urlIDBytes)
+
+ agents, err := params.DB.DB.Select("agents")
+ if err != nil {
+ params.Logger.Warn("Failed to fetch 'agents' from DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to fetch agents",
+ })
+ }
+
+ var agentsSlice []structs.Agent
+ err = surrealdb.Unmarshal(agents, &agentsSlice)
+ if err != nil {
+ params.Logger.Warn("Failed to unmarshal agents", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to unmarshal agents",
+ })
+ }
+
+ var requestedAgent *structs.Agent
+ for _, agent := range agentsSlice {
+ if agent.AgentID.String() == urlIDString {
+ requestedAgent = &agent
+ break
+ }
+ }
+
+ if requestedAgent == nil {
+ params.Logger.Warn("Agent not found", zap.String("AgentID", urlIDString))
+ return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
+ "error": "Agent not found",
+ })
+ }
+
+ hosts, err := params.DB.DB.Select("hosts")
+ if err != nil {
+ params.Logger.Warn("Failed to fetch 'hosts' from DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to fetch hosts",
+ })
+ }
+
+ var hostsSlice []structs.Host
+ err = surrealdb.Unmarshal(hosts, &hostsSlice)
+ if err != nil {
+ params.Logger.Warn("Failed to unmarshal hosts", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to unmarshal hosts",
+ })
+ }
+
+ var requestedHost *structs.Host
+ for _, host := range hostsSlice {
+ if host.HostID == requestedAgent.HostID {
+ requestedHost = &host
+ break
+ }
+ }
+
+ if requestedHost == nil {
+ params.Logger.Warn("No host associated with agent", zap.String("AgentID", requestedAgent.AgentID.String()))
+ return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
+ "error": "Host not found for the agent",
+ })
+ }
+
+ return c.Status(fiber.StatusOK).JSON(requestedHost)
+ }
+}
+
+func NewGetHostsHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ hosts, err := params.DB.DB.Select("hosts")
+ if err != nil {
+ params.Logger.Warn("Failed to fetch 'hosts' from DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to fetch hosts",
+ })
+ }
+
+ var hostsSlice []structs.Host
+ err = surrealdb.Unmarshal(hosts, &hostsSlice)
+ if err != nil {
+ params.Logger.Warn("Failed to unmarshal hosts", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to unmarshal hosts",
+ })
+ }
+
+ return c.Status(fiber.StatusOK).JSON(hostsSlice)
+ }
+}
+
+func NewGetAgentsHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ agents, err := params.DB.DB.Select("agents")
+ if err != nil {
+ params.Logger.Warn("Failed to fetch 'agents' from DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to fetch agents",
+ })
+ }
+
+ var agentsSlice []structs.Agent
+ err = surrealdb.Unmarshal(agents, &agentsSlice)
+ if err != nil {
+ params.Logger.Warn("Failed to unmarshal agents", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to unmarshal agents",
+ })
+ }
+
+ return c.Status(fiber.StatusOK).JSON(agentsSlice)
+ }
+}
+
+func NewRegisterHostHandler(params HandlerParams) fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ var newHost structs.Host
+
+ if err := c.BodyParser(&newHost); err != nil {
+ params.Logger.Warn("Cannot parse JSON into new Host", zap.Error(err))
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
+ "error": "Cannot parse JSON",
+ })
+ }
+
+ transaction, err := params.DB.DB.Create("hosts", newHost)
+ if err != nil {
+ params.Logger.Warn("Cannot insert new Host into DB", zap.Error(err))
+ return c.Status(fiber.StatusInternalServerError).JSON(nil)
+ }
+
+ params.Logger.Info("Created new Host", zap.String("HostID", newHost.HostID.String()))
+ return c.Status(fiber.StatusCreated).JSON(transaction)
+ }
+}
+
+// Module exports the handlers as an Fx module.
+var Module = fx.Options(
+ fx.Provide(NewHandlers),
+)
diff --git a/handler/hostgroup.go b/handler/hostgroup.go
deleted file mode 100644
index ba00f76..0000000
--- a/handler/hostgroup.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package handler
-
-import (
- "packagelock/db"
- "packagelock/logger"
- "packagelock/structs"
-
- "github.com/gofiber/fiber/v2"
-)
-
-// RegisterHost handles the registration of a new host.
-func RegisterHost(c *fiber.Ctx) error {
- var newHost structs.Host
-
- // Parse the JSON request body into newHost
- if err := c.BodyParser(&newHost); err != nil {
- logger.Logger.Warnf("Cannot parse JSON into new Host! Got: %s", err)
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
- "error": "Cannot parse JSON",
- })
- }
-
- transaction, err := db.DB.Create("hosts", newHost)
- if err != nil {
- logger.Logger.Warnf("Can't insert new Host into DB, got: %s", err)
- return c.Status(fiber.StatusInternalServerError).JSON(nil)
- }
-
- logger.Logger.Infof("Successfully Created new Host with ID: %s", newHost.HostID)
- return c.Status(fiber.StatusCreated).JSON(transaction)
-}
diff --git a/logger/logger.go b/logger/logger.go
index eea6429..b299a83 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -4,46 +4,69 @@ import (
"os"
"github.com/k0kubun/pp"
+ "go.uber.org/fx"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
)
-var Logger *zap.SugaredLogger
+// Module exports the logger module.
+var Module = fx.Provide(NewLogger)
-// InitLogger initializes the zap.Logger once and returns the instance.
-func InitLogger() (*zap.SugaredLogger, error) {
- err := os.MkdirAll("logs/", os.ModePerm)
- if err != nil {
- pp.Printf("Couldn't create 'logs' directory. Got: %s", err)
- panic(err)
+// NewLogger constructs a new logger instance.
+func NewLogger() (*zap.Logger, error) {
+ createLogDir()
+
+ // Initialize Lumberjack logger for log rotation
+ lumberjackLogger := &lumberjack.Logger{
+ Filename: "logs/app.log", // Log file name
+ MaxSize: 250, // Max size in MB before rotation
+ MaxBackups: 3, // Max number of old log files to retain
+ MaxAge: 28, // Max number of days to retain old log files
+ Compress: true, // Compress the rotated log files
}
- // Ensure the logger is initialized only once
- loggerConfig := zap.Config{
- Encoding: "console", // You can also use "json"
- OutputPaths: []string{"logs/app.log"},
- ErrorOutputPaths: []string{"logs/app_error.log"},
- Level: zap.NewAtomicLevelAt(zapcore.InfoLevel),
- EncoderConfig: zapcore.EncoderConfig{
- TimeKey: "timestamp",
- LevelKey: "level",
- NameKey: "logger",
- CallerKey: "caller",
- MessageKey: "msg",
- StacktraceKey: "stacktrace",
- LineEnding: zapcore.DefaultLineEnding,
- EncodeLevel: zapcore.CapitalLevelEncoder,
- EncodeTime: zapcore.ISO8601TimeEncoder,
- EncodeDuration: zapcore.StringDurationEncoder,
- EncodeCaller: zapcore.ShortCallerEncoder,
- },
+ // Encoder configuration
+ encoderConfig := zapcore.EncoderConfig{
+ TimeKey: "timestamp",
+ LevelKey: "level",
+ NameKey: "logger",
+ CallerKey: "caller",
+ MessageKey: "msg",
+ StacktraceKey: "stacktrace",
+ LineEnding: zapcore.DefaultLineEnding,
+ EncodeLevel: zapcore.CapitalLevelEncoder,
+ EncodeTime: zapcore.ISO8601TimeEncoder,
+ EncodeDuration: zapcore.StringDurationEncoder,
+ EncodeCaller: zapcore.ShortCallerEncoder,
}
- // Build the logger
- logger, err := loggerConfig.Build()
+
+ encoder := zapcore.NewConsoleEncoder(encoderConfig) // Use NewJSONEncoder for JSON logs
+
+ // Create WriteSyncer for Lumberjack logger
+ fileWriter := zapcore.AddSync(lumberjackLogger)
+ consoleWriter := zapcore.AddSync(os.Stdout)
+ writeSyncer := zapcore.NewMultiWriteSyncer(fileWriter, consoleWriter)
+
+ logLevel := zapcore.InfoLevel
+
+ core := zapcore.NewCore(encoder, writeSyncer, logLevel)
+ logger := zap.New(core,
+ zap.AddCaller(), // Include caller information
+ zap.AddStacktrace(zapcore.ErrorLevel), // Include stacktrace for error-level logs
+ )
+
+ logger.Info("--------------------------------------------")
+ logger.Info("Logger initialized successfully")
+
+ return logger, nil
+}
+
+// Creates to logs/ directory
+func createLogDir() {
+ err := os.MkdirAll("logs/", os.ModePerm)
if err != nil {
- // Handle logger initialization error (no panic)
- logger = nil
+ pp.Printf("Couldn't create 'logs' directory. Got: %s", err)
+ panic(err)
}
- // Return the initialized logger and any error that occurred
- return logger.Sugar(), err
}
diff --git a/main.go b/main.go
index 6a9eb1c..f939732 100644
--- a/main.go
+++ b/main.go
@@ -4,440 +4,55 @@ import (
"context"
"fmt"
"os"
- "os/signal"
"packagelock/certs"
+ "packagelock/cmd"
"packagelock/config"
"packagelock/db"
+ "packagelock/handler"
"packagelock/logger"
"packagelock/server"
- "packagelock/structs"
- "path/filepath"
- "strconv"
- "syscall"
- "text/template"
- "time"
+ "packagelock/tracing"
- "github.com/fsnotify/fsnotify"
- "github.com/google/uuid"
- "github.com/k0kubun/pp/v3"
- "github.com/sethvargo/go-password/password"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
- "github.com/surrealdb/surrealdb.go"
+ "go.uber.org/fx"
+ "go.uber.org/fx/fxevent"
+ "go.uber.org/zap"
)
-var (
- restartChan = make(chan struct{})
- quitChan = make(chan os.Signal, 1)
- AppVersion string // Version injected with ldflags
-)
-
-// Root command using Cobra
-var rootCmd = &cobra.Command{
- Use: "packagelock",
- Short: "Packagelock CLI tool",
- Long: `Packagelock CLI manages the server and other operations.`,
-}
-
-// Start command to run the server
-var startCmd = &cobra.Command{
- Use: "start",
- Short: "Start the server",
- Run: func(cmd *cobra.Command, args []string) {
- initServer()
- },
-}
-
-var restartCmd = &cobra.Command{
- Use: "restart",
- Short: "Restart the running server",
- Run: func(cmd *cobra.Command, args []string) {
- restartServer()
- },
-}
-
-var stopCmd = &cobra.Command{
- Use: "stop",
- Short: "Stop the running server",
- Run: func(cmd *cobra.Command, args []string) {
- stopServer()
- },
-}
-
-var setupCmd = &cobra.Command{
- Use: "setup",
- Short: "Setup PackageLock",
- Run: func(cmd *cobra.Command, args []string) {
- setup()
- },
-}
-
-// Generate command
-var generateCmd = &cobra.Command{
- Use: "generate [certs|config|admin-user]",
- Short: "Generate certs or config files or an admin-user",
- Long: "Generate certificates, configuration files or an admin-user required by the application.",
- Args: cobra.MatchAll(cobra.ExactArgs(1), validGenerateArgs()),
- ValidArgs: []string{"certs", "config", "admin"},
- Run: func(cmd *cobra.Command, args []string) {
- switch args[0] {
- case "certs":
- err := certs.CreateSelfSignedCert(
- config.Config.GetString("network.ssl-config.certificatepath"),
- config.Config.GetString("network.ssl-config.privatekeypath"))
- if err != nil {
- fmt.Println("There was an error generating the self signed certs: %w", err)
- logger.Logger.Warnf("There was an error generating the self signed certs: %s", err)
- }
- case "config":
- config.CreateDefaultConfig(config.Config)
- case "admin":
- err := generateAdmin()
- if err != nil {
- logger.Logger.Panicf("Failed to generate default admin, got: %s", err)
- }
- default:
- fmt.Println("Invalid argument. Use 'certs' or 'config' or 'admin'.")
- }
- },
-}
-
-func validGenerateArgs() cobra.PositionalArgs {
- return func(cmd *cobra.Command, args []string) error {
- validArgs := []string{"certs", "config", "admin"}
- for _, valid := range validArgs {
- if args[0] == valid {
- return nil
- }
- }
- return fmt.Errorf("invalid argument: '%s'. Must be one of 'certs' or 'config' or 'user'", args[0])
- }
-}
-
-func setup() {
- pp.Println("Starting The PackageLock Setup!")
-
- err := os.MkdirAll("logs/", os.ModePerm)
- if err != nil {
- pp.Printf("Couldn't create 'logs' directory. Got: %s", err)
- panic(err)
- }
- pp.Println("Generated Logs directory")
-
- err = os.MkdirAll("certs/", os.ModePerm)
- if err != nil {
- pp.Printf("Couldn't create 'logs' directory. Got: %s", err)
- panic(err)
- }
- pp.Println("Generated certs directory")
-
- generateUnitFile()
- pp.Println("Generated Unit File")
-
- pp.Println("Setup finished successfully!")
-}
-
-func generateUnitFile() {
- // SystemdTemplate defines the systemd unit file structure
- const SystemdTemplate = `[Unit]
-Description=PackageLock Management Server
-After=network.target
-
-[Service]
-ExecStart={{.ExecStart}} start
-Restart=always
-User={{.User}}
-Group={{.Group}}
-
-[Install]
-WantedBy=multi-user.target
-`
-
- // UnitFileData holds the dynamic data for the systemd unit file
- type UnitFileData struct {
- ExecStart string
- User string
- Group string
- }
-
- // Get the current executable path
- execPath, err := os.Executable()
- if err != nil {
- logger.Logger.Panicf("failed to get executable path: %w", err)
- }
- // Convert to an absolute path
- execPath, err = filepath.Abs(execPath)
- if err != nil {
- logger.Logger.Panicf("failed to get absolute executable path: %w", err)
- }
-
- // Define the data to be injected into the unit file
- data := UnitFileData{
- ExecStart: execPath, // The path of the Go binary
- User: "your-user", // Replace with your actual user
- Group: "your-group", // Replace with your actual group
- }
-
- // Open the systemd unit file for writing (requires sudo permission)
- filePath := "/etc/systemd/system/packagelock.service"
- file, err := os.Create(filePath)
- if err != nil {
- pp.Println("Seems like you cant generate the Unit File...")
- pp.Println("Did you ran this with 'sudo'?π")
- logger.Logger.Panicf("failed to create systemd unit file: %w", err)
- }
- defer file.Close()
-
- // Parse and execute the systemd template
- tmpl, err := template.New("systemd").Parse(SystemdTemplate)
- if err != nil {
- logger.Logger.Panicf("failed to parse systemd template: %w", err)
- }
-
- err = tmpl.Execute(file, data)
- if err != nil {
- logger.Logger.Panicf("failed to execute template: %w", err)
- }
-
- pp.Printf("Systemd unit file created at %s\n", filePath)
-}
-
-// INFO: init is ran everytime a cobra comand gets used.
-// It does not init the Server!
-// It only inits cobra!
-func init() {
- // Add commands to rootCmd
- rootCmd.AddCommand(startCmd)
- rootCmd.AddCommand(generateCmd)
- rootCmd.AddCommand(restartCmd)
- rootCmd.AddCommand(stopCmd)
- rootCmd.AddCommand(setupCmd)
-
- // Declare the Logger into global logger.Logger
- // Init here so commands can be logget to!
- var loggerError error
- logger.Logger, loggerError = logger.InitLogger()
- if loggerError != nil {
- // INFO: Essential APP-Part, so crash out asap
- panic(loggerError)
- }
-}
-
-// generate admin for login and Setup
-func generateAdmin() error {
- adminPw, err := password.Generate(64, 10, 10, false, false)
- if err != nil {
- logger.Logger.Panicf("Got error while generating ADmin Password: %s", err)
- }
-
- // Admin Data
- TemporalAdmin := structs.User{
- UserID: uuid.New(),
- Username: "admin",
- Password: adminPw,
- Groups: []string{"Admin", "StorageAdmin", "Audit"},
- CreationTime: time.Now(),
- UpdateTime: time.Now(),
- ApiKeys: nil,
- }
+var AppVersion string // Version injected with ldflags
- // Insert Admin
- adminInsertionData, err := db.DB.Create("user", TemporalAdmin)
- if err != nil {
- logger.Logger.Panicf("Got error while inserting Default Admin into DB: %s", err)
- }
-
- // Unmarshal data
- var createdUser structs.User
- err = surrealdb.Unmarshal(adminInsertionData, &createdUser)
- if err != nil {
- logger.Logger.Panicf("Got error while querring Default Admin: %s", err)
- }
-
- pp.Println(createdUser.Username)
- pp.Println(createdUser.Password)
- return nil
-}
-
-// initConfig initializes Viper and configures the application
-func initConfig() {
- config.Config = config.StartViper(viper.New())
-
- // If AppVersion is injected, set it in the configuration
- if AppVersion != "" {
- config.Config.SetDefault("general.app-version", AppVersion)
- }
-
- // Check and create self-signed certificates if missing
- if _, err := os.Stat(config.Config.GetString("network.ssl-config.certificatepath")); os.IsNotExist(err) {
- fmt.Println("Certificate files missing, creating new self-signed.")
- err := certs.CreateSelfSignedCert(
- config.Config.GetString("network.ssl-config.certificatepath"),
- config.Config.GetString("network.ssl-config.privatekeypath"))
- if err != nil {
- logger.Logger.Panicf("Error creating self-signed certificate: %v\n", err)
- }
- }
-}
-
-// Initializes everything that is needed for the Server
-// to run
-func initServer() {
- initConfig()
- err := db.InitDB()
- if err != nil {
- logger.Logger.Panicf("Got error from db.InitDB: %s", err)
- }
-
- // after init run Server
- startServer()
-}
-
-// startServer starts the Fiber server with appropriate configuration
-func startServer() {
- pid := os.Getpid()
- err := os.WriteFile("packagelock.pid", []byte(strconv.Itoa(pid)), 0644)
- if err != nil {
- logger.Logger.Panicf("Failed to write PID file: %v\n", err)
- return
- }
-
- if config.Config.GetString("general.production") == "false" {
- logger.Logger.Debug(config.Config.AllSettings())
- }
-
- signal.Notify(quitChan, os.Interrupt, syscall.SIGTERM)
-
- // Start the server in a goroutine
- go func() {
- for {
- Router := server.AddRoutes(config.Config)
-
- // Setup server address from config
- serverAddr := config.Config.GetString("network.fqdn") + ":" + config.Config.GetString("network.port")
-
- // Start server based on SSL config
- go func() {
- if config.Config.GetBool("network.ssl") {
-
- logger.Logger.Infof("Starting Fiber HTTPS server at https://%s...\n", serverAddr)
-
- err := server.ListenAndServeTLS(
- Router.Router,
- config.Config.GetString("network.ssl-config.certificatepath"),
- config.Config.GetString("network.ssl-config.privatekeypath"),
- serverAddr)
- if err != nil {
- logger.Logger.Panicf("Server error: %s\n", err)
- }
- } else {
- logger.Logger.Infof("Starting Fiber server at %s...\n", serverAddr)
-
- if err := Router.Router.Listen(serverAddr); err != nil {
- logger.Logger.Panicf("Server error: %s\n", err)
- }
- }
- }()
-
- // Handle restart or quit signals
- select {
- case <-restartChan:
-
- fmt.Println("Restarting Fiber server...")
- logger.Logger.Info("Restarting Fiber server...")
-
- _, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- if err := Router.Router.Shutdown(); err != nil {
- logger.Logger.Warnf("Server shutdown failed: %v\n", err)
- } else {
- // TODO: add Reason for restart/Stoping
- fmt.Println("Server stopped.")
- logger.Logger.Info("Server stopped.")
- }
-
- startServer()
-
- case <-quitChan:
-
- // TODO: add Reason fro Stopping
- fmt.Println("Shutting down Fiber server...")
- logger.Logger.Info("Shutting down Fiber server...")
- _, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- if err := Router.Router.Shutdown(); err != nil {
- logger.Logger.Warnf("Server shutdown failed: %v\n", err)
- } else {
- fmt.Println("Server stopped gracefully.")
- logger.Logger.Info("Server stopped gracefully.")
- }
- return
- }
- }
- }()
-
- // Watch for config changes
- config.Config.OnConfigChange(func(e fsnotify.Event) {
- logger.Logger.Infof("Config file changed:", e.Name)
- logger.Logger.Info("Restarting to apply changes...")
- fmt.Println("Restarting to apply changes...")
- restartChan <- struct{}{}
- })
- config.Config.WatchConfig()
-
- // Block until quit signal is received
- <-quitChan
- logger.Logger.Info("Main process exiting.")
- fmt.Println("Main process exiting.")
-}
-
-func restartServer() {
- stopServer()
- fmt.Println("Restarting the Server...")
- logger.Logger.Info("Restarting the Server...")
- time.Sleep(5 * time.Second)
- startServer()
-}
-
-func stopServer() {
- // Read the PID from the file using os.ReadFile
- data, err := os.ReadFile("packagelock.pid")
- if err != nil {
- logger.Logger.Panicf("Could not read PID file: %v\n", err)
- }
-
- pid, err := strconv.Atoi(string(data))
- if err != nil {
- logger.Logger.Panicf("Invalid PID found in file: %v\n", err)
- }
-
- // Send SIGTERM to the process
- fmt.Printf("Stopping the server with PID: %d\n", pid)
- logger.Logger.Infof("Stopping the server with PID: %d\n", pid)
- err = syscall.Kill(pid, syscall.SIGTERM)
- if err != nil {
- logger.Logger.Warn("Failed to stop the server: %v\n", err)
- return
+func main() {
+ app := fx.New(
+ fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
+ return &fxevent.ZapLogger{Logger: log}
+ }),
+
+ // fx.NopLogger,
+
+ fx.Provide(
+ func() string {
+ return AppVersion
+ },
+ logger.NewLogger,
+ config.NewConfig,
+ ),
+ certs.Module,
+ db.Module, // Include the database module
+ handler.Module, // Include the handlers module
+ server.Module, // Include the server module
+ cmd.Module, // Include the commands module
+ tracing.Module, // Include the tracing module
+ )
+
+ if err := app.Start(context.Background()); err != nil {
+ fmt.Println("Failed to start application:", err)
+ os.Exit(1)
}
- fmt.Println("Server stopped.")
- logger.Logger.Info("Server stopped.")
- // After successful stop, remove the PID file
- err = os.Remove("packagelock.pid")
- if err != nil {
- logger.Logger.Warnf("Failed to remove PID file: %v\n", err)
- } else {
- fmt.Println("PID file removed successfully.")
- logger.Logger.Info("PID file removed successfully.")
- }
-}
+ // Wait for the application to be signaled to exit
+ <-app.Done()
-func main() {
- // Execute the Cobra root command
- if err := rootCmd.Execute(); err != nil {
- fmt.Println(err)
+ if err := app.Stop(context.Background()); err != nil {
+ fmt.Println("Failed to stop application:", err)
os.Exit(1)
}
}
diff --git a/server/router.go b/server/router.go
index 3752156..9e3ea81 100644
--- a/server/router.go
+++ b/server/router.go
@@ -1,87 +1,138 @@
package server
import (
- "log"
+ "context"
"os"
- "packagelock/config"
"packagelock/handler"
- "packagelock/logger"
+ "strconv"
+ "github.com/ansrivas/fiberprometheus/v2"
+ "github.com/gofiber/contrib/fiberzap"
+ jwtware "github.com/gofiber/contrib/jwt"
+ "github.com/gofiber/contrib/otelfiber"
"github.com/gofiber/fiber/v2"
+ "github.com/gofiber/fiber/v2/middleware/healthcheck"
+ "github.com/gofiber/fiber/v2/middleware/monitor"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/template/html/v2"
-
- jwtware "github.com/gofiber/contrib/jwt"
"github.com/golang-jwt/jwt/v5"
+ "github.com/spf13/viper"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
)
-// Routes holds the Fiber app instance.
-type Routes struct {
- Router *fiber.App
-}
-
-// addAgentHandler sets up agent-related routes in Fiber.
-func (r Routes) addAgentHandler(group fiber.Router) {
- AgentGroup := group.Group("/agents")
+type ServerParams struct {
+ fx.In
- AgentGroup.Get("/", handler.GetAgentByID)
- AgentGroup.Post("/register", handler.RegisterAgent)
- logger.Logger.Debug("Added Agent Handlers.")
+ Lifecycle fx.Lifecycle
+ Logger *zap.Logger
+ Config *viper.Viper
+ Handlers *handler.Handlers // The injected Handlers struct
+ Tracer trace.Tracer // Injected Tracer
}
-// addGeneralHandler sets up general-related routes in Fiber.
-func (r Routes) addGeneralHandler(group fiber.Router) {
- GeneralGroup := group.Group("/general")
-
- GeneralGroup.Get("/hosts", handler.GetHosts)
- GeneralGroup.Get("/agents", handler.GetAgents)
- logger.Logger.Debug("Added General Handlers.")
-}
+func NewServer(params ServerParams) *fiber.App {
+ params.Logger.Info("Starting API-Server Initialization:")
+ // Initialize template engine
+ engine := html.New("./templates", ".html")
+ params.Logger.Info("Added template Engine.")
-// addHostHandler sets up host-related routes in Fiber.
-func (r Routes) addHostHandler(group fiber.Router) {
- HostGroup := group.Group("/hosts")
+ // Initialize Fiber app
+ app := fiber.New(fiber.Config{
+ Views: engine,
+ })
- HostGroup.Get("/", handler.GetHostByAgentID)
- HostGroup.Post("/register", handler.RegisterHost)
+ // This middleware eats too much to run always.
+ if os.Getenv("TRACING_ENABLED") == "true" {
+ // Middleware for tracing with OpenTelemetry using the injected Tracer
+ app.Use(otelfiber.Middleware(otelfiber.WithTracerProvider(otel.GetTracerProvider())))
+ params.Logger.Info("Added OpenTelemetry Middleware.")
+ }
- logger.Logger.Debug("Added Host Handlers.")
-}
+ // Middleware for simple resource Monitor
+ // only use in Non-Production mode
+ if !params.Config.GetBool("general.production") {
+ app.Get("/monitor", monitor.New(monitor.Config{Title: "PackageLock Dev Monitoring Page"}))
+ }
-func (r Routes) addLoginHandler(group fiber.Router) {
- LoginGroup := group.Group("/auth")
+ // Middleware for logging
+ app.Use(fiberzap.New(fiberzap.Config{
+ Logger: params.Logger,
+ }))
+ params.Logger.Info("Added Logging Middleware.")
- LoginGroup.Post("/login", handler.LoginHandler)
+ // Middleware to recover from panics
+ app.Use(recover.New())
+ params.Logger.Info("Added Recovery Middleware.")
+
+ // Middleware to export Prometheus Metrics at: 'addr:port/metrics'
+ if params.Config.GetString("general.monitoring") == "true" {
+ prometheus := fiberprometheus.New("PackageLock")
+ prometheus.RegisterAt(app, "/metrics")
+
+ // following path's will be ignored.
+ // As prometheus exports how often a path got called,
+ // we ignore everything authentication related (even misstypes)
+ // to cancel out possible sidechannel attack's
+ prometheus.SetSkipPaths([]string{"/auth/login", "/v1/auth/login", "/auth", "/login"})
+
+ app.Use(prometheus.Middleware)
+ params.Logger.Info("Added Monitoring Middleware.")
+ }
+
+ // Middleware for healthcheck
+ app.Use(healthcheck.New(healthcheck.Config{
+ LivenessProbe: func(c *fiber.Ctx) bool {
+ return true
+ },
+ LivenessEndpoint: "/livez",
+
+ ReadinessProbe: func(c *fiber.Ctx) bool {
+ return true
+ },
+ ReadinessEndpoint: "/readyz",
+ }))
+ params.Logger.Info("Added HealtCheck Middleware.")
+
+ // Add routes
+ addRoutes(app, params)
+ params.Logger.Info("Added routes.")
+
+ appVersion := params.Config.GetString("general.app-version")
- logger.Logger.Debug("Added Login Handlers.")
-}
+ // Add 404 handler
+ app.Use(func(c *fiber.Ctx) error {
+ return c.Status(fiber.StatusNotFound).Render("404", fiber.Map{
+ "AppVersion": appVersion,
+ })
+ })
+ params.Logger.Info("Added default 404 Handler.")
-// AddRoutes adds all handler groups to the current Fiber app.
-// It's exported and used in main() to return the configured Router.
-func AddRoutes(Config config.ConfigProvider) Routes {
- // Initialize template engine
- engine := html.New("./templates", ".html")
+ // Start the server using lifecycle hooks
+ params.Logger.Info("Finished API-Server Initialization.")
+ startServer(app, params)
- // Initialize Fiber app
- router := Routes{
- Router: fiber.New(fiber.Config{
- Views: engine,
- }),
- }
+ return app
+}
- router.addLoginHandler(router.Router)
+func addRoutes(app *fiber.App, params ServerParams) {
+ // Add login handler
+ addLoginHandler(app, params)
// Use JWT if in production
- if Config.Get("general.production") == true {
- logger.Logger.Info("Enabled Production! Adding JWT!")
+ if params.Config.GetBool("general.production") {
+ params.Logger.Info("Enabled Production! Adding JWT!")
+
// Read the private key for JWT
- keyData, err := os.ReadFile(Config.GetString("network.ssl.privatekeypath"))
+ keyData, err := os.ReadFile(params.Config.GetString("network.ssl-config.privatekeypath"))
if err != nil {
- log.Fatal(err)
+ params.Logger.Fatal("Failed to read private key for JWT", zap.Error(err))
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
if err != nil {
- logger.Logger.Panicf("Can't open Private Key File! Got: %s", err)
+ params.Logger.Fatal("Failed to parse private key for JWT", zap.Error(err))
}
// JWT Middleware to protect specific routes
@@ -90,36 +141,99 @@ func AddRoutes(Config config.ConfigProvider) Routes {
})
// Apply JWT protection to all routes in the "/v1" group
- v1 := router.Router.Group("/v1", jwtMiddleware)
+ v1 := app.Group("/v1", jwtMiddleware)
// Add route handlers to the protected group
- router.addGeneralHandler(v1)
- router.addAgentHandler(v1)
- router.addHostHandler(v1)
+ addGeneralHandler(v1, params)
+ addAgentHandler(v1, params)
+ addHostHandler(v1, params)
} else {
- logger.Logger.Info("Non-Production Setup! Disabled JWT!")
- // Create the versioned route group without JWT protection (for non-production environments)
- v1 := router.Router.Group("/v1")
+ params.Logger.Info("Non-Production Setup! Disabled JWT!")
+
+ // Create the versioned route group without JWT protection
+ v1 := app.Group("/v1")
// Add route handlers without JWT protection
- router.addGeneralHandler(v1)
- router.addAgentHandler(v1)
- router.addHostHandler(v1)
+ addGeneralHandler(v1, params)
+ addAgentHandler(v1, params)
+ addHostHandler(v1, params)
}
+}
- // Middleware to recover from panics
- router.Router.Use(recover.New())
-
- // Add 404 handler
- router.Router.Use(func(c *fiber.Ctx) error {
- return c.Status(fiber.StatusNotFound).Render("404", fiber.Map{})
+func startServer(app *fiber.App, params ServerParams) {
+ serverAddr := params.Config.GetString("network.fqdn") + ":" + params.Config.GetString("network.port")
+
+ params.Lifecycle.Append(fx.Hook{
+ OnStart: func(ctx context.Context) error {
+ // Write PID to file
+ pid := os.Getpid()
+ err := os.WriteFile("packagelock.pid", []byte(strconv.Itoa(pid)), 0644)
+ if err != nil {
+ params.Logger.Warn("Failed to write PID file", zap.Error(err))
+ } else {
+ params.Logger.Info("PID file written", zap.Int("PID", pid))
+ }
+
+ go func() {
+ if params.Config.GetBool("network.ssl") {
+ params.Logger.Info("Starting HTTPS server", zap.String("address", serverAddr))
+
+ certFile := params.Config.GetString("network.ssl-config.certificatepath")
+ keyFile := params.Config.GetString("network.ssl-config.privatekeypath")
+
+ if err := app.ListenTLS(serverAddr, certFile, keyFile); err != nil {
+ params.Logger.Fatal("Failed to start HTTPS server", zap.Error(err))
+ }
+ } else {
+ params.Logger.Info("Starting HTTP server", zap.String("address", serverAddr))
+
+ if err := app.Listen(serverAddr); err != nil {
+ params.Logger.Fatal("Failed to start HTTP server", zap.Error(err))
+ }
+ }
+ }()
+ return nil
+ },
+ OnStop: func(ctx context.Context) error {
+ params.Logger.Info("Shutting down server")
+ return app.Shutdown()
+ },
})
+}
- return router
+// Individual route handler functions
+func addAgentHandler(group fiber.Router, params ServerParams) {
+ agentGroup := group.Group("/agents")
+
+ agentGroup.Get("/", params.Handlers.GetAgentByID)
+ agentGroup.Post("/register", params.Handlers.RegisterAgent)
+ params.Logger.Debug("Added Agent Handlers.")
+}
+
+func addGeneralHandler(group fiber.Router, params ServerParams) {
+ generalGroup := group.Group("/general")
+
+ generalGroup.Get("/hosts", params.Handlers.GetHosts)
+ generalGroup.Get("/agents", params.Handlers.GetAgents)
+ params.Logger.Debug("Added General Handlers.")
+}
+
+func addHostHandler(group fiber.Router, params ServerParams) {
+ hostGroup := group.Group("/hosts")
+
+ hostGroup.Get("/", params.Handlers.GetHostByAgentID)
+ hostGroup.Post("/register", params.Handlers.RegisterHost)
+ params.Logger.Debug("Added Host Handlers.")
}
-// ListenAndServeTLS starts the Fiber server using TLS (HTTPS)
-func ListenAndServeTLS(router *fiber.App, certFile, keyFile, addr string) error {
- // Start HTTPS server using the provided certificate and key files
- return router.ListenTLS(addr, certFile, keyFile)
+func addLoginHandler(group fiber.Router, params ServerParams) {
+ loginGroup := group.Group("/auth")
+
+ loginGroup.Post("/login", params.Handlers.LoginHandler)
+ params.Logger.Debug("Added Login Handlers.")
}
+
+// Module exports the server module.
+var Module = fx.Options(
+ fx.Provide(NewServer),
+)
diff --git a/templates/404.html b/templates/404.html
index cc068c8..5d3628b 100644
--- a/templates/404.html
+++ b/templates/404.html
@@ -62,7 +62,8 @@
diff --git a/Dockerfile b/tools/Dockerfile
similarity index 100%
rename from Dockerfile
rename to tools/Dockerfile
diff --git a/tools/Dockerfile-tracing b/tools/Dockerfile-tracing
new file mode 100644
index 0000000..7983275
--- /dev/null
+++ b/tools/Dockerfile-tracing
@@ -0,0 +1,33 @@
+# syntax=docker/dockerfile:1
+
+FROM golang:latest
+
+# Set destination for COPY
+WORKDIR /app
+
+# Download Go modules
+
+ADD . /app
+
+RUN go mod download
+
+# Copy the source code. Note the slash at the end, as explained in
+# https://docs.docker.com/reference/dockerfile/#copy
+
+
+# Build
+ENV TRACING_ENABLED=true
+ARG APP_VERSION="v0.1.0+hotfixes"
+RUN \
+ CGO_ENABLED=0 GOOS=linux go build -ldflags "-X 'main.AppVersion=$APP_VERSION'" -o /app/packagelock
+
+# Optional:
+# To bind to a TCP port, runtime parameters must be supplied to the docker command.
+# But we can document in the Dockerfile what ports
+# the application is going to listen on by default.
+# https://docs.docker.com/reference/dockerfile/#expose
+EXPOSE 8080
+
+# Run
+CMD ["/app/packagelock","start"]
+
diff --git a/tracing/tracing.go b/tracing/tracing.go
new file mode 100644
index 0000000..cdab0a2
--- /dev/null
+++ b/tracing/tracing.go
@@ -0,0 +1,102 @@
+package tracing
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ // For setting span status
+
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/exporters/jaeger"
+ "go.opentelemetry.io/otel/sdk/resource"
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/fx"
+ "go.uber.org/zap"
+)
+
+// NewTracerProvider creates and configures a new TracerProvider
+// It looks for two env flags:
+// - 'TRACING_ENABLED' -> enables tracing, if not set/false uses noop-tracer
+// - 'TRACING_JAEGER_URL' -> sets custom jaeger url, if not set uses default localhost
+func NewTracerProvider(logger *zap.Logger) (*sdktrace.TracerProvider, error) {
+ tracingEnabled := os.Getenv("TRACING_ENABLED") == "true"
+ if !tracingEnabled {
+ logger.Info("Tracing is disabled via ENV-FLAG ('TRACING_ENABLED'). Using NoopTracerProvider.")
+ return sdktrace.NewTracerProvider(), nil // Returns a TracerProvider with no exporters
+ }
+ logger.Info("Tracing is enabled via ENV-FLAG ('TRACING_ENABLED'). Using TracerProvider.")
+
+ var exporter *jaeger.Exporter
+ var exporterErr error
+
+ jaegerAddress := os.Getenv("TRACING_JAEGER_URL")
+
+ if jaegerAddress != "" {
+
+ // Configure the Jaeger exporter with custom URL from ENV-FLAG
+ exporter, exporterErr = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerAddress)))
+ if exporterErr != nil {
+ logger.Error("Failed to create Jaeger exporter", zap.Error(exporterErr))
+ return nil, fmt.Errorf("failed to create Jaeger exporter: %w", exporterErr)
+ }
+ logger.Info("Configured Jaeger exporter with custom url from ENV-FLAG.")
+
+ } else {
+
+ // Configure the Jaeger exporter with default url
+ exporter, exporterErr = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
+ if exporterErr != nil {
+ logger.Error("Failed to create Jaeger exporter", zap.Error(exporterErr))
+ return nil, fmt.Errorf("failed to create Jaeger exporter: %w", exporterErr)
+ }
+ }
+
+ // Create the TracerProvider with batching and resource attributes
+ tp := sdktrace.NewTracerProvider(
+ sdktrace.WithBatcher(exporter),
+ sdktrace.WithResource(resource.NewWithAttributes(
+ semconv.SchemaURL,
+ semconv.ServiceNameKey.String("PackageLock"),
+ )),
+ )
+
+ // Set the global TracerProvider
+ otel.SetTracerProvider(tp)
+
+ logger.Info("Tracing is enabled and TracerProvider is configured.")
+ return tp, nil
+}
+
+// NewTracer provides a trace.Tracer instance
+func NewTracer(tp *sdktrace.TracerProvider, logger *zap.Logger) trace.Tracer {
+ tracingEnabled := os.Getenv("TRACING_ENABLED") == "true"
+ if !tracingEnabled {
+ logger.Info("Tracing is disabled via ENV-FLAG ('TRACING_ENABLED'). Using Nooptrace.Tracer.")
+ return otel.GetTracerProvider().Tracer("noop-tracer")
+ }
+ logger.Info("Tracing is enabled via ENV-FLAG ('TRACING_ENABLED'). Using trace.Tracer.")
+ return tp.Tracer("PackageLock")
+}
+
+// Module is the FX module for tracing
+var Module = fx.Options(
+ fx.Provide(
+ NewTracerProvider, // Provides *sdktrace.TracerProvider
+ NewTracer, // Provides trace.Tracer
+ ),
+ fx.Invoke(func(lc fx.Lifecycle, tp *sdktrace.TracerProvider, logger *zap.Logger) {
+ lc.Append(fx.Hook{
+ OnStop: func(ctx context.Context) error {
+ if err := tp.Shutdown(ctx); err != nil {
+ logger.Error("Error shutting down TracerProvider", zap.Error(err))
+ return err
+ }
+ logger.Info("TracerProvider shutdown successfully.")
+ return nil
+ },
+ })
+ }),
+)
diff --git a/wiki/API-Docs.md b/wiki/API-Docs.md
new file mode 100644
index 0000000..bc893ac
--- /dev/null
+++ b/wiki/API-Docs.md
@@ -0,0 +1,21 @@
+# TODO: Add API-Docs
+
+## Overview
+
+| Ressource | Endpoint | Description |
+| ----------------- | -------- | ----------- |
+|Auth|[Authenticate][auth_login]|Loginhandling|
+|Agents|[Get Agent][agents]|Get registered Agent by ID|
+|Agents|[Register Agent][agents_reg]|Registration of a new Agent|
+|General|[Get Agents][general_agents]|List all Agents|
+|General|[Get Hosts][general_hosts]|List all Hosts|
+|Hosts|[Get Host][hosts]|Get registered Host by AgentID|
+|Hosts|[Register Host][hosts_reg]|Registration of a new Host|
+
+[auth_login]: login
+[agents]: get_agent_by_id
+[agents_reg]: register_agent
+[general_agents]: get_agents
+[general_hosts]: get_hosts
+[hosts]: get_host_by_agentid
+[hosts_reg]: register_host
diff --git a/wiki/API/agents/_Sidebar.md b/wiki/API/agents/_Sidebar.md
new file mode 100644
index 0000000..d9e422e
--- /dev/null
+++ b/wiki/API/agents/_Sidebar.md
@@ -0,0 +1,6 @@
+
+# Navigation
+
+- [Home][home]
+
+[home]: Home
diff --git a/wiki/API/agents/get_agent_by_id.md b/wiki/API/agents/get_agent_by_id.md
new file mode 100644
index 0000000..278377c
--- /dev/null
+++ b/wiki/API/agents/get_agent_by_id.md
@@ -0,0 +1,34 @@
+# Get Registered Agent by ID
+
+return Information about a Agent by it's ID
+
+> [!WARNING]
+> Sample Information - WIP
+
+## URL
+
+```GET https://instance-url.com/v1/agents```
+
+## Authorization
+
+Requires a [Access-Token][access-token]
+
+[access-token]: access-token
+
+## Request Query Parameters
+
+|Parameter|Type|Required|Description|
+|---------|----|--------|-----------|
+|AgentID|String|Yes|The unique AgentID|
+
+## Response Body
+
+|Field|Type|Description|
+|-----|----|-----------|
+|name|String|The Name of the Agent|
+
+## Response Codes
+
+|Code|Description|
+|----|-----------|
+|200|OK|
diff --git a/wiki/API/agents/register_agent.md b/wiki/API/agents/register_agent.md
new file mode 100644
index 0000000..6bbf6f1
--- /dev/null
+++ b/wiki/API/agents/register_agent.md
@@ -0,0 +1,3 @@
+# Register new Agent
+
+TODO: Add Documentation
diff --git a/wiki/API/auth/_Sidebar.md b/wiki/API/auth/_Sidebar.md
new file mode 100644
index 0000000..d9e422e
--- /dev/null
+++ b/wiki/API/auth/_Sidebar.md
@@ -0,0 +1,6 @@
+
+# Navigation
+
+- [Home][home]
+
+[home]: Home
diff --git a/wiki/API/auth/access-token.md b/wiki/API/auth/access-token.md
new file mode 100644
index 0000000..6052abe
--- /dev/null
+++ b/wiki/API/auth/access-token.md
@@ -0,0 +1,3 @@
+# Access Token
+
+TODO: Add Documentation
\ No newline at end of file
diff --git a/wiki/API/auth/login.md b/wiki/API/auth/login.md
new file mode 100644
index 0000000..aab9e0c
--- /dev/null
+++ b/wiki/API/auth/login.md
@@ -0,0 +1,3 @@
+# Login
+
+TODO: Add Documentation
\ No newline at end of file
diff --git a/wiki/API/general/_Sidebar.md b/wiki/API/general/_Sidebar.md
new file mode 100644
index 0000000..d9e422e
--- /dev/null
+++ b/wiki/API/general/_Sidebar.md
@@ -0,0 +1,6 @@
+
+# Navigation
+
+- [Home][home]
+
+[home]: Home
diff --git a/wiki/API/general/get_agents.md b/wiki/API/general/get_agents.md
new file mode 100644
index 0000000..55647c5
--- /dev/null
+++ b/wiki/API/general/get_agents.md
@@ -0,0 +1,3 @@
+# Get all Agents
+
+TODO: Add Documentation
diff --git a/wiki/API/general/get_hosts.md b/wiki/API/general/get_hosts.md
new file mode 100644
index 0000000..bff6601
--- /dev/null
+++ b/wiki/API/general/get_hosts.md
@@ -0,0 +1,3 @@
+# Get all Hosts
+
+TODO: Add Documentation
diff --git a/wiki/API/hosts/_Sidebar.md b/wiki/API/hosts/_Sidebar.md
new file mode 100644
index 0000000..e46a8d5
--- /dev/null
+++ b/wiki/API/hosts/_Sidebar.md
@@ -0,0 +1,6 @@
+
+# Navigation
+
+- [Home][home]
+
+[home]: https://github.com/HilkopterBob/PackageLock/wiki/Home
diff --git a/wiki/API/hosts/get_host_by_agentid.md b/wiki/API/hosts/get_host_by_agentid.md
new file mode 100644
index 0000000..03e33a4
--- /dev/null
+++ b/wiki/API/hosts/get_host_by_agentid.md
@@ -0,0 +1,3 @@
+# Get Hosts by AgentID
+
+TODO: Add Documentation
diff --git a/wiki/API/hosts/register_host.md b/wiki/API/hosts/register_host.md
new file mode 100644
index 0000000..8d8f34d
--- /dev/null
+++ b/wiki/API/hosts/register_host.md
@@ -0,0 +1,3 @@
+# Register new Host
+
+TODO: Add Documentation
diff --git a/wiki/Home.md b/wiki/Home.md
new file mode 100644
index 0000000..fc2cba6
--- /dev/null
+++ b/wiki/Home.md
@@ -0,0 +1,47 @@
+
+
Welcome to the PackageLock wiki!
+
+![logo][logo]
+_PackageLock aims to be the distro-agnostic π-included one-stop Solution for patchmanagement on linux systems._
+
+**Features:**
+
+* Linux patchmanagement
+* TODO: Add more features
+
+***
+
+# Getting started
+
+Is this your first time using PackageLock? This is the place to get started!
+
+* **First steps:** [Introduction][introduction] | [Quickstart][quickstart]
+* **Working with PackageLock:** [Setting up Server][setup_server] | [Deploying your first agent][deploy_agent]
+* **Examples:** Examples are available in the [repository][repository].
+
+# Getting help
+
+If youβre having trouble with something, these resources might help.
+
+* Try the [Frequently Asked Questions][faq] first, it answers common questions.
+* Ask us and hang out with us in our [Discord][discord] server.
+* Report bugs in the [issue tracker][issues].
+
+# Manuals
+
+These pages go into great detail about everything.
+
+* [API Reference](https://github.com/HilkopterBob/PackageLock)
+
+
+
+
+[logo]: https://github.com/HilkopterBob/PackageLock/blob/master/README-Assets/logo.png
+[introduction]: https://github.com/HilkopterBob/PackageLock
+[quickstart]:https://github.com/HilkopterBob/PackageLock
+[setup_server]:https://github.com/HilkopterBob/PackageLock
+[deploy_agent]:https://github.com/HilkopterBob/PackageLock
+[repository]:https://github.com/HilkopterBob/PackageLock
+[faq]:https://github.com/HilkopterBob/PackageLock
+[discord]:https://discord.gg/CUFv3PU3
+[issues]:https://github.com/HilkopterBob/PackageLock/issues
\ No newline at end of file
diff --git a/wiki/_Footer.md b/wiki/_Footer.md
new file mode 100644
index 0000000..83ba22b
--- /dev/null
+++ b/wiki/_Footer.md
@@ -0,0 +1,3 @@
+---
+
+This project is licensed unter the MIT license
diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md
new file mode 100644
index 0000000..c533b91
--- /dev/null
+++ b/wiki/_Sidebar.md
@@ -0,0 +1,27 @@
+# Navigation
+
+- [Home][home]
+- [API Documentation][apidocs]
+ - Auth
+ - [Authenticate][auth_login]
+ - Agents
+ - [Get Agent][agents]
+ - [Register Agent][agents_reg]
+ - General
+ - [Get Agents][general_agents]
+ - [Get Hosts][general_hosts]
+ - Hosts
+ - [Get Host][hosts]
+ - [Register Host][hosts_reg]
+- [Dev-Home][dev-home]
+
+[home]: Home
+[apidocs]: API-Docs
+[auth_login]: login
+[agents]: get_agent_by_id
+[agents_reg]: register_agent
+[general_agents]: get_agents
+[general_hosts]: get_hosts
+[hosts]: get_host_by_agentid
+[hosts_reg]: register_host
+[dev-home]: dev-home
diff --git a/wiki/developer docs/Architecture-Overview.md b/wiki/developer docs/Architecture-Overview.md
new file mode 100644
index 0000000..e69de29
diff --git a/wiki/developer docs/Contributing.md b/wiki/developer docs/Contributing.md
new file mode 100644
index 0000000..8dc3470
--- /dev/null
+++ b/wiki/developer docs/Contributing.md
@@ -0,0 +1,34 @@
+# Contributing to Transcriptase
+We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
+
+- Reporting a bug
+- Discussing the current state of the code
+- Submitting a fix
+- Proposing new features
+- Becoming a maintainer
+
+## We Develop with Github
+We use github to host code, to track issues and feature requests, as well as accept pull requests.
+
+## We Use _GitHub Flow_, So All Code Changes Happen Through Pull Requests
+Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests:
+
+1. Fork the repo and create your branch from `devel`.
+2. If you've added code that should be tested, add tests.
+3. If you've changed APIs, update the documentation.
+4. Ensure the test suite passes.
+5. Make sure your code lints.
+6. Issue that pull request!
+
+## Any contributions you make will be under the **GNU General Public License v3.0** Software License
+In short, when you submit code changes, your submissions are understood to be under the same [GPLv3 License](https://choosealicense.com/licenses/gpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
+
+## Report bugs using Github's issues
+We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
+
+## Style guidelines
+Please Read our [Style-Guide]
+
+[Style-Guide]: Style-Guide
+
+
diff --git a/wiki/developer docs/Dev-Home.md b/wiki/developer docs/Dev-Home.md
new file mode 100644
index 0000000..0ee75b1
--- /dev/null
+++ b/wiki/developer docs/Dev-Home.md
@@ -0,0 +1,39 @@
+# PackageLock Developer Documentation
+
+Welcome to the official developer documentation for **PackageLock**! This wiki serves as a comprehensive resource for developers looking to understand, contribute to, and extend the project.
+
+## Table of Contents
+
+- [Getting Started][getting-started]
+- [Architecture Overview][architecture-overview]
+- [API Reference][api-reference]
+- [Contributing][contributing]
+- [FAQ][faq]
+- [Support][support]
+
+## Getting Started
+
+Begin your journey by following the [Getting Started][getting-started] guide. This section will help you set up the development environment, install dependencies, and run the project locally.
+
+## Architecture Overview
+
+Understand the core architecture of **PackageLock** in the [Architecture Overview][architecture-overview] section. Learn about the main components, their interactions, and the design principles that guide the project.
+
+## Contributing
+
+We welcome contributions from the community! Please read our [Contributing][contributing] guidelines to learn how you can help improve **PackageLock**. This includes information on coding standards, submitting issues, and making pull requests.
+
+## FAQ
+
+Have questions? Check out our [FAQ][faq] for answers to common questions.
+
+---
+
+_Happy Coding! π_
+
+[getting-started]: getting-started
+[architecture-overview]: architecture-overview
+[api-reference]: api-reference
+[contributing]: contributing
+[faq]: faq
+[support]: support
diff --git a/wiki/developer docs/FAQ.md b/wiki/developer docs/FAQ.md
new file mode 100644
index 0000000..e69de29
diff --git a/wiki/developer docs/Getting-Started.md b/wiki/developer docs/Getting-Started.md
new file mode 100644
index 0000000..e69de29
diff --git a/wiki/developer docs/Style-Guide.md b/wiki/developer docs/Style-Guide.md
new file mode 100644
index 0000000..ae24f29
--- /dev/null
+++ b/wiki/developer docs/Style-Guide.md
@@ -0,0 +1,3 @@
+# Style Guidelines
+
+?
diff --git a/wiki/developer docs/_Sidebar.md b/wiki/developer docs/_Sidebar.md
new file mode 100644
index 0000000..11b1335
--- /dev/null
+++ b/wiki/developer docs/_Sidebar.md
@@ -0,0 +1,13 @@
+# Navigation
+
+- [Home][home]
+- [Dev-Home][dev-home]
+- [Getting Started][getting-started]
+- [Contributing][contributing]
+- [FAQ][faq]
+
+[home]: Home
+[dev-home]: Dev-Home
+[getting-started]: Getting-Started
+[contributing]: Contributing
+[faq]: FAQ
diff --git a/wiki/developer docs/tracing.md b/wiki/developer docs/tracing.md
new file mode 100644
index 0000000..7bbf663
--- /dev/null
+++ b/wiki/developer docs/tracing.md
@@ -0,0 +1,34 @@
+# Tracing
+
+Tracing is an optional feature for performance analysis.
+It's based on the [OpenTelemetry Golang SDK](https://github.com/open-telemetry/opentelemetry-go) and [Jaeger Telemetry](https://www.jaegertracing.io/).
+
+## Prerequisites
+
+1. Docker && Docker Runtime
+2. PackageLock Binary
+
+## Usage
+
+1. set `TRACING_ENABLED` to `true` (`$ export TRACING_ENABLED=true`)
+2. Start the Jaeger Docker Container:
+```bash
+docker run -d --name jaeger \
+ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
+ -p 5775:5775/udp \
+ -p 6831:6831/udp \
+ -p 6832:6832/udp \
+ -p 5778:5778 \
+ -p 16686:16686 \
+ -p 14268:14268 \
+ -p 9411:9411 \
+ jaegertracing/all-in-one:latest
+```
+ - If Jaeger is running somewhere else, specify the location
+ with: `$ export TRACING_JAEGER_URL=http://:/api/traces`
+
+3. start PackageLock (eg. `go run . start`)
+
+PackageLock now traces itself and exports it to the `jaeger` Container.
+
+You can now open the Jaeger web interface at: `http://localhost:16686`