[
  {
    "path": ".envrc",
    "content": "use flake\n"
  },
  {
    "path": ".gitattributes",
    "content": "external/** -linguist-vendored\n"
  },
  {
    "path": ".gitignore",
    "content": ".venv/\nbook/\n\n*.iso\n*.log\n*.png\n*.tgz\n*kubeconfig.yaml\nChart.lock\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.3.0\n    hooks:\n      - id: check-added-large-files\n      - id: check-executables-have-shebangs\n      - id: check-merge-conflict\n      - id: check-shebang-scripts-are-executable\n      - id: detect-private-key\n      - id: end-of-file-fixer\n      - id: mixed-line-ending\n      - id: trailing-whitespace\n  - repo: https://github.com/adrienverge/yamllint\n    rev: v1.27.1\n    hooks:\n      - id: yamllint\n  - repo: https://github.com/gruntwork-io/pre-commit\n    rev: v0.1.24\n    hooks:\n      - id: helmlint\n      - id: shellcheck\n  - repo: https://github.com/tofuutils/pre-commit-opentofu\n    rev: v2.1.0\n    hooks:\n      - id: tofu_fmt\n"
  },
  {
    "path": ".woodpecker/helm-diff.yaml",
    "content": "when:\n  branch: ${CI_REPO_DEFAULT_BRANCH}\n\nmatrix:\n  STACK:\n    - system\n    - platform\n    - apps\n\nsteps:\n  # TODO DRY with nix develop and custom entrypoint https://github.com/woodpecker-ci/woodpecker/pull/2985,\n  # but first we need a Nix cache. See the nix-cache branch for the WIP.\n  diff:\n    image: nixery.dev/shell/git/python3/kubernetes-helm/diffutils/dyff # TODO replace with nix develop\n    commands:\n      - ./scripts/helm-diff --repository \"${CI_REPO_CLONE_URL}\" --source \"${CI_COMMIT_SOURCE_BRANCH}\" --target \"${CI_COMMIT_TARGET_BRANCH}\" --subpath \"${STACK}\"\n    when:\n      - event: pull_request\n        path: '${STACK}/**'\n    depends_on: []\n"
  },
  {
    "path": ".woodpecker/static-checks.yaml",
    "content": "when:\n  branch: ${CI_REPO_DEFAULT_BRANCH}\n\nsteps:\n  # TODO DRY with nix develop and custom entrypoint https://github.com/woodpecker-ci/woodpecker/pull/2985,\n  # but first we need a Nix cache. See the nix-cache branch for the WIP.\n  tools-versions:\n    image: nixos/nix\n    commands:\n      - echo 'experimental-features = flakes nix-command' >> /etc/nix/nix.conf\n      # - echo 'trusted-substituters = http://nix-cache.nix-cache' >> /etc/nix/nix.conf\n      # - echo 'substituters = http://nix-cache.nix-cache' >> /etc/nix/nix.conf\n      - nix develop --command make -C test filter=ToolsVersions\n    when:\n      - event: pull_request\n        path:\n          include:\n            - 'flake.*'\n    depends_on: []\n  pre-commit:\n    image: nixery.dev/shell/git/pre-commit # TODO replace with nix develop\n    commands:\n      - pre-commit run --color=always\n    when:\n      - event: pull_request\n    depends_on: []\n"
  },
  {
    "path": ".yamllint.yaml",
    "content": "ignore: |\n  templates/\n  mkdocs.yml\n\nextends: default\n\nrules:\n  document-start: disable\n  line-length: disable\n"
  },
  {
    "path": "LICENSE.md",
    "content": "### GNU GENERAL PUBLIC LICENSE\n\nVersion 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc.\n<https://fsf.org/>\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n### Preamble\n\nThe GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nthe GNU General Public License is intended to guarantee your freedom\nto share and change all versions of a program--to make sure it remains\nfree software for all its users. We, the Free Software Foundation, use\nthe GNU General Public License for most of our software; it applies\nalso to any other work released this way by its authors. You can apply\nit to your programs, too.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nTo protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights. Therefore, you\nhave certain responsibilities if you distribute copies of the\nsoftware, or if you modify it: responsibilities to respect the freedom\nof others.\n\nFor example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received. You must make sure that they, too, receive\nor can get the source code. And you must show them these terms so they\nknow their rights.\n\nDevelopers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\nFor the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software. For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\nSome devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the\nmanufacturer can do so. This is fundamentally incompatible with the\naim of protecting users' freedom to change the software. The\nsystematic pattern of such abuse occurs in the area of products for\nindividuals to use, which is precisely where it is most unacceptable.\nTherefore, we have designed this version of the GPL to prohibit the\npractice for those products. If such problems arise substantially in\nother domains, we stand ready to extend this provision to those\ndomains in future versions of the GPL, as needed to protect the\nfreedom of users.\n\nFinally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish\nto avoid the special danger that patents applied to a free program\ncould make it effectively proprietary. To prevent this, the GPL\nassures that patents cannot be used to render the program non-free.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n### TERMS AND CONDITIONS\n\n#### 0. Definitions.\n\n\"This License\" refers to version 3 of the GNU General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds\nof works, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of\nan exact copy. The resulting work is called a \"modified version\" of\nthe earlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user\nthrough a computer network, with no transfer of a copy, is not\nconveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\" to\nthe extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n#### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work for\nmaking modifications to it. \"Object code\" means any non-source form of\na work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users can\nregenerate automatically from other parts of the Corresponding Source.\n\nThe Corresponding Source for a work in source code form is that same\nwork.\n\n#### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not convey,\nwithout conditions so long as your license otherwise remains in force.\nYou may convey covered works to others for the sole purpose of having\nthem make modifications exclusively for you, or provide you with\nfacilities for running those works, provided that you comply with the\nterms of this License in conveying all material for which you do not\ncontrol copyright. Those thus making or running the covered works for\nyou must do so exclusively on your behalf, under your direction and\ncontrol, on terms that prohibit them from making any copies of your\ncopyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under the\nconditions stated below. Sublicensing is not allowed; section 10 makes\nit unnecessary.\n\n#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such\ncircumvention is effected by exercising rights under this License with\nrespect to the covered work, and you disclaim any intention to limit\noperation or modification of the work as a means of enforcing, against\nthe work's users, your or third parties' legal rights to forbid\ncircumvention of technological measures.\n\n#### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n#### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these\nconditions:\n\n-   a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n-   b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under\n    section 7. This requirement modifies the requirement in section 4\n    to \"keep intact all notices\".\n-   c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy. This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged. This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n-   d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n#### 6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms of\nsections 4 and 5, provided that you also convey the machine-readable\nCorresponding Source under the terms of this License, in one of these\nways:\n\n-   a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n-   b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the Corresponding\n    Source from a network server at no charge.\n-   c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source. This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n-   d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge. You need not require recipients to copy the\n    Corresponding Source along with the object code. If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source. Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n-   e) Convey the object code using peer-to-peer transmission,\n    provided you inform other peers where the object code and\n    Corresponding Source of the work are being offered to the general\n    public at no charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal,\nfamily, or household purposes, or (2) anything designed or sold for\nincorporation into a dwelling. In determining whether a product is a\nconsumer product, doubtful cases shall be resolved in favor of\ncoverage. For a particular product received by a particular user,\n\"normally used\" refers to a typical or common use of that class of\nproduct, regardless of the status of the particular user or of the way\nin which the particular user actually uses, or expects or is expected\nto use, the product. A product is a consumer product regardless of\nwhether the product has substantial commercial, industrial or\nnon-consumer uses, unless such uses represent the only significant\nmode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to\ninstall and execute modified versions of a covered work in that User\nProduct from a modified version of its Corresponding Source. The\ninformation must suffice to ensure that the continued functioning of\nthe modified object code is in no case prevented or interfered with\nsolely because modification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or\nupdates for a work that has been modified or installed by the\nrecipient, or for the User Product in which it has been modified or\ninstalled. Access to a network may be denied when the modification\nitself materially and adversely affects the operation of the network\nor violates the rules and protocols for communication across the\nnetwork.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n#### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders\nof that material) supplement the terms of this License with terms:\n\n-   a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n-   b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n-   c) Prohibiting misrepresentation of the origin of that material,\n    or requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n-   d) Limiting the use for publicity purposes of names of licensors\n    or authors of the material; or\n-   e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n-   f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions\n    of it) with contractual assumptions of liability to the recipient,\n    for any liability that these contractual assumptions directly\n    impose on those licensors and authors.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions; the\nabove requirements apply either way.\n\n#### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your license\nfrom a particular copyright holder is reinstated (a) provisionally,\nunless and until the copyright holder explicitly and finally\nterminates your license, and (b) permanently, if the copyright holder\nfails to notify you of the violation by some reasonable means prior to\n60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n#### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or run\na copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n#### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n#### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims owned\nor controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within the\nscope of its coverage, prohibits the exercise of, or is conditioned on\nthe non-exercise of one or more of the rights that are specifically\ngranted under this License. You may not convey a covered work if you\nare a party to an arrangement with a third party that is in the\nbusiness of distributing software, under which you make payment to the\nthird party based on the extent of your activity of conveying the\nwork, and under which the third party grants, to any of the parties\nwho would receive the covered work from you, a discriminatory patent\nlicense (a) in connection with copies of the covered work conveyed by\nyou (or copies made from those copies), or (b) primarily for and in\nconnection with specific products or compilations that contain the\ncovered work, unless you entered into that arrangement, or that patent\nlicense was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n#### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under\nthis License and any other pertinent obligations, then as a\nconsequence you may not convey it at all. For example, if you agree to\nterms that obligate you to collect a royalty for further conveying\nfrom those to whom you convey the Program, the only way you could\nsatisfy both those terms and this License would be to refrain entirely\nfrom conveying the Program.\n\n#### 13. Use with the GNU Affero General Public License.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n#### 14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions\nof the GNU General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in\ndetail to address new problems or concerns.\n\nEach version is given a distinguishing version number. If the Program\nspecifies that a certain numbered version of the GNU General Public\nLicense \"or any later version\" applies to it, you have the option of\nfollowing the terms and conditions either of that numbered version or\nof any later version published by the Free Software Foundation. If the\nProgram does not specify a version number of the GNU General Public\nLicense, you may choose any version ever published by the Free\nSoftware Foundation.\n\nIf the Program specifies that a proxy can decide which future versions\nof the GNU General Public License can be used, that proxy's public\nstatement of acceptance of a version permanently authorizes you to\nchoose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n#### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT\nWARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND\nPERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE\nDEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR\nCORRECTION.\n\n#### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR\nCONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES\nARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT\nNOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR\nLOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM\nTO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER\nPARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n\n#### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n### How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these\nterms.\n\nTo do so, attach the following notices to the program. It is safest to\nattach them to the start of each source file to most effectively state\nthe exclusion of warranty; and each file should have at least the\n\"copyright\" line and a pointer to where the full notice is found.\n\n        <one line to give the program's name and a brief idea of what it does.>\n        Copyright (C) <year>  <name of author>\n\n        This program is free software: you can redistribute it and/or modify\n        it under the terms of the GNU General Public License as published by\n        the Free Software Foundation, either version 3 of the License, or\n        (at your option) any later version.\n\n        This program is distributed in the hope that it will be useful,\n        but WITHOUT ANY WARRANTY; without even the implied warranty of\n        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n        GNU General Public License for more details.\n\n        You should have received a copy of the GNU General Public License\n        along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper\nmail.\n\nIf the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n        <program>  Copyright (C) <year>  <name of author>\n        This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n        This is free software, and you are welcome to redistribute it\n        under certain conditions; type `show c' for details.\n\nThe hypothetical commands \\`show w' and \\`show c' should show the\nappropriate parts of the General Public License. Of course, your\nprogram's commands might be different; for a GUI interface, you would\nuse an \"about box\".\n\nYou should also get your employer (if you work as a programmer) or\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary. For more information on this, and how to apply and follow\nthe GNU GPL, see <https://www.gnu.org/licenses/>.\n\nThe GNU General Public License does not permit incorporating your\nprogram into proprietary programs. If your program is a subroutine\nlibrary, you may consider it more useful to permit linking proprietary\napplications with the library. If this is what you want to do, use the\nGNU Lesser General Public License instead of this License. But first,\nplease read <https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "Makefile",
    "content": ".POSIX:\n.PHONY: *\n.EXPORT_ALL_VARIABLES:\n\nKUBECONFIG = $(shell pwd)/metal/kubeconfig.yaml\nKUBE_CONFIG_PATH = $(KUBECONFIG)\n\ndefault: metal system external smoke-test post-install clean\n\nconfigure:\n\t./scripts/configure\n\tgit status\n\nmetal:\n\tmake -C metal\n\nsystem:\n\tmake -C system\n\nexternal:\n\tmake -C external\n\nsmoke-test:\n\tmake -C test filter=Smoke\n\npost-install:\n\t@./scripts/hacks\n\n# TODO maybe there's a better way to manage backup with GitOps?\nbackup:\n\t./scripts/backup --action setup --namespace=actualbudget --pvc=actualbudget-data\n\t./scripts/backup --action setup --namespace=jellyfin --pvc=jellyfin-data\n\nrestore:\n\t./scripts/backup --action restore --namespace=actualbudget --pvc=actualbudget-data\n\t./scripts/backup --action restore --namespace=jellyfin --pvc=jellyfin-data\n\ntest:\n\tmake -C test\n\nclean:\n\tdocker compose --project-directory ./metal/roles/pxe_server/files down\n\ndocs:\n\tmkdocs serve\n\ngit-hooks:\n\tpre-commit install\n"
  },
  {
    "path": "README.md",
    "content": "# Khue's Homelab\n\n**[Features](#features) • [Get Started](#get-started) • [Documentation](https://homelab.khuedoan.com)**\n\n[![tag](https://img.shields.io/github/v/tag/khuedoan/homelab?style=flat-square&logo=semver&logoColor=white)](https://github.com/khuedoan/homelab/tags)\n[![document](https://img.shields.io/website?label=document&logo=gitbook&logoColor=white&style=flat-square&url=https%3A%2F%2Fhomelab.khuedoan.com)](https://homelab.khuedoan.com)\n[![license](https://img.shields.io/github/license/khuedoan/homelab?style=flat-square&logo=gnu&logoColor=white)](https://www.gnu.org/licenses/gpl-3.0.html)\n[![stars](https://img.shields.io/github/stars/khuedoan/homelab?logo=github&logoColor=white&color=gold&style=flat-square)](https://github.com/khuedoan/homelab)\n\nThis project utilizes [Infrastructure as Code](https://en.wikipedia.org/wiki/Infrastructure_as_code) and [GitOps](https://www.weave.works/technologies/gitops) to automate provisioning, operating, and updating self-hosted services in my homelab.\nIt can be used as a highly customizable framework to build your own homelab.\n\n> **What is a homelab?**\n>\n> Homelab is a laboratory at home where you can self-host, experiment with new technologies, practice for certifications, and so on.\n> For more information, please see the [r/homelab introduction](https://www.reddit.com/r/homelab/wiki/introduction) and the\n> [Home Operations Discord community](https://discord.gg/home-operations) (formerly known as [k8s-at-home](https://k8s-at-home.com)).\n\nIf you encounter an issue, please create [a bug report](https://github.com/khuedoan/homelab/issues/new?template=bug_report.md)\n(avoid asking for support about issues specific to this project in other communication channels).\n\n## Overview\n\nProject status: **ALPHA**\n\nThis project is still in the experimental stage, and I don't use anything critical on it.\nExpect breaking changes that may require a complete redeployment.\nA proper upgrade path is planned for the stable release.\nMore information can be found in [the roadmap](#roadmap) below.\n\n### Hardware\n\n![Hardware](https://user-images.githubusercontent.com/27996771/98970963-25137200-2543-11eb-8f2d-f9a2d45756ef.JPG)\n\n- 4 × NEC SFF `PC-MK26ECZDR` (Japanese version of the ThinkCentre M700):\n    - CPU: `Intel Core i5-6600T @ 2.70GHz`\n    - RAM: `16GB`\n    - SSD: `128GB`\n- TP-Link `TL-SG108` switch:\n    - Ports: `8`\n    - Speed: `1000Mbps`\n\n### Features\n\n- [x] Common applications: Gitea, Jellyfin, Paperless...\n- [x] Automated bare metal provisioning with PXE boot\n- [x] Automated Kubernetes installation and management\n- [x] Installing and managing applications using GitOps\n- [x] Automatic rolling upgrade for OS and Kubernetes\n- [x] Automatically update apps (with approval)\n- [x] Modular architecture, easy to add or remove features/components\n- [x] Automated certificate management\n- [x] Automatically update DNS records for exposed services\n- [x] VPN (Tailscale or Wireguard)\n- [x] Expose services to the internet securely with [Cloudflare Tunnel](https://www.cloudflare.com/products/tunnel/)\n- [x] CI/CD platform\n- [x] Private container registry\n- [x] Distributed storage\n- [x] Support multiple environments (dev, prod)\n- [x] Monitoring and alerting\n- [x] Automated backup and restore\n- [x] Single sign-on\n- [x] Infrastructure testing\n\nSome demo videos and screenshots are shown here.\nThey can't capture all the project's features, but they are sufficient to get a concept of it.\n\n| Demo                                                                                                            |\n| :--:                                                                                                            |\n| [![][deploy-demo]](https://asciinema.org/a/xkBRkwC6e9RAzVuMDXH3nGHp7)                                           |\n| Deploy with a single command (after updating the configuration files)                                           |\n| [![][pxe-demo]](https://www.youtube.com/watch?v=y-d7btNNAT8)                                                    |\n| PXE boot                                                                                                        |\n| [![][hubble-demo]][hubble-demo]                                                                                 |\n| Observe network traffic with Hubble, built on top of [Cilium](https://cilium.io) and eBPF                       |\n| [![][homepage-demo]][homepage-demo]                                                                             |\n| Homepage powered by... [Homepage](https://gethomepage.dev)                                                      |\n| [![][grafana-demo]][grafana-demo]                                                                               |\n| Monitoring dashboard powered by [Grafana](https://grafana.com)                                                  |\n| [![][gitea-demo]][gitea-demo]                                                                                   |\n| Git server powered by [Gitea](https://gitea.io/en-us)                                                           |\n| [![][matrix-demo]][matrix-demo]                                                                                 |\n| [Matrix](https://matrix.org/) chat server                                                                       |\n| [![][woodpecker-demo]][woodpecker-demo]                                                                         |\n| Continuous integration with [Woodpecker CI](https://woodpecker-ci.org)                                          |\n| [![][argocd-demo]][argocd-demo]                                                                                 |\n| Continuous deployment with [ArgoCD](https://argoproj.github.io/cd)                                              |\n| [![][alert-demo]][alert-demo]                                                                                   |\n| [ntfy](https://ntfy.sh) displaying received alerts                                                              |\n| [![][ai-demo]][ai-demo]                                                                                         |\n| Self-hosted AI powered by [Ollama](https://ollama.com) (experimental, not very fast because I don't have a GPU) |\n\n[deploy-demo]: https://asciinema.org/a/xkBRkwC6e9RAzVuMDXH3nGHp7.svg\n[pxe-demo]: https://user-images.githubusercontent.com/27996771/157303477-df2e7410-8f02-4648-a86c-71e6b7e89e35.png\n[hubble-demo]: https://github.com/khuedoan/homelab/assets/27996771/9c6677d0-3564-47c0-852b-24b6a554b4a3\n[homepage-demo]: https://github.com/khuedoan/homelab/assets/27996771/d0eaf620-be08-48d8-8420-40bcaa86093b\n[grafana-demo]: https://github.com/khuedoan/homelab/assets/27996771/ad937b26-e9bc-4761-83ae-1c7f512ea97f\n[gitea-demo]: https://github.com/khuedoan/homelab/assets/27996771/c245534f-88d9-4565-bde8-b39f60ccee9e\n[matrix-demo]: https://user-images.githubusercontent.com/27996771/149448510-7163310c-2049-4ccd-901d-f11f605bfc32.png\n[woodpecker-demo]: https://github.com/khuedoan/homelab/assets/27996771/5d887688-d20a-44c8-8f77-0c625527dfe4\n[argocd-demo]: https://github.com/khuedoan/homelab/assets/27996771/527e2529-4fe1-4664-ab8a-b9eb3c492d20\n[alert-demo]: https://github.com/khuedoan/homelab/assets/27996771/c922f755-e911-4ca0-9d4a-6e552d387f18\n[ai-demo]: https://github.com/khuedoan/homelab/assets/27996771/d77ba511-00b7-47c3-9032-55679a099e70\n\n### Tech stack\n\n<table>\n    <tr>\n        <th>Logo</th>\n        <th>Name</th>\n        <th>Description</th>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://simpleicons.org/icons/ansible.svg\"></td>\n        <td><a href=\"https://www.ansible.com\">Ansible</a></td>\n        <td>Automate bare metal provisioning and configuration</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/30269780\"></td>\n        <td><a href=\"https://argoproj.github.io/cd\">ArgoCD</a></td>\n        <td>GitOps tool built to deploy applications to Kubernetes</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://github.com/jetstack/cert-manager/raw/master/logo/logo.png\"></td>\n        <td><a href=\"https://cert-manager.io\">cert-manager</a></td>\n        <td>Cloud native certificate management</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/21054566?s=200&v=4\"></td>\n        <td><a href=\"https://cilium.io\">Cilium</a></td>\n        <td>eBPF-based Networking, Observability and Security (CNI, LB, Network Policy, etc.)</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/314135?s=200&v=4\"></td>\n        <td><a href=\"https://www.cloudflare.com\">Cloudflare</a></td>\n        <td>DNS and Tunnel</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://www.docker.com/wp-content/uploads/2022/03/Moby-logo.png\"></td>\n        <td><a href=\"https://www.docker.com\">Docker</a></td>\n        <td>Ephemeral PXE server</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://github.com/kubernetes-sigs/external-dns/raw/master/docs/img/external-dns.png\"></td>\n        <td><a href=\"https://github.com/kubernetes-sigs/external-dns\">ExternalDNS</a></td>\n        <td>Synchronizes exposed Kubernetes Services and Ingresses with DNS providers</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fedora_logo.svg/267px-Fedora_logo.svg.png\"></td>\n        <td><a href=\"https://getfedora.org/en/server\">Fedora Server</a></td>\n        <td>Base OS for Kubernetes nodes</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://upload.wikimedia.org/wikipedia/commons/b/bb/Gitea_Logo.svg\"></td>\n        <td><a href=\"https://gitea.com\">Gitea</a></td>\n        <td>Self-hosted Git service</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://grafana.com/static/img/menu/grafana2.svg\"></td>\n        <td><a href=\"https://grafana.com\">Grafana</a></td>\n        <td>Observability platform</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://helm.sh/img/helm.svg\"></td>\n        <td><a href=\"https://helm.sh\">Helm</a></td>\n        <td>The package manager for Kubernetes</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/49319725\"></td>\n        <td><a href=\"https://k3s.io\">K3s</a></td>\n        <td>Lightweight distribution of Kubernetes</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://kanidm.com/images/logo.svg\"></td>\n        <td><a href=\"https://kanidm.com\">Kanidm</a></td>\n        <td>Modern and simple identity management platform</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/13629408\"></td>\n        <td><a href=\"https://kubernetes.io\">Kubernetes</a></td>\n        <td>Container-orchestration system, the backbone of this project</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://github.com/grafana/loki/blob/main/docs/sources/logo.png?raw=true\"></td>\n        <td><a href=\"https://grafana.com/oss/loki\">Loki</a></td>\n        <td>Log aggregation system</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/1412239?s=200&v=4\"></td>\n        <td><a href=\"https://www.nginx.com\">NGINX</a></td>\n        <td>Kubernetes Ingress Controller</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://raw.githubusercontent.com/NixOS/nixos-artwork/refs/heads/master/logo/nix-snowflake-colours.svg\"></td>\n        <td><a href=\"https://nixos.org\">Nix</a></td>\n        <td>Convenient development shell</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://ntfy.sh/_next/static/media/logo.077f6a13.svg\"></td>\n        <td><a href=\"https://ntfy.sh\">ntfy</a></td>\n        <td>Notification service to send notifications to your phone or desktop</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/3380462\"></td>\n        <td><a href=\"https://prometheus.io\">Prometheus</a></td>\n        <td>Systems monitoring and alerting toolkit</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://docs.renovatebot.com/assets/images/logo.png\"></td>\n        <td><a href=\"https://www.whitesourcesoftware.com/free-developer-tools/renovate\">Renovate</a></td>\n        <td>Automatically update dependencies</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://raw.githubusercontent.com/rook/artwork/master/logo/blue.svg\"></td>\n        <td><a href=\"https://rook.io\">Rook Ceph</a></td>\n        <td>Cloud-Native Storage for Kubernetes</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/48932923?s=200&v=4\"></td>\n        <td><a href=\"https://tailscale.com\">Tailscale</a></td>\n        <td>VPN without port forwarding</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/13991055?s=200&v=4\"></td>\n        <td><a href=\"https://www.wireguard.com\">Wireguard</a></td>\n        <td>Fast, modern, secure VPN tunnel</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://avatars.githubusercontent.com/u/84780935?s=200&v=4\"></td>\n        <td><a href=\"https://woodpecker-ci.org\">Woodpecker CI</a></td>\n        <td>Simple yet powerful CI/CD engine with great extensibility</td>\n    </tr>\n    <tr>\n        <td><img width=\"32\" src=\"https://zotregistry.dev/v2.0.2/assets/images/logo.svg\"></td>\n        <td><a href=\"https://zotregistry.dev\">Zot Registry</a></td>\n        <td>Private container registry</td>\n    </tr>\n</table>\n\n## Get Started\n\n- [Try it out locally](https://homelab.khuedoan.com/installation/sandbox) without any hardware (just 4 commands!)\n- [Deploy on real hardware](https://homelab.khuedoan.com/installation/production/prerequisites) for production workload\n\n## Roadmap\n\nSee [roadmap](https://homelab.khuedoan.com/reference/roadmap) and [open issues](https://github.com/khuedoan/homelab/issues) for a list of proposed features and known issues.\n\n## Contributing\n\nAny contributions you make are greatly appreciated.\n\nPlease see [contributing guide](https://homelab.khuedoan.com/reference/contributing) for more information.\n\n## License\n\nCopyright &copy; 2020 - 2024 Khue Doan\n\nDistributed under the GPLv3 License.\nSee [license page](https://homelab.khuedoan.com/reference/license) or `LICENSE.md` file for more information.\n\n## Acknowledgements\n\nReferences:\n\n- [Ephemeral PXE server inspired by Minimal First Machine in the DC](https://speakerdeck.com/amcguign/minimal-first-machine-in-the-dc)\n- [ArgoCD usage and monitoring configuration in locmai/humble](https://github.com/locmai/humble)\n- [README template](https://github.com/othneildrew/Best-README-Template)\n- [Run the same Cloudflare Tunnel across many `cloudflared` processes](https://developers.cloudflare.com/cloudflare-one/tutorials/many-cfd-one-tunnel)\n- [MAC address environment variable in GRUB config](https://askubuntu.com/questions/1272400/how-do-i-automate-network-installation-of-many-ubuntu-18-04-systems-with-efi-and)\n- [Official k3s systemd service file](https://github.com/k3s-io/k3s/blob/master/k3s.service)\n- [Official Cloudflare Tunnel examples](https://github.com/cloudflare/argo-tunnel-examples)\n- [Initialize GitOps repository on Gitea and integrate with Tekton by RedHat](https://github.com/redhat-scholars/tekton-tutorial/tree/master/triggers)\n- [SSO configuration from xUnholy/k8s-gitops](https://github.com/xUnholy/k8s-gitops)\n- [Pre-commit config from k8s-at-home/flux-cluster-template](https://github.com/k8s-at-home/flux-cluster-template)\n- [Diátaxis technical documentation framework](https://diataxis.fr)\n- [Official Terratest examples](https://github.com/gruntwork-io/terratest/tree/master/test)\n- [Self-host an automated Jellyfin media streaming stack](https://zerodya.net/self-host-jellyfin-media-streaming-stack)\n- [App Template Helm chart by bjw-s](https://bjw-s-labs.github.io/helm-charts/docs/app-template)\n- [Various application configurations in onedr0p/home-ops](https://github.com/onedr0p/home-ops)\n\nHere is a list of the contributors who have helped to improve this project.\nBig shout-out to them!\n\n- <img width=\"24\" height=\"24\" src=\"https://github.com/locmai.png?size=24\" /> [@locmai](https://github.com/locmai)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/MatthewJohn.png?size=24\" /> [@MatthewJohn](https://github.com/MatthewJohn)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/karpfediem.png?size=24\" /> [@karpfediem](https://github.com/karpfediem)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/linhng98.png?size=24\" /> [@linhng98](https://github.com/linhng98)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/elliotblackburn.png?size=24\" /> [@elliotblackburn](https://github.com/elliotblackburn)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/dotdiego.png?size=24\" /> [@dotdiego](https://github.com/dotdiego)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/Crimrose.png?size=24\" /> [@Crimrose](https://github.com/Crimrose)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/eventi.png?size=24\" /> [@eventi](https://github.com/eventi)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/Bourne-ID.png?size=24\" /> [@Bourne-ID](https://github.com/Bourne-ID)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/akwan.png?size=24\" /> [@akwan](https://github.com/akwan)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/trangmaiq.png?size=24\" /> [@trangmaiq](https://github.com/trangmaiq)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/tangowithfoxtrot.png?size=24\" /> [@tangowithfoxtrot](https://github.com/tangowithfoxtrot)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/raedkit.png?size=24\" /> [@raedkit](https://github.com/raedkit)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/ClashTheBunny.png?size=24\" /> [@ClashTheBunny](https://github.com/ClashTheBunny)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/retX0.png?size=24\" /> [@retX0](https://github.com/retX0)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/zalsader.png?size=24\" /> [@zalsader](https://github.com/zalsader)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/serpro69.png?size=24\" /> [@serpro69](https://github.com/serpro69)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/llajas.png?size=24\" /> [@llajas](https://github.com/llajas)\n- <img width=\"24\" height=\"24\" src=\"https://github.com/zalsader.png?size=24\" /> [@zalsader](https://github.com/zalsader)\n\nIf you feel you're missing from this list, please feel free to add yourself in a PR.\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/khuedoan/homelab.svg)](https://starchart.cc/khuedoan/homelab)\n"
  },
  {
    "path": "apps/actualbudget/Chart.yaml",
    "content": "apiVersion: v2\nname: actualbudget\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/actualbudget/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: docker.io/actualbudget/actual-server\n            tag: 24.12.0-alpine\n  service:\n    main:\n      ports:\n        http:\n          port: 5006\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &host budget.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: actualbudget-tls-certificate\n  persistence:\n    data:\n      accessMode: ReadWriteOnce\n      size: 1Gi\n      globalMounts:\n        - path: /data\n"
  },
  {
    "path": "apps/blog/Chart.yaml",
    "content": "apiVersion: v2\nname: blog\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.2.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/blog/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: registry.khuedoan.com/blog\n            tag: latest\n  service:\n    main:\n      ports:\n        http:\n          port: 3000\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n        external-dns.alpha.kubernetes.io/target: homelab-tunnel.khuedoan.com\n        external-dns.alpha.kubernetes.io/cloudflare-proxied: 'true'\n      hosts:\n        - host: &host www.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: blog-tls-certificate\n"
  },
  {
    "path": "apps/excalidraw/Chart.yaml",
    "content": "apiVersion: v2\nname: excalidraw\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.2.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/excalidraw/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: docker.io/excalidraw/excalidraw\n            tag: latest\n  service:\n    main:\n      ports:\n        http:\n          port: 80\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n        external-dns.alpha.kubernetes.io/target: homelab-tunnel.khuedoan.com\n        external-dns.alpha.kubernetes.io/cloudflare-proxied: 'true'\n      hosts:\n        - host: &host draw.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: excalidraw-tls-certificate\n"
  },
  {
    "path": "apps/homepage/Chart.yaml",
    "content": "apiVersion: v2\nname: homepage\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/homepage/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: ghcr.io/gethomepage/homepage\n            tag: v0.8.8\n  service:\n    main:\n      ports:\n        http:\n          port: 3000\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &host home.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: homepage-tls-certificate\n  persistence:\n    config:\n      enabled: true\n      type: configMap\n      name: homepage-config\n      globalMounts:\n        - path: /app/config/settings.yaml\n          subPath: settings.yaml\n        - path: /app/config/widgets.yaml\n          subPath: widgets.yaml\n        - path: /app/config/services.yaml\n          subPath: services.yaml\n        - path: /app/config/bookmarks.yaml\n          subPath: bookmarks.yaml\n  configMaps:\n    config:\n      enabled: true\n      data:\n        settings.yaml: |\n          background: https://images.unsplash.com/photo-1502790671504-542ad42d5189?auto=format&fit=crop&w=2560&q=80\n          cardBlur: md\n          theme: dark\n          headerStyle: boxed\n          hideVersion: true\n        widgets.yaml: |\n          - search:\n              provider: google\n              target: _blank\n        services.yaml: |\n          - Popular:\n            - Matrix:\n                href: https://chat.khuedoan.com\n                description: Chat client\n                icon: element.svg\n            - Jellyfin:\n                href: https://jellyfin.khuedoan.com\n                description: Media system (movies, music, etc.)\n                icon: jellyfin.svg\n            - Jellyseerr:\n                href: https://jellyseerr.khuedoan.com\n                description: Request media\n                icon: jellyseerr.svg\n            - Paperless:\n                href: https://paperless.khuedoan.com\n                description: Document management system\n                icon: paperless.svg\n          - Management:\n            - Transmission:\n                href: https://transmission.khuedoan.com\n                description: Bittorrent client\n                icon: transmission.svg\n            - Prowlarr:\n                href: https://prowlarr.khuedoan.com\n                description: Indexer manager\n                icon: prowlarr.svg\n            - Radarr:\n                href: https://radarr.khuedoan.com\n                description: Movie manager\n                icon: radarr.svg\n            - Sonarr:\n                href: https://sonarr.khuedoan.com\n                description: TV show manager\n                icon: sonarr.svg\n            - Kanidm:\n                href: https://auth.khuedoan.com\n                description: Identity management\n                icon: https://auth.khuedoan.com/pkg/img/logo-square.svg\n          - Development:\n            - Gitea:\n                href: https://git.khuedoan.com\n                description: Git forge\n                icon: gitea.svg\n            - Woodpecker:\n                href: https://ci.khuedoan.com\n                description: Continuous integration\n                icon: woodpecker-ci.svg\n            - ArgoCD:\n                href: https://argocd.khuedoan.com\n                description: Continuous deployment\n                icon: argocd.svg\n            - Registry:\n                href: https://registry.khuedoan.com\n                description: Container registry\n                icon: docker.svg\n            - Grafana:\n                href: https://grafana.khuedoan.com\n                description: Observability dashboards\n                icon: grafana.svg\n          - Utilities:\n            - Excalidraw:\n                href: https://draw.khuedoan.com\n                description: Virtual whiteboard\n                icon: excalidraw.svg\n            - Speedtest:\n                href: https://speedtest.khuedoan.com\n                description: Internal network speed test\n                icon: openspeedtest.png\n        bookmarks.yaml: |\n          - Homelab:\n            - Documentation:\n              - href: https://homelab.khuedoan.com\n                icon: google-docs.svg\n            - Public homelab repository:\n              - href: https://github.com/khuedoan/homelab\n                icon: github.svg\n          - Managed services:\n            - Cloudflare:\n              - href: https://dash.cloudflare.com\n                icon: cloudflare.svg\n            - Terraform Cloud:\n              - href: https://app.terraform.io\n                icon: terraform.svg\n          - Infrastructure:\n            - Router:\n              - href: https://192.168.1.1\n                icon: router.svg\n            - Proxmox:\n              - href: https://proxmox:8006\n                icon: proxmox.svg\n            - Google Cloud:\n              - href: https://console.cloud.google.com\n                icon: google-cloud-platform.svg\n            - Oracle Cloud:\n              - href: https://cloud.oracle.com\n                icon: oracle-cloud.svg\n"
  },
  {
    "path": "apps/jellyfin/Chart.yaml",
    "content": "apiVersion: v2\nname: jellyfin\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/jellyfin/values.yaml",
    "content": "app-template:\n  defaultPodOptions:\n    securityContext:\n      fsGroup: 1000\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: docker.io/jellyfin/jellyfin\n            tag: 10.8.13\n        transmission:\n          image:\n            repository: lscr.io/linuxserver/transmission\n            tag: 4.0.5\n        prowlarr:\n          image:\n            repository: lscr.io/linuxserver/prowlarr\n            tag: 1.13.3\n        radarr:\n          image:\n            repository: lscr.io/linuxserver/radarr\n            tag: 5.3.6\n        sonarr:\n          image:\n            repository: lscr.io/linuxserver/sonarr\n            tag: 4.0.2\n        jellyseerr:\n          image:\n            repository: docker.io/fallenbagel/jellyseerr\n            tag: 1.7.0\n  service:\n    main:\n      ports:\n        http:\n          port: 8096\n          protocol: HTTP\n        transmission:\n          port: 9091\n          protocol: HTTP\n        prowlarr:\n          port: 9696\n          protocol: HTTP\n        radarr:\n          port: 7878\n          protocol: HTTP\n        sonarr:\n          port: 8989\n          protocol: HTTP\n        jellyseerr:\n          port: 5055\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &jellyfinHost jellyfin.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n        - host: &transmissionHost transmission.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: transmission\n        - host: &prowlarrHost prowlarr.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: prowlarr\n        - host: &radarrHost radarr.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: radarr\n        - host: &sonarrHost sonarr.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: sonarr\n        - host: &jellyseerrHost jellyseerr.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: jellyseerr\n      tls:\n        - secretName: jellyfin-tls-certificate\n          hosts:\n            - *jellyfinHost\n            - *transmissionHost\n            - *prowlarrHost\n            - *radarrHost\n            - *sonarrHost\n            - *jellyseerrHost\n  persistence:\n    data:\n      accessMode: ReadWriteOnce\n      size: 50Gi\n      advancedMounts:\n        main:\n          main:\n            - path: /config\n              subPath: jellyfin/config\n            - path: /media/movies\n              subPath: movies\n            - path: /media/shows\n              subPath: shows\n          transmission:\n            - path: /config\n              subPath: transmission/config\n            - path: /downloads\n              subPath: transmission/downloads\n          prowlarr:\n            - path: /config\n              subPath: prowlarr/config\n          radarr:\n            - path: /config\n              subPath: radarr/config\n            - path: /downloads/complete\n              subPath: transmission/downloads/complete\n            - path: /movies\n              subPath: movies\n          sonarr:\n            - path: /config\n              subPath: sonarr/config\n            - path: /downloads/complete\n              subPath: transmission/downloads/complete\n            - path: /shows\n              subPath: shows\n          jellyseerr:\n            - path: /app/config\n              subPath: jellyseerr/config\n"
  },
  {
    "path": "apps/matrix/Chart.yaml",
    "content": "apiVersion: v2\nname: elementweb\nversion: 0.0.0\ndependencies:\n  - name: elementweb\n    version: 0.0.6\n    repository: https://locmai.github.io/charts  # TODO switch to official chart\n  - name: dendrite\n    version: 0.13.5\n    repository: https://matrix-org.github.io/dendrite\n"
  },
  {
    "path": "apps/matrix/values.yaml",
    "content": "elementweb:\n  ingress:\n    enabled: true\n    className: nginx\n    annotations:\n      cert-manager.io/cluster-issuer: letsencrypt-prod\n      external-dns.alpha.kubernetes.io/target: \"homelab-tunnel.khuedoan.com\"\n      external-dns.alpha.kubernetes.io/cloudflare-proxied: \"true\"\n    hosts:\n      - host: &frontend_host chat.khuedoan.com\n        paths:\n          - path: /\n            pathType: Prefix\n    tls:\n      - secretName: element-tls-certificate\n        hosts:\n          - *frontend_host\n  config:\n    default:\n      base_url: https://matrix.khuedoan.com\n      server_name: khuedoan.com\n\ndendrite:\n  dendrite_config:\n    global:\n      server_name: matrix.khuedoan.com\n  ingress:\n    enabled: true\n    className: nginx\n    annotations:\n      cert-manager.io/cluster-issuer: letsencrypt-prod\n    hostName: matrix.khuedoan.com\n    tls:\n      - hosts:\n          - matrix.khuedoan.com\n        secretName: matrix-tls-certificate\n  postgresql:\n    enabled: true\n"
  },
  {
    "path": "apps/ollama/Chart.yaml",
    "content": "apiVersion: v2\nname: ollama\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/ollama/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: docker.io/ollama/ollama\n            tag: 0.1.29\n    ui:\n      containers:\n        main:\n          image:\n            repository: ghcr.io/open-webui/open-webui\n            tag: latest\n          env:\n            OLLAMA_BASE_URL: http://ollama:11434\n  service:\n    main:\n      ports:\n        http:\n          port: 11434\n          protocol: HTTP\n    ui:\n      controller: ui\n      ports:\n        http:\n          port: 8080\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &ollamaHost ollama.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n        - host: &uiHost ai.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: ui\n                port: http\n      tls:\n        - hosts:\n            - *ollamaHost\n            - *uiHost\n          secretName: ollama-tls-certificate\n  persistence:\n    data:\n      accessMode: ReadWriteOnce\n      size: 10Gi\n      advancedMounts:\n        main:\n          main:\n            - path: /root/.ollama\n"
  },
  {
    "path": "apps/pairdrop/Chart.yaml",
    "content": "apiVersion: v2\nname: pairdrop\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/pairdrop/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: lscr.io/linuxserver/pairdrop\n            tag: 1.10.7\n  service:\n    main:\n      ports:\n        http:\n          port: 3000\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &host drop.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: pairdrop-tls-certificate\n"
  },
  {
    "path": "apps/paperless/Chart.yaml",
    "content": "apiVersion: v2\nname: paperless\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/paperless/templates/secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: {{ .Release.Name }}-secret\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  dataFrom:\n    - extract:\n        key: paperless.admin\n"
  },
  {
    "path": "apps/paperless/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: ghcr.io/paperless-ngx/paperless-ngx\n            tag: 2.5.4\n          env:\n            PAPERLESS_PORT: 8000\n            PAPERLESS_ADMIN_USER: admin\n            PAPERLESS_URL: https://paperless.khuedoan.com\n          envFrom:\n            - secret: \"{{ .Release.Name }}-secret\"\n        redis:\n          image:\n            repository: docker.io/library/redis\n            tag: 7.2.4\n  service:\n    main:\n      ports:\n        http:\n          port: 8000\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &host paperless.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: paperless-tls-certificate\n  persistence:\n    data:\n      accessMode: ReadWriteOnce\n      size: 10Gi\n      advancedMounts:\n        main:\n          main:\n            - path: /usr/src/paperless/data\n              subPath: data\n            - path: /usr/src/paperless/media\n              subPath: media\n"
  },
  {
    "path": "apps/speedtest/Chart.yaml",
    "content": "apiVersion: v2\nname: speedtest\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.6.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/speedtest/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      containers:\n        main:\n          image:\n            repository: docker.io/openspeedtest/latest\n            tag: latest\n  service:\n    main:\n      ports:\n        http:\n          port: 3000\n          protocol: HTTP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        nginx.ingress.kubernetes.io/proxy-body-size: 50m\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      hosts:\n        - host: &host speedtest.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: http\n      tls:\n        - hosts:\n            - *host\n          secretName: speedtest-tls-certificate\n"
  },
  {
    "path": "apps/tailscale/Chart.yaml",
    "content": "apiVersion: v2\nname: tailscale\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 3.1.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/tailscale/templates/role.yaml",
    "content": "# https://github.com/tailscale/tailscale/blob/main/docs/k8s/role.yaml\n# Copyright (c) Tailscale Inc & AUTHORS\n# SPDX-License-Identifier: BSD-3-Clause\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: tailscale\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"create\"]\n- apiGroups: [\"\"]\n  resourceNames: [\"tailscale\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\", \"update\", \"patch\"]\n"
  },
  {
    "path": "apps/tailscale/templates/rolebinding.yaml",
    "content": "# https://github.com/tailscale/tailscale/blob/main/docs/k8s/rolebinding.yaml\n# Copyright (c) Tailscale Inc & AUTHORS\n# SPDX-License-Identifier: BSD-3-Clause\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: tailscale\n  namespace: {{ .Release.Namespace }}\nsubjects:\n  - kind: ServiceAccount\n    name: tailscale\nroleRef:\n  kind: Role\n  name: tailscale\n  apiGroup: rbac.authorization.k8s.io\n"
  },
  {
    "path": "apps/tailscale/templates/secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: tailscale-auth\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  data:\n    - secretKey: TS_AUTHKEY\n      remoteRef:\n        key: external\n        property: tailscale-auth-key\n"
  },
  {
    "path": "apps/tailscale/templates/serviceaccount.yaml",
    "content": "# https://github.com/tailscale/tailscale/blob/main/docs/k8s/sa.yaml\n# Copyright (c) Tailscale Inc & AUTHORS\n# SPDX-License-Identifier: BSD-3-Clause\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tailscale\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "apps/tailscale/values.yaml",
    "content": "app-template:\n  serviceAccount:\n    name: tailscale\n  controllers:\n    tailscale:\n      containers:\n        app:\n          image:\n            repository: ghcr.io/tailscale/tailscale\n            tag: latest\n          env:\n            TS_HOSTNAME: homelab-router\n            TS_USERSPACE: false\n            TS_KUBE_SECRET: tailscale\n            TS_ROUTES: 192.168.1.224/27\n            TS_AUTHKEY:\n              valueFrom:\n                secretKeyRef:\n                  name: tailscale-auth\n                  key: TS_AUTHKEY\n          securityContext:\n            capabilities:\n              add:\n                - NET_ADMIN\n"
  },
  {
    "path": "apps/wireguard/Chart.yaml",
    "content": "apiVersion: v2\nname: wireguard\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 3.5.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "apps/wireguard/values.yaml",
    "content": "app-template:\n  controllers:\n    wireguard:\n      containers:\n        app:\n          image:\n            repository: lscr.io/linuxserver/wireguard\n            tag: latest\n          env:\n            LOG_CONFS: false\n            USE_COREDNS: true\n          securityContext:\n            capabilities:\n              add:\n                - NET_ADMIN\n  service:\n    wireguard:\n      controller: wireguard\n      type: LoadBalancer\n      ports:\n        http:\n          port: 51820\n          protocol: UDP\n  persistence:\n    config:\n      type: secret\n      name: \"{{ .Release.Name }}-secret\"\n      globalMounts:\n        - path: /config/wg_confs\n  rawResources:\n    secret:\n      apiVersion: external-secrets.io/v1beta1\n      kind: ExternalSecret\n      spec:\n        spec:\n          secretStoreRef:\n            kind: ClusterSecretStore\n            name: global-secrets\n          data:\n            - secretKey: WIREGUARD_PRIVATE_KEY\n              remoteRef:\n                key: external\n                property: wireguard-private-key\n          target:\n            template:\n              data:\n                wg0.conf: |\n                  [Interface]\n                  Address = 172.16.0.1/32\n                  ListenPort = 51820\n                  PrivateKey = {{ `{{ .WIREGUARD_PRIVATE_KEY }}` }}\n                  PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE\n                  PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE\n\n                  # Note that WireGuard will ignore a peer whose public key matches\n                  # the interface's private key. So you can distribute a single\n                  # list of peers everywhere.\n                  # https://lists.zx2c4.com/pipermail/wireguard/2018-December/003703.html\n\n                  # Servers\n\n                  [Peer]\n                  # homelab\n                  PublicKey = sSAZS1Z3vB7Wx8e2yVqXfeHjgWTa80wnSYoma3mZkiU=\n                  AllowedIPs = 172.16.0.1/32, 192.168.1.100/32, 192.168.1.224/27\n\n                  [Peer]\n                  # horus\n                  PublicKey = zVwYqwvGn/IL7o6CD84y4/Y/OnRAUl/jw6T7DtNqWGM=\n                  Endpoint = horus.khuedoan.com:51820\n                  PersistentKeepalive = 25\n                  AllowedIPs = 172.16.0.2/32\n\n                  # Clients\n\n                  [Peer]\n                  # khuedoan-ryzentower\n                  PublicKey = qnDY23ZWUOlCRUmB8anpXH1uzX0A17F1YtQJmVi7GWM=\n                  AllowedIPs = 172.16.0.10/32\n\n                  [Peer]\n                  # khuedoan-thinkpadz13\n                  PublicKey = hSVWn2lBasJeueHApQYmYR0s2zNpXrqZX6F8gyEqpy0=\n                  AllowedIPs = 172.16.0.11/32\n\n                  [Peer]\n                  # khuedoan-phone\n                  PublicKey = nITHFdgTkNZOTWeSWqnGXjgwlCJMKRCnnUsjMx2yp2U=\n                  AllowedIPs = 172.16.0.12/32\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "mermaid*.js\n"
  },
  {
    "path": "docs/concepts/certificate-management.md",
    "content": "# Certificate management\n\nCertificates are generated and managed by [cert-manager](https://cert-manager.io) with [Let's Encrypt](https://letsencrypt.org).\nBy default certificates are valid for 90 days and will be renewed after 60 days.\n\ncert-manager watches `Ingress` resources across the cluster. When you create an `Ingress` with a [supported annotation](https://cert-manager.io/docs/usage/ingress/#supported-annotations):\n\n```yaml hl_lines=\"5 13 14\"\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\n  name: foo\nspec:\n  rules:\n    - host: foo.example.com\n      # ...\n  tls:\n    - hosts:\n        - foo.example.com\n      secretName: foo-tls-certificate\n```\n\n```mermaid\nflowchart LR\n  User -- 6 --> Ingress\n\n  subgraph cluster[Homelab cluster]\n    Ingress --- Secret\n    Ingress -. 1 .-> Certificate\n    Certificate -. 5 .-> Secret\n    Certificate -- 2 --> CertificateRequest -- 3 --> Order -- 4 --> Challenge\n  end\n\n  Order -.- ACMEServer[ACME server]\n\n  subgraph dnsprovider[DNS provider]\n    TXT\n  end\n\n  Challenge -- 4.a --> TXT\n  ACMEServer -.- Challenge\n  ACMEServer -. 4.b .-> TXT\n```\n\n1. cert-manager creates a corresponding `Certificate` resources\n2. Based on the `Certificate` resource, cert-manager creates a `CertificateRequest` resource to request a signed certificate from the configured `ClusterIssuer`\n3. The `CertificateRequest` will create an order with an ACME server (we use Let's Encrypt), which is represented by the `Order` resource\n4. Then cert-manager will perform a [DNS-01](https://cert-manager.io/docs/configuration/acme/dns01) `Challenge`:\n    1. Create a DNS TXT record (contains a computed key)\n    2. The ACME server retrieve this key via a DNS lookup and validate that we own the domain for the requested certificate\n7. cert-manager stores the certificate (typically `tls.crt` and `tls.key`) in the `Secret` specified in the `Ingress` configuration\n8. Now you can access the HTTPS website with a valid certificate\n\nA much more detailed diagram can be found in the official documentation under [certificate lifecycle](https://cert-manager.io/docs/concepts/certificate/#certificate-lifecycle).\n"
  },
  {
    "path": "docs/concepts/development-shell.md",
    "content": "# Development shell\n\nThe development shell makes it easy to get all of the dependencies needed to interact with the homelab.\n\n## Prerequisites\n\n!!! info\n\n    NixOS users can skip this step.\n\nInstall Nix using one of the following methods:\n\n- [Official Nix installer](https://nixos.org/download)\n- [Determinate Nix Installer](https://docs.determinate.systems/getting-started/#installer)\n\nIf you're using the official installer, add the following to your\n`~/.config/nix/nix.conf` to enable [Flakes](https://nixos.wiki/wiki/Flakes):\n\n```conf\nexperimental-features = nix-command flakes\n```\n\n## How to open it\n\nRun the following command:\n\n```sh\nnix develop\n```\n\nIt will open a shell with all the dependencies defined in `./flake.nix`:\n\n```\n[khuedoan@ryzentower:~/Documents/homelab]$ which kubectl\n/nix/store/0558zzzqynzw7rx9dp2i7jymvznd1cqx-kubectl-1.30.1/bin/kubectl\n```\n\n!!! tip\n\n    If you have [`direnv`](https://direnv.net) installed, you can run `direnv\n    allow` once and it will automatically enter the Nix shell every time you\n    `cd` into the project.\n"
  },
  {
    "path": "docs/concepts/pxe-boot.md",
    "content": "# PXE boot\n\n```mermaid\nflowchart TD\n  subgraph controller[Initial controller]\n    Ansible\n    dhcp[DHCP server]\n    tftp[TFTP server]\n    http[HTTP server]\n  end\n\n  machine[Bare metal machine]\n\n  Ansible -. 1 .-> machine\n  machine <-. 2, 3 .-> dhcp\n  machine <-. 4, 5 .-> tftp\n  machine <-. 6, 7 .-> http\n```\n\n1. Ansible: Hey MAC address `xx:xx:xx:xx:xx:xx`, wake up!\n2. Machine: Hello everyone, I just woke up in network mode, could someone please show me how to boot?\n3. DHCP server: I hear you, here's your IP address, proceed to the next server to obtain your bootloader.\n4. Machine: Hello, could you please send me my bootloader?\n5. TFTP server: Here you go. Grab your boot configuration, kernel, and initial ramdisk as well.\n6. Machine: Hi, I just booted into my bootloader, and my boot parameters instructed me to get the installation instructions, packages, etc. from this site.\n7. HTTP server: It's all yours.\n8. Machine: Great, now I can install the OS and reboot!\n\nHere's how it looks like in action:\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube-nocookie.com/embed/y-d7btNNAT8\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n"
  },
  {
    "path": "docs/concepts/secrets-management.md",
    "content": "# Secrets management\n\n## Overview\n\n- Global secrets are stored in the `global-secrets` namespace.\n- Integrate with GitOps using [External Secrets Operator](https://external-secrets.io).\n- Secrets that can be generated are automatically generated and stored in the `global-secrets` namespace.\n\n!!! info\n\n    Despite the name _External_ Secrets Operator, global secrets are created in the same cluster and synced\n    to other namespaces using the [Kubernetes provider](https://external-secrets.io/latest/provider/kubernetes).\n\n    While not supported by default in this project, you can also use other external providers such as HashiCorp Vault,\n    AWS Secret Manager, Google Cloud Secret Manager, Azure Key Vault, 1Password, etc.\n\n```mermaid\nflowchart TD\n  subgraph global-secrets-namespace[global-secrets namespace]\n    secret-generator[Secret Generator] -- generate if not exist --> source-secrets[Source Secrets]\n  end\n\n  subgraph app-namespace[application namespace]\n    ExternalSecret -- create --> Secret\n    App -- read --> Secret\n  end\n\n  ClusterSecretStore -- read --> source-secrets\n  ExternalSecret --- ClusterSecretStore\n```\n\n## Randomly generated secrets\n\nThis is useful when you want to generate random secrets like admin password and store in global secrets.\n\n```yaml title=\"./platform/global-secrets/files/secret-generator/config.yaml\" hl_lines=\"2-6\"\n--8<--\n./platform/global-secrets/files/secret-generator/config.yaml\n--8<--\n```\n\n## Extra third-party secrets\n\nFor third-party secrets that you don't control, add them to `external/terraform.tfvars` under the `extra_secrets` key,\nthen run `make external`.\n\nThey will be available as a Secret named `external` in the `global-secrets` namespace.\nYou can use it with `ExternalSecret` just like any other global secret.\n\n## How secrets are pulled from global secrets to other namespaces\n\nWhen you apply an `ExternalSecret` object, for example:\n\n```yaml hl_lines=\"4 21-23\"\napiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: gitea-admin-secret\n  namespace: gitea\nspec:\n  data:\n  - remoteRef:\n      conversionStrategy: Default\n      key: gitea.admin\n      property: password\n    secretKey: password\n  refreshInterval: 1h\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  target:\n    creationPolicy: Owner\n    deletionPolicy: Retain\n    template:\n      data:\n        password: '{{ .password }}'\n        username: gitea_admin\n      engineVersion: v2\n```\n\nThis will create a corresponding Kubernetes secret:\n\n`kubectl describe secrets -n gitea gitea-admin-secret`\n\n```yaml hl_lines=\"1 8-11\"\nName:         gitea-admin-secret\nNamespace:    gitea\nLabels:       <none>\nAnnotations:  reconcile.external-secrets.io/data-hash: <REDACTED>\n\nType:  Opaque\n\nData\n====\npassword:  32 bytes\nusername:  11 bytes\n```\n\nPlease see the official documentation for more information:\n\n- [External Secrets Operator](https://external-secrets.io)\n- [API specification](https://external-secrets.io/latest/spec)\n"
  },
  {
    "path": "docs/concepts/testing.md",
    "content": "# Testing infrastructure code\n\nWe use [Terratest](https://terratest.gruntwork.io) for automated tests.\nThe tests are written in Go and can be found at `./test`.\n\nTODO: more docs here (PR welcomed)\n"
  },
  {
    "path": "docs/getting-started/install-pre-commit-hooks.md",
    "content": "# Install pre-commit hooks\n\nGit hook scripts are useful for identifying simple issues before commiting changes.\n\nInstall [pre-commit](https://pre-commit.com/#install) first, one-liner for Arch users:\n\n```sh\nsudo pacman -S python-pre-commit\n```\n\nThen install git hook scripts:\n\n```sh\nmake git-hooks\n```\n\nIf you want to enable pre-commit on all repositories without enabling it manually, see [automatically enabling pre-commit on repositories](https://pre-commit.com/#automatically-enabling-pre-commit-on-repositories).\n"
  },
  {
    "path": "docs/getting-started/user-onboarding.md",
    "content": "# User onboarding\n\n=== \"For user\"\n\n    ## Create user\n\n    Ask an admin to create your account, provide the following information:\n\n    - [ ] Full name (John Doe)\n    - [ ] Select a username (`johndoe`)\n    - [ ] Email address (`johndoe@example.com`)\n\n    ## Install companion apps\n\n    For all users:\n\n    - [ ] A password manager (I personally recommend [Bitwarden](https://bitwarden.com/download))\n    - [ ] A [Matrix chat client](https://matrix.org/clients) (optional, you can use the web version)\n\n    For technical users:\n\n    - [ ] [Docker](https://docs.docker.com/engine/install)\n    - [ ] [Nix](https://nixos.org/download) and [direnv](https://direnv.net) (optional, but highly recommended)\n    - [ ] [Lens](https://k8slens.dev) (optional, you can use the included `kubectl` or `k9s` command in the tools container)\n\n=== \"For admin\"\n\n    Run the following script:\n\n    ```sh\n    ./scripts/onboard-user johndoe \"John Doe\" \"johndoe@example.com\"\n    ```\n\n    Let the user scan the QR code or follow the link to set up passkeys or password + TOTP.\n"
  },
  {
    "path": "docs/getting-started/vpn-setup.md",
    "content": "# VPN setup\n\nYou can choose between [Tailscale](https://tailscale.com),\n[Wireguard](https://www.wireguard.com), or use both like me. I primarily use\nWireGuard but keep Tailscale as a backup for when the WireGuard server is down.\n\n## Tailscale (requires third-party account)\n\nGet an [auth key](https://tailscale.com/kb/1085/auth-keys) from [Tailscale admin console](https://login.tailscale.com/admin/authkeys):\n\n- Description: homelab\n- Reusable: optionally set this to true\n\nAdd it to `external/terraform.tfvars` as an extra secret:\n\n```hcl\nextra_secrets = {\n  tailscale-auth-key = \"tskey-auth-myauthkeyhere\"\n}\n```\n\nYou may want to back up the `external/terraform.tfvars` file to a secure location.\n\nApply the secret:\n\n```sh\nmake external\n```\n\nFinally, [enable subnet routes](https://tailscale.com/kb/1019/subnets#step-3-enable-subnet-routes-from-the-admin-console) for `homelab-router`\nfrom the [admin console](https://login.tailscale.com/admin/machines).\n\nYou can now connect to your homelab via Tailscale and [invite user to your Tailscale network](https://tailscale.com/kb/1371/invite-users).\n\n## Wireguard (requires port-forwarding)\n\n### Prerequisites\n\nFind your public IP address using:\n\n```sh\ncurl -4 ifconfig.me\n```\n\nIf you don’t have a static IP address, use dynamic DNS and replace the IP with\nyour domain name.\n\nNext, configure port forwarding in your router for the WireGuard service.\n\n!!! example\n\n    Each router is different, here's mine for reference:\n\n    - Protocol: `UDP`\n    - Start Port: `51820`\n    - End Port: `51820`\n    - Local IP Address: `192.168.1.226` (find it with `kubectl get service -n wireguard wireguard`)\n    - Start Port Local: `51820`\n    - End Port Local: `51820`\n\nGenerate a key pair for the server:\n\n```sh\nwg genkey | tee /dev/tty | wg pubkey\n```\n\nThis will generate a private key and a public key, in that order. Add the\nprivate key to `external/terraform.tfvars` as an extra secret:\n\n```hcl\nextra_secrets = {\n  wireguard-private-key = \"privatekeyhere\"\n}\n```\n\nYou may want to back up the `external/terraform.tfvars` file to a secure location.\n\nApply the secret:\n\n```sh\nmake external\n```\n\nI use `172.16.0.0/12` as the private IP range for WireGuard, but you can choose\nany private IP address range you prefer in `./apps/wireguard/values.yaml`. I\nalso recommend removing my peers and adding your own.\n\n### Add a new device to the server\n\n!!! info\n\n    Each device requires its own configuration.\n\nGenerate a new key pair for the device. You can generate it for the user, or\nthey can generate it themselves if they prefer to keep the private key\nconfidential:\n\n```sh\nwg genkey | tee /dev/tty | wg pubkey\n```\n\nThis will generate a private key and a public key, in that order. The private\nkey must be saved in a secure password manager, and save the public key for the\nnext step.\n\nUpdate the list of peers in `./apps/wireguard/values.yaml`, make sure you\nreplace all of my peers with yours.\n\n!!! example\n\n    Example configuration for my phone:\n\n    ```ini\n    [Peer]\n    PublicKey = nITHFdgTkNZOTWeSWqnGXjgwlCJMKRCnnUsjMx2yp2U=\n    AllowedIPs = 172.16.0.12/32\n    ```\n\n    - The public key is the one generated in the previous step.\n    - `172.16.0.12/32` is the device's private IP address, manually selected from\n      the `172.16.0.0/12` range mentioned above.\n\n### Add the Wireguard config to the device\n\nCreate a new configuration file for the device:\n\n```ini\n[Interface]\nAddress = <CLIENT PRIVATE IP>/32\nPrivateKey = <CLIENT PRIVATE KEY>\n\n[Peer]\nPublicKey = <SERVER PUBLIC KEY>\nEndpoint = <SERVER PUBLIC IP>:51820\nAllowedIPs = <SERVER PRIVATE IP>/32, <LOAD BALANCER IP RANGE>\n```\n\nReplace placeholders with actual values and save as `wg0.conf`.\n\n!!! example\n\n    Example configuration for my phone:\n\n    ```ini\n    [Interface]\n    Address = 172.16.0.12/32\n    PrivateKey = <REDACTED>\n\n    [Peer]\n    PublicKey = sSAZS1Z3vB7Wx8e2yVqXfeHjgWTa80wnSYoma3mZkiU\n    Endpoint = <HOME IP>:51820\n    AllowedIPs = 172.16.0.1/32, 192.168.1.224/27\n    ```\n\nThe client can now import this configuration and connect to your WireGuard\nmesh. Make sure you clean up the `wg0.conf` file after importing it to the\nclient.\n\n=== \"Mobile\"\n\n    Generate a QR code from the configuration file:\n\n    ```sh\n    qrencode -t ansiutf8 -r wg0.conf\n    ```\n\n    Then scan the QR code using the official WireGuard app.\n\n=== \"Linux\"\n\n    Import the WireGuard configuration using NetworkManager:\n\n    ```sh\n    nmcli connection import type wireguard file wg0.conf\n    ```\n\n    Activate the connection:\n\n    ```sh\n    nmcli connection up wg0\n    ```\n\n## Headscale\n\n[Headscale](https://headscale.net) is a self-hosted implementation of the\nTailscale control server that does not require a third-party account. To set it\nup, take a look at the changes proposed by [this PR](https://github.com/khuedoan/homelab/pull/190).\n"
  },
  {
    "path": "docs/how-to-guides/add-or-remove-nodes.md",
    "content": "# Add or remove nodes\n\nOr how to scale vertically. To replace the same node with a clean OS, remove it and add it again.\n\n## Add new nodes\n\n!!! tip\n\n    You can add multiple nodes at the same time\n\nAdd its details to the inventory **at the end of the group** (masters or workers):\n\n```diff title=\"metal/inventories/prod.yml\"\ndiff --git a/metal/inventories/prod.yml b/metal/inventories/prod.yml\nindex 7f6474a..1bb2cbc 100644\n--- a/metal/inventories/prod.yml\n+++ b/metal/inventories/prod.yml\n@@ -8,3 +8,4 @@ metal:\n     workers:\n       hosts:\n         metal3: {ansible_host: 192.168.1.113, mac: '00:23:24:d1:f5:69', disk: sda, network_interface: eno1}\n+        metal4: {ansible_host: 192.168.1.114, mac: '00:11:22:33:44:55', disk: sda, network_interface: eno1}\n```\n\nInstall the OS and join the cluster:\n\n```\nmake metal\n```\n\nThat's it!\n\n## Remove a node\n\n!!! danger\n\n    It is recommended to remove nodes one at a time\n\nRemove it from the inventory:\n\n```diff title=\"metal/inventories/prod.yml\"\ndiff --git a/metal/inventories/prod.yml b/metal/inventories/prod.yml\nindex 7f6474a..d12b50a 100644\n--- a/metal/inventories/prod.yml\n+++ b/metal/inventories/prod.yml\n@@ -4,7 +4,6 @@ metal:\n       hosts:\n         metal0: {ansible_host: 192.168.1.110, mac: '00:23:24:d1:f3:f0', disk: sda, network_interface: eno1}\n         metal1: {ansible_host: 192.168.1.111, mac: '00:23:24:d1:f4:d6', disk: sda, network_interface: eno1}\n-        metal2: {ansible_host: 192.168.1.112, mac: '00:23:24:e7:04:60', disk: sda, network_interface: eno1}\n     workers:\n       hosts:\n         metal3: {ansible_host: 192.168.1.113, mac: '00:23:24:d1:f5:69', disk: sda, network_interface: eno1}\n```\n\nDrain the node:\n\n```sh\nkubectl drain ${NODE_NAME} --delete-emptydir-data --ignore-daemonsets --force\n```\n\nRemove the node from the cluster\n\n```sh\nkubectl delete node ${NODE_NAME}\n```\n\nShutdown the node:\n\n```\nssh root@${NODE_IP} poweroff\n```\n"
  },
  {
    "path": "docs/how-to-guides/alternate-dns-setup.md",
    "content": "# Alternate DNS setup\n\n!!! info\n\n        Skip this step if you already use the included Cloudflare setup\n\nBefore you can access the home page at <https://home.example.com>, you'll need to update your DNS config.\n\nSome options for DNS config (choose one):\n\n- Change the DNS config at your domain registrar (already included and automated)\n- Change the DNS config in your router (also works if you don't own a domain)\n- Use [nip.io](https://nip.io) (suitable for a test environment)\n\n## At your domain registrar (recommended)\n\nThe default configuration is for Cloudflare DNS, but you can change the code to use other providers.\n\n## In your router\n\n!!! tip\n\n    If you don't have a domain, you can use the `home.arpa` domain (according to [RFC-8375](https://datatracker.ietf.org/doc/html/rfc8375)).\n\nYou can add each subdomain one by one, or use a wildcard `*.example.com` and point it to the IP address of the load balancer.\nTo acquire a list of subdomains and their addresses, use this command:\n\n```sh\n./scripts/get-dns-config\n```\n\n## Use [nip.io](https://nip.io)\n\nPreconfigured in the `dev` branch.\n"
  },
  {
    "path": "docs/how-to-guides/backup-and-restore.md",
    "content": "# Backup and restore\n\n## Prerequisites\n\nCreate an S3 bucket to store backups. You can use AWS S3, Minio, or\nany other S3-compatible provider.\n\n- For AWS S3, your bucket URL might look something like this:\n  `https://s3.amazonaws.com/my-homelab-backup`.\n- For Minio, your bucket URL might look something like this:\n  `https://my-s3-host.example.com/homelab-backup`.\n\nFollow your provider's documentation to create a service account with the\nfollowing policy (replace `my-homelab-backup` with your actual bucket name):\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"s3:GetObject\",\n        \"s3:PutObject\",\n        \"s3:DeleteObject\",\n        \"s3:ListBucket\"\n      ],\n      \"Resource\": [\n        \"arn:aws:s3:::my-homelab-backup\",\n        \"arn:aws:s3:::my-homelab-backup/*\"\n      ]\n    }\n  ]\n}\n```\n\nSave the access key and secret key to a secure location, such as a password\nmanager. While you're at it, generate a new password for Restic encryption and\nsave it there as well.\n\n!!! example\n\n    I use Minio for my homelab backups. Here's how I set it up:\n\n    - Create a bucket named `homelab-backup`.\n    - Create a service account under Identity -> Service Accounts -> Create\n      Service Account:\n        - Enable Restrict beyond user policy.\n        - Paste the policy above.\n        - Click Create and copy the access key and secret key\n    - I also set up Minio replication to store backups in two locations: one in\n      my house and one remotely.\n\n## Add backup credentials to global secrets\n\nAdd the following to `external/terraform.tfvars`:\n\n```hcl\nextra_secrets = {\n  restic-password = \"xxxxxxxxxxxxxxxxxxxxxxxx\"\n  restic-s3-bucket = \"https://s3.amazonaws.com/my-homelab-backup-xxxxxxxxxx\"\n  restic-s3-access-key = \"xxxxxxxxxxxxxxxx\"\n  restic-s3-secret-key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n}\n```\n\nThen apply the changes:\n\n```sh\nmake external\n```\n\nYou may want to back up the `external/terraform.tfvars` file to a secure location as well.\n\n## Add backup configuration for volumes\n\n!!! warning\n\n    Do not run the backup command when building a new cluster where you intend\n    to restore backups, as it may overwrite existing backup data. To restore\n    data on a new cluster, refer to the [restore from\n    backup](#restore-from-backup) section.\n\nFor now, you need to run a command to opt-in volumes until we have a better\nGitOps solution:\n\n```sh\nmake backup\n```\n\nThis command will set up Restic repositories and back up the volumes configured\nin `./Makefile`. You can adjust the list there to add or remove volumes from the\nbackup. You only need to run this command once, the backup configuration will\nbe stored in the cluster and run on a schedule.\n\n## Restore from backup\n\nThe restore process is ad-hoc, you need to run a command to restore application volumes:\n\n```sh\nmake restore\n```\n\nThe command above will restore the latest backup of recommended volumes. Like\nwith backups, you can modify `./Makefile` to adjust the list of volumes you\nwant to restore.\n"
  },
  {
    "path": "docs/how-to-guides/disable-dhcp-proxy-in-dnsmasq.md",
    "content": "# Disable DHCP proxy in dnsmasq\n\n## Overview\n\nDnsmasq is used as either a DHCP server or DHCP proxy server for PXE metal provisioning.\n\nProxy mode is enabled by default allowing the use of existing DHCP servers on the network.\nA good description on how DHCP Proxy works can be found on the related [FOG project wiki page](https://wiki.fogproject.org/wiki/index.php?title=ProxyDHCP_with_dnsmasq).\n\n## Disabling Proxy mode\n\nCertain scenarios will require this project to use a DHCP server, such as an air-gap deployment or dedicated VLAN.\n\nTo disable proxy mode thereby using dnsmasq as a DHCP server, modify `metal/roles/pxe_server/defaults/main.yml` and set `dhcp_proxy` to `false`.\n"
  },
  {
    "path": "docs/how-to-guides/expose-services-to-the-internet.md",
    "content": "# Expose services to the internet\n\n!!! info\n\n    This tutorial is for Cloudflare Tunnel users, please skip if you use port forwarding.\n\nApply the `./external` layer to create a tunnel if you haven't already,\nthen add the following annotations to your `Ingress` object (replace `example.com` with your domain):\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  annotations:\n    external-dns.alpha.kubernetes.io/target: \"homelab-tunnel.example.com\"\n    external-dns.alpha.kubernetes.io/cloudflare-proxied: \"true\"\n# ...\n```\n"
  },
  {
    "path": "docs/how-to-guides/media-management.md",
    "content": "# Media management\n\n!!! warning\n\n    This is for educational purposes only :wink: Use it at your own risk!\n\n## Initial setup\n\n- Jellyfin `https://jellyfin.example.com`:\n    - Create an `admin` user and save the credentials to your password manager\n    - Add media libraries:\n        - Movies at `/media/movies`\n        - Shows at `/media/shows`\n- Radarr `https://radarr.example.com`:\n    - Authentication method: Forms\n    - Create an `admin` user and save the credentials to your password manager\n    - Navigate to Settings -> Download Clients -> Add -> Transmission (you can keep the default address and port)\n    - Navigate to Settings -> Media Management -> Add Root Folder `/movies`\n    - Navigate to Settings -> General -> API Key -> copy it for the next steps (or save it to your password manager)\n- Sonarr `https://sonarr.example.com`: same as Radarr but use `/shows` for the root folder\n- Prowlarr `https://prowlarr.example.com`:\n    - Authentication method: Forms\n    - Create an `admin` user and save the credentials to your password manager\n    - Navigate to Settings -> Apps -> Add:\n        - Radarr: paste the API key (you can keep the default address and port)\n        - Sonarr: same as Radarr\n    - Go back to Indexers -> Add New Indexers\n- Jellyseerr `https://jellyseerr.example.com`:\n    - Sign In\n        - Use your Jellyfin account\n        - URL: `https://jellyfin.example.com`\n        - Email: you can enter anything\n        - Username: `admin`\n        - Password: same as Jellyfin\n    - Configure Media Server\n        - Enable Movies and Shows\n    - Configure Services:\n        - Add Radarr Server:\n            - Default Server: true\n            - Name: Radarr\n            - Hostname: localhost\n            - Port: use default\n            - API Key: from previous step\n            - Click Test\n            - Quality Profile: choose whatever suits you\n            - Root folder: `/movies`\n            - External URL: `https://radarr.example.com`\n            - Enable Scan: true\n        - Add Sonarr Server: similar to Radarr\n\nOptionally, for convenience, you can add a `guest` account without a password in Jellyfin,\nallow access to all libraries, and allow that account to manage requests in Jellyseerr.\n\n## Usage\n\nHere's a suggested flow:\n\n- Users using the `guest` account can request media in Jellyseerr\n- Admins approve the request (or you can enable auto-approve)\n- Wait for the media to be downloaded\n- Watch on Jellyfin\n\nYou may need to increase the volume size depending on your usage.\n"
  },
  {
    "path": "docs/how-to-guides/run-commands-on-multiple-nodes.md",
    "content": "# Run commands on multiple nodes\n\nUse [ansible-console](https://docs.ansible.com/ansible/latest/cli/ansible-console.html):\n\n```sh\ncd metal\nmake console\n```\n\nThen enter the command(s) you want to run.\n\n!!! example\n\n    `root@all (4)[f:5]$ uptime`\n\n    ```console\n    metal0 | CHANGED | rc=0 >>\n     10:52:02 up 2 min,  1 user,  load average: 0.17, 0.15, 0.06\n    metal1 | CHANGED | rc=0 >>\n     10:52:02 up 2 min,  1 user,  load average: 0.14, 0.11, 0.04\n    metal3 | CHANGED | rc=0 >>\n     10:52:02 up 2 min,  1 user,  load average: 0.03, 0.02, 0.00\n    metal2 | CHANGED | rc=0 >>\n     10:52:02 up 2 min,  1 user,  load average: 0.06, 0.06, 0.02\n    ```\n"
  },
  {
    "path": "docs/how-to-guides/single-node-cluster-adjustments.md",
    "content": "# Single node cluster adjustments\n\n!!! danger\n\n    This is not officially supported and I don't regularly test it,\n    I highly recommend using multiple nodes.\n\n    Using a single node could lead to data loss unless your backup strategy is rock solid,\n    make sure you are **ABSOLUTELY CERTAIN** this is what you want.\n\nUpdate the following changes, then commit and push.\n\n## Remove storage redundancy\n\nSet pod counts and number of data copies to `1`:\n\n```yaml title=\"system/rook-ceph/values.yaml\" hl_lines=\"4 6 11 12 18 22 25\"\nrook-ceph-cluster:\n  cephClusterSpec:\n    mon:\n      count: 1\n    mgr:\n      count: 1\n  cephBlockPools:\n    - name: standard-rwo\n      spec:\n        replicated:\n          size: 1\n          requireSafeReplicaSize: false\n  cephFileSystems:\n    - name: standard-rwx\n      spec:\n        metadataPool:\n          replicated:\n            size: 1\n        dataPools:\n          - name: data0\n            replicated:\n              size: 1\n        metadataServer:\n          activeCount: 1\n          activeStandby: false\n```\n\n## Disable automatic upgrade\n\nBecause they will try to drain the only node, the pods will have no place to go.\nRemove them entirely:\n\n```sh\nrm -rf system/kured\n```\n\nCommit and push the change.\nYou can revert it later when you add more nodes.\n"
  },
  {
    "path": "docs/how-to-guides/troubleshooting/pxe-boot.md",
    "content": "# PXE boot\n\n## PXE server logs\n\nTo view PXE server (includes DHCP, TFTP and HTTP server) logs:\n\n```sh\n./scripts/pxe-logs\n```\n\n!!! tip\n\n    You can view the logs of one or more containers selectively, for example:\n\n    ```sh\n    ./scripts/pxe-logs dnsmasq\n    ./scripts/pxe-logs http\n    ```\n\n## Nodes not booting from the network\n\n- Plug a monitor and a keyboard to one of the bare metal node if possible to make the debugging process easier\n- Check if the controller (PXE server) is on the same subnet with bare metal nodes (sometimes Wifi will not work or conflict with wired Ethernet, try to turn it off)\n- Check if bare metal nodes are configured to boot from the network\n- Check if Wake-on-LAN is enabled\n- Check if the operating system ISO file is mounted\n- Check the controller's firewall config to make sure that the following ports are open:\n    - DHCP (67/68)\n    - TFTP (69)\n    - HTTP (80)\n- Check PXE server Docker logs\n- Check if the servers are booting to the correct OS (Fedora Server installer instead of the previously installed OS), if not try to select it manually or remove the previous OS boot entry\n- Examine the network boot process with [Wireshark](https://www.wireshark.org) or [Termshark](https://termshark.io)\n"
  },
  {
    "path": "docs/how-to-guides/updating-documentation.md",
    "content": "# Updating documentation (this website)\n\nThis project uses the [Diátaxis](https://diataxis.fr) technical documentation framework.\nThe website is generated using [Material for MkDocs](https://squidfunk.github.io/mkdocs-material) and can be viewed at [homelab.khuedoan.com](https://homelab.khuedoan.com).\n\nThere are 4 main parts:\n\n- [Getting started (tutorials)](https://diataxis.fr/tutorials): learning-oriented\n- [Concepts (explanation)](https://diataxis.fr/explanation): understanding-oriented\n- [How-to guides](https://diataxis.fr/how-to-guides): goal-oriented\n- [Reference](https://diataxis.fr/reference): information-oriented\n\n## Local development\n\nTo edit and view locally, run:\n\n```sh\nmake docs\n```\n\nThen visit [localhost:8000](http://localhost:8000)\n\n## Deployment\n\nIt's running on my other cluster in the [khuedoan/horus](https://github.com/khuedoan/horus) project\n(so if the homelab goes down everyone can still read the documentation).\n\n<!-- TODO -->\n<!-- This website is running in both my homelab cluster and on my other cluster in the [khuedoan/horus](https://github.com/khuedoan/horus) project (both in `apps/homelab-docs`), -->\n<!-- with manual DNS switch over in case I want to rebuild either of them (this is the most cost effective way to do this that I can think of). -->\n\n<!-- You don't have to do this, you can host it on 1 cluster just fine. -->\n<!-- But for 0.000000000001% of you who have 2 clusters like me, here's how to switch between them: -->\n\n<!-- - Add the following annotation to the Ingress on the new cluster: `TODO` -->\n<!-- - Go to DNS config on Cloudflare dashboard -->\n<!-- - Find the TXT record for `homelab.khuedoan.com` and switch the `ownerID` between `homelab` and `horus` -->\n<!-- - Wait for the matching CNAME or A record to change -->\n<!-- - Check if you can still access the website -->\n<!-- - Do what ever you want to do -->\n<!-- - (Optional) Switch back to the previous cluster -->\n"
  },
  {
    "path": "docs/how-to-guides/use-both-github-and-gitea.md",
    "content": "# Use both GitHub and Gitea\n\nEven though we self-host Gitea, you may still want to use GitHub as a backup and for discovery.\n\nAdd both push URLs (replace my repositories with yours):\n\n```sh\ngit remote set-url --add --push origin git@git.khuedoan.com:ops/homelab\ngit remote set-url --add --push origin git@github.com:khuedoan/homelab\n```\n\nNow you can just run `git push` like usual and it will push to both GitHub and Gitea.\n"
  },
  {
    "path": "docs/index.md",
    "content": "--8<--\nREADME.md\n--8<--\n"
  },
  {
    "path": "docs/installation/post-installation.md",
    "content": "# Post-installation\n\n## Backup secrets\n\nSave the following files to a safe location like a password manager (if you're using the sandbox, you can skip this step):\n\n- `~/.ssh/id_ed25519`\n- `~/.ssh/id_ed25519.pub`\n- `./metal/kubeconfig.yaml`\n- `~/.terraform.d/credentials.tfrc.json`\n- `./external/terraform.tfvars`\n\n## Admin credentials\n\n- ArgoCD:\n    - Username: `admin`\n    - Password: run `./scripts/argocd-admin-password`\n- Gitea:\n    - Username: `gitea_admin`\n    - Password: get from `global-secrets` namespace\n- Kanidm:\n    - Usernames: `admin` and `idm_admin`\n    - Password: run `./scripts/kanidm-reset-password admin` and `./scripts/kanidm-reset-password idm_admin`\n- Jellyfin and other applications in the \\*arr stack: see the [dedicated guide for media management](../how-to-guides/media-management.md)\n- Other apps:\n    - Username: `admin`\n    - Password: get from `global-secrets` namespace\n\n## Backup\n\nNow is a good time to set up backups for your homelab.\nFollow the [backup and restore guide](../how-to-guides/backup-and-restore.md) to get started.\n\n## Run the full test suite\n\nAfter the homelab has been stabilized, you can run the full test suite to ensure that everything is working properly:\n\n```sh\nmake test\n```\n\n!!! info\n\n    The \"full\" test suit is still in its early stages, so any contribution is greatly appreciated.\n"
  },
  {
    "path": "docs/installation/production/configuration.md",
    "content": "# Configuration\n\nOpen the [development shell](../../concepts/development-shell.md), which includes all the tools needed:\n\n```sh\nnix develop\n```\n\nRun the following script to configure the homelab:\n\n```sh\nmake configure\n```\n\n!!! example\n\n    <!-- TODO update example input -->\n\n    ```\n    Text editor (nvim):\n    Enter seed repo (github.com/khuedoan/homelab): github.com/example/homelab\n    Enter your domain (khuedoan.com): example.com\n    ```\n\nIt will prompt you to edit the inventory:\n\n- IP address: the desired one, not the current one, since your servers have no operating system installed yet\n- Disk: based on `/dev/$DISK`, in my case it's `sda`, but yours can be `sdb`, `nvme0n1`...\n- Network interface: usually it's `eth0`, mine is `eno1`\n- MAC address: the **lowercase, colon separated** MAC address of the above network interface\n\n!!! example\n\n    ```yaml title=\"metal/inventories/prod.yml\"\n    --8<--\n    metal/inventories/prod.yml\n    --8<--\n    ```\n\nAt the end it will show what has changed. After examining the diff, commit and push the changes.\n"
  },
  {
    "path": "docs/installation/production/deployment.md",
    "content": "# Deployment\n\nOpen the development shell if you haven't already:\n\n```sh\nnix develop\n```\n\nBuild the lab:\n\n```sh\nmake\n```\n\nYes it's that simple!\n\n!!! example\n\n    <script id=\"asciicast-xkBRkwC6e9RAzVuMDXH3nGHp7\" src=\"https://asciinema.org/a/xkBRkwC6e9RAzVuMDXH3nGHp7.js\" async></script>\n\nIt will take a while to download everything,\nyou can read the [architecture document](../../reference/architecture/overview.md) while waiting for the deployment to complete.\n"
  },
  {
    "path": "docs/installation/production/external-resources.md",
    "content": "# External resources\n\n!!! info\n\n    These resources are optional, the homelab still works without them but will lack some features like trusted certificates and offsite backup\n\nAlthough I try to keep the amount of external resources to the minimum, there's still need for a few of them.\nBelow is a list of external resources and why we need them (also see some [alternatives](#alternatives) below).\n\n| Provider        | Resource  | Purpose                                                                                                     |\n| --------        | --------  | -------                                                                                                     |\n| Terraform Cloud | Workspace | Terraform state backend                                                                                     |\n| Cloudflare      | DNS       | DNS and [DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) for certificates |\n| Cloudflare      | Tunnel    | Public services to the internet without port forwarding                                                     |\n| ntfy            | Topic     | External notification service to receive alerts                                                             |\n\n## Create credentials\n\nYou'll be asked to provide these credentials on first build.\n\n### Create Terraform workspace\n\nTerraform is stateful, which means it needs somewhere to store its state. Terraform Cloud is one option for a state backend with a generous free tier, perfect for a homelab.\n\n1. Sign up for a [Terraform Cloud](https://cloud.hashicorp.com/products/terraform) account\n2. Create a workspace named `homelab-external`, this is the workspace where your homelab state will be stored.\n3. Change the \"Execution Mode\" from \"Remote\" to \"Local\". This will ensure your local machine, which can access your lab, is the one executing the Terraform plan rather than the cloud runners.\n\nIf you decide to use a [different Terraform backend](https://www.terraform.io/language/settings/backends#available-backends), you'll need to edit the `external/versions.tf` file as required.\n\n### Cloudflare\n\n- Buy a domain and [transfer it to Cloudflare](https://developers.cloudflare.com/registrar/get-started/transfer-domain-to-cloudflare) if you haven't already\n- Get Cloudflare email and account ID\n- Global API key: <https://dash.cloudflare.com/profile/api-tokens>\n\n<!-- TODO switch to API token instead of API key? -->\n<!-- Terraform API token summary: -->\n\n<!-- ``` -->\n<!-- This API token will affect the below accounts and zones, along with their respective permissions -->\n\n<!-- └── Khue Doan - Argo Tunnel:Edit, Account Settings:Read -->\n<!--     └── khuedoan.com - Zone:Read, DNS:Edit -->\n\n<!-- Client IP Address Filtering -->\n\n<!-- └── Is in - 117.xxx.xxx.xxx, 2402:xxx:xxx:xxx:xxx:xxx:xxx:xxx -->\n<!-- ``` -->\n\n<!-- ### Create Minio keys -->\n\n<!-- TODO: skip this for now -->\n\n<!-- ### Create AWS API key -->\n\n<!-- TODO: skip this for now -->\n\n### ntfy\n\n- Choose a topic name like <https://ntfy.sh/random_topic_name_here_a8sd7fkjxlkcjasdw33813> (treat it like you password)\n\n## Alternatives\n\nTo avoid vendor lock-in, each external provider must have an equivalent alternative that is easy to replace:\n\n- Terraform Cloud:\n    - Any other [Terraform backends](https://www.terraform.io/language/settings/backends)\n- Cloudflare DNS:\n    - Update cert-manager and external-dns to use a different provider\n    - [Alternate DNS setup](../../how-to-guides/alternate-dns-setup.md)\n- Cloudflare Tunnel:\n    - Use port forwarding if it's available\n    - Create a small VPS in the cloud and utilize Wireguard to route traffic via it\n    - Access everything via VPN\n    - See also [awesome tunneling](https://github.com/anderspitman/awesome-tunneling)\n- ntfy:\n    - [Self-host your own ntfy server](https://docs.ntfy.sh/install)\n    - Any other [integration supported by Grafana Alerting](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/manage-contact-points/integrations/#list-of-supported-integrations)\n"
  },
  {
    "path": "docs/installation/production/prerequisites.md",
    "content": "# Prerequisites\n\n## Fork this repository\n\nBecause [this project](https://github.com/khuedoan/homelab) applies GitOps practices,\nit's the source of truth for _my_ homelab, so you'll need to fork it to make it yours:\n\n[:fontawesome-solid-code-fork: Fork khuedoan/homelab](https://github.com/khuedoan/homelab/fork){ .md-button }\n\nBy using this project you agree to [the license](../../reference/license.md).\n\n\n!!! summary \"License TL;DR\"\n\n     - This project is free to use for any purpose, but it comes with no warranty\n     - You must use the same [GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html)  in `LICENSE.md`\n     - You must keep the copy right notice and/or include an acknowledgement\n     - Your project must remain open-source\n\n## Hardware requirements\n\n### Initial controller\n\n!!! info\n\n    The initial controller is the machine used to bootstrap the cluster, we only need it once, you can use your laptop or desktop\n\n- A **Linux** machine that can run Docker (because the `host` networking driver used for PXE boot [only supports Linux](https://docs.docker.com/network/host/), you can use a Linux virtual machine with bridged networking if you're on macOS or Windows).\n\n### Servers\n\nAny modern `x86_64` computer(s) should work, you can use old PCs, laptops or servers.\n\n!!! info\n\n    This is the requirements for _each_ node\n\n| Component  | Minimum                                                                                                      | Recommended                                                                                  |\n| :--        | :--                                                                                                          | :--                                                                                          |\n| CPU        | 2 cores                                                                                                      | 4 cores                                                                                      |\n| RAM        | 8 GB                                                                                                         | 16 GB                                                                                        |\n| Hard drive | 128 GB                                                                                                       | 512 GB (depending on your storage usage, the base installation will not use more than 128GB) |\n| Node count | 1 (checkout the [single node cluster adjustments](../../how-to-guides/single-node-cluster-adjustments.md) tutorial) | 3 or more for high availability                                                              |\n\nAdditional capabilities:\n\n- Ability to boot from the network (PXE boot)\n- Wake-on-LAN capability, used to wake the machines up automatically without physically touching the power button\n\n### Network setup\n\n- All servers must be connected to the same **wired** network with the initial controller\n- You have the access to change DNS config (on your router or at your domain registrar)\n\n## Domain\n\nBuying a domain is highly recommended, but if you don't have one, see [alternate DNS setup](../../how-to-guides/alternate-dns-setup.md).\n\n## BIOS setup\n\n!!! info\n\n    You need to do it once per machine if the default config is not sufficent,\n    usually for consumer hardware this can not be automated\n    (it requires something like [IPMI](https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface) to automate).\n\nCommon settings:\n\n- Enable Wake-on-LAN (WoL) and network boot\n- Use UEFI mode and disable CSM (legacy) mode\n- Disable secure boot\n\nBoot order options (select one, each has their pros and cons):\n\n1. Only boot from the network if no operating system found: works on most hardware but you need to manually wipe your hard drive or delete the existing boot record for the current OS\n2. Prefer booting from the network if turned on via WoL: more convenience but your BIOS must support it, and you must test it throughly to ensure you don't accidentally wipe your servers\n\n!!! example\n\n    Below is my BIOS setup for reference. Your motherboard may have a different name for the options, so you'll need to adapt it to your hardware.\n\n    ```yaml\n    Devices:\n      NetworkSetup:\n        PXEIPv4: true\n        PXEIPv6: false\n    Advanced:\n      CPUSetup:\n        VT-d: true\n    Power:\n      AutomaticPowerOn:\n        WoL: Automatic  # Use network boot if Wake-on-LAN\n    Security:\n      SecureBoot: false\n    Startup:\n      CSM: false\n    ```\n\n## Gather information\n\n- [ ] MAC address for each machine\n- [ ] OS disk name (for example `/dev/sda`)\n- [ ] Network interface name (for example `eth0`)\n- [ ] Choose a static IP address for each machine (just the desired address, we don't set anything up yet)\n"
  },
  {
    "path": "docs/installation/sandbox.md",
    "content": "# Development sandbox\n\nThe sandbox is intended for trying out the homelab without any hardware or testing changes before applying them to the production environment.\n\n## Prerequisites\n\nHost machine:\n\n- Recommended hardware specifications:\n    - CPU: 4 cores\n    - RAM: 16 GiB\n- OS: Linux (Windows and macOS are untested, please let me know if it works)\n- Available ports: `80` and `443`\n\nInstall the following packages:\n\n- `docker`\n- `nix` (see [development shell](../concepts/development-shell.md) for the installation guide)\n\nClone the repository and checkout the development branch:\n\n```sh\ngit clone https://github.com/khuedoan/homelab\ngit checkout dev\n```\n\n## Build\n\nOpen the development shell, which includes all the tools needed:\n\n```sh\nnix develop\n```\n\nBuild a development cluster and bootstrap it:\n\n```\nmake\n```\n\n!!! note\n\n    It will take about 15 to 30 minutes to build depending on your internet connection\n\n## Explore\n\nThe homepage should be available at <https://home.127-0-0-1.nip.io> (ignore the security warning because we don't have valid certificates).\n\nSee [admin credentials](../post-installation/#admin-credentials) for default passwords.\n\nIf you want to make some changes, simply commit to the local `dev` branch and push it to Gitea in the sandbox:\n\n```sh\ngit remote add sandbox https://git.127-0-0-1.nip.io/ops/homelab\ngit config http.https://git.127-0-0-1.nip.io.sslVerify false\n\ngit add foobar.txt\ngit commit -m \"feat: harness the power of the sun\"\ngit push sandbox # you can use the gitea_admin account\n```\n\n## Clean up\n\nDelete the cluster:\n\n```sh\nk3d cluster delete homelab-dev\n```\n\n## Caveats compare to production environment\n\nThe development cluster doesn't have the following features:\n\n- There is no valid domain name, hence no SSL certificates (some services require valid SSL certificates)\n- Only accessible on the host machine\n- No backup\n\nPlease keep in mind that the development cluster may be unstable and things may break (it's for development after all).\n"
  },
  {
    "path": "docs/reference/architecture/decision-records.md",
    "content": "# Decision records\n\nThese are the records of design decisions for future reference in order to understand why things are the way they are.\nThey are not permanent, we can change them in the future if better alternatives become available.\n\n??? Template\n\n    ## Description of the the change\n\n    **Context**\n\n    CHANGEME\n\n    **Decision**\n\n    CHANGEME\n\n    **Consequences**\n\n    - CHANGEME\n\n## Remove the Docker wrapper for Nix shell\n\n**Context**\n\nThe Docker wrapper was originally added because not everyone has Nix installed.\nHowever, it is not a good abstraction. Depending on how Docker (or Podman) was\ninstalled, there may be permission issues, and it is slower than running Nix\ndirectly on the host.\n\n**Decision**\n\nRemove the Docker wrapper and run the Nix development shell directly. While not\neveryone has Nix installed, not everyone has the same Docker setup either.\n\n**Consequences**\n\n- Requires an additional step to install Nix, but this is a one-time setup.\n- The `make tools` command has been removed, you now need to run `nix develop`\n  directly. Although it can be added to the `Makefile`, this requires `make` to\n  be installed, and not everyone has the same version of `make`.\n\n## Remove HashiCorp Vault\n\n**Context**\n\n- HashiCorp changed their license, and it's no longer free/libre software.\n  One of the highest priorities of this project is to minimize\n  the usage of non-free software as much as possible, so I don't really\n  want to keep Vault, especially considering the next point.\n- Vault is fairly complex to maintain properly. This project only uses\n  Vault for two things: basic key-value secret store and its API to\n  create and manage secrets dynamically. With the new Kubernetes secret\n  provider in External Secrets, both features can be replaced with\n  Kubernetes's built-in secrets and API server.\n- A related goal of using Vault as an identity provider for SSO will be\n  discarded, and we'll use Authelia instead, which has a beta identity\n  provider feature (or use another alternative).\n\n**Decision**\n\nReplace Vault with a simplier in-cluster global secret store.\n\n**Consequences**\n\nUnlike secret path in Vault, Kubernetes does not support `/` in object name.\nWe need to change secret convention from `path` to `name` and replace `/` with `.`.\n\nUpdate secret generator config:\n\n```diff\n-- path: gitea/admin\n+- name: gitea.admin\n   data:\n     - key: password\n       length: 32\n```\n\nUpdate secret references in `kind: ExternalSecret`:\n\n```diff\n remoteRef:\n-  key: /gitea/admin\n+  key: gitea.admin\n```\n\n## Manage package versions in development shell\n\n**Context**\n\nWhile Nix is reproducible, we need a way to control the versions of the tools and keep them up-to-date.\nFor example, if we update the nixpkgs hash (in `flake.nix`) from `abcd1234` to `defa5678`:\n\n- `ansible`: 2.12.1 -> 2.12.6\n- `terraform`: 1.2.0 -> 1.2.2\n- `foobar`: 1.8.0 -> 1.9.0\n\nThat looks good. But when we update it from `defa5678` to `cdef9012`:\n\n- `ansible`: 2.12.6 -> 2.13.0\n- `terraform`: 1.2.2 -> 1.3.1\n- `foobar`: 1.9.0 -> 2.0.0\n\nThis time it breaks `foobar` because the new major version contains a breaking change.\n\nWe can pin the specific version of each dependency in `flake.nix`,\nhowever, the maintenance burden is too high (even with Renovate) because we need to update the version of each package regularly rather than just the nixpkgs hash.\nInstead, we can just bump the nixpkgs hash and run some tests to ensure there is no breaking change.\n\n**Decision**\n\nUpdate the tests to ensure that the versions remain within the desired range (i.e. no breaking change).\n\n**Consequences**\n\nWe have the rail guard from the tests to ensure that we don't upgrade to a new major version with breaking changes,\nand we can make a conscious decision to take the necessary steps prior to upgrading to the new major version.\n\n## Refactor the tools container from plain Dockerfile to [Nix](https://nixos.org)\n\n**Context**\n\nThe previous implementation of the tools container is not reproducible, if someone builds it a few weeks after me, they will receive different versions of the tools.\nAlso, if you change something in the tool list, everything will be downloaded again, with no caching.\n\n**Decision**\n\nMove to Nix shell with a Docker wrapper, in case Nix is not available (see commit `adbaf32`).\n\n**Consequences**\n\n- All versions are pinned\n- When you change the list of tools, rebuilding is much faster\n\n## Combine dhcpd and tftpd to dnsmasq in PXE server\n\n**Context**\n\n[Original proposal from @Bourne-ID](https://github.com/khuedoan/homelab/issues/70):\n\n> **Issue statement**\n>\n> The use of dhcpd is great for air-gap solutions where a new DHCP is required. However for some home networks which does not have the VLAN capability or for users who would like to use common router DHCP services, the use of DHCPD will cause duplicate DHCP servers and will result in potential network disruption, or will limit the ability to auto-provision the Metal stage of this project.\n>\n> **Proposed Solution: DHCP Proxy**\n>\n> Use DHCP Proxy services to add PXE features such as Next Server into this project. This allows for users to use the existing DHCP servers which may be locked down or incapable of using Next Server/PXE settings on their network to be able to auto-provision hardware through PXE (with certain common configurations, like static IP allocation or reduction in DHCP request ranges on the DHCP server)\n>\n> **Proposed Application: DNSMasq**\n>\n> DNSMasq in Proxy mode interoperates with existing DHCP servers over IPv4 to add features such as next-server, TFTP, etc. where such hardware is either locked or unconfigurable for such services. This would be an opt-in change, configurable through the pxe_server defaults file.\n>\n> **Proposed Target Audience**\n>\n> Users who either do not want to create their own VLAN or lack the hardware to configure such services. Users who want to use common router services for DHCP and have router access to configure static IP and/or DHCP allocation ranges.\n>\n> **Additional Risks with Proposed Change**\n>\n>   - Additional Surface Area for Break-Out Attacks: Originally this project is locked to its own DHCP/VLAN, so any break-outs should be contained accordingly. Using common home networks increases the surface area of break-out attacks if the deployment is compromised.\n>   - Mitigation: Enrolment into this change is opt in only.\n>\n> **Proposed Next Steps**\n>\n> 1. Trial/Adopt/Halt - A discussion with all or a decision by the project maintainers to identify if this change should exist in this project or live on a fork.\n> 2. Documentation (This is in flight in any situation).\n\n**Decision**\n\nMigrate to dnsmasq (see commit `f650c89`, thanks to [@Bourne-ID](https://github.com/Bourne-ID))\n\n**Consequences**\n\n- DHCP proxy is enabled by default because most people have a standard home network with existing DHCP server, but it can still be disabled to restore the previous behavior\n\n## Migrate documentation from [mdBook](https://rust-lang.github.io/mdBook) to [MkDocs](https://squidfunk.github.io/mkdocs-material)\n\n**Context**\n\nmdBook is very minimal and light weight, which I personally prefer.\nHowever, [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) which I plan to use currently only supports mkDocs.\n\n**Decision**\n\nMigrate documentation from mdBook to MkDocs (see commit `cd41343`).\n\n**Consequences**\n\n- We can no longer include only a portion of a file, see [facelessuser/pymdown-extensions#1462](https://github.com/facelessuser/pymdown-extensions/issues/1462).\n\n## Choosing the base OS\n\n**Context**\n\nI've tried several distributions, and each has advantages and disadvantages.\nFedora has a good (enough) balance between stability and new features.\n\nAlternatives considered:\n\n- Fedora CoreOS (moved to Rocky in `7667254`):\n    - Pros: automatic and atomic upgrade, immutable, quick installation\n    - Cons: hard to run Ansible on (Python is not included)\n- CentOS/Rocky Linux (moved to Fedora in `022b816`):\n    - Pros: relatively stable (however we did encounter a breaking change [#63](https://github.com/khuedoan/homelab/issues/63), still not sure why)\n    - Cons: kernel and packages are too old\n- Debian: couldn't get it to work with PXE boot and Rocky Linux was sufficient so I didn't push any further\n- Cluster API (previous attempt in ` a8e4a85`, I hope to get this to work someday):\n    - Pros: control bare metal machines via Kubernetes API, open the possibility for autoscaling and autohealing\n    - Cons: doesn't support simple WoL and shutdown via SSH (or similar)\n\n**Decision**\n\nUse Fedora as the base OS.\n\n**Consequences**\n\n`¯\\_(ツ)_/¯`\n"
  },
  {
    "path": "docs/reference/architecture/networking.md",
    "content": "# Networking\n\n```mermaid\nflowchart TD\n  subgraph LAN\n    laptop/desktop/phone <--> LoadBalancer\n    subgraph k8s[Kubernetes cluster]\n      Pod --> Service\n      Service --> Ingress\n\n      LoadBalancer\n\n      cloudflared\n      cloudflared <--> Ingress\n    end\n    LoadBalancer <--> Ingress\n  end\n\n  cloudflared -- outbound --> Cloudflare\n  Internet -- inbound --> Cloudflare\n```\n\nTODO (PR welcomed)\n"
  },
  {
    "path": "docs/reference/architecture/overview.md",
    "content": "# Overview\n\n## Components\n\n```\n+--------------+\n|    ./apps    |\n|--------------|\n|  ./platform  |\n|--------------|       +------------+\n|   ./system   |- - - -| ./external |\n|--------------|       +------------+\n|   ./metal    |\n|--------------|\n|   HARDWARE   |\n+--------------+\n```\n\nMain components:\n\n- `./metal`: bare metal management, install Linux and Kubernetes\n- `./system`: critical system components for the cluster (load balancer, storage, ingress, operation tools...)\n- `./platform`: essential components for service hosting platform (git, build runners, dashboards...)\n- `./apps`: user facing applications\n- `./external` (optional): externally managed services\n\nSupport components:\n\n- `./docs`: all documentation go here, this will generate a searchable web UI\n- `./scripts`: scripts to automate common tasks\n\n## Provisioning flow\n\nEverything is automated, after you edit the configuration files, you just need to run a single `make` command and it will:\n\n- (1) Build the `./metal` layer:\n    - Create an ephemeral, stateless PXE server\n    - Install Linux on all servers in parallel\n    - Build a Kubernetes cluster (based on k3s)\n- (2) Bootstrap the `./system` layer:\n    - Install ArgoCD and the root app to manage itself and other layers, from now on ArgoCD will do the rest\n    - Install the remaining components (storage, monitoring, etc)\n- (3) Build the `./platform` layer (Gitea, Grafana, SSO, etc)\n- (4) Deploy applications in the `./apps` layer\n\n```mermaid\nflowchart TD\n  subgraph metal[./metal]\n    pxe[PXE Server] -.-> linux[Fedora Server] --> k3s\n  end\n\n  subgraph system[./system]\n    argocd[ArgoCD and root app]\n    nginx[NGINX]\n    rook-ceph[Rook Ceph]\n    cert-manager\n    external-dns[External DNS]\n    cloudflared\n  end\n\n  subgraph external[./external]\n    letsencrypt[Let's Encrypt]\n    cloudflare[Cloudflare]\n  end\n\n  letsencrypt -.-> cert-manager\n  cloudflare -.-> cert-manager\n  cloudflare -.-> external-dns\n  cloudflare -.-> cloudflared\n\n  subgraph platform[./platform]\n    Gitea\n    Woodpecker\n    Grafana\n  end\n\n  subgraph apps[./apps]\n    homepage[Homepage]\n    jellyfin[Jellyfin]\n    matrix[Matrix]\n    paperless[Paperless]\n  end\n\n  make[Run make] -- 1 --> metal -- 2 --> system -. 3 .-> platform -. 4 .-> apps\n```\n"
  },
  {
    "path": "docs/reference/changelog.md",
    "content": "# Changelog\n\n## v0.0.8\n\nNotable changes:\n\n- **build:** run post install scripts by default\n- **build:** set `KUBECONFIG` from global Makefile\n- **feat(external-dns)!:** add cluster name as owner ID\n- **feat(tools):** install `yamllint`, `ansible-lint` and `k9s`\n- **feat(tools):** set `KUBECONFIG` by default\n- **feat:** add pre-commit hooks\n- **feat:** add script to setup Gitea tokens and OAuth apps\n- **perf(argocd):** turning on selective sync\n- **refactor(docs):** migrate to [mkdocs](https://squidfunk.github.io/mkdocs-material)\n- **refactor(metal):** migrate to Fedora 36 for newer packages\n- **refactor(pxe)!:** combine dhcpd and tftpd to dnsmasq\n- Many bug fixes\n\nPlease see git log for full change log.\n\n## 0.0.7-alpha\n\n- Replace standard Vault with Vault Operator\n- Automatically initialize and unseal Vault\n- Declarative secret generation and management\n- Declarative Gitea configuration with YAML\n- Automatic OS rolling upgrade\n- Automatic Kubernetes rolling upgrade\n- Automatic application updates using Renovate (still require manual token generation)\n- Add script to wait for essential services after deployment\n- Add icons and bookmarks to the home page\n- Deploy Matrix chat\n- Replace Authentik with Dex for SSO (still require manual token generation)\n- Switch to Mermaid for diagrams in documentation\n- Replace Vagrant with k3d for development environment\n- Use nip.io domain for development environment\n- Remove Backblaze (S3 Glacier and/or Minio will be added in future version)\n- Enable monitor for the majority of applications\n- Many code refactorings and bug fixes\n\n## 0.0.6-alpha\n\n- Upgrade to Kubernetes 1.23\n- Support external resources:\n    - Cloudflare DNS and Tunnel\n    - Backblaze for backup\n    - Auto inject secrets to required namespaces\n- Replace self-signed certificates with Let's Encrypt production (with API token injected from the `external` layer)\n- Add DNS records automatically using external-dns\n- Easy Cloudflare Tunnel configuration with annotations\n- Offsite backup to Backblaze B2 bucket using k8up-operator\n- Add private container registry\n- Remove Knative to save resources (temporarily)\n- Enable encryption at rest for Kubernetes Secrets\n- Add more Tekton tasks and pipelines\n- Initialize GitOps repository on Gitea automatically after install\n- Generate MetalLB address pool automatically (default to the last `/27` subnet)\n- Some bug fixes\n\n## 0.0.5-alpha\n\n- Add convenience scripts\n- Add Loki for logging\n- Add custom health check for Application and ApplicationSet\n- Use Vault with dev mode on (temporarily until we hit beta)\n- Replace Authelia with Authentik\n- Upgrade to Kubernetes 1.22\n- Upgrade most services to the latest version\n- Set ingress class and storage class explicitly\n- Initial Linkerd and Knative setup (not working yet)\n- Set up Hajimari for home page with automatic ingress discovery\n- Add dev VM for local development or evaluation\n- Optimize bare metal provisioning performance\n- Replace Syncthing with Seafile (may use both in the feature)\n- Enable Gitea SSH cloning via Ingress\n- Various code clean up\n- Add more documents\n\n## 0.0.4-alpha\n\n- Switch to Rocky Linux\n- Some optimization for bare metal provisioning\n- Switch to k3s and combine Kubernetes cluster config in `./infra` layer to `./metal` layer (because k3s is also configured using Ansible)\n- Split boostrap Helm charts in `./infra` layer to `./bootstrap` layer (with new ArgoCD pattern) and `./system` layer\n- Add `./platform` layer for some applications like Gitea, Tekton...\n- User only need to provision `./metal` and `bootstrap` layer, the `./bootstrap` layer will deploy the remaining layers\n- Provisioning time from empty disk to running services is significantly reduced (thanks to k3s and new bootstrap pattern)\n- Use [mdBook](https://rust-lang.github.io/mdBook/) for documents\n- Replace Drone CI with Tekton\n- Enable TLS on all Ingresses (using [cert-manager](https://cert-manager.io))\n- Add some new applications\n\n## 0.0.3-alpha\n\n- Generate Terraform backend config automatically\n- Switch to CoreOS\n- Better PXE boot setup\n- Diagrams as code\n\n## 0.0.2-alpha\n\n- Ensure idempotency for bare metal provisioning\n- Extract instead of mounting the OS ISO file\n- Easy initial controller setup (with only Docker)\n- Switch to Fedora\n- Remove LXD\n- Move etcd (Terraform state backend) back to Docker\n\n## 0.0.1-alpha\n\n- Bare metal provisioning with PXE\n- LXD cluster\n- Terraform state backend (etcd)\n- RKE cluster\n- Core services (Vault, Gitea, ArgoCD,...)\n- Public services to the internet (via port forwarding or Cloudflare Tunnel)\n"
  },
  {
    "path": "docs/reference/contributing.md",
    "content": "# Contributing\n\n## How to contribute\n\n### Bug report\n\nYou can [create a new GitHub issue](https://github.com/khuedoan/homelab/issues/new/choose) with the bug report template.\n\n### Submitting patches\n\nBecause you may have a lot of changes in your fork, you can't create a pull request directly from your `master` branch.\nInstead, create a branch from the upstream repository and commit your changes there:\n\n```sh\ngit remote add upstream https://github.com/khuedoan/homelab\ngit fetch upstream\ngit checkout upstream/master\ngit checkout -b contrib-fix-something\n\n# Make your changes here\n#\n# nvim README.md\n# git cherry-pick a1b2c3\n#\n# commit, push, etc. as usual\n```\n\nThen you can send the patch using [GitHub pull request](https://github.com/khuedoan/homelab/pulls) or `git send-email` to <mail@khuedoan.com>.\n"
  },
  {
    "path": "docs/reference/faq.md",
    "content": "# FAQ\n\n## Is it necessary to install Linux on my servers before setting up the homelab?\n\nNo, and that's the beauty of this setup.\nYou start with empty hard drives, type a single command on your laptop/PC,\nand it will install the operating system for you automatically and in parallel over the network.\n\n## Is it necessary to keep the PXE server running?\n\nNo, the ephemeral PXE server is stateless, once Linux is installed on your servers,\nyou can shut it down (or not, ideally, you don't even need to be aware of its existence).\nThe Ansible setup in `./metal` is idempotent and will start the PXE server if needed.\n"
  },
  {
    "path": "docs/reference/license.md",
    "content": "Copyright &copy; 2020 - 2024 Khue Doan\n\n--8<--\nLICENSE.md\n--8<--\n"
  },
  {
    "path": "docs/reference/roadmap.md",
    "content": "# Roadmap\n\n!!! info\n\n    Current status: **ALPHA**\n\n## Alpha requirements\n\nLiterally anything that works.\n\n## Beta requirements\n\nGood enough for tinkering and personal usage, and reasonably secure.\n\n- [x] Automated bare metal provisioning\n    - [x] Controller set up (Docker)\n    - [x] OS installation (PXE boot)\n- [x] Automated cluster creation (k3s)\n- [x] Automated application deployment (ArgoCD)\n- [x] Automated DNS management\n- [x] Initialize GitOps repository on Gitea automatically\n- [x] Observability\n    - [x] Monitoring\n    - [x] Logging\n    - [x] Alerting\n- [x] SSO\n- [ ] Reasonably secure\n    - [x] Automated certificate management\n    - [x] Declarative secret management\n    - [ ] Replace all default passwords with randomly generated ones\n    - [x] Expose services to the internet securely with Cloudflare Tunnel\n- [x] Only use open-source technologies (except external managed services in `./external`)\n- [x] Everything is defined as code\n- [ ] Backup solution (3 copies, 2 seperate devices, 1 offsite)\n- [ ] Define [SLOs](https://en.wikipedia.org/wiki/Service-level_objective):\n    - [ ] 70% availability (might break in the weekend due to new experimentation)\n- [x] Core applications\n    - [x] Gitea\n    - [x] Woodpecker\n    - [x] Private container registry\n    - [x] Homepage\n\n## Stable requirements\n\nCan be used in \"production\" (for family or even small scale businesses).\n\n- [x] A single command to deploy everything\n- [x] Fast deployment time (from empty hard drive to running services in under 1 hour)\n- [ ] Fully _automatic_, not just _automated_\n    - [x] Bare-metal OS rolling upgrade\n    - [x] Kubernetes version rolling upgrade\n    - [x] Application version upgrade\n    - [ ] Encrypted backups\n    - [ ] Secrets rotation\n    - [x] Self healing\n- [ ] Secure by default\n    - [ ] SELinux\n    - [ ] Network policies\n- [ ] Static code analysis\n- [ ] Chaos testing\n- [x] Minimal dependency on external services\n- [ ] Complete documentation\n    - [x] Diagram as code\n    - [x] Book (this book)\n    - [ ] Walkthrough tutorial and feature demo (video)\n- [x] Configuration script for new users\n- [ ] More dashboards and alert rules\n- [ ] SLOs:\n    - [ ] 99,9% availability (less than 9 hours of downtime per year)\n    - [ ] 99,99% data durability\n- [ ] Clear upgrade path\n- [ ] Additional applications\n    - [ ] Matrix with bridges\n    - [ ] VPN server\n    - [ ] PeerTube\n    - [x] Blog\n    - [ ] [Development dashboard](https://github.com/khuedoan/homelab-backstage)\n\n## Unplanned\n\nNice to have\n\n- [ ] Addition applications\n    - [ ] Mail server\n- [ ] Air-gap install\n- [ ] Automated testing\n- [ ] Security audit\n- [ ] Cluster API ([last attempt](https://github.com/khuedoan/homelab/pull/2))\n- [ ] Split DNS (requires a better router)\n"
  },
  {
    "path": "external/.gitignore",
    "content": ".terraform*\nterraform.tfstate\nterraform.tfvars\n"
  },
  {
    "path": "external/Makefile",
    "content": ".POSIX:\n\ndefault: apply\n\n~/.terraform.d/credentials.tfrc.json:\n\ttofu login\n\nterraform.tfvars:\n\tcp terraform.tfvars.example ${@}\n\tnvim ${@}\n\n.terraform.lock.hcl: ~/.terraform.d/credentials.tfrc.json versions.tf terraform.tfvars\n\ttofu init\n\ttouch ${@}\n\nnamespaces:\n\tansible-playbook namespaces.yml\n\nplan: .terraform.lock.hcl\n\ttofu plan\n\napply: .terraform.lock.hcl namespaces\n\ttofu apply -auto-approve\n"
  },
  {
    "path": "external/main.tf",
    "content": "module \"cloudflare\" {\n  source                = \"./modules/cloudflare\"\n  cloudflare_account_id = var.cloudflare_account_id\n  cloudflare_email      = var.cloudflare_email\n  cloudflare_api_key    = var.cloudflare_api_key\n}\n\nmodule \"ntfy\" {\n  source = \"./modules/ntfy\"\n  auth   = var.ntfy\n}\n\nmodule \"extra_secrets\" {\n  source = \"./modules/extra-secrets\"\n  data   = var.extra_secrets\n}\n"
  },
  {
    "path": "external/modules/cloudflare/main.tf",
    "content": "data \"cloudflare_zone\" \"zone\" {\n  name = \"khuedoan.com\"\n}\n\ndata \"cloudflare_api_token_permission_groups\" \"all\" {}\n\nresource \"random_password\" \"tunnel_secret\" {\n  length  = 64\n  special = false\n}\n\nresource \"cloudflare_tunnel\" \"homelab\" {\n  account_id = var.cloudflare_account_id\n  name       = \"homelab\"\n  secret     = base64encode(random_password.tunnel_secret.result)\n}\n\n# Not proxied, not accessible. Just a record for auto-created CNAMEs by external-dns.\nresource \"cloudflare_record\" \"tunnel\" {\n  zone_id = data.cloudflare_zone.zone.id\n  type    = \"CNAME\"\n  name    = \"homelab-tunnel\"\n  value   = \"${cloudflare_tunnel.homelab.id}.cfargotunnel.com\"\n  proxied = false\n  ttl     = 1 # Auto\n}\n\nresource \"kubernetes_secret\" \"cloudflared_credentials\" {\n  metadata {\n    name      = \"cloudflared-credentials\"\n    namespace = \"cloudflared\"\n\n    annotations = {\n      \"app.kubernetes.io/managed-by\" = \"Terraform\"\n    }\n  }\n\n  data = {\n    \"credentials.json\" = jsonencode({\n      AccountTag   = var.cloudflare_account_id\n      TunnelName   = cloudflare_tunnel.homelab.name\n      TunnelID     = cloudflare_tunnel.homelab.id\n      TunnelSecret = base64encode(random_password.tunnel_secret.result)\n    })\n  }\n}\n\nresource \"cloudflare_api_token\" \"external_dns\" {\n  name = \"homelab_external_dns\"\n\n  policy {\n    permission_groups = [\n      data.cloudflare_api_token_permission_groups.all.zone[\"Zone Read\"],\n      data.cloudflare_api_token_permission_groups.all.zone[\"DNS Write\"]\n    ]\n    resources = {\n      \"com.cloudflare.api.account.zone.*\" = \"*\"\n    }\n  }\n}\n\nresource \"kubernetes_secret\" \"external_dns_token\" {\n  metadata {\n    name      = \"cloudflare-api-token\"\n    namespace = \"external-dns\"\n\n    annotations = {\n      \"app.kubernetes.io/managed-by\" = \"Terraform\"\n    }\n  }\n\n  data = {\n    \"value\" = cloudflare_api_token.external_dns.value\n  }\n}\n\nresource \"cloudflare_api_token\" \"cert_manager\" {\n  name = \"homelab_cert_manager\"\n\n  policy {\n    permission_groups = [\n      data.cloudflare_api_token_permission_groups.all.zone[\"Zone Read\"],\n      data.cloudflare_api_token_permission_groups.all.zone[\"DNS Write\"]\n    ]\n    resources = {\n      \"com.cloudflare.api.account.zone.*\" = \"*\"\n    }\n  }\n}\n\nresource \"kubernetes_secret\" \"cert_manager_token\" {\n  metadata {\n    name      = \"cloudflare-api-token\"\n    namespace = \"cert-manager\"\n\n    annotations = {\n      \"app.kubernetes.io/managed-by\" = \"Terraform\"\n    }\n  }\n\n  data = {\n    \"api-token\" = cloudflare_api_token.cert_manager.value\n  }\n}\n"
  },
  {
    "path": "external/modules/cloudflare/variables.tf",
    "content": "variable \"cloudflare_email\" {\n  type = string\n}\n\nvariable \"cloudflare_api_key\" {\n  type      = string\n  sensitive = true\n}\n\nvariable \"cloudflare_account_id\" {\n  type = string\n}\n"
  },
  {
    "path": "external/modules/cloudflare/versions.tf",
    "content": "terraform {\n  required_providers {\n    cloudflare = {\n      source  = \"cloudflare/cloudflare\"\n      version = \"~> 4.30.0\"\n    }\n\n    kubernetes = {\n      source  = \"hashicorp/kubernetes\"\n      version = \"~> 2.26.0\"\n    }\n\n    http = {\n      source  = \"hashicorp/http\"\n      version = \"~> 3.4.0\"\n    }\n  }\n}\n\nprovider \"cloudflare\" {\n  email   = var.cloudflare_email\n  api_key = var.cloudflare_api_key\n}\n"
  },
  {
    "path": "external/modules/extra-secrets/main.tf",
    "content": "resource \"kubernetes_secret\" \"external\" {\n  metadata {\n    name      = var.name\n    namespace = var.namespace\n\n    annotations = {\n      \"app.kubernetes.io/managed-by\" = \"Terraform\"\n    }\n  }\n\n  data = var.data\n}\n"
  },
  {
    "path": "external/modules/extra-secrets/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"external\"\n}\n\nvariable \"namespace\" {\n  type    = string\n  default = \"global-secrets\"\n}\n\nvariable \"data\" {\n  type = map(string)\n}\n"
  },
  {
    "path": "external/modules/extra-secrets/versions.tf",
    "content": "terraform {\n  required_providers {\n    kubernetes = {\n      source  = \"hashicorp/kubernetes\"\n      version = \"~> 2.26.0\"\n    }\n  }\n}\n"
  },
  {
    "path": "external/modules/ntfy/main.tf",
    "content": "resource \"kubernetes_secret\" \"ntfy_auth\" {\n  metadata {\n    name      = \"webhook-transformer\"\n    namespace = \"monitoring-system\"\n\n    annotations = {\n      \"app.kubernetes.io/managed-by\" = \"Terraform\"\n    }\n  }\n\n  data = {\n    NTFY_URL   = var.auth.url\n    NTFY_TOPIC = var.auth.topic\n  }\n}\n"
  },
  {
    "path": "external/modules/ntfy/variables.tf",
    "content": "variable \"auth\" {\n  type = object({\n    url   = string\n    topic = string\n  })\n}\n"
  },
  {
    "path": "external/modules/ntfy/versions.tf",
    "content": "terraform {\n  required_providers {\n    kubernetes = {\n      source  = \"hashicorp/kubernetes\"\n      version = \"~> 2.26.0\"\n    }\n  }\n}\n"
  },
  {
    "path": "external/namespaces.yml",
    "content": "- hosts: localhost\n  tasks:\n    - name: Ensure required namespaces exist\n      kubernetes.core.k8s:\n        api_version: v1\n        kind: Namespace\n        name: \"{{ item }}\"\n        state: present\n      loop:\n        - cert-manager\n        - cloudflared\n        - external-dns\n        - global-secrets\n        - k8up-operator\n        - monitoring-system\n"
  },
  {
    "path": "external/terraform.tfvars.example",
    "content": "# https://dash.cloudflare.com/profile\ncloudflare_email = \"myaccount@example.com\"\n# https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids\ncloudflare_account_id = \"foobarid\"\n# https://dash.cloudflare.com/profile/api-tokens\ncloudflare_api_key = \"foobarkey\"\n\nntfy = {\n  # https://ntfy.sh or your own instance\n  url = \"https://ntfy.sh\"\n  # Your topic name\n  topic = \"random_topic_name_here_a8sd7fkjxlkcjasdw33813\"\n}\n\nextra_secrets = {\n  # Try to keep this to a minimum with third-party secrets\n  # Consider using the secret generator if possible\n  # ../platform/global-secrets/files/secret-generator/config.yaml\n  # Here's some examples of what you might want to add:\n  #\n  # wireguard-private-key = \"wg genkey output here\"\n  # tailscale-auth-key = \"tskey-auth-xxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n  # restic-password = \"xxxxxxxxxxxxxxxxxxxxxxxx\"\n  # restic-s3-bucket = \"https://s3.amazonaws.com/my-homelab-backup-xxxxxxxxxx\"\n  # restic-s3-access-key = \"xxxxxxxxxxxxxxxx\"\n  # restic-s3-secret-key = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n}\n"
  },
  {
    "path": "external/variables.tf",
    "content": "variable \"cloudflare_email\" {\n  type = string\n}\n\nvariable \"cloudflare_api_key\" {\n  type      = string\n  sensitive = true\n}\n\nvariable \"cloudflare_account_id\" {\n  type = string\n}\n\nvariable \"ntfy\" {\n  type = object({\n    url   = string\n    topic = string\n  })\n\n  sensitive = true\n}\n\nvariable \"extra_secrets\" {\n  type        = map(string)\n  description = \"Key-value pairs of extra secrets that cannot be randomly generated (e.g. third party API tokens)\"\n  sensitive   = true\n  default     = {}\n}\n"
  },
  {
    "path": "external/versions.tf",
    "content": "terraform {\n  required_version = \"~> 1.7\"\n\n  backend \"remote\" {\n    hostname     = \"app.terraform.io\"\n    organization = \"khuedoan\"\n\n    workspaces {\n      name = \"homelab-external\"\n    }\n  }\n\n  required_providers {\n    cloudflare = {\n      source  = \"cloudflare/cloudflare\"\n      version = \"~> 4.30.0\"\n    }\n\n    kubernetes = {\n      source  = \"hashicorp/kubernetes\"\n      version = \"~> 2.26.0\"\n    }\n\n    http = {\n      source  = \"hashicorp/http\"\n      version = \"~> 3.4.0\"\n    }\n  }\n}\n\nprovider \"cloudflare\" {\n  email   = var.cloudflare_email\n  api_key = var.cloudflare_api_key\n}\n\nprovider \"kubernetes\" {\n  # Use KUBE_CONFIG_PATH environment variables\n  # Or in cluster service account\n}\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"Homelab\";\n\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-24.05\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n  };\n\n  outputs = { self, nixpkgs, flake-utils }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        pkgs = import nixpkgs { inherit system; };\n      in\n      with pkgs;\n      {\n        devShells.default = mkShell {\n          packages = [\n            ansible\n            ansible-lint\n            bmake\n            diffutils\n            docker\n            docker-compose\n            dyff\n            git\n            glibcLocales\n            go\n            gotestsum\n            iproute2\n            jq\n            k9s\n            kanidm\n            kube3d\n            kubectl\n            kubernetes-helm\n            kustomize\n            libisoburn\n            neovim\n            openssh_gssapi\n            opentofu # Drop-in replacement for Terraform\n            p7zip\n            pre-commit\n            qrencode\n            shellcheck\n            wireguard-tools\n            yamllint\n\n            (python3.withPackages (p: with p; [\n              jinja2\n              kubernetes\n              mkdocs-material\n              netaddr\n              pexpect\n              rich\n            ]))\n          ];\n        };\n      }\n    );\n}\n"
  },
  {
    "path": "metal/Makefile",
    "content": ".POSIX:\n\nenv ?= prod\nexport KUBECONFIG = $(shell pwd)/kubeconfig.yaml\n\ndefault: boot cluster\n\n~/.ssh/id_ed25519:\n\tssh-keygen -t ed25519 -P '' -f \"$@\"\n\nboot: ~/.ssh/id_ed25519\n\tansible-playbook \\\n\t\t--inventory inventories/${env}.yml \\\n\t\tboot.yml\n\ncluster:\n\tansible-playbook \\\n\t\t--inventory inventories/${env}.yml \\\n\t\tcluster.yml\n\nconsole:\n\tansible-console \\\n\t\t--inventory inventories/${env}.yml\n"
  },
  {
    "path": "metal/ansible.cfg",
    "content": "[defaults]\nhost_key_checking=false\nstdout_callback=debug\nstderr_callback=debug\nforce_color=true\n"
  },
  {
    "path": "metal/boot.yml",
    "content": "- name: Start PXE server\n  hosts: localhost\n  roles:\n    - pxe_server\n\n- name: Provision bare metal machines\n  hosts: metal\n  gather_facts: false\n  roles:\n    - wake\n"
  },
  {
    "path": "metal/cluster.yml",
    "content": "- name: Create Kubernetes cluster\n  hosts: metal\n  roles:\n    - prerequisites\n    - k3s\n    - automatic_upgrade\n\n- name: Install Kubernetes addons\n  hosts: localhost\n  roles:\n    - cilium\n"
  },
  {
    "path": "metal/group_vars/all.yml",
    "content": "ansible_user: root\nansible_ssh_private_key_file: ~/.ssh/id_ed25519\nssh_public_key: \"{{ lookup('file', '~/.ssh/id_ed25519.pub') }}\"\ndns_server: \"8.8.8.8\"\n"
  },
  {
    "path": "metal/inventories/prod.yml",
    "content": "all:\n  vars:\n    control_plane_endpoint: 192.168.1.100\n    load_balancer_ip_pool:\n      - 192.168.1.224/27\nmetal:\n  children:\n    masters:\n      hosts:\n        metal0: {ansible_host: 192.168.1.110, mac: '00:23:24:d1:f5:69', disk: sda, network_interface: eno1}\n        metal1: {ansible_host: 192.168.1.111, mac: '00:23:24:d1:f3:f0', disk: sda, network_interface: eno1}\n        metal2: {ansible_host: 192.168.1.112, mac: '00:23:24:e7:04:60', disk: sda, network_interface: eno1}\n    workers:\n      hosts:\n        metal3: {ansible_host: 192.168.1.113, mac: '00:23:24:d1:f4:d6', disk: sda, network_interface: eno1}\n"
  },
  {
    "path": "metal/inventories/stag.yml",
    "content": "metal:\n  children:\n    masters:\n      hosts:\n        proxmox0: {ansible_host: 192.168.1.169, mac: 'c2:f5:cf:1f:3e:c0', disk: sda, network_interface: ens18}\n    workers:\n      hosts: {}\n"
  },
  {
    "path": "metal/k3d-dev.yaml",
    "content": "apiVersion: k3d.io/v1alpha4\nkind: Simple\nmetadata:\n  name: homelab-dev\nimage: docker.io/rancher/k3s:v1.27.1-k3s1\nservers: 1\nagents: 0\noptions:\n  k3s:\n    extraArgs:\n      - arg: --disable=traefik\n        nodeFilters:\n          - server:*\n      - arg: --disable-cloud-controller\n        nodeFilters:\n          - server:*\nports:\n  - port: 80:80\n    nodeFilters:\n      - loadbalancer\n  - port: 443:443\n    nodeFilters:\n      - loadbalancer\n"
  },
  {
    "path": "metal/roles/automatic_upgrade/files/automatic.conf",
    "content": "[commands]\nupgrade_type = default\napply_updates = yes\n"
  },
  {
    "path": "metal/roles/automatic_upgrade/tasks/main.yml",
    "content": "- name: Install packages for automatic upgrade\n  ansible.builtin.dnf:\n    name:\n      - dnf-automatic\n      - dnf-utils\n\n- name: Copy automatic upgrade config file\n  ansible.builtin.copy:\n    src: automatic.conf\n    dest: /etc/dnf/automatic.conf\n    mode: 0644\n\n- name: Enable automatic upgrade service\n  ansible.builtin.systemd:\n    name: dnf-automatic.timer\n    state: started\n    enabled: true\n"
  },
  {
    "path": "metal/roles/cilium/defaults/main.yml",
    "content": "cilium_repo_url: https://helm.cilium.io\ncilium_version: 1.16.1\ncilium_namespace: kube-system\ncilium_values:\n  operator:\n    replicas: 1\n  kubeProxyReplacement: true\n  l2announcements:\n    enabled: true\n  # TODO the host and port are k3s-specific, generic solution is in progress\n  # https://github.com/cilium/cilium/issues/19038\n  # https://github.com/cilium/cilium/pull/28741\n  k8sServiceHost: 127.0.0.1\n  k8sServicePort: 6444\n  hubble:\n    relay:\n      enabled: true\n    ui:\n      enabled: true\n"
  },
  {
    "path": "metal/roles/cilium/tasks/main.yml",
    "content": "- name: Install Cilium\n  kubernetes.core.helm:\n    name: cilium\n    chart_ref: cilium\n    chart_repo_url: \"{{ cilium_repo_url }}\"\n    chart_version: \"{{ cilium_version }}\"\n    release_namespace: \"{{ cilium_namespace }}\"\n    values: \"{{ cilium_values }}\"\n\n- name: Wait for Cilium CRDs\n  kubernetes.core.k8s_info:\n    kind: CustomResourceDefinition\n    name: \"{{ item }}\"\n  loop:\n    - ciliuml2announcementpolicies.cilium.io\n    - ciliumloadbalancerippools.cilium.io\n  register: crd\n  until: crd.resources | length > 0\n  retries: 5\n  delay: 10\n\n- name: Apply Cilium resources\n  kubernetes.core.k8s:\n    template: \"{{ item }}\"\n  loop:\n    - ciliuml2announcementpolicy.yaml\n    - ciliumloadbalancerippool.yaml\n"
  },
  {
    "path": "metal/roles/cilium/templates/ciliuml2announcementpolicy.yaml",
    "content": "apiVersion: cilium.io/v2alpha1\nkind: CiliumL2AnnouncementPolicy\nmetadata:\n  name: default\nspec:\n  externalIPs: true\n  loadBalancerIPs: true\n"
  },
  {
    "path": "metal/roles/cilium/templates/ciliumloadbalancerippool.yaml",
    "content": "apiVersion: cilium.io/v2alpha1\nkind: CiliumLoadBalancerIPPool\nmetadata:\n  name: default\nspec:\n  blocks:\n    {% for cidr in load_balancer_ip_pool %}\n    - cidr: {{ cidr }}\n    {% endfor %}\n"
  },
  {
    "path": "metal/roles/k3s/defaults/main.yml",
    "content": "k3s_version: v1.30.4+k3s1\nk3s_config_file: /etc/rancher/k3s/config.yaml\nk3s_token_file: /etc/rancher/node/password\nk3s_service_file: /etc/systemd/system/k3s.service\nk3s_data_dir: /var/lib/rancher/k3s\nk3s_kubeconfig_file: /etc/rancher/k3s/k3s.yaml\nk3s_server_config:\n  tls-san:\n    - \"{{ control_plane_endpoint }}\"\n  disable:\n    - local-storage\n    - servicelb\n    - traefik\n  disable-helm-controller: true\n  disable-kube-proxy: true\n  disable-network-policy: true\n  flannel-backend: none\n  secrets-encryption: true\n"
  },
  {
    "path": "metal/roles/k3s/files/bin/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "metal/roles/k3s/tasks/main.yml",
    "content": "- name: Download k3s binary\n  ansible.builtin.get_url:\n    url: https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/k3s\n    checksum: sha256:https://github.com/k3s-io/k3s/releases/download/{{ k3s_version }}/sha256sum-amd64.txt\n    dest: \"{{ role_path }}/files/bin/k3s\"\n    mode: 0755\n  delegate_to: localhost\n  run_once: true\n  register: k3s_binary\n\n- name: Copy k3s binary to nodes\n  ansible.builtin.copy:\n    src: bin/k3s\n    dest: /usr/local/bin/k3s\n    owner: root\n    group: root\n    mode: 0755\n\n- name: Ensure config directories exist\n  ansible.builtin.file:\n    path: \"{{ item }}\"\n    state: directory\n    mode: 0755\n  loop:\n    - /etc/rancher/k3s\n    - /etc/rancher/node\n    - \"{{ k3s_data_dir }}/agent/pod-manifests\"\n\n- name: Check if k3s token file exists on the first node\n  run_once: true\n  ansible.builtin.stat:\n    path: \"{{ k3s_token_file }}\"\n  register: k3s_token_file_stat\n\n- name: Generate k3s token file on the first node if not exist yet\n  run_once: true\n  when: not k3s_token_file_stat.stat.exists\n  ansible.builtin.copy:\n    content: \"{{ lookup('community.general.random_string', length=32) }}\"\n    dest: \"{{ k3s_token_file }}\"\n    mode: 0600\n\n- name: Get k3s token from the first node\n  run_once: true\n  ansible.builtin.slurp:\n    src: \"{{ k3s_token_file }}\"\n  register: k3s_token_base64\n\n- name: Ensure all nodes has the same token\n  ansible.builtin.copy:\n    content: \"{{ k3s_token_base64.content | b64decode }}\"\n    dest: \"{{ k3s_token_file }}\"\n    mode: 0600\n\n- name: Copy k3s config files\n  ansible.builtin.template:\n    src: \"{{ item.src }}\"\n    dest: \"{{ item.dest }}\"\n    mode: 0644\n  loop:\n    - src: config.yaml.j2\n      dest: \"{{ k3s_config_file }}\"\n    - src: k3s.service.j2\n      dest: \"{{ k3s_service_file }}\"\n\n- name: Copy kube-vip manifests\n  when: \"'masters' in group_names\"\n  ansible.builtin.template:\n    src: \"{{ item.src }}\"\n    dest: \"{{ item.dest }}\"\n    mode: 0644\n  loop:\n    - src: kube-vip.yaml.j2\n      dest: \"{{ k3s_data_dir }}/agent/pod-manifests/kube-vip.yaml\"\n\n- name: Enable k3s service\n  ansible.builtin.systemd:\n    name: k3s\n    enabled: true\n    state: started\n  register: k3s_service\n  until: k3s_service is succeeded\n  retries: 5\n\n- name: Get Kubernetes config file\n  run_once: true\n  ansible.builtin.slurp:\n    src: \"{{ k3s_kubeconfig_file }}\"\n  register: kubeconfig_base64\n\n- name: Write Kubernetes config file with the correct cluster address\n  ansible.builtin.copy:\n    content: \"{{ kubeconfig_base64.content | b64decode | replace('127.0.0.1', control_plane_endpoint) }}\"\n    dest: \"{{ playbook_dir }}/kubeconfig.yaml\"\n    mode: 0600\n  delegate_to: localhost\n  run_once: true\n"
  },
  {
    "path": "metal/roles/k3s/templates/config.yaml.j2",
    "content": "{% if inventory_hostname == groups['masters'][0] %}\ncluster-init: true\n{% else %}\nserver: https://{{ control_plane_endpoint }}:6443\n{% endif %}\ntoken-file: {{ k3s_token_file }}\n{% if 'masters' in group_names %}\n{{ k3s_server_config | to_nice_yaml }}\n{% endif %}\nsnapshotter: stargz\n"
  },
  {
    "path": "metal/roles/k3s/templates/k3s.service.j2",
    "content": "[Unit]\nDescription=Lightweight Kubernetes\nDocumentation=https://k3s.io\nAfter=network-online.target\n\n[Service]\nType=notify\nExecStartPre=-/sbin/modprobe br_netfilter\nExecStartPre=-/sbin/modprobe overlay\nExecStart=/usr/local/bin/k3s {{ 'server' if 'masters' in group_names else 'agent' }}\nKillMode=process\nDelegate=yes\n# Having non-zero Limit*s causes performance problems due to accounting overhead\n# in the kernel. We recommend using cgroups to do container-local accounting.\nLimitNOFILE=1048576\nLimitNPROC=infinity\nLimitCORE=infinity\nTasksMax=infinity\nTimeoutStartSec=0\nRestart=always\nRestartSec=5s\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "metal/roles/k3s/templates/kube-vip.yaml.j2",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: kube-vip\n  namespace: kube-system\nspec:\n  containers:\n    - name: kube-vip\n      image: ghcr.io/kube-vip/kube-vip:v0.6.4\n      args:\n        - manager\n      env:\n        - name: address\n          value: {{ control_plane_endpoint }}\n        - name: vip_arp\n          value: \"true\"\n        - name: cp_enable\n          value: \"true\"\n        - name: vip_leaderelection\n          value: \"true\"\n        - name: lb_enable\n          value: \"true\"\n      securityContext:\n        capabilities:\n          add:\n            - NET_ADMIN\n            - NET_RAW\n      volumeMounts:\n        - mountPath: /etc/kubernetes/admin.conf\n          name: kubeconfig\n  hostAliases:\n    - hostnames:\n        - kubernetes\n      ip: 127.0.0.1\n  hostNetwork: true\n  volumes:\n    - hostPath:\n        path: {{ k3s_kubeconfig_file }}\n      name: kubeconfig\n"
  },
  {
    "path": "metal/roles/prerequisites/tasks/main.yml",
    "content": "- name: Adjust kernel parameters\n  ansible.posix.sysctl:\n    name: \"{{ item.name }}\"\n    value: \"{{ item.value }}\"\n  loop:\n    - {name: \"fs.inotify.max_queued_events\", value: 16384}\n    - {name: \"fs.inotify.max_user_instances\", value: 8192}\n    - {name: \"fs.inotify.max_user_watches\", value: 524288}\n"
  },
  {
    "path": "metal/roles/pxe_server/defaults/main.yml",
    "content": "iso_url: \"https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/39/Server/x86_64/iso/Fedora-Server-dvd-x86_64-39-1.5.iso\"\niso_checksum: \"sha256:2755cdff6ac6365c75be60334bf1935ade838fc18de53d4c640a13d3e904f6e9\"\ntimezone: Asia/Ho_Chi_Minh\ndhcp_proxy: true\n"
  },
  {
    "path": "metal/roles/pxe_server/files/data/init-config/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "metal/roles/pxe_server/files/data/iso/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "metal/roles/pxe_server/files/data/os/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "metal/roles/pxe_server/files/data/pxe-config/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "metal/roles/pxe_server/files/dnsmasq/Dockerfile",
    "content": "FROM alpine:3.19\n\nRUN apk --no-cache add dnsmasq\n\nENTRYPOINT [\"dnsmasq\", \"-k\"]\n"
  },
  {
    "path": "metal/roles/pxe_server/files/docker-compose.yml",
    "content": "services:\n  dnsmasq:\n    build: ./dnsmasq\n    volumes:\n      - ./data/pxe-config/dnsmasq.conf:/etc/dnsmasq.conf\n      - ./data/pxe-config/grub.cfg:/tftp/grub.cfg\n      - ./data/os/EFI/BOOT/grubx64.efi:/tftp/grubx64.efi\n      - ./data/os/images/pxeboot/initrd.img:/tftp/initrd.img\n      - ./data/os/images/pxeboot/vmlinuz:/tftp/vmlinuz\n    network_mode: host\n    cap_add:\n      - NET_ADMIN\n  http:\n    build: ./http\n    network_mode: host\n    volumes:\n      - ./data/os:/usr/share/nginx/html/os\n      - ./data/init-config/:/usr/share/nginx/html/init-config\n    environment:\n      NGINX_PORT: 80\n"
  },
  {
    "path": "metal/roles/pxe_server/files/http/Dockerfile",
    "content": "FROM nginx:1.25-alpine\n"
  },
  {
    "path": "metal/roles/pxe_server/tasks/main.yml",
    "content": "- name: Get Docker info\n  docker_host_info: {}\n  register: docker_info_result\n\n- name: Ensure Docker is running on a supported operating system\n  fail:\n    msg: Docker host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac or Windows (you can use a Linux VM with bridged networking instead)\n  when:\n    - docker_info_result.host_info.OperatingSystem == \"Docker Desktop\"\n\n- name: Download boot image\n  ansible.builtin.get_url:\n    url: \"{{ iso_url }}\"\n    dest: \"{{ role_path }}/files/data/iso/{{ iso_url | basename }}\"\n    checksum: \"{{ iso_checksum }}\"\n    mode: 0644\n  register: iso\n\n- name: Extract boot image\n  ansible.builtin.command:\n    cmd: \"xorriso -osirrox on -indev {{ iso.dest }} -extract / {{ role_path }}/files/data/os\"\n    creates: \"{{ role_path }}/files/data/os/.treeinfo\"\n\n- name: Generate dnsmasq config\n  ansible.builtin.template:\n    src: dnsmasq.conf.j2\n    dest: \"{{ role_path }}/files/data/pxe-config/dnsmasq.conf\"\n    mode: 0644\n\n- name: Generate GRUB config\n  ansible.builtin.template:\n    src: grub.cfg.j2\n    dest: \"{{ role_path }}/files/data/pxe-config/grub.cfg\"\n    mode: 0644\n\n- name: Generate init config for each machine\n  ansible.builtin.template:\n    src: kickstart.ks.j2\n    dest: \"{{ role_path }}/files/data/init-config/{{ hostvars[item]['mac'] }}.ks\"\n    mode: 0644\n  loop: \"{{ groups['metal'] }}\"\n\n- name: Start the ephemeral PXE server\n  community.docker.docker_compose_v2:\n    project_src: \"{{ role_path }}/files\"\n    state: present\n    build: always\n"
  },
  {
    "path": "metal/roles/pxe_server/templates/dnsmasq.conf.j2",
    "content": "# Disable DNS Server.\nport=0\n{% if dhcp_proxy == true %}\n# We're DHCP proxying on the network of the homelab host\ndhcp-range={{ ansible_default_ipv4.address }},proxy\npxe-service=X86-64_EFI, \"Boot From Network, (UEFI)\", grubx64.efi\n{% else %}\n# We're DHCP configuring on this range\ndhcp-range={{ ansible_default_ipv4.network | ansible.netcommon.ipmath(1) }},{{ ansible_default_ipv4.broadcast | ansible.netcommon.ipmath(-1) }},{{ ansible_default_ipv4.netmask }},12h\ndhcp-option=3,{{ ansible_default_ipv4.gateway }}\n\n# Match Arch Types efi x86 and x64\ndhcp-match=set:efi-x86_64,option:client-arch,7\ndhcp-match=set:efi-x86_64,option:client-arch,9\n\n# Set the Boot file based on the tag from above\ndhcp-boot=tag:efi-x86_64,grubx64.efi\n{% endif %}\n# Log DHCP queries to stdout\nlog-queries\nlog-dhcp\nlog-facility=-\n\n# Enable TFTP server\nenable-tftp\ntftp-root=/tftp\n"
  },
  {
    "path": "metal/roles/pxe_server/templates/grub.cfg.j2",
    "content": "set timeout=1\n\nmenuentry '{{ iso_url | basename | splitext | first }} (PXE)' {\n    linux vmlinuz \\\n        ip=dhcp \\\n        inst.ks=http://{{ ansible_default_ipv4.address }}/init-config/${net_default_mac}.ks\n    initrd initrd.img\n}\n"
  },
  {
    "path": "metal/roles/pxe_server/templates/kickstart.ks.j2",
    "content": "#version=RHEL8\n\n# Do not use graphical install\ntext\n\n# Keyboard layouts\nkeyboard --xlayouts='us'\n# System language\nlang en_US.UTF-8\n\n# Partition clearing information\nclearpart --all --drives={{ hostvars[item]['disk'] }}\n# Partitioning\nignoredisk --only-use={{ hostvars[item]['disk'] }}\npartition /boot/efi --fstype=vfat --size=512\npartition / --fstype=ext4 --size=65536\n\n# Network information\nnetwork --bootproto=static --device={{ hostvars[item]['network_interface'] }} --ip={{ hostvars[item]['ansible_host'] }} --gateway={{ ansible_default_ipv4.gateway }} --nameserver={{ dns_server }} --netmask={{ ansible_default_ipv4.netmask }} --ipv6=auto --hostname={{ hostvars[item]['inventory_hostname'] }} --activate\n\n# Use network installation\nrepo --name=\"Repository\" --baseurl=http://{{ ansible_default_ipv4.address }}/os\nurl --url=\"http://{{ ansible_default_ipv4.address }}/os\"\n# Disable Setup Agent on first boot\nfirstboot --disable\n# Do not configure the X Window System\nskipx\n# Enable NTP\nservices --enabled=\"chronyd\"\n# System timezone\ntimezone {{ timezone }} --utc\n\n# Create user (locked by default)\nuser --groups=wheel --name=admin\n# Add SSH key\nsshkey --username=root \"{{ ssh_public_key }}\"\n# Disable root password login\nrootpw --lock\n\n# Disable SELinux\nselinux --disabled\n\n# Disable firewall\nfirewall --disabled\n\n%packages\n@^custom-environment\nopenssh-server\n%end\n\n# Create a raw partition for Ceph using the remaining space\n# Using a post script because there is no built-in feature in Kickstart\n# The three empty lines are equivalent to pressing Enter to use the default values for:\n# - Partition number\n# - First sector\n# - Last sector\n%post\nfdisk /dev/{{ hostvars[item]['disk'] }} << EOF\nnew\n\n\n\nwrite\nEOF\n%end\n\nreboot\n"
  },
  {
    "path": "metal/roles/wake/tasks/main.yml",
    "content": "- name: Send Wake-on-LAN magic packets\n  community.general.wakeonlan:\n    mac: \"{{ mac }}\"\n  delegate_to: localhost\n\n- name: Wait for the machines to come online\n  ansible.builtin.wait_for_connection:\n    timeout: 600\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json\n\nsite_name: Khue's Homelab\ncopyright: Copyright &copy; 2020 - 2024 Khue Doan\n\nrepo_url: https://github.com/khuedoan/homelab\n\ntheme:\n  favicon: https://github.com/khuedoan/homelab/assets/27996771/d33be1af-3687-4712-a671-4370da13cc92\n  name: material\n  palette:\n    primary: black\n  features:\n    - navigation.expand\n    - navigation.instant\n    - navigation.sections\n    - search.highlight\n    - search.share\n\nmarkdown_extensions:\n  - pymdownx.emoji:\n      emoji_index: !!python/name:material.extensions.emoji.twemoji\n      emoji_generator: !!python/name:material.extensions.emoji.to_svg\n  - attr_list\n  - admonition\n  - pymdownx.details\n  - pymdownx.snippets:\n      check_paths: true\n  - def_list\n  - pymdownx.tasklist:\n  - pymdownx.superfences:\n      custom_fences:\n        - name: mermaid\n          class: mermaid\n          format: !!python/name:pymdownx.superfences.fence_code_format\n  - pymdownx.tabbed:\n      alternate_style: true\n\nnav:\n  - Home: index.md\n  - Installation:\n    - installation/sandbox.md\n    - Production:\n      - installation/production/prerequisites.md\n      - installation/production/external-resources.md\n      - installation/production/configuration.md\n      - installation/production/deployment.md\n    - installation/post-installation.md\n  - Getting started:\n    - getting-started/vpn-setup.md\n    - getting-started/user-onboarding.md\n    - getting-started/install-pre-commit-hooks.md\n  - Concepts:\n    - concepts/pxe-boot.md\n    - concepts/secrets-management.md\n    - concepts/certificate-management.md\n    - concepts/development-shell.md\n    - concepts/testing.md\n  - How-to guides:\n    - how-to-guides/alternate-dns-setup.md\n    - how-to-guides/expose-services-to-the-internet.md\n    - how-to-guides/backup-and-restore.md\n    - how-to-guides/use-both-github-and-gitea.md\n    - how-to-guides/add-or-remove-nodes.md\n    - how-to-guides/run-commands-on-multiple-nodes.md\n    - how-to-guides/single-node-cluster-adjustments.md\n    - how-to-guides/disable-dhcp-proxy-in-dnsmasq.md\n    - how-to-guides/media-management.md\n    - how-to-guides/updating-documentation.md\n    - Troubleshooting:\n      - how-to-guides/troubleshooting/pxe-boot.md\n  - Reference:\n    - Architecture:\n      - reference/architecture/overview.md\n      - reference/architecture/networking.md\n      - reference/architecture/decision-records.md\n    - reference/license.md\n    - reference/changelog.md\n    - reference/roadmap.md\n    - reference/contributing.md\n    - reference/faq.md\n"
  },
  {
    "path": "platform/dex/Chart.yaml",
    "content": "apiVersion: v2\nname: dex\nversion: 0.0.0\ndependencies:\n  - name: dex\n    version: 0.16.0\n    repository: https://charts.dexidp.io\n"
  },
  {
    "path": "platform/dex/templates/secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: dex-secrets\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  target:\n    name: dex-secrets\n  data:\n    # Connectors\n    - secretKey: KANIDM_CLIENT_ID\n      remoteRef:\n        key: kanidm.dex\n        property: client_id\n    - secretKey: KANIDM_CLIENT_SECRET\n      remoteRef:\n        key: kanidm.dex\n        property: client_secret\n    # Clients\n    - secretKey: GRAFANA_SSO_CLIENT_SECRET\n      remoteRef:\n        key: dex.grafana\n        property: client_secret\n    - secretKey: GITEA_CLIENT_SECRET\n      remoteRef:\n        key: dex.gitea\n        property: client_secret\n"
  },
  {
    "path": "platform/dex/values.yaml",
    "content": "dex:\n  config:\n    issuer: https://dex.khuedoan.com\n    storage:\n      type: kubernetes\n      config:\n        inCluster: true\n    oauth2:\n      skipApprovalScreen: true\n    connectors:\n      - type: oidc\n        id: kanidm\n        name: Kanidm\n        config:\n          clientID: $KANIDM_CLIENT_ID\n          clientSecret: $KANIDM_CLIENT_SECRET\n          redirectURI: https://dex.khuedoan.com/callback\n          issuer: https://auth.khuedoan.com/oauth2/openid/dex\n          # TODO https://github.com/dexidp/dex/pull/3188\n          # enablePKCE: true\n          scopes:\n            - openid\n            - profile\n            - email\n            - groups\n    staticClients:\n      - id: grafana-sso\n        name: Grafana\n        redirectURIs:\n          - 'https://grafana.khuedoan.com/login/generic_oauth'\n        secretEnv: GRAFANA_SSO_CLIENT_SECRET\n      - id: gitea\n        name: Gitea\n        redirectURIs:\n          - 'https://git.khuedoan.com/user/oauth2/Dex/callback'\n        secretEnv: GITEA_CLIENT_SECRET\n  envFrom:\n    - secretRef:\n        name: dex-secrets\n  ingress:\n    enabled: true\n    className: nginx\n    annotations:\n      cert-manager.io/cluster-issuer: letsencrypt-prod\n    hosts:\n      - host: &host dex.khuedoan.com\n        paths:\n          - path: /\n            pathType: ImplementationSpecific\n    tls:\n      - secretName: dex-tls-certificate\n        hosts:\n          - *host\n"
  },
  {
    "path": "platform/external-secrets/Chart.yaml",
    "content": "apiVersion: v2\nname: external-secrets\nversion: 0.0.0\ndependencies:\n  - name: external-secrets\n    version: 0.10.2\n    repository: https://charts.external-secrets.io\n"
  },
  {
    "path": "platform/gitea/Chart.yaml",
    "content": "apiVersion: v2\nname: gitea\nversion: 0.0.0\ndependencies:\n  - name: gitea\n    version: 10.1.3\n    repository: https://dl.gitea.io/charts/\n"
  },
  {
    "path": "platform/gitea/files/config/config.yaml",
    "content": "# TODO create user and access token\n# users:\n#   - name: renovate\n#     fullName: Renovate\n#     email: bot@renovateapp.com\n#     tokenSecretRef: renovate-secret # ???\norganizations:\n  - name: ops\n    description: Operations\n    teams:\n      - name: Owners\n        members:\n          - renovate\nrepositories:\n  - name: homelab\n    owner: ops\n    private: false\n    migrate:\n      source: https://github.com/khuedoan/homelab\n      mirror: false\n  - name: blog\n    owner: khuedoan\n    migrate:\n      source: https://github.com/khuedoan/blog\n      mirror: true\n  - name: backstage\n    owner: khuedoan\n    migrate:\n      source: https://github.com/khuedoan/backstage\n      mirror: true\n"
  },
  {
    "path": "platform/gitea/files/config/go.mod",
    "content": "module git.khuedoan.com/khuedoan/homelab/gitea/config\n\ngo 1.19\n\nrequire (\n\tcode.gitea.io/sdk/gitea v0.15.1\n\tgopkg.in/yaml.v2 v2.4.0\n)\n\nrequire github.com/hashicorp/go-version v1.2.1 // indirect\n"
  },
  {
    "path": "platform/gitea/files/config/go.sum",
    "content": "code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=\ncode.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=\ncode.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=\ngithub.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\n"
  },
  {
    "path": "platform/gitea/files/config/main.go",
    "content": "package main\n\n// TODO WIP clean this up\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"code.gitea.io/sdk/gitea\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype Organization struct {\n\tName        string\n\tDescription string\n}\n\ntype Repository struct {\n\tName    string\n\tOwner   string\n\tPrivate bool\n\tMigrate struct {\n\t\tSource string\n\t\tMirror bool\n\t}\n}\n\ntype Config struct {\n\tOrganizations []Organization\n\tRepositories  []Repository\n}\n\nfunc main() {\n\tdata, err := os.ReadFile(\"./config.yaml\")\n\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to read config file: %v\", err)\n\t}\n\n\tconfig := Config{}\n\n\terr = yaml.Unmarshal([]byte(data), &config)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"error: %v\", err)\n\t}\n\n\tgitea_host := os.Getenv(\"GITEA_HOST\")\n\tgitea_user := os.Getenv(\"GITEA_USER\")\n\tgitea_password := os.Getenv(\"GITEA_PASSWORD\")\n\n\toptions := (gitea.SetBasicAuth(gitea_user, gitea_password))\n\tclient, err := gitea.NewClient(gitea_host, options)\n\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfor _, org := range config.Organizations {\n\t\t_, _, err = client.CreateOrg(gitea.CreateOrgOption{\n\t\t\tName:        org.Name,\n\t\t\tDescription: org.Description,\n\t\t})\n\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Create organization %s: %v\", org.Name, err)\n\t\t}\n\t}\n\n\tfor _, repo := range config.Repositories {\n\t\tif repo.Migrate.Source != \"\" {\n\t\t\t_, _, err = client.MigrateRepo(gitea.MigrateRepoOption{\n\t\t\t\tRepoName:       repo.Name,\n\t\t\t\tRepoOwner:      repo.Owner,\n\t\t\t\tCloneAddr:      repo.Migrate.Source,\n\t\t\t\tService:        gitea.GitServicePlain,\n\t\t\t\tMirror:         repo.Migrate.Mirror,\n\t\t\t\tPrivate:        repo.Private,\n\t\t\t\tMirrorInterval: \"10m\",\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Migrate %s/%s: %v\", repo.Owner, repo.Name, err)\n\t\t\t}\n\t\t} else {\n\t\t\t_, _, err = client.AdminCreateRepo(repo.Owner, gitea.CreateRepoOption{\n\t\t\t\tName: repo.Name,\n\t\t\t\t// Description: \"TODO\",\n\t\t\t\tPrivate: repo.Private,\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "platform/gitea/templates/admin-secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: {{ .Values.gitea.gitea.admin.existingSecret }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  target:\n    template:\n      engineVersion: v2\n      data:\n        username: gitea_admin\n        password: {{` \"{{ .password }}\" `}}\n  data:\n    - secretKey: password\n      remoteRef:\n        key: gitea.admin\n        property: password\n"
  },
  {
    "path": "platform/gitea/templates/config-job.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: gitea-config-{{ include (print $.Template.BasePath \"/config-source.yaml\") . | sha256sum | trunc 7 }}\n  namespace: {{ .Release.Namespace }}\n  annotations:\n    argocd.argoproj.io/sync-wave: \"1\"\nspec:\n  backoffLimit: 10\n  template:\n    spec:\n      restartPolicy: Never\n      containers:\n        - name: apply\n          image: golang:1.19-alpine\n          env:\n            - name: GITEA_HOST\n              value: http://gitea-http:3000\n            - name: GITEA_USER\n              valueFrom:\n                secretKeyRef:\n                  name: gitea-admin-secret\n                  key: username\n            - name: GITEA_PASSWORD\n              valueFrom:\n                secretKeyRef:\n                  name: gitea-admin-secret\n                  key: password\n          workingDir: /go/src/gitea-config\n          command:\n            - sh\n            - -c\n          args:\n            - |\n              go get .\n              go run .\n          volumeMounts:\n            - name: source\n              mountPath: /go/src/gitea-config\n      volumes:\n        - name: source\n          configMap:\n            name: gitea-config-source\n"
  },
  {
    "path": "platform/gitea/templates/config-source.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: gitea-config-source\n  namespace: {{ .Release.Namespace }}\ndata:\n{{ (.Files.Glob \"files/config/*\").AsConfig | indent 2 }}\n"
  },
  {
    "path": "platform/gitea/values.yaml",
    "content": "gitea:\n  ingress:\n    enabled: true\n    className: nginx\n    annotations:\n      cert-manager.io/cluster-issuer: letsencrypt-prod\n    hosts:\n      - host: &host git.khuedoan.com\n        paths:\n          - path: /\n            pathType: Prefix\n    tls:\n      - secretName: gitea-tls-certificate\n        hosts:\n          - *host\n  gitea:\n    admin:\n      existingSecret: gitea-admin-secret\n    config:\n      server:\n        LANDING_PAGE: explore\n        ROOT_URL: https://git.khuedoan.com\n        OFFLINE_MODE: true\n      repository:\n        DISABLED_REPO_UNITS: repo.wiki,repo.projects,repo.packages\n        DISABLE_STARS: true\n        DEFAULT_BRANCH: master\n      # TODO it's not reading the username from Dex correctly for now, related issues:\n      # https://github.com/go-gitea/gitea/issues/25725\n      # https://github.com/go-gitea/gitea/issues/24957\n      # oauth2_client:\n      #   ENABLE_AUTO_REGISTRATION: true\n      #   USERNAME: userid\n      service.explore:\n        DISABLE_USERS_PAGE: true\n      actions:\n        ENABLED: false\n      webhook:\n        ALLOWED_HOST_LIST: private\n"
  },
  {
    "path": "platform/global-secrets/Chart.yaml",
    "content": "apiVersion: v2\nname: global-secrets\nversion: 0.0.0\n"
  },
  {
    "path": "platform/global-secrets/files/secret-generator/config.yaml",
    "content": "# Gitea\n- name: gitea.admin\n  data:\n    - key: password\n      length: 32\n      special: true\n\n# Dex\n- name: dex.grafana\n  data:\n    - key: client_secret\n      length: 32\n      special: false\n- name: dex.gitea\n  data:\n    - key: client_secret\n      length: 32\n      special: false\n\n# Registry\n- name: registry.admin\n  data:\n    - key: password\n      length: 32\n      special: true\n\n# Woodpecker\n- name: woodpecker.agent\n  data:\n    - key: secret\n      length: 32\n      special: false\n\n# Paperless\n- name: paperless.admin\n  data:\n    - key: PAPERLESS_ADMIN_PASSWORD\n      length: 32\n      special: true\n"
  },
  {
    "path": "platform/global-secrets/files/secret-generator/go.mod",
    "content": "module git.khuedoan.com/khuedoan/homelab/platform/secret-generator\n\ngo 1.19\n\nrequire (\n\tgithub.com/sethvargo/go-password v0.2.0\n\tgopkg.in/yaml.v2 v2.4.0\n\tk8s.io/api v0.28.4\n\tk8s.io/apimachinery v0.28.4\n\tk8s.io/client-go v0.28.4\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.9.0 // indirect\n\tgithub.com/go-logr/logr v1.2.4 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.6 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.22.3 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/protobuf v1.5.3 // indirect\n\tgithub.com/google/gnostic-models v0.6.8 // indirect\n\tgithub.com/google/go-cmp v0.5.9 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/imdario/mergo v0.3.6 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgolang.org/x/net v0.17.0 // indirect\n\tgolang.org/x/oauth2 v0.8.0 // indirect\n\tgolang.org/x/sys v0.13.0 // indirect\n\tgolang.org/x/term v0.13.0 // indirect\n\tgolang.org/x/text v0.13.0 // indirect\n\tgolang.org/x/time v0.3.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/protobuf v1.31.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/klog/v2 v2.100.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect\n\tk8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect\n\tsigs.k8s.io/yaml v1.3.0 // indirect\n)\n"
  },
  {
    "path": "platform/global-secrets/files/secret-generator/go.sum",
    "content": "github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=\ngithub.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=\ngithub.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=\ngithub.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=\ngithub.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=\ngithub.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=\ngithub.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=\ngolang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=\ngolang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=\ngoogle.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=\nk8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=\nk8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=\nk8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=\nk8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=\nk8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=\nk8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=\nk8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=\nk8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=\nk8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=\nk8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=\nsigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=\nsigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=\n"
  },
  {
    "path": "platform/global-secrets/files/secret-generator/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\n\t\"github.com/sethvargo/go-password/password\"\n\t\"gopkg.in/yaml.v2\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nconst namespace = \"global-secrets\"\n\ntype RandomSecret struct {\n\tName string\n\tData []struct {\n\t\tKey     string\n\t\tLength  int\n\t\tSpecial bool\n\t}\n}\n\nfunc getClient() (*kubernetes.Clientset, error) {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\toverrides := &clientcmd.ConfigOverrides{}\n\n\tconfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides).ClientConfig()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Error building client config: %v\", err)\n\t}\n\n\treturn kubernetes.NewForConfig(config)\n}\n\nfunc generateRandomPassword(length int, special bool) (string, error) {\n\tnumDigits := int(math.Ceil(float64(length) * 0.2))\n\tnumSymbols := 0\n\n\tif special {\n\t\tnumSymbols = int(math.Ceil(float64(length) * 0.2))\n\t}\n\n\treturn password.Generate(length, numDigits, numSymbols, false, true)\n}\n\nfunc readConfigFile(filename string) ([]RandomSecret, error) {\n\tdata, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to read config file: %v\", err)\n\t}\n\n\tvar randomSecrets []RandomSecret\n\terr = yaml.Unmarshal(data, &randomSecrets)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Error parsing config file: %v\", err)\n\t}\n\n\treturn randomSecrets, nil\n}\n\nfunc createOrUpdateSecret(client *kubernetes.Clientset, name string, randomSecret RandomSecret) error {\n\tsecret, err := client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})\n\n\tif err != nil {\n\t\t// Secret not found, create a new one\n\t\tsecretData := map[string][]byte{}\n\n\t\tfor _, randomPassword := range randomSecret.Data {\n\t\t\tpassword, err := generateRandomPassword(randomPassword.Length, randomPassword.Special)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error generating password for key '%s': %v\", randomPassword.Key, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsecretData[randomPassword.Key] = []byte(password)\n\t\t}\n\n\t\tnewSecret := &v1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      name,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t\tData: secretData,\n\t\t}\n\n\t\t_, err := client.CoreV1().Secrets(namespace).Create(context.Background(), newSecret, metav1.CreateOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Unable to create secret: %v\", err)\n\t\t}\n\t\tlog.Printf(\"Secret '%s' created successfully.\", name)\n\t} else {\n\t\t// Secret exists, check for new keys\n\t\tfor _, randomKey := range randomSecret.Data {\n\t\t\tif _, exists := secret.Data[randomKey.Key]; !exists {\n\t\t\t\t// New key found, generate new password\n\t\t\t\tlog.Printf(\"New key '%s' found in config for secret '%s', generating new password\", randomKey.Key, name)\n\t\t\t\tpassword, err := generateRandomPassword(randomKey.Length, randomKey.Special)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error generating password for key '%s': %v\", randomKey.Key, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsecret.Data[randomKey.Key] = []byte(password)\n\t\t\t}\n\t\t}\n\n\t\t// Update the secret\n\t\t_, err := client.CoreV1().Secrets(namespace).Update(context.Background(), secret, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Unable to update secret: %v\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc main() {\n\tconfigFilename := \"./config.yaml\"\n\trandomSecrets, err := readConfigFile(configFilename)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error reading config file: %v\", err)\n\t}\n\n\tclient, err := getClient()\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to create Kubernetes client: %v\", err)\n\t}\n\n\tfor _, randomSecret := range randomSecrets {\n\t\terr := createOrUpdateSecret(client, randomSecret.Name, randomSecret)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error processing secret %s: %v\", randomSecret.Name, err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "platform/global-secrets/templates/clustersecretstore/clustersecretstore.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ClusterSecretStore\nmetadata:\n  name: global-secrets\nspec:\n  provider:\n    kubernetes:\n      remoteNamespace: {{ .Release.Namespace }}\n      server:\n        caProvider:\n          type: ConfigMap\n          name: kube-root-ca.crt\n          namespace: {{ .Release.Namespace }}\n          key: ca.crt\n      auth:\n        serviceAccount:\n          name: external-secrets-kubernetes-global-secrets\n          namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "platform/global-secrets/templates/clustersecretstore/role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: external-secrets-kubernetes-global-secrets\n  namespace: {{ .Release.Namespace }}\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - authorization.k8s.io\n    resources:\n      - selfsubjectrulesreviews\n    verbs:\n      - create\n"
  },
  {
    "path": "platform/global-secrets/templates/clustersecretstore/rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: external-secrets-kubernetes-global-secrets\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: external-secrets-kubernetes-global-secrets\nsubjects:\n  - kind: ServiceAccount\n    name: external-secrets-kubernetes-global-secrets\n    namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "platform/global-secrets/templates/clustersecretstore/serviceaccount.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: external-secrets-kubernetes-global-secrets\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "platform/global-secrets/templates/secret-generator/configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: secret-generator\n  namespace: {{ .Release.Namespace }}\ndata:\n{{ (.Files.Glob \"files/secret-generator/*\").AsConfig | indent 2 }}\n"
  },
  {
    "path": "platform/global-secrets/templates/secret-generator/job.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: secret-generator-{{ include (print $.Template.BasePath \"/secret-generator/configmap.yaml\") . | sha256sum | trunc 7 }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  backoffLimit: 3\n  template:\n    spec:\n      restartPolicy: Never\n      containers:\n        - name: secret-generator\n          image: golang:1.19-alpine\n          workingDir: /go/src/secret-generator\n          command:\n            - sh\n            - -c\n          args:\n            - |\n              go get .\n              go run .\n          volumeMounts:\n            - name: source\n              mountPath: /go/src/secret-generator\n      serviceAccount: secret-generator\n      volumes:\n        - name: source\n          configMap:\n            name: secret-generator\n"
  },
  {
    "path": "platform/global-secrets/templates/secret-generator/role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-generator\n  namespace: {{ .Release.Namespace }}\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - patch\n  - apiGroups:\n      - authorization.k8s.io\n    resources:\n      - selfsubjectrulesreviews\n    verbs:\n      - create\n"
  },
  {
    "path": "platform/global-secrets/templates/secret-generator/rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: secret-generator\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: secret-generator\nsubjects:\n  - kind: ServiceAccount\n    name: secret-generator\n    namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "platform/global-secrets/templates/secret-generator/serviceaccount.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: secret-generator\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "platform/grafana/Chart.yaml",
    "content": "apiVersion: v2\nname: grafana\nversion: 0.0.0\ndependencies:\n  - name: grafana\n    repository: https://grafana.github.io/helm-charts\n    version: 7.3.3\n"
  },
  {
    "path": "platform/grafana/templates/secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: grafana-secrets\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  target:\n    name: grafana-secrets\n  data:\n    - secretKey: GRAFANA_SSO_CLIENT_SECRET\n      remoteRef:\n        key: dex.grafana\n        property: client_secret\n"
  },
  {
    "path": "platform/grafana/values.yaml",
    "content": "grafana:\n  ingress:\n    enabled: true\n    ingressClassName: nginx\n    annotations:\n      cert-manager.io/cluster-issuer: letsencrypt-prod\n    hosts:\n      - &host grafana.khuedoan.com\n    tls:\n      - secretName: grafana-general-tls\n        hosts:\n          - *host\n  sidecar:\n    dashboards:\n      enabled: true\n      searchNamespace: monitoring-system\n    datasources:\n      enabled: true\n      searchNamespace: monitoring-system\n  envFromSecret: grafana-secrets\n  grafana.ini:\n    server:\n      root_url: https://grafana.khuedoan.com\n    auth.generic_oauth:\n      enabled: true\n      allow_sign_up: true\n      name: Dex\n      client_id: grafana-sso\n      client_secret: $__env{GRAFANA_SSO_CLIENT_SECRET}\n      scopes: openid profile email groups\n      auth_url: https://dex.khuedoan.com/auth\n      token_url: https://dex.khuedoan.com/token\n      api_url: https://dex.khuedoan.com/userinfo\n"
  },
  {
    "path": "platform/kanidm/Chart.yaml",
    "content": "apiVersion: v2\nname: kanidm\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 2.2.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "platform/kanidm/templates/certificate.yaml",
    "content": "# TODO https://github.com/kanidm/kanidm/issues/1227\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: kanidm-selfsigned\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretName: kanidm-selfsigned-certificate\n  issuerRef:\n    kind: Issuer\n    name: kanidm-selfsigned\n  dnsNames:\n    - home.arpa\n"
  },
  {
    "path": "platform/kanidm/templates/issuer.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: kanidm-selfsigned\n  namespace: {{ .Release.Namespace }}\nspec:\n  selfSigned: {}\n"
  },
  {
    "path": "platform/kanidm/values.yaml",
    "content": "app-template:\n  controllers:\n    main:\n      type: statefulset\n      containers:\n        main:\n          image:\n            repository: docker.io/kanidm/server\n            tag: 1.3.3\n      statefulset:\n        volumeClaimTemplates:\n          - name: data\n            size: 1Gi\n            globalMounts:\n              - path: /data\n            accessMode: \"ReadWriteOnce\"\n  configMaps:\n    config:\n      enabled: true\n      data:\n        server.toml: |\n          bindaddress = \"[::]:443\"\n          ldapbindaddress = \"[::]:636\"\n          trust_x_forward_for = true\n          db_path = \"/data/kanidm.db\"\n          tls_chain = \"/data/ca.crt\"\n          tls_key = \"/data/tls.key\"\n          domain = \"auth.khuedoan.com\"\n          origin = \"https://auth.khuedoan.com\"\n  service:\n    main:\n      ports:\n        http:\n          enabled: false\n        https:\n          port: 443\n          protocol: HTTPS\n        ldap:\n          port: 636\n          protocol: TCP\n  ingress:\n    main:\n      enabled: true\n      className: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n        nginx.ingress.kubernetes.io/backend-protocol: HTTPS\n      hosts:\n        - host: &host auth.khuedoan.com\n          paths:\n            - path: /\n              pathType: Prefix\n              service:\n                name: main\n                port: https\n      tls:\n        - hosts:\n            - *host\n          secretName: kanidm-tls-certificate\n  persistence:\n    config:\n      enabled: true\n      type: configMap\n      name: kanidm-config\n      globalMounts:\n        - path: /data/server.toml\n          subPath: server.toml\n    tls:\n      enabled: true\n      type: secret\n      name: kanidm-selfsigned-certificate\n      globalMounts:\n        - path: /data/ca.crt\n          subPath: ca.crt\n        - path: /data/tls.key\n          subPath: tls.key\n"
  },
  {
    "path": "platform/renovate/Chart.yaml",
    "content": "apiVersion: v2\nname: renovate\nversion: 0.0.0\ndependencies:\n  - name: renovate\n    version: 31.97.3\n    repository: https://docs.renovatebot.com/helm-charts\n"
  },
  {
    "path": "platform/renovate/templates/secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: {{ .Values.renovate.existingSecret }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  target:\n    template:\n      engineVersion: v2\n      data:\n        RENOVATE_TOKEN: {{` \"{{ .token }}\" `}}\n  data:\n    - secretKey: token\n      remoteRef:\n        key: gitea.renovate\n        property: token\n"
  },
  {
    "path": "platform/renovate/values.yaml",
    "content": "renovate:\n  cronjob:\n    schedule: '0 9 * * *'  # Everyday at 09:00\n  renovate:\n    config: |\n      {\n        \"platform\": \"gitea\",\n        \"endpoint\": \"https://git.khuedoan.com/api/v1\",\n        \"gitAuthor\": \"Renovate Bot <bot@renovateapp.com>\",\n        \"autodiscover\": true\n      }\n  existingSecret: renovate-secret\n"
  },
  {
    "path": "platform/woodpecker/Chart.yaml",
    "content": "apiVersion: v2\nname: woodpecker\nversion: 0.0.0\ndependencies:\n  - name: woodpecker\n    version: 1.5.1\n    repository: https://woodpecker-ci.org\n"
  },
  {
    "path": "platform/woodpecker/templates/secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: woodpecker-secret\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  data:\n    - secretKey: WOODPECKER_GITEA_CLIENT\n      remoteRef:\n        key: gitea.woodpecker\n        property: client_id\n    - secretKey: WOODPECKER_GITEA_SECRET\n      remoteRef:\n        key: gitea.woodpecker\n        property: client_secret\n    - secretKey: WOODPECKER_AGENT_SECRET\n      remoteRef:\n        key: woodpecker.agent\n        property: secret\n"
  },
  {
    "path": "platform/woodpecker/values.yaml",
    "content": "woodpecker:\n  agent:\n    replicaCount: 2\n    env:\n      WOODPECKER_BACKEND_K8S_STORAGE_RWX: false\n      # Agents will spawn pods to run workflow steps using the\n      # Kubernetes backend instead of running them directly on\n      # the agent pod, so we can run many workflows per agent.\n      WOODPECKER_MAX_WORKFLOWS: 10\n  server:\n    env:\n      WOODPECKER_HOST: https://ci.khuedoan.com\n      WOODPECKER_WEBHOOK_HOST: http://woodpecker-server.woodpecker\n      WOODPECKER_GITEA: true\n      WOODPECKER_GITEA_URL: https://git.khuedoan.com\n      WOODPECKER_OPEN: true\n      WOODPECKER_ADMIN: gitea_admin\n    ingress:\n      enabled: true\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      ingressClassName: nginx\n      hosts:\n        - host: &host ci.khuedoan.com\n          paths:\n            - path: /\n      tls:\n        - secretName: woodpecker-tls-certificate\n          hosts:\n            - *host\n"
  },
  {
    "path": "platform/zot/Chart.yaml",
    "content": "apiVersion: v2\nname: zot\nversion: 0.0.0\ndependencies:\n  - name: zot\n    version: 0.1.52\n    repository: http://zotregistry.dev/helm-charts\n"
  },
  {
    "path": "platform/zot/templates/admin-secret.yaml",
    "content": "apiVersion: external-secrets.io/v1beta1\nkind: ExternalSecret\nmetadata:\n  name: registry-admin-secret\n  namespace: {{ .Release.Namespace }}\nspec:\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: global-secrets\n  target:\n    template:\n      engineVersion: v2\n      data:\n        username: admin\n        password: {{` \"{{ .password }}\" `}}\n  data:\n    - secretKey: password\n      remoteRef:\n        key: registry.admin\n        property: password\n"
  },
  {
    "path": "platform/zot/values.yaml",
    "content": "zot:\n  ingress:\n    enabled: true\n    annotations:\n      cert-manager.io/cluster-issuer: letsencrypt-prod\n      nginx.ingress.kubernetes.io/proxy-body-size: \"0\"\n    className: nginx\n    hosts:\n      - host: &host registry.khuedoan.com\n        paths:\n          - path: /\n    tls:\n      - secretName: zot-tls-certificate\n        hosts:\n          - *host\n  # TODO enable auth\n  persistence: true\n  pvc:\n    create: true\n    storage: 10Gi\n"
  },
  {
    "path": "renovate.json5",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"packageRules\": [\n    {\n      \"matchPackagePatterns\": [\n        \"*\"\n      ],\n      \"matchUpdateTypes\": [\n        \"minor\",\n        \"patch\"\n      ],\n      \"groupName\": \"all non-major dependencies\",\n      \"groupSlug\": \"all-minor-patch\"\n    }\n  ]\n}\n"
  },
  {
    "path": "scripts/argocd-admin-password",
    "content": "#!/bin/sh\n\necho \"WARNING: ArgoCD admin can do anything in the cluster, only use it for just enough initial setup or in emergencies.\" >&2\nexport KUBECONFIG=./metal/kubeconfig.yaml\nkubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=\"{.data.password}\" | base64 -d\n"
  },
  {
    "path": "scripts/backup",
    "content": "#!/usr/bin/env python\n\nimport argparse\nfrom kubernetes import client, config\nfrom kubernetes.client.rest import ApiException\n\nconfig.load_kube_config()\n\narg_parser = argparse.ArgumentParser()\narg_parser.add_argument(\"--namespace\", required=True)\narg_parser.add_argument(\"--pvc\", required=True)\narg_parser.add_argument(\"--action\", required=True)\nargs = arg_parser.parse_args()\n\nnamespace = args.namespace\npvc = args.pvc\nsecret = f\"{pvc}-backup-repository\"\n\n\ndef apply_custom_resource(api, group, version, plural, name, namespace, body):\n    try:\n        # Check if the resource exists\n        api.get_namespaced_custom_object(\n            group=group,\n            version=version,\n            namespace=namespace,\n            plural=plural,\n            name=name,\n        )\n\n        print(f\"Patching {body['kind']} {name}\")\n        api.patch_namespaced_custom_object(\n            group=group,\n            version=version,\n            namespace=namespace,\n            plural=plural,\n            name=name,\n            body=body,\n        )\n    except ApiException as e:\n        if e.status == 404:\n            print(f\"Creating {body['kind']} {name}\")\n            api.create_namespaced_custom_object(\n                group=group,\n                version=version,\n                namespace=namespace,\n                plural=plural,\n                body=body,\n            )\n        else:\n            raise e\n\n\napply_custom_resource(\n    api=client.CustomObjectsApi(),\n    group=\"external-secrets.io\",\n    version=\"v1beta1\",\n    plural=\"externalsecrets\",\n    name=secret,\n    namespace=namespace,\n    body={\n        \"apiVersion\": \"external-secrets.io/v1beta1\",\n        \"kind\": \"ExternalSecret\",\n        \"metadata\": {\n            \"name\": secret,\n            \"namespace\": namespace,\n            \"annotations\": {\n                \"app.kubernetes.io/managed-by\": \"scripts/backup\",\n            },\n        },\n        \"spec\": {\n            \"secretStoreRef\": {\n                \"kind\": \"ClusterSecretStore\",\n                \"name\": \"global-secrets\",\n            },\n            \"data\": [\n                {\n                    \"remoteRef\": {\n                        \"key\": \"external\",\n                        \"property\": \"restic-s3-bucket\",\n                    },\n                    \"secretKey\": \"restic_s3_bucket\",\n                },\n                {\n                    \"remoteRef\": {\n                        \"key\": \"external\",\n                        \"property\": \"restic-s3-access-key\",\n                    },\n                    \"secretKey\": \"restic_s3_access_key\",\n                },\n                {\n                    \"remoteRef\": {\n                        \"key\": \"external\",\n                        \"property\": \"restic-s3-secret-key\",\n                    },\n                    \"secretKey\": \"restic_s3_secret_key\",\n                },\n                {\n                    \"remoteRef\": {\n                        \"key\": \"external\",\n                        \"property\": \"restic-password\",\n                    },\n                    \"secretKey\": \"restic_password\",\n                },\n            ],\n            \"target\": {\n                \"template\": {\n                    \"data\": {\n                        \"RESTIC_REPOSITORY\": f\"s3:{{{{ .restic_s3_bucket }}}}/{namespace}/{pvc}\",\n                        \"RESTIC_PASSWORD\": \"{{ .restic_password }}\",\n                        \"AWS_ACCESS_KEY_ID\": \"{{ .restic_s3_access_key }}\",\n                        \"AWS_SECRET_ACCESS_KEY\": \"{{ .restic_s3_secret_key }}\",\n                    }\n                }\n            },\n        },\n    },\n)\n\nif args.action == \"setup\":\n    apply_custom_resource(\n        api=client.CustomObjectsApi(),\n        group=\"volsync.backube\",\n        version=\"v1alpha1\",\n        plural=\"replicationsources\",\n        name=pvc,\n        namespace=namespace,\n        body={\n            \"apiVersion\": \"volsync.backube/v1alpha1\",\n            \"kind\": \"ReplicationSource\",\n            \"metadata\": {\n                \"name\": pvc,\n                \"namespace\": namespace,\n                \"annotations\": {\n                    \"app.kubernetes.io/managed-by\": \"scripts/backup\",\n                },\n            },\n            \"spec\": {\n                \"sourcePVC\": pvc,\n                \"trigger\": {\"schedule\": \"*/30 * * * *\"},\n                \"restic\": {\n                    \"pruneIntervalDays\": 14,\n                    \"repository\": secret,\n                    \"retain\": {\n                        \"hourly\": 6,\n                        \"daily\": 5,\n                        \"weekly\": 4,\n                        \"monthly\": 2,\n                        \"yearly\": 1,\n                    },\n                    \"copyMethod\": \"Snapshot\",\n                },\n            },\n        },\n    )\nelif args.action == \"restore\":\n    apply_custom_resource(\n        api=client.CustomObjectsApi(),\n        group=\"volsync.backube\",\n        version=\"v1alpha1\",\n        plural=\"replicationdestinations\",\n        name=pvc,\n        namespace=namespace,\n        body={\n            \"apiVersion\": \"volsync.backube/v1alpha1\",\n            \"kind\": \"ReplicationDestination\",\n            \"metadata\": {\n                \"name\": pvc,\n                \"namespace\": namespace,\n                \"annotations\": {\n                    \"app.kubernetes.io/managed-by\": \"scripts/backup\",\n                },\n            },\n            \"spec\": {\n                \"trigger\": {\"manual\": \"restore-once\"},\n                \"restic\": {\n                    \"repository\": secret,\n                    \"destinationPVC\": pvc,\n                    \"copyMethod\": \"Direct\",\n                },\n            },\n        },\n    )\nelse:\n    raise ValueError(f\"Invalid action: {args.action}\")\n"
  },
  {
    "path": "scripts/configure",
    "content": "#!/usr/bin/env python\n\n# WIP\n# TODO clean this up\n\n\"\"\"\nBasic configure script for new users\n\"\"\"\n\nimport fileinput\nimport subprocess\nimport sys\n\nfrom rich.prompt import Confirm, Prompt\n\nupstream_config = {\n    \"seed_repo\": \"https://github.com/khuedoan/homelab\",\n    \"domain\": \"khuedoan.com\",\n    \"timezone\": \"Asia/Ho_Chi_Minh\",\n    \"terraform_workspace\": \"khuedoan\",\n    \"loadbalancer_ip_range\": \"192.168.1.224/27\",\n}\n\n\ndef check_python_version(required_version: str) -> None:\n    if sys.version_info < tuple(map(int, required_version.split('.'))):\n        raise Exception(f\"Must be using Python >= {required_version}\")\n\n\ndef find_and_replace(pattern: str, replacement: str, paths: list[str]) -> None:\n    files_with_matches = subprocess.run(\n        [\"git\", \"grep\", \"--files-with-matches\", pattern, \"--\"] + paths,\n        capture_output=True,\n        text=True\n    ).stdout.splitlines()\n\n    for file_with_maches in files_with_matches:\n        with fileinput.FileInput(file_with_maches, inplace=True) as file:\n            for line in file:\n                print(line.replace(pattern, replacement), end='')\n\n\ndef main() -> None:\n    check_python_version(\n        required_version='3.10.0'\n    )\n\n    editor = Prompt.ask(\"Select text editor\", default='nvim')\n    domain = Prompt.ask(\"Enter your domain\", default=upstream_config['domain'])\n    seed_repo = Prompt.ask(\"Enter seed repo\", default=upstream_config['seed_repo'])\n    timezone = Prompt.ask(\"Enter time zone\", default=upstream_config['timezone'])\n    loadbalancer_ip_range = Prompt.ask(\"Enter IP range for load balancer\", default=upstream_config['loadbalancer_ip_range'])\n\n    find_and_replace(\n        pattern=upstream_config['domain'],\n        replacement=domain,\n        paths=[\n            \".ci\",\n            \"apps\",\n            \"platform\",\n            \"system\",\n            \"external\"\n        ]\n    )\n\n    find_and_replace(\n        pattern=upstream_config['seed_repo'],\n        replacement=seed_repo,\n        paths=[\n            \"system\",\n            \"platform\"\n        ]\n    )\n\n    find_and_replace(\n        pattern=upstream_config['timezone'],\n        replacement=timezone,\n        paths=[\n            \"apps\",\n            \"system\",\n            \"metal\"\n        ]\n    )\n\n    find_and_replace(\n        pattern=upstream_config['loadbalancer_ip_range'],\n        replacement=loadbalancer_ip_range,\n        paths=[\n            \"metal/inventories/prod.yml\",\n            \"apps/tailscale/values.yaml\",\n        ]\n    )\n\n    if Confirm.ask(\"Update server list?\", default=True):\n        subprocess.run(\n            [editor, 'metal/inventories/prod.yml']\n        )\n\n\n    if Confirm.ask(\"Do you want to use managed services?\"):\n        terraform_workspace = Prompt.ask(\"Enter Terraform Workspace\", default=upstream_config['terraform_workspace'])\n\n        find_and_replace(\n            pattern=upstream_config['terraform_workspace'],\n            replacement=terraform_workspace,\n            paths=[\n                \"external/versions.tf\"\n            ]\n        )\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/get-dns-config",
    "content": "#!/bin/sh\n\nexport KUBECONFIG=./metal/kubeconfig.yaml\nkubectl get ingress --all-namespaces --no-headers --output custom-columns=\"ADDRESS:.status.loadBalancer.ingress[0].ip,HOST:.spec.rules[0].host\"\n"
  },
  {
    "path": "scripts/get-status",
    "content": "#!/bin/sh\n\nexport KUBECONFIG=./metal/kubeconfig.yaml\n\nkubectl get applicationsets --namespace argocd\nkubectl get applications --namespace argocd\nkubectl get ingress --all-namespaces\n"
  },
  {
    "path": "scripts/get-wireguard-config",
    "content": "#!/bin/sh\n\nset -eu\n\nPEER=\"${1}\"\n\nexport KUBECONFIG=./metal/kubeconfig.yaml\n\nkubectl -n wireguard exec -it deployment/wireguard -- /app/show-peer \"${PEER}\"\nkubectl -n wireguard exec -it deployment/wireguard -- cat \"/config/peer_${PEER}/peer_${PEER}.conf\"\n"
  },
  {
    "path": "scripts/hacks",
    "content": "#!/usr/bin/env python\n\n\"\"\"\nQuick and dirty script for things that I can't/don't have time to do properly yet\nTODO: retire this script\n\"\"\"\n\nimport base64\nimport json\nimport json\nimport pexpect\nimport requests\nimport subprocess\nimport sys\nimport urllib\n\nfrom rich.console import Console\nfrom kubernetes import client, config\nfrom kubernetes.stream import stream\n\n# https://git.khuedoan.com/user/settings/applications\n# Doing this properly inside the cluster requires:\n# - Kubernetes service account\nconfig.load_config()\n\ngitea_host = client.NetworkingV1Api().read_namespaced_ingress('gitea', 'gitea').spec.rules[0].host\ngitea_user_secret = client.CoreV1Api().read_namespaced_secret('gitea-admin-secret', 'gitea')\ngitea_user = base64.b64decode(gitea_user_secret.data['username']).decode(\"utf-8\")\ngitea_pass = base64.b64decode(gitea_user_secret.data['password']).decode(\"utf-8\")\ngitea_url = f\"http://{gitea_host}\"\n\nkanidm_host = client.NetworkingV1Api().read_namespaced_ingress('kanidm', 'kanidm').spec.rules[0].host\n\ndef apply_secret(name: str, namespace: str, data: dict) -> None:\n    try:\n        client.CoreV1Api().read_namespaced_secret(name, namespace)\n        patch_body = client.V1Secret(\n            metadata=client.V1ObjectMeta(name=name),\n            data=data,\n        )\n        client.CoreV1Api().replace_namespaced_secret(name, namespace, patch_body)\n    except client.exceptions.ApiException:\n        # Secret doesn't exist, create a new one\n        new_secret = client.V1Secret(\n            metadata=client.V1ObjectMeta(name=name),\n            data=data,\n        )\n        client.CoreV1Api().create_namespaced_secret(namespace, new_secret)\n\ndef setup_gitea_access_token(name: str, scopes: list[str]) -> None:\n    current_tokens = requests.get(\n        url=f\"{gitea_url}/api/v1/users/{gitea_user}/tokens\",\n        auth=(gitea_user,gitea_pass),\n    ).json()\n\n    if not any(token['name'] == name for token in current_tokens):\n        resp = requests.post(\n            url=f\"{gitea_url}/api/v1/users/{gitea_user}/tokens\",\n            auth=(gitea_user,gitea_pass),\n            headers={\n                'Content-Type': 'application/json'\n            },\n            data=json.dumps({\n                'name': name,\n                'scopes': scopes\n            })\n        )\n\n        if resp.status_code == 201:\n            apply_secret(\n                f\"gitea.{name}\",\n                \"global-secrets\",\n                {\n                    'token': base64.b64encode(resp.json()['sha1'].encode(\"utf-8\")).decode(\"utf-8\")\n                }\n            )\n        else:\n            print(f\"Error creating access token {name} ({resp.status_code})\")\n            print(resp.content)\n            sys.exit(1)\n\ndef setup_gitea_oauth_app(name: str, redirect_uri: str) -> None:\n    # TODO use the new global application, while it's there in the UI, there's no API yet.\n    current_apps = requests.get(\n        url=f\"{gitea_url}/api/v1/user/applications/oauth2\",\n        auth=(gitea_user,gitea_pass),\n    ).json()\n\n    if not any(app['name'] == name for app in current_apps):\n        resp = requests.post(\n            url=f\"{gitea_url}/api/v1/user/applications/oauth2\",\n            auth=(gitea_user,gitea_pass),\n            headers={\n                'Content-Type': 'application/json'\n            },\n            data=json.dumps({\n                'name': name,\n                'redirect_uris': [redirect_uri],\n                'confidential_client': True\n            })\n        )\n\n        if resp.status_code == 201:\n            apply_secret(\n                f\"gitea.{name}\",\n                \"global-secrets\",\n                {\n                    'client_id': base64.b64encode(resp.json()['client_id'].encode(\"utf-8\")).decode(\"utf-8\"),\n                    'client_secret': base64.b64encode(resp.json()['client_secret'].encode(\"utf-8\")).decode(\"utf-8\"),\n                }\n            )\n        else:\n            print(f\"Error creating OAuth application {name} ({resp.status_code})\")\n            print(resp.content)\n            sys.exit(1)\n\ndef setup_gitea_auth_with_dex():\n    gitea_pod = client.CoreV1Api().list_namespaced_pod(namespace='gitea', label_selector='app=gitea').items[0].metadata.name\n    client_secret = base64.b64decode(\n        client.CoreV1Api().read_namespaced_secret('dex.gitea', 'global-secrets').data['client_secret']\n    ).decode(\"utf-8\")\n    discovery_url = f\"https://{client.NetworkingV1Api().read_namespaced_ingress('dex', 'dex').spec.rules[0].host}/.well-known/openid-configuration\"\n\n    # TODO currently there's no API to add new authentication sources in Gitea,\n    # so we have to workaround by running Gitea CLI in a Gitea pod.\n    stream(\n        client.CoreV1Api().connect_get_namespaced_pod_exec,\n        gitea_pod,\n        'gitea',\n        command=[\n            'gitea', 'admin', 'auth', 'add-oauth',\n            '--name', 'Dex',\n            '--provider', 'openidConnect',\n            '--key', 'gitea',\n            '--secret', client_secret,\n            '--auto-discover-url', discovery_url\n        ],\n        stderr=True, stdin=False,\n        stdout=False, tty=False\n    )\n\ndef reset_kanidm_account_password(account: str) -> str:\n    resp = stream(\n        client.CoreV1Api().connect_get_namespaced_pod_exec,\n        'kanidm-0',\n        'kanidm',\n        command=[\"kanidmd\", \"recover-account\", \"--output\", \"json\", account],\n        stderr=False, stdin=False,\n        stdout=True, tty=False\n    ).splitlines()[-1]\n\n    return json.loads(resp)['password']\n\n# TODO Proper automation will be added later, waiting for client library update:\n# https://github.com/kanidm/kanidm/pull/2301\ndef kanidm_login(accounts: list[str]) -> None:\n    for account in accounts:\n        password = reset_kanidm_account_password(account)\n\n        # There's no way to input password using the standard library, so we have to use pexpect\n        # https://stackoverflow.com/questions/2387731/use-subprocess-to-send-a-password\n        cli_login = pexpect.spawn(f\"kanidm login --url https://{kanidm_host} --name {account}\")\n        cli_login.sendline(password)\n        cli_login.read()\n\ndef setup_kanidm_group(name: str) -> None:\n    subprocess.run(\n        [\"kanidm\", \"group\", \"create\", \"--url\", f\"https://{kanidm_host}\", \"--name\", \"idm_admin\", name],\n        capture_output=True,\n    )\n\ndef setup_kanidm_oauth_app(name: str, redirect_uri: str) -> None:\n    try:\n        subprocess.run(\n            [\"kanidm\", \"system\", \"oauth2\", \"create\", \"--url\", f\"https://{kanidm_host}\", \"--name\", \"idm_admin\", name, name, redirect_uri],\n            capture_output=True,\n            check=True,\n        )\n    except subprocess.CalledProcessError:\n        return\n\n    # TODO https://github.com/dexidp/dex/pull/3188\n    subprocess.run(\n        [\"kanidm\", \"system\", \"oauth2\", \"warning-insecure-client-disable-pkce\", \"--url\", f\"https://{kanidm_host}\", \"--name\", \"idm_admin\", name],\n        capture_output=True,\n        check=True,\n    )\n\n    subprocess.run(\n        # TODO better group management\n        [\"kanidm\", \"system\", \"oauth2\", \"create-scope-map\", \"--url\", f\"https://{kanidm_host}\", \"--name\", \"idm_admin\", name, \"editor\", \"openid\", \"profile\", \"email\", \"groups\"],\n        capture_output=True,\n        check=True,\n    )\n\n    client_secret = json.loads(subprocess.run(\n        [\"kanidm\", \"system\", \"oauth2\", \"show-basic-secret\", \"--url\", f\"https://{kanidm_host}\", \"--name\", \"idm_admin\", \"--output\", \"json\", name],\n        capture_output=True,\n        check=True,\n    ).stdout.decode(\"utf-8\"))['secret']\n\n    apply_secret(\n        f\"kanidm.{name}\",\n        \"global-secrets\",\n        {\n            'client_id': base64.b64encode(name.encode(\"utf-8\")).decode(\"utf-8\"),\n            'client_secret': base64.b64encode(client_secret.encode(\"utf-8\")).decode(\"utf-8\"),\n        }\n    )\n\ndef main() -> None:\n    with Console().status(\"Completing the remaining sorcery\"):\n        gitea_access_tokens = [\n            {\n                'name': 'renovate',\n                'scopes': [\n                    \"write:repository\",\n                    \"read:user\",\n                    \"write:issue\",\n                    \"read:organization\",\n                    \"read:misc\"\n                ]\n            }\n        ]\n\n        gitea_oauth_apps = [\n            {'name': 'woodpecker', 'redirect_uri': f\"https://{client.NetworkingV1Api().read_namespaced_ingress('woodpecker-server', 'woodpecker').spec.rules[0].host}/authorize\"},\n        ]\n\n        kanidm_groups = [\n            # TODO better group management\n            {'name': 'editor'},\n        ]\n\n        kanidm_oauth_apps = [\n            {'name': 'dex', 'redirect_uri': f\"https://{client.NetworkingV1Api().read_namespaced_ingress('dex', 'dex').spec.rules[0].host}/callback\"},\n        ]\n\n        for token in gitea_access_tokens:\n            setup_gitea_access_token(token['name'], token['scopes'])\n\n        for app in gitea_oauth_apps:\n            setup_gitea_oauth_app(app['name'], app['redirect_uri'])\n\n        setup_gitea_auth_with_dex()\n\n        kanidm_login([\"admin\", \"idm_admin\"])\n\n        for group in kanidm_groups:\n            setup_kanidm_group(group['name'])\n\n        for app in kanidm_oauth_apps:\n            setup_kanidm_oauth_app(app['name'], app['redirect_uri'])\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/helm-diff",
    "content": "#!/usr/bin/env python\n\nfrom argparse import ArgumentParser\nfrom glob import glob\nfrom os import path\nfrom subprocess import run\nfrom tempfile import mkdtemp, NamedTemporaryFile\n\n\ndef clone_repository(repo, branch, target_path):\n    run(\n        ['git', 'clone', repo, '--depth', '1', '--branch', branch, target_path],\n        check=True\n    )\n\n\ndef render_helm_chart(chart_path, namespace, release_name, rendered_path):\n    # Even if there is no Helm chart at the specified chart path, do not raise an error.\n    # This accommodates cases where the entire chart is removed, or a new chart is added.\n    # In such cases, the rendered file will simply be empty.\n    if path.isdir(chart_path):\n        run(\n            ['helm', 'dependency', 'update', chart_path],\n            check=True\n        )\n\n    run(\n        ['helm', 'template', '--namespace', namespace, release_name, chart_path],\n        stdout=open(rendered_path, 'w'),\n        check=True\n    )\n\n\ndef changed_charts(source_path, target_path, subpath):\n    changed_charts = []\n\n    # Convert to set for deduplication\n    all_charts = set(\n        glob(f\"*\", root_dir=f\"{source_path}/{subpath}\")\n        + glob(f\"*\", root_dir=f\"{target_path}/{subpath}\")\n    )\n\n    for chart in all_charts:\n        source_chart_path = path.join(source_path, subpath, chart)\n        target_chart_path = path.join(target_path, subpath, chart)\n\n        if run(['diff', source_chart_path, target_chart_path], capture_output=True).returncode != 0:\n            changed_charts.append(chart)\n\n    return changed_charts\n\n\ndef main():\n    parser = ArgumentParser(description='Compare Helm charts in a directory between two Git revisions.')\n    parser.add_argument('--repository', required=True, help='Repository to clone')\n    parser.add_argument('--source', required=True, help='Source branch (e.g. pull request branch)')\n    parser.add_argument('--target', required=True, help='Target branch (e.g. master branch)')\n    parser.add_argument('--subpath', required=True, help='Subpath containing the charts (e.g. system)')\n\n    args = parser.parse_args()\n\n    source_path = mkdtemp()\n    target_path = mkdtemp()\n\n    clone_repository(args.repository, args.source, source_path)\n    clone_repository(args.repository, args.target, target_path)\n\n    for chart in changed_charts(source_path, target_path, args.subpath):\n        with NamedTemporaryFile(suffix='.yaml', mode='w+', delete=False) as f_source, NamedTemporaryFile(suffix='.yaml', mode='w+', delete=False) as f_target:\n            render_helm_chart(f\"{source_path}/{args.subpath}/{chart}\", chart, chart, f_source.name)\n            render_helm_chart(f\"{target_path}/{args.subpath}/{chart}\", chart, chart, f_target.name)\n\n            diff_result = run(\n                ['dyff', 'between', '--omit-header', '--use-go-patch-style', '--color=on', '--truecolor=off', f_target.name, f_source.name],\n                capture_output=True,\n                text=True,\n                check=True\n            )\n\n            print(diff_result.stdout)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/kanidm-reset-password",
    "content": "#!/bin/sh\n\nset -eu\n\naccount=\"${1}\"\n\necho \"WARNING: Kanidm admin can do anything in the cluster, only use it for just enough initial setup or in emergencies.\" >&2\nexport KUBECONFIG=./metal/kubeconfig.yaml\nkubectl exec -it -n kanidm statefulset/kanidm -- kanidmd recover-account \"${account}\"\n"
  },
  {
    "path": "scripts/new-service",
    "content": "#!/bin/sh\n\nmkdir -p \"apps/${1}\"\n\ncat << EOT > \"apps/${1}/Chart.yaml\"\napiVersion: v2\nname: CHANGEME\nversion: 0.0.0\ndependencies:\n- name: CHANGEME\n  version: CHANGEME\n  repository: CHANGEME\nEOT\n\ntouch \"apps/${1}/values.yaml\"\n"
  },
  {
    "path": "scripts/onboard-user",
    "content": "#!/bin/sh\n\nusername=\"${1}\"\nfullname=\"${2}\"\nmail=\"${3}\"\n\nexport KUBECONFIG=./metal/kubeconfig.yaml\nhost=\"$(kubectl get ingress --namespace kanidm kanidm --output jsonpath='{.spec.rules[0].host}')\"\n\nkanidm person create \"${username}\" \"${fullname}\" --url \"https://${host}\" --name idm_admin\nkanidm person update \"${username}\" --url \"https://${host}\" --name idm_admin --mail \"${mail}\"\n# TODO better group management\nkanidm group add-members \"editor\" \"${username}\" --url \"https://${host}\" --name idm_admin\nkanidm person credential create-reset-token \"${username}\" --url \"https://${host}\" --name idm_admin\n"
  },
  {
    "path": "scripts/pxe-logs",
    "content": "#!/bin/sh\n\ndocker compose \\\n    --project-directory ./metal/roles/pxe_server/files/ \\\n    logs \\\n    --follow \\\n    \"${@}\"\n"
  },
  {
    "path": "scripts/take-screenshots",
    "content": "#!/usr/bin/env python\n\n# WIP\n# - [x] take screenshot\n# - [ ] self contained\n# - [ ] login automatically credentials from Kubernetes Secrets (is this really needed?)\n\n# TODO put this in ../flake.nix or use Docker\n# pip install selenium\n# sudo pacman -S geckodriver\n\nimport time\nfrom selenium import webdriver\n\napps = [\n    {\n        'name': 'home',\n        'url': 'https://home.khuedoan.com'\n    },\n    {\n        'name': 'gitea',\n        'url': 'https://git.khuedoan.com/ops/homelab'\n    },\n    {\n        'name': 'argocd',\n        'url': 'https://argocd.khuedoan.com/applications/root'\n    },\n    {\n        'name': 'matrix',\n        'url': 'https://chat.khuedoan.com/#/room/#random:matrix.khuedoan.com'\n    },\n    {\n        'name': 'grafana',\n        'url': 'https://grafana.khuedoan.com/d/efa86fd1d0c121a26444b636a3f509a8/kubernetes-compute-resources-cluster'  # wtf is this ID\n    },\n]\n\noptions = webdriver.firefox.options.Options()\noptions.headless = True\n\ndriver = webdriver.Firefox(\n    options=options,\n    firefox_profile=webdriver.FirefoxProfile(  # TODO deprecated\n        profile_directory=\"/home/khuedoan/.mozilla/firefox/h05irklw.default-release\"  # TODO do not hard code\n    )\n)\ndriver.set_window_size(1920, 1080)\n\nfor app in apps:\n    print(f\"Opening {app['url']}\")\n    driver.get(app['url'])\n    time.sleep(3)  # TODO wait for full page load instead of sleep\n    driver.save_screenshot(f\"{app['name']}.png\")\n    print(f\"Screenshot saved to {app['name']}.png\")\n\ndriver.close()\n"
  },
  {
    "path": "system/Makefile",
    "content": ".POSIX:\n\nexport KUBECONFIG = $(shell pwd)/../metal/kubeconfig.yaml\n\n.PHONY: bootstrap\nbootstrap:\n\tansible-playbook \\\n\t\tbootstrap.yml\n"
  },
  {
    "path": "system/argocd/Chart.yaml",
    "content": "apiVersion: v2\nname: argocd\nversion: 0.0.0\ndependencies:\n  - name: argo-cd\n    version: 7.5.2\n    repository: https://argoproj.github.io/argo-helm\n  - name: argocd-apps\n    version: 2.0.0\n    repository: https://argoproj.github.io/argo-helm\n"
  },
  {
    "path": "system/argocd/values-seed.yaml",
    "content": "argo-cd:\n  server:\n    metrics: &metrics\n      enabled: false\n      serviceMonitor:\n        enabled: false\n  controller:\n    metrics: *metrics\n  repoServer:\n    metrics: *metrics\n  redis:\n    metrics: *metrics\nargocd-apps:\n  applicationsets:\n    root:\n      generators:\n        - git:\n            repoURL: &repoURL https://github.com/khuedoan/homelab\n            revision: &revision master\n            directories:\n              - path: system/*\n              - path: platform/*\n              - path: apps/*\n      template:\n        spec:\n          source:\n            repoURL: *repoURL\n            targetRevision: *revision\n"
  },
  {
    "path": "system/argocd/values.yaml",
    "content": "argo-cd:\n  global:\n    domain: argocd.khuedoan.com\n  configs:\n    params:\n      server.insecure: true\n      controller.diff.server.side: true\n    cm:\n      resource.ignoreResourceUpdatesEnabled: true\n      resource.customizations.ignoreResourceUpdates.all: |\n        jsonPointers:\n          - /status\n  server:\n    ingress:\n      enabled: true\n      ingressClassName: nginx\n      annotations:\n        cert-manager.io/cluster-issuer: letsencrypt-prod\n      tls: true\n    metrics: &metrics\n      enabled: true\n      serviceMonitor:\n        enabled: true\n  dex:\n    enabled: false\n  controller:\n    metrics: *metrics\n  repoServer:\n    metrics: *metrics\n  redis:\n    metrics: *metrics\nargocd-apps:\n  applicationsets:\n    root:\n      namespace: argocd\n      generators:\n        - git:\n            repoURL: &repoURL http://gitea-http.gitea:3000/ops/homelab\n            revision: &revision master\n            directories:\n              - path: system/*\n              - path: platform/*\n              - path: apps/*\n      template:\n        metadata:\n          name: '{{path.basename}}'\n        spec:\n          destination:\n            name: in-cluster\n            namespace: '{{path.basename}}'\n          project: default  # TODO\n          source:\n            repoURL: *repoURL\n            path: '{{path}}'\n            targetRevision: *revision\n          syncPolicy:\n            automated:\n              prune: true\n              selfHeal: true\n            retry:\n              limit: 10\n              backoff:\n                duration: 1m\n                factor: 2\n                maxDuration: 16m\n            syncOptions:\n              - CreateNamespace=true\n              - ApplyOutOfSyncOnly=true\n              - ServerSideApply=true\n            managedNamespaceMetadata:\n              annotations:\n                # Enable privileged VolSync movers by default for all namespaces\n                # TODO this may be refactored in the future for finer granularity\n                # See also https://volsync.readthedocs.io/en/stable/usage/permissionmodel.html\n                volsync.backube/privileged-movers: \"true\"\n"
  },
  {
    "path": "system/bootstrap.yml",
    "content": "- name: Bootstrapping the cluster\n  hosts: localhost\n  tasks:\n    - name: Create ArgoCD namespace\n      kubernetes.core.k8s:\n        api_version: v1\n        kind: Namespace\n        name: argocd\n        state: present\n\n    - name: Check if this is the first installation\n      kubernetes.core.k8s_info:\n        kind: Pod\n        label_selectors:\n          - app.kubernetes.io/instance=gitea\n        field_selectors:\n          - status.phase=Running\n      register: first_install\n\n    - name: Render ArgoCD manifests from Helm chart\n      kubernetes.core.helm_template:\n        chart_ref: ./argocd\n        include_crds: true\n        release_name: argocd\n        release_namespace: argocd\n        dependency_update: true\n        values_files:\n          - \"argocd/{{ (first_install.resources | length == 0) | ternary('values-seed.yaml', 'values.yaml') }}\"\n      register: argocd_manifests\n\n    - name: Apply ArgoCD manifests\n      kubernetes.core.k8s:\n        resource_definition: \"{{ argocd_manifests.stdout }}\"\n        apply: true\n        server_side_apply:\n          field_manager: argocd-controller\n"
  },
  {
    "path": "system/cert-manager/Chart.yaml",
    "content": "apiVersion: v2\nname: cert-manager\nversion: 0.0.0\ndependencies:\n  - name: cert-manager\n    version: v1.15.3\n    repository: https://charts.jetstack.io\n"
  },
  {
    "path": "system/cert-manager/templates/clusterissuer.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt-prod\nspec:\n  acme:\n    server: https://acme-v02.api.letsencrypt.org/directory\n    privateKeySecretRef:\n      name: letsencrypt-prod\n    solvers:\n    - dns01:\n        cloudflare:\n          apiTokenSecretRef:\n            name: cloudflare-api-token\n            key: api-token\n"
  },
  {
    "path": "system/cert-manager/values.yaml",
    "content": "cert-manager:\n  installCRDs: true\n  prometheus:\n    enabled: true\n    servicemonitor:\n      enabled: true\n"
  },
  {
    "path": "system/cloudflared/Chart.yaml",
    "content": "apiVersion: v2\nname: cloudflared\nversion: 0.0.0\ndependencies:\n  - name: app-template\n    version: 3.1.0\n    repository: https://bjw-s-labs.github.io/helm-charts\n"
  },
  {
    "path": "system/cloudflared/values.yaml",
    "content": "app-template:\n  controllers:\n    cloudflared:\n      containers:\n        app:\n          image:\n            repository: docker.io/cloudflare/cloudflared\n            tag: 2024.4.0\n          args:\n            - tunnel\n            - --config\n            - /etc/cloudflared/config.yaml\n            - run\n  configMaps:\n    config:\n      enabled: true\n      data:\n        config.yaml: |\n          tunnel: homelab\n          credentials-file: /etc/cloudflared/credentials.json\n          metrics: 0.0.0.0:2000\n          no-autoupdate: true\n          ingress:\n            - hostname: '*.khuedoan.com'\n              service: https://ingress-nginx-controller.ingress-nginx\n              originRequest:\n                noTLSVerify: true\n            - service: http_status:404\n  persistence:\n    config:\n      enabled: true\n      type: configMap\n      name: cloudflared-config\n      globalMounts:\n        - path: /etc/cloudflared/config.yaml\n          subPath: config.yaml\n    credentials:\n      enabled: true\n      type: secret\n      # Created by ../../external/cloudflared\n      name: cloudflared-credentials\n      globalMounts:\n        - path: /etc/cloudflared/credentials.json\n          subPath: credentials.json\n"
  },
  {
    "path": "system/external-dns/Chart.yaml",
    "content": "apiVersion: v2\nname: external-dns\nversion: 0.0.0\ndependencies:\n  - name: external-dns\n    version: 1.14.3\n    repository: https://kubernetes-sigs.github.io/external-dns\n"
  },
  {
    "path": "system/external-dns/values.yaml",
    "content": "external-dns:\n  provider: cloudflare\n  txtOwnerId: homelab\n  env:\n    - name: CF_API_TOKEN\n      valueFrom:\n        secretKeyRef:\n          name: cloudflare-api-token\n          key: value\n  extraArgs:\n    - --annotation-filter=external-dns.alpha.kubernetes.io/exclude notin (true)\n  interval: 5m\n  triggerLoopOnEvent: true\n  metrics:\n    enabled: true\n    serviceMonitor:\n      enabled: true\n"
  },
  {
    "path": "system/ingress-nginx/Chart.yaml",
    "content": "apiVersion: v2\nname: ingress-nginx\nversion: 0.0.0\ndependencies:\n  - name: ingress-nginx\n    version: 4.11.2\n    repository: https://kubernetes.github.io/ingress-nginx\n"
  },
  {
    "path": "system/ingress-nginx/values.yaml",
    "content": "ingress-nginx:\n  controller:\n    admissionWebhooks:\n      timeoutSeconds: 30\n    metrics:\n      enabled: true\n      serviceMonitor:\n        enabled: true\n  tcp:\n    22: gitea/gitea-ssh:22\n"
  },
  {
    "path": "system/kured/Chart.yaml",
    "content": "apiVersion: v2\nname: kured\nversion: 0.0.0\ndependencies:\n  - name: kured\n    version: 4.7.0\n    repository: https://kubereboot.github.io/charts\n"
  },
  {
    "path": "system/kured/values.yaml",
    "content": "kured:\n  configuration:\n    annotateNodes: true\n    rebootSentinelCommand: sh -c \"! needs-restarting --reboothint\"\n    timeZone: Asia/Ho_Chi_Minh\n"
  },
  {
    "path": "system/loki/Chart.yaml",
    "content": "apiVersion: v2\nname: loki\nversion: 0.0.0\ndependencies:\n  - name: loki-stack\n    version: 2.10.1\n    repository: https://grafana.github.io/helm-charts\n"
  },
  {
    "path": "system/loki/values.yaml",
    "content": "loki-stack:\n  loki:\n    serviceMonitor:\n      enabled: true\n"
  },
  {
    "path": "system/monitoring-system/Chart.yaml",
    "content": "apiVersion: v2\nname: kube-prometheus-stack\nversion: 0.0.0\ndependencies:\n  - name: kube-prometheus-stack\n    version: 56.19.0\n    repository: https://prometheus-community.github.io/helm-charts\n"
  },
  {
    "path": "system/monitoring-system/files/webhook-transformer/alertmanager-to-ntfy.jsonnet",
    "content": "local get_tags(status, severity) =\n  // https://docs.ntfy.sh/emojis\n  if status == \"resolved\" then\n    [\"tada\"]\n  else\n    std.get({\n      critical: [\"rotating_light\"],\n      warning: [\"warning\"],\n      info: [\"newspaper\"],\n    }, severity, [\"question\"]);\n\nlocal get_priority(status, severity) =\n  // https://docs.ntfy.sh/publish/#message-priority\n  if status == \"resolved\" then\n    2\n  else\n    std.get({\n      critical: 5,\n      warning: 3,\n      info: 1,\n    }, severity, 3);\n\nlocal get_actions(status, annotations) =\n  // https://docs.ntfy.sh/publish/#action-buttons\n  if status == \"resolved\" || !(\"runbook_url\" in annotations) then\n    []\n  else\n    [\n      {\n        action: \"view\",\n        label: \"Open runbook\",\n        url: annotations.runbook_url,\n      },\n    ];\n\n// TODO support multiple alerts\n{\n  topic: env.NTFY_TOPIC,\n  title: \"[\" + std.asciiUpper(body.status) + \"] \" + body.alerts[0].labels.alertname,\n  message: body.alerts[0].annotations.description,\n  tags: get_tags(body.status, body.alerts[0].labels.severity),\n  priority: get_priority(body.status, body.alerts[0].labels.severity),\n  actions: get_actions(body.status, body.alerts[0].annotations),\n}\n"
  },
  {
    "path": "system/monitoring-system/templates/configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: webhook-transformer\n  namespace: {{ .Release.Namespace }}\ndata:\n{{ (.Files.Glob \"files/webhook-transformer/*\").AsConfig | indent 2 }}\n"
  },
  {
    "path": "system/monitoring-system/values.yaml",
    "content": "kube-prometheus-stack:\n  grafana:\n    enabled: false\n    forceDeployDatasources: true\n    forceDeployDashboards: true\n    additionalDataSources:\n      - name: Loki\n        type: loki\n        url: http://loki.loki:3100\n  prometheus:\n    prometheusSpec:\n      ruleSelectorNilUsesHelmValues: false\n      serviceMonitorSelectorNilUsesHelmValues: false\n      podMonitorSelectorNilUsesHelmValues: false\n      probeSelectorNilUsesHelmValues: false\n  alertmanager:\n    alertmanagerSpec:\n      containers:\n        - name: ntfy-relay\n          image: ghcr.io/khuedoan/webhook-transformer:v0.0.3\n          args:\n            - --port=8081\n            - --config=/config/alertmanager-to-ntfy.jsonnet\n            - --upstream-host=https://ntfy.sh\n          envFrom:\n            - secretRef:\n                name: webhook-transformer\n          volumeMounts:\n            - name: config\n              mountPath: /config\n      volumes:\n        - name: config\n          configMap:\n            name: webhook-transformer\n    config:\n      route:\n        receiver: ntfy\n        group_by:\n          - namespace\n        group_wait: 30s\n        group_interval: 5m\n        repeat_interval: 12h\n        routes:\n          - receiver: ntfy\n            matchers:\n              - alertname = \"Watchdog\"\n      receivers:\n        - name: ntfy\n          webhook_configs:\n            - url: http://localhost:8081\n              send_resolved: true\n"
  },
  {
    "path": "system/rook-ceph/Chart.yaml",
    "content": "apiVersion: v2\nname: rook-ceph\nversion: 0.0.0\ndependencies:\n  - name: rook-ceph\n    version: 1.13.5\n    repository: https://charts.rook.io/release\n  - name: rook-ceph-cluster\n    version: 1.13.5\n    repository: https://charts.rook.io/release\n  # TODO switch to official chart when there is one\n  # https://github.com/kubernetes-csi/external-snapshotter/issues/812\n  - name: snapshot-controller\n    version: 2.2.1\n    repository: https://piraeus.io/helm-charts\n"
  },
  {
    "path": "system/rook-ceph/values.yaml",
    "content": "rook-ceph:\n  monitoring:\n    enabled: true\nrook-ceph-cluster:\n  monitoring:\n    enabled: true\n    createPrometheusRules: true\n  cephClusterSpec:\n    mon:\n      count: 3\n    mgr:\n      count: 2\n    dashboard:\n      ssl: false\n    logCollector:\n      enabled: false\n    removeOSDsIfOutAndSafeToRemove: true\n    resources:\n      mgr:\n        limits:\n          memory: \"1Gi\"\n        requests:\n          cpu: \"100m\"\n          memory: \"512Mi\"\n      mon:\n        limits:\n          memory: \"2Gi\"\n        requests:\n          cpu: \"100m\"\n          memory: \"100Mi\"\n      osd:\n        limits:\n          memory: \"4Gi\"\n        requests:\n          cpu: \"100m\"\n          memory: \"512Mi\"\n  cephBlockPools:\n    - name: standard-rwo\n      spec:\n        replicated:\n          size: 2\n      storageClass:\n        enabled: true\n        name: standard-rwo\n        isDefault: true\n        allowVolumeExpansion: true\n        parameters:\n          imageFeatures: layering,fast-diff,object-map,deep-flatten,exclusive-lock\n          csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner\n          csi.storage.k8s.io/provisioner-secret-namespace: \"{{ .Release.Namespace }}\"\n          csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner\n          csi.storage.k8s.io/controller-expand-secret-namespace: \"{{ .Release.Namespace }}\"\n          csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node\n          csi.storage.k8s.io/node-stage-secret-namespace: \"{{ .Release.Namespace }}\"\n  cephBlockPoolsVolumeSnapshotClass:\n    enabled: true\n    isDefault: true\n  cephFileSystems:\n    - name: standard-rwx\n      spec:\n        metadataPool:\n          replicated:\n            size: 2\n        dataPools:\n          - name: data0\n            replicated:\n              size: 2\n        metadataServer:\n          activeCount: 1\n          activeStandby: true\n          resources:\n            limits:\n              memory: \"4Gi\"\n            requests:\n              cpu: \"100m\"\n              memory: \"100Mi\"\n          priorityClassName: system-cluster-critical\n      storageClass:\n        enabled: true\n        name: standard-rwx\n        isDefault: false\n        allowVolumeExpansion: true\n        pool: data0\n        parameters:\n          csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner\n          csi.storage.k8s.io/provisioner-secret-namespace: \"{{ .Release.Namespace }}\"\n          csi.storage.k8s.io/controller-expand-secret-name: rook-csi-cephfs-provisioner\n          csi.storage.k8s.io/controller-expand-secret-namespace: \"{{ .Release.Namespace }}\"\n          csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node\n          csi.storage.k8s.io/node-stage-secret-namespace: \"{{ .Release.Namespace }}\"\n  cephFileSystemVolumeSnapshotClass:\n    enabled: true\n    isDefault: false\n  cephObjectStores: []\n"
  },
  {
    "path": "system/volsync-system/Chart.yaml",
    "content": "apiVersion: v2\nname: volsync\nversion: 0.0.0\ndependencies:\n  - name: volsync\n    version: 0.9.1\n    repository: https://backube.github.io/helm-charts\n"
  },
  {
    "path": "test/Makefile",
    "content": ".POSIX:\n\nfilter=.\n\ndefault: test\n\ntest:\n\tgotestsum --format testname -- -timeout 30m -run \"${filter}\"\n"
  },
  {
    "path": "test/benchmark/security/kube-bench.yaml",
    "content": "# kubectl apply -f kube-bench.yaml\n# https://github.com/aquasecurity/kube-bench/blob/main/job.yaml\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: kube-bench\nspec:\n  template:\n    metadata:\n      labels:\n        app: kube-bench\n    spec:\n      containers:\n        - command: [\"kube-bench\"]\n          image: docker.io/aquasec/kube-bench:v0.7.2\n          name: kube-bench\n          volumeMounts:\n            - name: var-lib-cni\n              mountPath: /var/lib/cni\n              readOnly: true\n            - mountPath: /var/lib/etcd\n              name: var-lib-etcd\n              readOnly: true\n            - mountPath: /var/lib/kubelet\n              name: var-lib-kubelet\n              readOnly: true\n            - mountPath: /var/lib/kube-scheduler\n              name: var-lib-kube-scheduler\n              readOnly: true\n            - mountPath: /var/lib/kube-controller-manager\n              name: var-lib-kube-controller-manager\n              readOnly: true\n            - mountPath: /etc/systemd\n              name: etc-systemd\n              readOnly: true\n            - mountPath: /lib/systemd/\n              name: lib-systemd\n              readOnly: true\n            - mountPath: /srv/kubernetes/\n              name: srv-kubernetes\n              readOnly: true\n            - mountPath: /etc/kubernetes\n              name: etc-kubernetes\n              readOnly: true\n            - mountPath: /usr/local/mount-from-host/bin\n              name: usr-bin\n              readOnly: true\n            - mountPath: /etc/cni/net.d/\n              name: etc-cni-netd\n              readOnly: true\n            - mountPath: /opt/cni/bin/\n              name: opt-cni-bin\n              readOnly: true\n      hostPID: true\n      restartPolicy: Never\n      volumes:\n        - name: var-lib-cni\n          hostPath:\n            path: /var/lib/cni\n        - hostPath:\n            path: /var/lib/etcd\n          name: var-lib-etcd\n        - hostPath:\n            path: /var/lib/kubelet\n          name: var-lib-kubelet\n        - hostPath:\n            path: /var/lib/kube-scheduler\n          name: var-lib-kube-scheduler\n        - hostPath:\n            path: /var/lib/kube-controller-manager\n          name: var-lib-kube-controller-manager\n        - hostPath:\n            path: /etc/systemd\n          name: etc-systemd\n        - hostPath:\n            path: /lib/systemd\n          name: lib-systemd\n        - hostPath:\n            path: /srv/kubernetes\n          name: srv-kubernetes\n        - hostPath:\n            path: /etc/kubernetes\n          name: etc-kubernetes\n        - hostPath:\n            path: /usr/bin\n          name: usr-bin\n        - hostPath:\n            path: /etc/cni/net.d/\n          name: etc-cni-netd\n        - hostPath:\n            path: /opt/cni/bin/\n          name: opt-cni-bin\n"
  },
  {
    "path": "test/benchmark/storage/dbench-rwo.yaml",
    "content": "# kubectl apply -f dbench-rwo.yaml\n---\nkind: PersistentVolumeClaim\napiVersion: v1\nmetadata:\n  name: dbench-rwo\nspec:\n  storageClassName: standard-rwo\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 10Gi\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: dbench-rwo\nspec:\n  template:\n    spec:\n      containers:\n        - name: dbench\n          image: zayashv/dbench:latest\n          imagePullPolicy: Always\n          env:\n            - name: DBENCH_MOUNTPOINT\n              value: /data\n            - name: DBENCH_QUICK\n              value: \"no\"\n          volumeMounts:\n            - name: data\n              mountPath: /data\n      restartPolicy: Never\n      volumes:\n        - name: data\n          persistentVolumeClaim:\n            claimName: dbench-rwo\n"
  },
  {
    "path": "test/benchmark/storage/dbench-rwx.yaml",
    "content": "# kubectl apply -f dbench-rwx.yaml\n---\nkind: PersistentVolumeClaim\napiVersion: v1\nmetadata:\n  name: dbench-rwx\nspec:\n  storageClassName: standard-rwx\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 10Gi\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: dbench-rwx\nspec:\n  template:\n    spec:\n      containers:\n        - name: dbench\n          image: zayashv/dbench:latest\n          imagePullPolicy: Always\n          env:\n            - name: DBENCH_MOUNTPOINT\n              value: /data\n            - name: DBENCH_QUICK\n              value: \"no\"\n          volumeMounts:\n            - name: data\n              mountPath: /data\n      restartPolicy: Never\n      volumes:\n        - name: data\n          persistentVolumeClaim:\n            claimName: dbench-rwx\n"
  },
  {
    "path": "test/external_test.go",
    "content": "package test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/gruntwork-io/terratest/modules/terraform\"\n\ttest_structure \"github.com/gruntwork-io/terratest/modules/test-structure\"\n)\n\nfunc TestTerraformExternal(t *testing.T) {\n\tt.Parallel()\n\n\t// Make a copy of the terraform module to a temporary directory. This allows running multiple tests in parallel\n\t// against the same terraform module.\n\texampleFolder := test_structure.CopyTerraformFolderToTemp(t, \"../external\", \".\")\n\n\tterraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{\n\t\tTerraformDir: exampleFolder,\n\t})\n\n\tterraform.Init(t, terraformOptions)\n\tterraform.Validate(t, terraformOptions)\n}\n"
  },
  {
    "path": "test/go.mod",
    "content": "module git.khuedoan.com/ops/homelab\n\ngo 1.21\n\ntoolchain go1.21.4\n\nrequire github.com/gruntwork-io/terratest v0.46.1\n\nrequire (\n\tcloud.google.com/go v0.105.0 // indirect\n\tcloud.google.com/go/compute v1.12.1 // indirect\n\tcloud.google.com/go/compute/metadata v0.2.1 // indirect\n\tcloud.google.com/go/iam v0.7.0 // indirect\n\tcloud.google.com/go/storage v1.27.0 // indirect\n\tgithub.com/agext/levenshtein v1.2.3 // indirect\n\tgithub.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect\n\tgithub.com/aws/aws-sdk-go v1.44.122 // indirect\n\tgithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect\n\tgithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.9.0 // indirect\n\tgithub.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect\n\tgithub.com/go-logr/logr v1.2.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.6 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.1 // indirect\n\tgithub.com/go-openapi/swag v0.22.3 // indirect\n\tgithub.com/go-sql-driver/mysql v1.4.1 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/protobuf v1.5.3 // indirect\n\tgithub.com/google/gnostic v0.5.7-v3refs // indirect\n\tgithub.com/google/go-cmp v0.5.9 // indirect\n\tgithub.com/google/gofuzz v1.1.0 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.7.0 // indirect\n\tgithub.com/gruntwork-io/go-commons v0.8.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.0.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-getter v1.7.1 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.0 // indirect\n\tgithub.com/hashicorp/go-safetemp v1.0.0 // indirect\n\tgithub.com/hashicorp/go-version v1.6.0 // indirect\n\tgithub.com/hashicorp/hcl/v2 v2.9.1 // indirect\n\tgithub.com/hashicorp/terraform-json v0.13.0 // indirect\n\tgithub.com/imdario/mergo v0.3.11 // indirect\n\tgithub.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.15.11 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/go-testing-interface v1.14.1 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/moby/spdystream v0.2.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/pquerna/otp v1.2.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/stretchr/testify v1.8.1 // indirect\n\tgithub.com/tmccombs/hcl2json v0.3.3 // indirect\n\tgithub.com/ulikunitz/xz v0.5.10 // indirect\n\tgithub.com/urfave/cli v1.22.2 // indirect\n\tgithub.com/zclconf/go-cty v1.9.1 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgolang.org/x/crypto v0.14.0 // indirect\n\tgolang.org/x/net v0.17.0 // indirect\n\tgolang.org/x/oauth2 v0.1.0 // indirect\n\tgolang.org/x/sys v0.13.0 // indirect\n\tgolang.org/x/term v0.13.0 // indirect\n\tgolang.org/x/text v0.13.0 // indirect\n\tgolang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect\n\tgolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect\n\tgoogle.golang.org/api v0.103.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c // indirect\n\tgoogle.golang.org/grpc v1.51.0 // indirect\n\tgoogle.golang.org/protobuf v1.31.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/api v0.27.2 // indirect\n\tk8s.io/apimachinery v0.27.2 // indirect\n\tk8s.io/client-go v0.27.2 // indirect\n\tk8s.io/klog/v2 v2.90.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect\n\tk8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect\n\tsigs.k8s.io/yaml v1.3.0 // indirect\n)\n\n// TODO https://github.com/gruntwork-io/terratest/pull/1182\nreplace github.com/gruntwork-io/terratest v0.46.1 => github.com/khuedoan/terratest v0.0.0-20231027122225-118d656063b1\n"
  },
  {
    "path": "test/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=\ncloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=\ncloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=\ncloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=\ncloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=\ncloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=\ncloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=\ncloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=\ncloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=\ncloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=\ncloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=\ncloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=\ncloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=\ncloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=\ncloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=\ncloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=\ncloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=\ncloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=\ncloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=\ncloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=\ncloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=\ncloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=\ncloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=\ncloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=\ncloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=\ncloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=\ncloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=\ncloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=\ncloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=\ncloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=\ncloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=\ncloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=\ncloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=\ncloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=\ncloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=\ncloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=\ncloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=\ncloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=\ncloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=\ncloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=\ncloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=\ncloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=\ncloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=\ncloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=\ncloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=\ncloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=\ncloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=\ncloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=\ncloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=\ncloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=\ncloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=\ncloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=\ncloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=\ncloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=\ncloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=\ncloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=\ncloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=\ncloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=\ncloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=\ncloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=\ncloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=\ncloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=\ncloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=\ncloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=\ncloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=\ncloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs=\ncloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=\ncloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=\ncloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=\ncloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=\ncloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=\ncloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=\ncloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=\ncloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=\ncloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=\ncloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=\ncloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=\ncloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=\ncloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=\ncloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=\ncloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=\ncloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=\ncloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=\ncloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=\ncloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=\ncloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=\ncloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=\ncloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=\ncloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=\ncloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=\ncloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=\ncloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=\ncloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=\ncloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=\ncloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=\ncloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=\ncloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=\ncloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=\ncloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=\ncloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=\ncloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=\ncloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=\ncloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=\ncloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=\ncloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=\ncloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=\ncloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=\ncloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=\ncloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=\ncloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=\ncloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=\ncloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=\ncloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=\ncloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=\ncloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=\ncloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=\ncloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=\ncloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ=\ncloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=\ncloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=\ncloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=\ncloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=\ncloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=\ncloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=\ncloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=\ncloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=\ncloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=\ncloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=\ncloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=\ncloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=\ngithub.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=\ngithub.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=\ngithub.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=\ngithub.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=\ngithub.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo=\ngithub.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=\ngithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=\ngithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=\ngithub.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU=\ngithub.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=\ngithub.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=\ngithub.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=\ngithub.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=\ngithub.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=\ngithub.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=\ngithub.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=\ngithub.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=\ngithub.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=\ngithub.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=\ngithub.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=\ngithub.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=\ngithub.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro=\ngithub.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78=\ngithub.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY=\ngithub.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744=\ngithub.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=\ngithub.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=\ngithub.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=\ngithub.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/hcl/v2 v2.9.1 h1:eOy4gREY0/ZQHNItlfuEZqtcQbXIxzojlP301hDpnac=\ngithub.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=\ngithub.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY=\ngithub.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=\ngithub.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=\ngithub.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/khuedoan/terratest v0.0.0-20231027122225-118d656063b1 h1:tUqeZ6zdVYNz+VvLtQzxVlDfZocX5ttfQh6M1i7f6FU=\ngithub.com/khuedoan/terratest v0.0.0-20231027122225-118d656063b1/go.mod h1:gl//tb5cLnbpQs1FTSNwhsrbhsoG00goCJPfOnyliiU=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=\ngithub.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=\ngithub.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg=\ngithub.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=\ngithub.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=\ngithub.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=\ngithub.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=\ngithub.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=\ngithub.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=\ngithub.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=\ngithub.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ=\ngithub.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w=\ngithub.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=\ngithub.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=\ngithub.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=\ngithub.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=\ngithub.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=\ngithub.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=\ngithub.com/zclconf/go-cty v1.9.1 h1:viqrgQwFl5UpSxc046qblj78wZXVDFnSOufaOTER+cc=\ngithub.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=\ngithub.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=\ngolang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=\ngolang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=\ngolang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=\ngolang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=\ngolang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=\ngoogle.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=\ngoogle.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=\ngoogle.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=\ngoogle.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=\ngoogle.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=\ngoogle.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=\ngoogle.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=\ngoogle.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=\ngoogle.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=\ngoogle.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c h1:S34D59DS2GWOEwWNt4fYmTcFrtlOgukG2k9WsomZ7tg=\ngoogle.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=\ngoogle.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=\ngoogle.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nk8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo=\nk8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4=\nk8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg=\nk8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=\nk8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE=\nk8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ=\nk8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=\nk8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=\nk8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=\nk8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=\nk8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=\nsigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=\nsigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=\n"
  },
  {
    "path": "test/integration_test.go",
    "content": "package test\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\thttp_helper \"github.com/gruntwork-io/terratest/modules/http-helper\"\n\t\"github.com/gruntwork-io/terratest/modules/k8s\"\n)\n\nfunc TestArgoCDCheck(t *testing.T) {\n\tt.Parallel()\n\n\t// Setup the kubectl config and context. Here we choose to use the defaults, which is:\n\t// - $KUBECONFIG for the kubectl config file\n\t// - Current context of the kubectl config file\n\toptions := k8s.NewKubectlOptions(\"\", \"\", \"argocd\")\n\n\t// This will wait up to 10 seconds for the service to become available, to ensure that we can access it\n\tk8s.WaitUntilIngressAvailable(t, options, \"argocd-server\", 10, 1*time.Second)\n\n\t// Now we verify that the service will successfully boot and start serving requests\n\tingress := k8s.GetIngress(t, options, \"argocd-server\")\n\n\t// Setup a TLS configuration to submit with the helper, a blank struct is acceptable\n\ttlsConfig := tls.Config{}\n\n\t// Test the endpoint for up to 5 minutes. This will only fail if we timeout waiting for the service to return a 200 response\n\thttp_helper.HttpGetWithRetryWithCustomValidation(\n\t\tt,\n\t\tfmt.Sprintf(\"https://%s\", ingress.Spec.Rules[0].Host),\n\t\t&tlsConfig,\n\t\t30,\n\t\t30*time.Second,\n\t\tfunc(statusCode int, body string) bool {\n\t\t\treturn statusCode == 200\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "test/smoke_test.go",
    "content": "package test\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\thttp_helper \"github.com/gruntwork-io/terratest/modules/http-helper\"\n\t\"github.com/gruntwork-io/terratest/modules/k8s\"\n)\n\nfunc TestSmoke(t *testing.T) {\n\tt.Parallel()\n\n\tvar mainApps = []struct {\n\t\tname      string\n\t\tnamespace string\n\t}{\n\t\t{\"argocd-server\", \"argocd\"},\n\t\t{\"gitea\", \"gitea\"},\n\t\t{\"grafana\", \"grafana\"},\n\t\t{\"homepage\", \"homepage\"},\n\t\t{\"kanidm\", \"kanidm\"},\n\t\t{\"zot\", \"zot\"},\n\t}\n\n\tfor _, app := range mainApps {\n\t\tapp := app // https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables\n\t\tt.Run(app.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\toptions := k8s.NewKubectlOptions(\"\", \"\", app.namespace)\n\n\t\t\t// Wait the service to become available to ensure that we can access it\n\t\t\tk8s.WaitUntilIngressAvailable(t, options, app.name, 30, 60*time.Second)\n\n\t\t\t// Now we verify that the service will successfully boot and start serving requests\n\t\t\tingress := k8s.GetIngress(t, options, app.name)\n\n\t\t\t// Setup a TLS configuration, ignore the certificate because we may not use cert-manager (like the sandbox environment)\n\t\t\ttlsConfig := tls.Config{\n\t\t\t\tInsecureSkipVerify: os.Getenv(\"INSECURE_SKIP_VERIFY\") != \"\",\n\t\t\t}\n\n\t\t\t// Test the endpoint, this will only fail if we timeout waiting for the service to return a 200 response\n\t\t\thttp_helper.HttpGetWithRetryWithCustomValidation(\n\t\t\t\tt,\n\t\t\t\tfmt.Sprintf(\"https://%s\", ingress.Spec.Rules[0].Host),\n\t\t\t\t&tlsConfig,\n\t\t\t\t30,\n\t\t\t\t60*time.Second,\n\t\t\t\tfunc(statusCode int, body string) bool {\n\t\t\t\t\treturn statusCode == 200\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "test/tools_test.go",
    "content": "package test\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/gruntwork-io/terratest/modules/shell\"\n\t\"github.com/gruntwork-io/terratest/modules/version-checker\"\n)\n\nfunc TestToolsVersions(t *testing.T) {\n\tt.Parallel()\n\n\tvar tools = []struct {\n\t\tbinaryPath        string\n\t\tversionArg        string\n\t\tversionConstraint string\n\t}{\n\t\t{\"ansible\", \"--version\", \">= 2.12.6, < 3.0.0\"},\n\t\t{\"docker\", \"--version\", \">= 25.0.0, < 26.0.0\"},\n\t\t{\"git\", \"--version\", \">= 2.37.1, < 3.0.0\"},\n\t\t{\"go\", \"version\", \">= 1.22.0, < 1.23.0\"},\n\t\t{\"helm\", \"version\", \">= 3.9.4, < 4.0.0\"},\n\t\t{\"kubectl\", \"version\", \">= 1.30.0, < 1.32.0\"}, // https://kubernetes.io/releases/version-skew-policy/#kubectl\n\t\t{\"kustomize\", \"version\", \">= 5.0.3, < 6.0.0\"},\n\t\t{\"pre-commit\", \"--version\", \">= 3.3.2, < 4.0.0\"},\n\t\t{\"tofu\", \"--version\", \">= 1.7.0, < 1.9.0\"},\n\t}\n\n\tfor _, tool := range tools {\n\t\ttool := tool // https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables\n\t\tt.Run(tool.binaryPath, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tparams := version_checker.CheckVersionParams{\n\t\t\t\tBinaryPath:        tool.binaryPath,\n\t\t\t\tVersionConstraint: tool.versionConstraint,\n\t\t\t\tVersionArg:        tool.versionArg,\n\t\t\t\tWorkingDir:        \".\",\n\t\t\t}\n\n\t\t\tversion_checker.CheckVersion(t, params)\n\t\t})\n\t}\n}\n\nfunc TestToolsNixShell(t *testing.T) {\n\tt.Parallel()\n\n\tprojectRoot, err := filepath.Abs(\"../\")\n\tif err != nil {\n\t\tt.FailNow()\n\t}\n\n\tcommand := shell.Command{\n\t\tCommand: \"nix\",\n\t\tArgs: []string{\n\t\t\t\"develop\",\n\t\t\t\"--experimental-features\", \"nix-command flakes\",\n\t\t\t\"--command\", \"true\",\n\t\t},\n\t\tWorkingDir: projectRoot,\n\t}\n\n\tshell.RunCommand(t, command)\n}\n"
  }
]