diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 9a9eb59..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: ci -on: - push: - branches: - - master - - main -permissions: - contents: write -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.x - - uses: actions/cache@v4 - with: - key: ${{ github.ref }} - path: .cache - - run: pip install -r requirements.txt - - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c086726..0000000 --- a/.gitignore +++ /dev/null @@ -1,170 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# PyPI configuration file -.pypirc diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..91d8d5a --- /dev/null +++ b/404.html @@ -0,0 +1,2134 @@ + + + + + + + + + + + + + + + + + + + Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md deleted file mode 100644 index 424333e..0000000 --- a/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Web3 Pi Documentation - -## How to Run the Project - -## Prerequisites - -1. Ensure you have **Python 3.11** or newer installed on your system. -2. Make sure you have `pip` installed to manage Python packages. - -## Steps to Run the Project - -1. **Clone the Repository** - - Clone the repository to your local system using the command below: - - ```bash - git clone git@github.com:Web3-Pi/docs.git - ``` - -2. **Navigate to the Project Directory** - - Switch to the project's root directory: - - ```bash - cd docs - ``` - -3. **Create and Activate a Virtual Environment** - - It is recommended to use a virtual environment to isolate the project dependencies. Use the following commands to - create and activate the virtual environment: - - ```bash - python -m venv .venv - source .venv/bin/activate # On Linux/macOS - .venv\Scripts\activate # On Windows - ``` - -4. **Install Dependencies** - - Install the required Python dependencies: - - ```bash - pip install -r requirements.txt - ``` - -5. **Build the Documentation** - - To build and serve the documentation locally, run the following command: - - ```bash - mkdocs serve - ``` - - This will start a local development server and you will be able to access the documentation at: - - ``` - http://127.0.0.1:8000/ - ``` - -6. **Check the Deployed Documentation** - - You can also view the deployed documentation online at [https://docs.web3pi.io/](https://docs.web3pi.io/). - -## Additional Notes - -- This project uses the **Material for MkDocs theme**, which is defined in the `mkdocs.yml` configuration file. -- Ensure the directories and assets (such as `extra_css`, `extra_javascript`, and logo images) defined in `mkdocs.yml` - are accessible when building the project. diff --git a/advanced-setup/2fa/index.html b/advanced-setup/2fa/index.html new file mode 100644 index 0000000..71bb301 --- /dev/null +++ b/advanced-setup/2fa/index.html @@ -0,0 +1,2560 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Two-Factor Authentication (2FA) - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + + + + +

Two-Factor Authentication (2FA) for Cockpit

+

Adding Two-Factor Authentication (2FA) to Cockpit increases the security of your server by requiring a time-based one-time password (TOTP) in addition to your regular credentials.

+

The Web3 Pi team developed a plugin to easily enable 2FA in Cockpit using a graphical interface. Alternatively, you can manually configure 2FA using the libpam-google-authenticator package.

+
+
+
+

Step 1: Install Required Packages

+

Navigate to the Web3 Pi Updater section in Cockpit and install Two Factor Authentication Plugin for Cockpit:

+

Install 2FA Plugin

+

Alternatively, you can install the plugin manually by opening a terminal and running the following command:

+
sudo apt-get install w3p-two-factor-auth
+
+
+

Note

+

Cockpit will restart after the installation of the plugin, so you may need to refresh the page and log in again.

+
+

Step 2: Configure 2FA

+

After installing the plugin, navigate to the Web3 Pi 2FA section in Cockpit:

+

2FA Configuration

+

Click Enable Two-Factor Authentication to start the setup process.

+

Enable 2FA button

+

Follow the on-screen instructions to set up 2FA:

+
    +
  1. Scan the QR code with your preferred authenticator app (e.g., Google Authenticator, Authy).
  2. +
  3. Enter the verification code from your authenticator app to complete the setup.
  4. +
  5. Save the emergency scratch codes in a safe place.
  6. +
+
+

Note

+

Scratch codes are one-time use only. If you lose access to your authenticator app, enter one of these codes to log in and recreate your 2FA setup.

+
+

Step 3: Test Your Setup

+
    +
  1. Log out of Cockpit.
  2. +
  3. Log back in. You should be prompted for a verification code from your authenticator app.
  4. +
+

Verification Code Prompt

+

Uninstalling 2FA

+

To remove 2FA from Cockpit, navigate back to the Web3 Pi 2FA section and click Disable Two-Factor Authentication.

+

Disable 2FA button

+
+
+

Step 1: Install Required Packages

+

Open a terminal and run:

+
sudo apt-get install libpam-google-authenticator -y
+
+

This installs the PAM module for Google Authenticator.

+

Step 2: Configure Google Authenticator for Your User

+

Run the following command to set up Google Authenticator with recommended options:

+
google-authenticator -t -d -f -r 3 -R 30 -W -Q UTF8
+
+
+

Note

+
    +
  • -t use TOTP instead of HOTP (recommended).
  • +
  • -d disable reuse of previously used TOTP tokens.
  • +
  • -f disable confirmation before writing the ~/.google_authenticator file.
  • +
  • -r 3 -R 30 limits the number of login attempts to 3 every 30 seconds.
  • +
  • -W by default google-authenticator allows the use of codes that were generated a little before or a little after the current time. This option disables that feature (recommended for security).
  • +
  • -Q UTF8 specifies the encoding for the QR code. Change to -Q ANSI if you're having issues with viewing the QR code.
  • +
+
+
    +
  • This will generate a secret key, QR code, and emergency scratch codes.
  • +
  • Scan the QR code with your preferred authenticator app (e.g., Google Authenticator, Authy).
  • +
  • Enter the verification code from your authenticator app to complete the setup.
  • +
  • Save the emergency scratch codes in a safe place.
  • +
+
+

Note

+

Scratch codes are one-time use only. If you lose access to your authenticator app, enter one of these codes to log in and recreate your 2FA setup.

+
+

Step 3: Enable 2FA for Cockpit

+

Use the following command to add the Google Authenticator PAM module to the Cockpit PAM configuration:

+
sudo bash -c 'echo "auth required pam_google_authenticator.so nullok" >> /etc/pam.d/cockpit'
+
+

This tells Cockpit to require a TOTP code during login.

+
+

Note

+
    +
  • The nullok option disables 2FA for users that do not have a ~/.google_authenticator file.
  • +
+
+

Step 4: Restart Cockpit

+

Restart the Cockpit service to apply the changes:

+
sudo systemctl restart cockpit
+
+

Step 5: Test Your Setup

+
    +
  1. Log out of Cockpit.
  2. +
  3. Log back in. You should be prompted for a verification code from your authenticator app.
  4. +
+

Verification Code Prompt

+

Uninstalling 2FA

+

To remove 2FA from Cockpit, simply delete the line you added to the PAM configuration:

+
sudo bash -c 'sed -i "/pam_google_authenticator.so nullok/d" /etc/pam.d/cockpit'
+
+

Then restart the Cockpit service:

+
sudo systemctl restart cockpit
+
+

You can also remove the generated ~/.google_authenticator file and the installed packages if you no longer need 2FA:

+
sudo apt remove libpam-google-authenticator -y
+rm ~/.google_authenticator
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/advanced-setup/OC/index.html b/advanced-setup/OC/index.html new file mode 100644 index 0000000..5600ef8 --- /dev/null +++ b/advanced-setup/OC/index.html @@ -0,0 +1,2690 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Overclocking - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Overclocking

+

Raspberry Pi 5

+

There are two things that can be tweaked on the Raspberry Pi 5 to increase Ethereum Node performance.

+
    +
  • CPU frequency
  • +
  • PCIe generation
  • +
+
+

Note about PCIe generation settings

+

This makes sense only if using a PCIe to m.2 adapter for storage.

+
+

CPU Overclocking

+

By default, the Raspberry Pi 5 CPU clock is set to 2.4 GHz, but it is relatively easy to overclock. An overclocked CPU with a significant load will require an active cooling solution or a high-quality cooling case.

+

The maximum stable clock that can be achieved depends on a particular device.

+

Safe for all devices is 2.6 GHz. +The reasonable top is 3.0 GHz.

+

The Raspberry Pi has enough power to handle an Ethereum node without OC, so our recommendation is to keep stable settings like 2.6 GHz.

+

How to overclock the CPU

+
    +
  • Edit the /boot/firmware/config.txt file: +
    sudo nano /boot/firmware/config.txt
    +
  • +
  • Find the last [pi5] section, almost at the end of the file. Look for this comment: +
     [rpi5]
    + # Overclocking for Raspberry Pi 5
    + # Active cooling is required
    + over_voltage_delta=50000
    + arm_freq=2800
    + #2400MHz is default
    + #3000MHz is max (not all boards will work stable)
    + #2800MHz is reasonable OC
    +
  • +
  • Exit the editor by pressing Ctrl+x and save the changes.
  • +
  • Restart the device: +
    sudo reboot
    +
  • +
  • After reboot, you can check if the frequency is correctly recognized by the OS.
  • +
+
sudo cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
+
+

It should output 2600000

+

If the procedure is successful, the device should be up and running with the updated OC settings.

+
+

For more information about overclocking the Raspberry Pi 5, please visit:

+

https://www.jeffgeerling.com/blog/2023/overclocking-and-underclocking-raspberry-pi-5

+
+

PCIe generation select

+

The Raspberry Pi by default uses PCIe gen 2. But the Broadcom BCM2712 offers PCIe generation 3, which is twice as fast. +By default, it is set to gen 2 because of compatibility reasons with different adapters. +In most cases, you can safely set it to gen 3, which can double the performance of the NVMe drive.

+
    +
  • Edit the /boot/firmware/config.txt file: +
    sudo nano /boot/firmware/config.txt
    +
  • +
  • Find the last [pi5] section, almost at the end of the file. Look for this comment: +
     #Enable PCIe
    + dtparam=pciex1
    + #Enable PCIe gen.3 (default is gen.2)
    + dtparam=pciex1_gen=3
    +
  • +
  • Exit the editor by pressing Ctrl+x and save the changes.
  • +
  • Restart the device: +
    sudo reboot
    +
  • +
+

For more information, please visit: https://www.jeffgeerling.com/blog/2023/nvme-ssd-boot-raspberry-pi-5

+

Raspberry Pi 4

+

To overclock the Raspberry Pi 4, you need to edit the config.txt file located in the /boot/firmware/ directory.

+
    +
  1. +

    Access the File:

    +
  2. +
  3. +

    Open a terminal on your Raspberry Pi.

    +
  4. +
  5. Edit the config.txt file using a text editor such as nano.
  6. +
+
sudo nano /boot/firmware/config.txt
+
+
    +
  1. +

    Add Overclocking Settings:

    +
  2. +
  3. +

    Uncomment the following lines near the end of the config.txt file. Adjust the values based on your desired overclock settings and the stability of your system.

    +
  4. +
+
[pi4]
+over_voltage=6
+arm_freq=1800
+gpu_freq=600
+
+

Explanation:

+
    +
  • over_voltage=6: Increases the core voltage. Values range from 0 to 8. Higher values increase stability but also generate more heat.
  • +
  • arm_freq=1800: Sets the CPU frequency to 1800 MHz (1.8 GHz).
      +
    • The default is 1500 MHz
    • +
    • Moderate OC is 1800 MHz
    • +
    • A high overclock is 2000 MHz
    • +
    +
  • +
  • +

    gpu_freq=600: Sets the GPU frequency to 600 MHz.

    +
      +
    • The default is 500 MHz
    • +
    • Moderate OC is 600 MHz
    • +
    • A high overclock is 750 MHz
    • +
    +
  • +
  • +

    Save and Reboot:

    +
  • +
  • +

    Save the file (Ctrl+O and Enter in nano) and exit the text editor (Ctrl+X in nano).

    +
  • +
  • Reboot the Raspberry Pi to apply the changes.
  • +
+
sudo reboot
+
+

Raspberry Pi CM4

+

Overclocking the Raspberry Pi Compute Module 4 (CM4) is similar to overclocking the Raspberry Pi 4, but there are a few key differences to consider due to the form factor and intended use cases of the CM4.

+

Similarities

+
    +
  1. Configuration File:
  2. +
  3. Both use the config.txt file located in the /boot directory for overclocking settings.
  4. +
  5. +

    Overclocking Parameters:

    +
  6. +
  7. +

    Parameters such as over_voltage, arm_freq, and gpu_freq are used in the same way to adjust voltage, CPU frequency, and GPU frequency.

    +
  8. +
  9. +

    Monitoring and Testing:

    +
  10. +
  11. Tools and methods for monitoring temperature, checking for throttling, and stress testing are the same.
  12. +
+

Differences

+
    +
  1. +

    Form Factor and Cooling:

    +
  2. +
  3. +

    The CM4 is designed to be used with custom carrier boards, which may affect cooling solutions. Ensure your carrier board design allows for adequate cooling, especially when overclocking.

    +
  4. +
  5. +

    Power Supply:

    +
  6. +
  7. The power supply and power delivery to the CM4 might be different depending on the carrier board. Ensure that the carrier board can supply sufficient power for overclocking.
  8. +
+

Monitoring and Stability

+
    +
  1. +

    Monitor Temperatures

    +
  2. +
  3. +

    Use tools like vcgencmd to monitor the temperature of your Raspberry Pi.

    +
  4. +
+
vcgencmd measure_temp
+
+
    +
  • +

    Ideally, temperatures should remain below 85°C. If temperatures are higher, consider improving your cooling solution.

    +
  • +
  • +

    Stress Test

    +
  • +
  • +

    Run stress tests to ensure stability. The stress tool can be used for this purpose.

    +
  • +
+
sudo apt install stress
+stress --cpu 4 --timeout 600
+
+
    +
  1. +

    Check for Throttling

    +
  2. +
  3. +

    Use vcgencmd to check if the Raspberry Pi is throttling due to high temperatures or insufficient power.

    +
  4. +
+
vcgencmd get_throttled
+
+
    +
  • A result of 0x0 indicates no throttling.
  • +
+

Safety Tips

+
    +
  1. Incremental Changes: Start with small increments and gradually increase the values. Monitor stability and temperatures at each step.
  2. +
  3. Cooling: Ensure you have sufficient cooling. Consider adding a fan or better heatsinks if necessary.
  4. +
  5. Power Supply: Use a high-quality power supply that can handle the increased power demands.
  6. +
  7. Testing: Perform extensive testing to ensure that your system remains stable under load.
  8. +
+

Conclusion

+

Overclocking the Raspberry Pi can provide significant performance improvements, making it more capable for an Ethereum node. However, it is crucial to approach overclocking with caution, ensuring adequate cooling and power supply, and thoroughly testing for stability. By following these guidelines, you can safely and effectively overclock your Raspberry Pi to meet your performance needs.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/advanced-setup/config/index.html b/advanced-setup/config/index.html new file mode 100644 index 0000000..59dce1a --- /dev/null +++ b/advanced-setup/config/index.html @@ -0,0 +1,2456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + The config.txt File - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Advanced Setup: The config.txt File

+

Overview

+

Web3 Pi utilizes a central configuration file, /boot/firmware/config.txt, to manage both its own settings (like which clients to run and network selection) and the underlying Raspberry Pi hardware configuration (like boot options, hardware interfaces, and overclocking).

+

This file is read by the Raspberry Pi firmware during the early boot process.

+

For most users, the default settings generated by the Web3 Pi Imager are sufficient and should not need modification. This page is intended for advanced users who understand the implications of changing these parameters.

+

⚠️ WARNING: Incorrectly editing /boot/firmware/config.txt can prevent your Raspberry Pi from booting correctly or cause your Ethereum node clients to malfunction. Always back up the file before making changes.

+

Location and Editing

+

The configuration file is located at:

+

/boot/firmware/config.txt

+

You will need root privileges to edit this file. Connect via SSH and use a text editor like nano:

+
# First, create a backup
+sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.backup
+
+# Then, edit the file
+sudo nano /boot/firmware/config.txt
+
+

After saving changes, you must reboot your Raspberry Pi for them to take effect:

+
sudo reboot
+
+

Web3 Pi Specific Sections

+

These sections control the behavior of the Web3 Pi software suite.

+

[web3pi] Section

+
    +
  • geth=true|false: Enables or disables the Geth execution client service (w3p_geth).
  • +
  • nimbus=true|false: Enables or disables the Nimbus consensus client service (w3p_nimbus-beacon).
  • +
  • lighthouse=true|false: Enables or disables the Lighthouse consensus client service (w3p_lighthouse-beacon). (Note: Typically, only one consensus client should be enabled).
  • +
  • influxdb=true|false: Enables or disables the InfluxDB time-series database service (for monitoring).
  • +
  • grafana=true|false: Enables or disables the Grafana dashboard service (for monitoring).
  • +
  • bsm=true|false: Enables or disables the Basic System Monitor service.
  • +
  • bnm=true|false: Enables or disables the Basic Eth2 Node Monitor service.
  • +
  • exec_url=http://localhost:8551: Specifies the URL the consensus client uses to connect to the execution client's Engine API. The default assumes both clients are on the same machine.
  • +
  • eth_network=mainnet|sepolia|hoodi|...: Crucially, sets the target Ethereum network for all clients. Must match the network you intend to sync.
  • +
+

Client Port Sections ([geth], [nimbus], [lighthouse]):

+
    +
  • geth_port=30303: Sets the P2P port Geth uses for peer discovery and communication.
  • +
  • nimbus_port=9000: Sets the P2P port Nimbus uses.
  • +
  • lighthouse_port=9000: Sets the P2P port Lighthouse uses.
  • +
+

Make sure to update the UFW Firewall after changing these ports.

+

Standard Raspberry Pi Sections

+

The rest of the config.txt file contains standard Raspberry Pi configuration directives, grouped under conditional filters like [all], [pi4], [pi5], [cm4], etc. These control hardware aspects:

+
    +
  • Boot Options: (kernel, cmdline, initramfs) Defines how the Linux kernel is loaded.
  • +
  • Hardware Interfaces: (dtparam=audio, i2c_arm, spi, enable_uart) Enables or disables onboard hardware like audio, I2C, SPI, and serial ports.
  • +
  • Display Settings: (disable_overscan, hdmi_drive, hdmi_force_hotplug, display_auto_detect) Configures HDMI output behavior.
  • +
  • Graphics: (dtoverlay=vc4-kms-v3d) Configures the graphics driver.
  • +
  • Camera: (camera_auto_detect) Auto-detects connected cameras.
  • +
  • USB: (dtoverlay=dwc2, usb_max_current_enable) Configures USB ports, including enabling higher current output on Pi 5.
  • +
  • PCIe (Pi 5 / CM4): (dtparam=pciex1, dtparam=pciex1_gen=3) Important for NVMe drives. Enables the PCIe interface and sets its speed (Gen 2 or Gen 3). Web3 Pi typically enables Gen 3 for better NVMe performance.
  • +
  • Overclocking: (Lines often starting with # over_voltage, # arm_freq) These settings (commented out by default) allow advanced users to potentially increase CPU performance. Requires active cooling and carries risks of instability or hardware damage if done improperly.
  • +
+

Further Reading

+

For an exhaustive explanation of all standard Raspberry Pi config.txt options, refer to the official documentation:

+ +

Remember to exercise caution when editing this file. Stick to the defaults unless you have a specific need and understand the parameter you are changing.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/advanced-setup/index.html b/advanced-setup/index.html new file mode 100644 index 0000000..2f944c0 --- /dev/null +++ b/advanced-setup/index.html @@ -0,0 +1,2238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Advanced Setup - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Advanced Setup

+

This section provides advanced setup guides for your Web3 Pi node that aim to optimize your node's performance and reliability. None of these steps are required, but they can help you achieve better uptime and reduce maintenance burden.

+

Table of Contents

+ + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/advanced-setup/poe/index.html b/advanced-setup/poe/index.html new file mode 100644 index 0000000..cca1785 --- /dev/null +++ b/advanced-setup/poe/index.html @@ -0,0 +1,2287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Power over Ethernet - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Advanced Setup: Power over Ethernet (PoE)

+

Power over Ethernet (PoE) is a networking feature that allows network cables to carry electrical power in addition to data. By using PoE, you can power your Web3 Pi Raspberry Pi using the same Ethernet cable that provides its network connection, eliminating the need for a separate USB-C power adapter.

+

This can simplify cable management and potentially allow for centralized power backup if your network switch is connected to a UPS.

+

Benefits of Using PoE

+
    +
  • Simplified Wiring: Reduces cable clutter by combining power and data into a single Ethernet cable run to the Raspberry Pi.
  • +
  • Flexible Placement: Allows placing the Raspberry Pi further from power outlets, as long as an Ethernet cable can reach it.
  • +
  • Centralized Power Backup: If your PoE-providing network switch or injector is connected to a UPS (Uninterruptible Power Supply), your Raspberry Pi will also benefit from backup power.
  • +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/advanced-setup/ufw/index.html b/advanced-setup/ufw/index.html new file mode 100644 index 0000000..8c15447 --- /dev/null +++ b/advanced-setup/ufw/index.html @@ -0,0 +1,2451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Firewall Configuration (UFW) - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Advanced Setup: Firewall Configuration (UFW)

+

Understanding the Firewall

+

Web3 Pi includes and enables UFW (Uncomplicated Firewall) by default to provide a baseline level of network security for your node. UFW is a user-friendly frontend for managing the underlying iptables firewall rules on Linux systems like Ubuntu.

+

Its primary purpose is to control incoming and outgoing network traffic, ensuring that only necessary connections are allowed, thus reducing the potential attack surface of your device.

+

Default Status and Policy

+
    +
  • Enabled by Default: UFW is installed and enabled at the end of the Web3 Pi setup process.
  • +
  • Default Incoming Policy: DENY - All incoming connections are blocked unless explicitly allowed by a specific rule.
  • +
  • Default Outgoing Policy: ALLOW - All outgoing connections initiated by the Raspberry Pi are permitted.
  • +
+

Default Allowed Incoming Ports

+

The Web3 Pi installation script configures UFW to allow incoming traffic on the specific ports required for node operation, management, and monitoring based on your configuration choices during setup. The standard ports opened are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PortProtocolServicePurpose
22TCPSSHSecure remote command-line access
80TCPInstallation Monitor / Status PageViewing setup progress and basic status
3000TCPGrafana DashboardViewing node performance and health
5353UDPmDNS (Avahi Daemon)Hostname discovery (e.g., web3pi.local)
7197TCPBasic System Monitor JSON APIProgrammatic access to monitoring data
8545TCPExecution Client JSON-RPC (Geth)Wallet connections
8546TCPExecution Client WebSocket RPC (Geth)WebSocket connections for dApps/tools
8551TCPExecution Client Engine API (Geth)Communication between EL & CL clients
9090TCPCockpit System DashboardWeb-based system management
9000 (default)TCP & UDPConsensus Client P2P (Lighthouse/Nimbus)Peer discovery and communication
30303 (default)TCP & UDPExecution Client P2P (Geth)Peer discovery and communication
+

Checking Firewall Status and Rules

+

You can view the current UFW status and the list of active rules by connecting via SSH and running the following commands:

+
sudo ufw status numbered
+
+

Adding or Removing Ports

+

To add a new port, use the ufw allow command. For example, to allow incoming TPC traffic on port 12345, run the following command:

+
sudo ufw allow 12345/tcp comment 'This port is used by XYZ service'
+
+

To remove a port, use the ufw delete command. It's recommended to use the ufw status numbered command to identify the rule number before deleting it. For example, to delete the rule with the number 100, run the following command:

+
sudo ufw delete 100
+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/advanced-setup/ups/index.html b/advanced-setup/ups/index.html new file mode 100644 index 0000000..5b96dbf --- /dev/null +++ b/advanced-setup/ups/index.html @@ -0,0 +1,2651 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Backup Power - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Power Backup Solutions for Raspberry Pi 5 and Raspberry Pi 4

+

Why Use Power Backup for an Ethereum Full Node?

+

Running a full Ethereum node on Raspberry Pi, especially in projects like Web3 Pi, requires consistent and stable power. A power outage or fluctuation can result in corrupted data since the node continuously writes to storage. In addition, even if you live in a region with seemingly stable electricity, brief voltage dips or surges can occur unnoticed, potentially causing instability, freezing, or unexpected reboots of your Raspberry Pi.

+

For optimal reliability, power backup systems should ideally cover not just the Raspberry Pi but the entire network path (e.g., routers and switches). However, even just powering the Raspberry Pi can significantly enhance stability.

+

LG-310330 +X1200 +X728

+

Backup Power Options for Raspberry Pi

+

There are two primary solutions for providing backup power to Raspberry Pi:

+
    +
  1. Conventional 230/110V UPS
  2. +
  3. Dedicated UPS for Raspberry Pi SBC
  4. +
+

Conventional 230/110V UPS

+

Advantages:

+
    +
  • Widely available globally in various models and capacities.
  • +
  • Can power multiple devices, such as routers, switches, or multiple Raspberry Pis.
  • +
+

Disadvantages:

+
    +
  • Larger in size and often equipped with fans, which may produce noise.
  • +
+ +
    +
  • Legrand UPS KEOR PDU (EAN: 3414971529380)
      +
    • Silent operation and tested for reliability.
    • +
    • More details.
    • +
    +
  • +
+

Dedicated Raspberry Pi UPS

+

Advantages:

+
    +
  • Compact and silent.
  • +
  • Designed specifically for Raspberry Pi, often as a HAT or a small external device.
  • +
  • Equipped with popular 18650 cells for longer battery life, depending on the number of cells.
  • +
  • Can interface with Raspberry Pi to detect low battery levels and initiate safe shutdowns.
  • +
+

Disadvantages:

+
    +
  • Less commonly available but can be ordered online.
  • +
+

Recommendations:

+
For Raspberry Pi 5:
+ +
For Raspberry Pi 4:
+
    +
  • Geekworm Raspberry Pi X728
      +
    • Features:
        +
      • Supports three 18650 batteries.
      • +
      • Integrated power management for safe shutdown.
      • +
      • Output: 5V/6A.
      • +
      • Compact design that mounts directly onto Raspberry Pi.
      • +
      +
    • +
    • More information.
    • +
    +
  • +
+

Installation and Setup

+

The installation and configuration processes for these UPS devices are detailed on their respective product pages. Refer to the manufacturer's guides for precise instructions.

+

Recommendation

+

We highly recommend using a power backup solution to enhance the stability of your Ethereum node and reduce potential problems caused by power interruptions.

+

For Ethereum Solo Staking, power backup is critical. A reliable power supply minimizes downtime, ensures data integrity, and helps avoid penalties related to missed attestations or blocks.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/Redwing-Medium.otf b/assets/Redwing-Medium.otf similarity index 100% rename from docs/assets/Redwing-Medium.otf rename to assets/Redwing-Medium.otf diff --git a/docs/assets/color.svg b/assets/color.svg similarity index 100% rename from docs/assets/color.svg rename to assets/color.svg diff --git a/docs/assets/favicon.ico b/assets/favicon.ico similarity index 100% rename from docs/assets/favicon.ico rename to assets/favicon.ico diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.13a4f30d.min.js b/assets/javascripts/bundle.13a4f30d.min.js new file mode 100644 index 0000000..c31fa1a --- /dev/null +++ b/assets/javascripts/bundle.13a4f30d.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Vi=Object.getOwnPropertyDescriptor;var Di=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,zi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Ni=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Di(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Vi(t,n))||o.enumerable});return e};var Lt=(e,t,r)=>(r=e!=null?Wi(zi(e)):{},Ni(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function ee(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,ee())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((dy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Rt=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Rt=="object"?Rt.ClipboardJS=r():t.ClipboardJS=r()})(Rt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(D){try{return document.execCommand(D)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(D){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=D,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var V=f()(F);return u("copy"),F.remove(),V},ee=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=ee;function k(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(D)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,V=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:V});if(Y)return F==="cut"?y(Y):J(Y,{container:V})},qe=ft;function Fe(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(D)}function ki(D,A){if(!(D instanceof A))throw new TypeError("Cannot call a class as a function")}function no(D,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=Fe(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var Y=this;this.listener=c()(V,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(V){var Y=V.delegateTarget||V.currentTarget,$e=this.action(Y)||"copy",Wt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Wt?"success":"error",{action:$e,text:Wt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return vr("action",V)}},{key:"defaultTarget",value:function(V){var Y=vr("target",V);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(V){return vr("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(V,Y)}},{key:"cut",value:function(V){return y(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof V=="string"?[V]:V,$e=!!document.queryCommandSupported;return Y.forEach(function(Wt){$e=$e&&!!document.queryCommandSupported(Wt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function z(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],z(i)),z(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function Nt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var _t={now:function(){return(_t.delegate||Date).now()},delegate:void 0};var At=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=_t);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&o===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Kt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Kt(Hr(e))?e.pop():void 0}function Yt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Bt(e){return H(e==null?void 0:e.then)}function Gt(e){return H(e[bt])}function Jt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Xt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Zt=Zi();function er(e){return H(e==null?void 0:e[Zt])}function tr(e){return fo(this,arguments,function(){var r,o,n,i;return Dt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rr(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Gt(e))return ea(e);if(xt(e))return ta(e);if(Bt(e))return ra(e);if(Jt(e))return Ao(e);if(er(e))return oa(e);if(rr(e))return na(e)}throw Xt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?Ve(t):Qo(function(){return new nr}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},ee=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;ee(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(ee,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(ee,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function Ht(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?kt(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function wt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?wt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function Tt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function De(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>De(e)),Q(De(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function ze(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return ze(e).pipe(m(({y:r})=>{let o=ce(e),n=Tt(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function Ne(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function $t(e){let t=matchMedia(e);return ir(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function zr(e,t){return e.pipe(v(r=>r?t():S))}function Nr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return Nr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return N([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(te("size")),n=N([o,r]).pipe(m(()=>De(e)));return N([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),St=JSON.parse(Ca.textContent);St.base=`${new URL(St.base,ye())}`;function xe(){return St}function B(e){return St.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?St.translations[e].replace("#",t.toString()):St.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Pt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Lt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=N([et(e),Ht(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(ze),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>N([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(kt(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());N([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>Ht(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return N([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),N([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>N([tn(e),ze(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Va(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Va(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Lt(Br());var Da=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function za(e){return ge(e).pipe(m(({width:t})=>({scrollable:Tt(e).width>t})),te("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Da++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),za(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function Na(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Na(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?wt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Vn=x("table");function Dn(e){return e.replaceWith(Vn),Vn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function zn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));N([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=De(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),N([ze(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=Tt(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function Nn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Dn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>zn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?ze(o):I({x:0,y:0}),i=O(et(t),Ht(t)).pipe(K());return N([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=De(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Pt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=Ne("search");return N([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>N([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(te("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),te("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),te("bottom"))));return N([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=$t("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Lt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(te("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),te("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(te("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Lt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function It(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),Ne("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),N([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let p=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(p)||(p=[p]);e:for(let c of p)for(let l of n.aliases.concat(n.version))if(new RegExp(c,"i").test(l)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let p of ae("outdated"))p.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),Ne("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(It)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return N([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));N([t.pipe(Ae(It)),r],(i,a)=>a).pipe(te("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(te("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);Ne("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(It)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Dr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return N([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return N([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=De(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),Ve({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),Ve({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),Ve({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),te("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(te("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(te("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),te("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return N([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),te("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Vr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){N([Ne("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?wt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ft=sn(),Ot=ln(Ft),to=an(),Oe=gn(),hr=$t("(min-width: 960px)"),Mi=$t("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ft,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ft,Ot).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),jt=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Ot})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>Nn(e,{viewport$:Oe,target$:Ot,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ft}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:jt})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?zr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt})):zr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ft;window.target$=Ot;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.13a4f30d.min.js.map + diff --git a/assets/javascripts/bundle.13a4f30d.min.js.map b/assets/javascripts/bundle.13a4f30d.min.js.map new file mode 100644 index 0000000..8941cb8 --- /dev/null +++ b/assets/javascripts/bundle.13a4f30d.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an - - -## What’s Inside? - -The WelcomeBox includes the following components: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PhotoProduct nameStore Link
Raspberry Pi 5 8GB RAMRaspberry Pi 5 - 8GB RAM

The heart of Web3 Pi. Powered by an efficient ARM processor, it delivers high performance while maintaining energy efficiency.
TME / Kamami / Botland
 Official Raspberry Pi 5 power supply

Official Raspberry Pi 5 power adapter with a USB-C connector. Delivers up to 27W for stable and reliable operation.
TME / Kamami / Botland
 2TB m.2 storage: Lexar NM790 or Goodram PX700

A high-speed 2TB NVMe storage for fast blockchain synchronization. Designed for low power consumption, high performance, and long life.
Lexar NM790:
Product page / Morele.net

Goodram PX700:
Product page / Morele.net
 Argon Neo 5 NVMe enclosure

A compact enclosure with active cooling. It looks sleek, provides excellent thermal management, and fits in any corner.
Kamami / Botland
 microSD Card

High-speed microSD card (preferably 32GB or larger) for storing the operating system and node data files, with plenty of space for future expansions.
Kamami / Botland
 Color LCD Dashboard

A high-quality LCD screen designed to provide real-time insights into your node and operating system status.
Kamami / Botland
 3D Printed Cover

A custom-designed plastic cover, 3D printed specifically for Web3 Pi. It allows for mounting the LCD display while providing a sleek and functional finish.
3D models
 Ethernet Cable cat. 5e

A 2-meter high-quality Ethernet cable, included to complete the set.
TME / Kamami / Botland
 microSD Card Reader: Ugreen CR127

A handy tool for flashing your microSD card with the operating system and node data.
Ugreen / Morele.net
 microHDMI-HDMI cable

Not required for running Web3 Pi, but invaluable for troubleshooting and initial setup.
TME / Kamami / Botland
 Pliers

Handy pliers that make hardware assembly easier. Ideal for gripping small components and included for your convenience.
TME / Kamami / Botland
 Screwdrivers: PH0 and flat 3mm

A set of screwdrivers to tighten screws and secure components. Included to ensure you have everything needed to set up Web3 Pi.
PH0: TME
flat: TME
- -_(Note: Exact components like storage brand or specific tool types may vary slightly based on availability, but will meet the required specifications.)_ - ---- - -## Assembly and Setup - -Follow the instructions in the [Full Setup Guide](../setup/single-mode/software-setup.md) to assemble and configure your Web3 Pi. - ---- - -## Availability - -_Coming soon_ diff --git a/downloads/index.html b/downloads/index.html new file mode 100644 index 0000000..c6618ab --- /dev/null +++ b/downloads/index.html @@ -0,0 +1,2281 @@ + + + + + + + + + + + + + + + + + + + + + + + Downloads - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Downloads

+

Web3 Pi Image

+

A comprehensive image packed with tools and references for creating and hosting Ethereum Nodes on Raspberry Pi +devices.
+Ethereum on Raspberry Pi →

+

Web3 Pi Imager

+

An intuitive application designed to streamline the process of burning Web3 Pi images effortlessly.
+Web3 Pi Imager →

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/2fa-in-cockpit.png b/img/2fa-in-cockpit.png similarity index 100% rename from docs/img/2fa-in-cockpit.png rename to img/2fa-in-cockpit.png diff --git a/docs/img/2fa-in-updater.png b/img/2fa-in-updater.png similarity index 100% rename from docs/img/2fa-in-updater.png rename to img/2fa-in-updater.png diff --git a/docs/img/ChooseStorage.jpg b/img/ChooseStorage.jpg similarity index 100% rename from docs/img/ChooseStorage.jpg rename to img/ChooseStorage.jpg diff --git a/docs/img/EditSettings.jpg b/img/EditSettings.jpg similarity index 100% rename from docs/img/EditSettings.jpg rename to img/EditSettings.jpg diff --git a/docs/img/GrafanaPanelSynced.png b/img/GrafanaPanelSynced.png similarity index 100% rename from docs/img/GrafanaPanelSynced.png rename to img/GrafanaPanelSynced.png diff --git a/docs/img/LG-310330-WEB-R.jpg b/img/LG-310330-WEB-R.jpg similarity index 100% rename from docs/img/LG-310330-WEB-R.jpg rename to img/LG-310330-WEB-R.jpg diff --git a/docs/img/WelcomeBox.jpg b/img/WelcomeBox.jpg similarity index 100% rename from docs/img/WelcomeBox.jpg rename to img/WelcomeBox.jpg diff --git a/docs/img/X1200.png b/img/X1200.png similarity index 100% rename from docs/img/X1200.png rename to img/X1200.png diff --git a/docs/img/X728.png b/img/X728.png similarity index 100% rename from docs/img/X728.png rename to img/X728.png diff --git a/docs/img/cockpit-verification-code.png b/img/cockpit-verification-code.png similarity index 100% rename from docs/img/cockpit-verification-code.png rename to img/cockpit-verification-code.png diff --git a/docs/img/cockpit.png b/img/cockpit.png similarity index 100% rename from docs/img/cockpit.png rename to img/cockpit.png diff --git a/docs/img/disable-2fa-button.png b/img/disable-2fa-button.png similarity index 100% rename from docs/img/disable-2fa-button.png rename to img/disable-2fa-button.png diff --git a/docs/img/dual_advanced.jpg b/img/dual_advanced.jpg similarity index 100% rename from docs/img/dual_advanced.jpg rename to img/dual_advanced.jpg diff --git a/docs/img/dual_consensus_complete.jpg b/img/dual_consensus_complete.jpg similarity index 100% rename from docs/img/dual_consensus_complete.jpg rename to img/dual_consensus_complete.jpg diff --git a/docs/img/dual_execution_complete.jpg b/img/dual_execution_complete.jpg similarity index 100% rename from docs/img/dual_execution_complete.jpg rename to img/dual_execution_complete.jpg diff --git a/docs/img/dual_network.jpg b/img/dual_network.jpg similarity index 100% rename from docs/img/dual_network.jpg rename to img/dual_network.jpg diff --git a/docs/img/dual_network2.jpg b/img/dual_network2.jpg similarity index 100% rename from docs/img/dual_network2.jpg rename to img/dual_network2.jpg diff --git a/docs/img/dual_web3pi_settings.jpg b/img/dual_web3pi_settings.jpg similarity index 100% rename from docs/img/dual_web3pi_settings.jpg rename to img/dual_web3pi_settings.jpg diff --git a/docs/img/dual_write_complete.jpg b/img/dual_write_complete.jpg similarity index 100% rename from docs/img/dual_write_complete.jpg rename to img/dual_write_complete.jpg diff --git a/docs/img/dual_write_consensus.jpg b/img/dual_write_consensus.jpg similarity index 100% rename from docs/img/dual_write_consensus.jpg rename to img/dual_write_consensus.jpg diff --git a/docs/img/dual_write_execution.jpg b/img/dual_write_execution.jpg similarity index 100% rename from docs/img/dual_write_execution.jpg rename to img/dual_write_execution.jpg diff --git a/docs/img/enable-2fa-button.png b/img/enable-2fa-button.png similarity index 100% rename from docs/img/enable-2fa-button.png rename to img/enable-2fa-button.png diff --git a/docs/img/hardware0.jpg b/img/hardware0.jpg similarity index 100% rename from docs/img/hardware0.jpg rename to img/hardware0.jpg diff --git a/docs/img/hardware0a.jpg b/img/hardware0a.jpg similarity index 100% rename from docs/img/hardware0a.jpg rename to img/hardware0a.jpg diff --git a/docs/img/hardware1.jpg b/img/hardware1.jpg similarity index 100% rename from docs/img/hardware1.jpg rename to img/hardware1.jpg diff --git a/docs/img/hardware10.jpg b/img/hardware10.jpg similarity index 100% rename from docs/img/hardware10.jpg rename to img/hardware10.jpg diff --git a/docs/img/hardware11.jpg b/img/hardware11.jpg similarity index 100% rename from docs/img/hardware11.jpg rename to img/hardware11.jpg diff --git a/docs/img/hardware11a.jpg b/img/hardware11a.jpg similarity index 100% rename from docs/img/hardware11a.jpg rename to img/hardware11a.jpg diff --git a/docs/img/hardware12.jpg b/img/hardware12.jpg similarity index 100% rename from docs/img/hardware12.jpg rename to img/hardware12.jpg diff --git a/docs/img/hardware13.jpg b/img/hardware13.jpg similarity index 100% rename from docs/img/hardware13.jpg rename to img/hardware13.jpg diff --git a/docs/img/hardware14.jpg b/img/hardware14.jpg similarity index 100% rename from docs/img/hardware14.jpg rename to img/hardware14.jpg diff --git a/docs/img/hardware15.jpg b/img/hardware15.jpg similarity index 100% rename from docs/img/hardware15.jpg rename to img/hardware15.jpg diff --git a/docs/img/hardware2.jpg b/img/hardware2.jpg similarity index 100% rename from docs/img/hardware2.jpg rename to img/hardware2.jpg diff --git a/docs/img/hardware2a.jpg b/img/hardware2a.jpg similarity index 100% rename from docs/img/hardware2a.jpg rename to img/hardware2a.jpg diff --git a/docs/img/hardware3.jpg b/img/hardware3.jpg similarity index 100% rename from docs/img/hardware3.jpg rename to img/hardware3.jpg diff --git a/docs/img/hardware3b.jpg b/img/hardware3b.jpg similarity index 100% rename from docs/img/hardware3b.jpg rename to img/hardware3b.jpg diff --git a/docs/img/hardware4.jpg b/img/hardware4.jpg similarity index 100% rename from docs/img/hardware4.jpg rename to img/hardware4.jpg diff --git a/docs/img/hardware5.jpg b/img/hardware5.jpg similarity index 100% rename from docs/img/hardware5.jpg rename to img/hardware5.jpg diff --git a/docs/img/hardware6.jpg b/img/hardware6.jpg similarity index 100% rename from docs/img/hardware6.jpg rename to img/hardware6.jpg diff --git a/docs/img/hardware7.jpg b/img/hardware7.jpg similarity index 100% rename from docs/img/hardware7.jpg rename to img/hardware7.jpg diff --git a/docs/img/hardware8.jpg b/img/hardware8.jpg similarity index 100% rename from docs/img/hardware8.jpg rename to img/hardware8.jpg diff --git a/docs/img/hardware9.jpg b/img/hardware9.jpg similarity index 100% rename from docs/img/hardware9.jpg rename to img/hardware9.jpg diff --git a/docs/img/hero.png b/img/hero.png similarity index 100% rename from docs/img/hero.png rename to img/hero.png diff --git a/docs/img/img-rpi5-connection-diagram-1.png b/img/img-rpi5-connection-diagram-1.png similarity index 100% rename from docs/img/img-rpi5-connection-diagram-1.png rename to img/img-rpi5-connection-diagram-1.png diff --git a/docs/img/install1.jpg b/img/install1.jpg similarity index 100% rename from docs/img/install1.jpg rename to img/install1.jpg diff --git a/docs/img/install2.jpg b/img/install2.jpg similarity index 100% rename from docs/img/install2.jpg rename to img/install2.jpg diff --git a/docs/img/install3.jpg b/img/install3.jpg similarity index 100% rename from docs/img/install3.jpg rename to img/install3.jpg diff --git a/docs/img/install4.jpg b/img/install4.jpg similarity index 100% rename from docs/img/install4.jpg rename to img/install4.jpg diff --git a/docs/img/install5.jpg b/img/install5.jpg similarity index 100% rename from docs/img/install5.jpg rename to img/install5.jpg diff --git a/docs/img/install6.jpg b/img/install6.jpg similarity index 100% rename from docs/img/install6.jpg rename to img/install6.jpg diff --git a/docs/img/install7.jpg b/img/install7.jpg similarity index 100% rename from docs/img/install7.jpg rename to img/install7.jpg diff --git a/docs/img/lcd-dashboard.jpg b/img/lcd-dashboard.jpg similarity index 100% rename from docs/img/lcd-dashboard.jpg rename to img/lcd-dashboard.jpg diff --git a/docs/img/link-in-cockpit.png b/img/link-in-cockpit.png similarity index 100% rename from docs/img/link-in-cockpit.png rename to img/link-in-cockpit.png diff --git a/docs/img/link-screen-1.png b/img/link-screen-1.png similarity index 100% rename from docs/img/link-screen-1.png rename to img/link-screen-1.png diff --git a/docs/img/rp_choose_device.jpg b/img/rp_choose_device.jpg similarity index 100% rename from docs/img/rp_choose_device.jpg rename to img/rp_choose_device.jpg diff --git a/docs/img/rp_choose_os.jpg b/img/rp_choose_os.jpg similarity index 100% rename from docs/img/rp_choose_os.jpg rename to img/rp_choose_os.jpg diff --git a/docs/img/rp_conf.jpg b/img/rp_conf.jpg similarity index 100% rename from docs/img/rp_conf.jpg rename to img/rp_conf.jpg diff --git a/docs/img/rp_imager.jpg b/img/rp_imager.jpg similarity index 100% rename from docs/img/rp_imager.jpg rename to img/rp_imager.jpg diff --git a/docs/img/rp_imager_OS.jpg b/img/rp_imager_OS.jpg similarity index 100% rename from docs/img/rp_imager_OS.jpg rename to img/rp_imager_OS.jpg diff --git a/docs/img/rp_imager_config.jpg b/img/rp_imager_config.jpg similarity index 100% rename from docs/img/rp_imager_config.jpg rename to img/rp_imager_config.jpg diff --git a/docs/img/rp_imager_device.jpg b/img/rp_imager_device.jpg similarity index 100% rename from docs/img/rp_imager_device.jpg rename to img/rp_imager_device.jpg diff --git a/docs/img/rp_imager_edit_settings.jpg b/img/rp_imager_edit_settings.jpg similarity index 100% rename from docs/img/rp_imager_edit_settings.jpg rename to img/rp_imager_edit_settings.jpg diff --git a/docs/img/rp_success.jpg b/img/rp_success.jpg similarity index 100% rename from docs/img/rp_success.jpg rename to img/rp_success.jpg diff --git a/docs/img/rpc-edit-url.png b/img/rpc-edit-url.png similarity index 100% rename from docs/img/rpc-edit-url.png rename to img/rpc-edit-url.png diff --git a/docs/img/rpc-edit.png b/img/rpc-edit.png similarity index 100% rename from docs/img/rpc-edit.png rename to img/rpc-edit.png diff --git a/docs/img/rpc-in-wallet.png b/img/rpc-in-wallet.png similarity index 100% rename from docs/img/rpc-in-wallet.png rename to img/rpc-in-wallet.png diff --git a/docs/img/rpc-select.png b/img/rpc-select.png similarity index 100% rename from docs/img/rpc-select.png rename to img/rpc-select.png diff --git a/docs/img/script-runner-in-cockpit.png b/img/script-runner-in-cockpit.png similarity index 100% rename from docs/img/script-runner-in-cockpit.png rename to img/script-runner-in-cockpit.png diff --git a/docs/img/script-runner.png b/img/script-runner.png similarity index 100% rename from docs/img/script-runner.png rename to img/script-runner.png diff --git a/docs/img/sd_reader_and_card.jpg b/img/sd_reader_and_card.jpg similarity index 100% rename from docs/img/sd_reader_and_card.jpg rename to img/sd_reader_and_card.jpg diff --git a/docs/img/storage_consensus.jpg b/img/storage_consensus.jpg similarity index 100% rename from docs/img/storage_consensus.jpg rename to img/storage_consensus.jpg diff --git a/docs/img/storage_execution.jpg b/img/storage_execution.jpg similarity index 100% rename from docs/img/storage_execution.jpg rename to img/storage_execution.jpg diff --git a/docs/img/tx-firewall/admin-warn.png b/img/tx-firewall/admin-warn.png similarity index 100% rename from docs/img/tx-firewall/admin-warn.png rename to img/tx-firewall/admin-warn.png diff --git a/docs/img/tx-firewall/auth_addr.png b/img/tx-firewall/auth_addr.png similarity index 100% rename from docs/img/tx-firewall/auth_addr.png rename to img/tx-firewall/auth_addr.png diff --git a/docs/img/tx-firewall/cockpit1.png b/img/tx-firewall/cockpit1.png similarity index 100% rename from docs/img/tx-firewall/cockpit1.png rename to img/tx-firewall/cockpit1.png diff --git a/docs/img/tx-firewall/connection-error.png b/img/tx-firewall/connection-error.png similarity index 100% rename from docs/img/tx-firewall/connection-error.png rename to img/tx-firewall/connection-error.png diff --git a/docs/img/tx-firewall/diag1.png b/img/tx-firewall/diag1.png similarity index 100% rename from docs/img/tx-firewall/diag1.png rename to img/tx-firewall/diag1.png diff --git a/docs/img/tx-firewall/frontend.png b/img/tx-firewall/frontend.png similarity index 100% rename from docs/img/tx-firewall/frontend.png rename to img/tx-firewall/frontend.png diff --git a/docs/img/tx-firewall/known_contracts.png b/img/tx-firewall/known_contracts.png similarity index 100% rename from docs/img/tx-firewall/known_contracts.png rename to img/tx-firewall/known_contracts.png diff --git a/docs/img/tx-firewall/logs.png b/img/tx-firewall/logs.png similarity index 100% rename from docs/img/tx-firewall/logs.png rename to img/tx-firewall/logs.png diff --git a/docs/img/tx-firewall/metamask.png b/img/tx-firewall/metamask.png similarity index 100% rename from docs/img/tx-firewall/metamask.png rename to img/tx-firewall/metamask.png diff --git a/docs/img/tx-firewall/ports-warn.png b/img/tx-firewall/ports-warn.png similarity index 100% rename from docs/img/tx-firewall/ports-warn.png rename to img/tx-firewall/ports-warn.png diff --git a/docs/img/tx-firewall/ports.png b/img/tx-firewall/ports.png similarity index 100% rename from docs/img/tx-firewall/ports.png rename to img/tx-firewall/ports.png diff --git a/docs/img/tx-firewall/settings.png b/img/tx-firewall/settings.png similarity index 100% rename from docs/img/tx-firewall/settings.png rename to img/tx-firewall/settings.png diff --git a/docs/img/tx-firewall/tx-contract.png b/img/tx-firewall/tx-contract.png similarity index 100% rename from docs/img/tx-firewall/tx-contract.png rename to img/tx-firewall/tx-contract.png diff --git a/docs/img/tx-firewall/tx-no-data.png b/img/tx-firewall/tx-no-data.png similarity index 100% rename from docs/img/tx-firewall/tx-no-data.png rename to img/tx-firewall/tx-no-data.png diff --git a/docs/img/tx-firewall/tx-predefined.png b/img/tx-firewall/tx-predefined.png similarity index 100% rename from docs/img/tx-firewall/tx-predefined.png rename to img/tx-firewall/tx-predefined.png diff --git a/docs/img/tx-firewall/tx-transfer.png b/img/tx-firewall/tx-transfer.png similarity index 100% rename from docs/img/tx-firewall/tx-transfer.png rename to img/tx-firewall/tx-transfer.png diff --git a/docs/img/tx-firewall/updater.png b/img/tx-firewall/updater.png similarity index 100% rename from docs/img/tx-firewall/updater.png rename to img/tx-firewall/updater.png diff --git a/docs/img/updater-in-cockpit.png b/img/updater-in-cockpit.png similarity index 100% rename from docs/img/updater-in-cockpit.png rename to img/updater-in-cockpit.png diff --git a/docs/img/updater-packages.png b/img/updater-packages.png similarity index 100% rename from docs/img/updater-packages.png rename to img/updater-packages.png diff --git a/docs/img/updater-refresh.png b/img/updater-refresh.png similarity index 100% rename from docs/img/updater-refresh.png rename to img/updater-refresh.png diff --git a/docs/img/w3-imager5.jpg b/img/w3-imager5.jpg similarity index 100% rename from docs/img/w3-imager5.jpg rename to img/w3-imager5.jpg diff --git a/docs/img/w3-imager6.jpg b/img/w3-imager6.jpg similarity index 100% rename from docs/img/w3-imager6.jpg rename to img/w3-imager6.jpg diff --git a/docs/img/w3-imager7.jpg b/img/w3-imager7.jpg similarity index 100% rename from docs/img/w3-imager7.jpg rename to img/w3-imager7.jpg diff --git a/docs/img/w3_imager1.jpg b/img/w3_imager1.jpg similarity index 100% rename from docs/img/w3_imager1.jpg rename to img/w3_imager1.jpg diff --git a/docs/img/w3_imager2.jpg b/img/w3_imager2.jpg similarity index 100% rename from docs/img/w3_imager2.jpg rename to img/w3_imager2.jpg diff --git a/docs/img/w3_imager4.jpg b/img/w3_imager4.jpg similarity index 100% rename from docs/img/w3_imager4.jpg rename to img/w3_imager4.jpg diff --git a/docs/img/w3_imageradvanced.jpg b/img/w3_imageradvanced.jpg similarity index 100% rename from docs/img/w3_imageradvanced.jpg rename to img/w3_imageradvanced.jpg diff --git a/docs/img/web3pilink.png b/img/web3pilink.png similarity index 100% rename from docs/img/web3pilink.png rename to img/web3pilink.png diff --git a/docs/img/welcomebox-svg/argon-neo.svg b/img/welcomebox-svg/argon-neo.svg similarity index 100% rename from docs/img/welcomebox-svg/argon-neo.svg rename to img/welcomebox-svg/argon-neo.svg diff --git a/docs/img/welcomebox-svg/eth.svg b/img/welcomebox-svg/eth.svg similarity index 100% rename from docs/img/welcomebox-svg/eth.svg rename to img/welcomebox-svg/eth.svg diff --git a/docs/img/welcomebox-svg/hdmi.svg b/img/welcomebox-svg/hdmi.svg similarity index 100% rename from docs/img/welcomebox-svg/hdmi.svg rename to img/welcomebox-svg/hdmi.svg diff --git a/docs/img/welcomebox-svg/lcd.svg b/img/welcomebox-svg/lcd.svg similarity index 100% rename from docs/img/welcomebox-svg/lcd.svg rename to img/welcomebox-svg/lcd.svg diff --git a/docs/img/welcomebox-svg/microsd.svg b/img/welcomebox-svg/microsd.svg similarity index 100% rename from docs/img/welcomebox-svg/microsd.svg rename to img/welcomebox-svg/microsd.svg diff --git a/docs/img/welcomebox-svg/pliers.svg b/img/welcomebox-svg/pliers.svg similarity index 100% rename from docs/img/welcomebox-svg/pliers.svg rename to img/welcomebox-svg/pliers.svg diff --git a/docs/img/welcomebox-svg/power-supply.svg b/img/welcomebox-svg/power-supply.svg similarity index 100% rename from docs/img/welcomebox-svg/power-supply.svg rename to img/welcomebox-svg/power-supply.svg diff --git a/docs/img/welcomebox-svg/reader.svg b/img/welcomebox-svg/reader.svg similarity index 100% rename from docs/img/welcomebox-svg/reader.svg rename to img/welcomebox-svg/reader.svg diff --git a/docs/img/welcomebox-svg/rpi.svg b/img/welcomebox-svg/rpi.svg similarity index 100% rename from docs/img/welcomebox-svg/rpi.svg rename to img/welcomebox-svg/rpi.svg diff --git a/docs/img/welcomebox-svg/screwdrivers.svg b/img/welcomebox-svg/screwdrivers.svg similarity index 100% rename from docs/img/welcomebox-svg/screwdrivers.svg rename to img/welcomebox-svg/screwdrivers.svg diff --git a/docs/img/welcomebox-svg/storage.svg b/img/welcomebox-svg/storage.svg similarity index 100% rename from docs/img/welcomebox-svg/storage.svg rename to img/welcomebox-svg/storage.svg diff --git a/docs/img/welcomebox-svg/topcase.svg b/img/welcomebox-svg/topcase.svg similarity index 100% rename from docs/img/welcomebox-svg/topcase.svg rename to img/welcomebox-svg/topcase.svg diff --git a/index.html b/index.html new file mode 100644 index 0000000..b85f323 --- /dev/null +++ b/index.html @@ -0,0 +1,2376 @@ + + + + + + + + + + + + + + + + + + + + + + + Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+
+

+ Web3 Pi +

+ documentation +

+ This documentation provides everything you need to get started with Web3 Pi.
Find step-by-step guides for setup, detailed explanations of features, monitoring tools, troubleshooting and tips for running your Ethereum node smoothly on Raspberry Pi. +

+ + Get Started + +
+
+ + + +

Learn

+

Learn more about the Web3 Pi project and benefits of running an Ethereum node on a Raspberry Pi.

+
+
    +
  • +

    About Web3 Pi

    +
    +

    Essential information before starting your Web3 Pi journey

    +

    The Web3 Pi Project

    +
  • +
  • +

    Running an Ethereum Node

    +
    +

    Learn about the benefits of running your very own Ethereum node on a Raspberry Pi

    +

    What is an Ethereum Node?

    +
  • +
+
+

Setup & Configuration

+

Get started with running your very own Ethereum node on a Raspberry Pi.

+
+
    +
  • +

    Prerequisites

    +
    +

    Hardware and software requirements to run an Ethereum node on a Raspberry Pi

    +

    Prerequisites

    +
  • +
  • +

    Setup Guide

    +
    +

    A complete step-by-step guide to setting up your own Ethereum node on a Raspberry Pi using Web3 Pi

    +

    Setup guide

    +
  • +
+
+

Monitoring & Management

+

Tips and tricks to keep your node running smoothly.

+
+ +
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/introduction/ethereum-node/index.html b/introduction/ethereum-node/index.html new file mode 100644 index 0000000..1498cc2 --- /dev/null +++ b/introduction/ethereum-node/index.html @@ -0,0 +1,2347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + What is an Ethereum Node? - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

What is an Ethereum Node?

+

Before running your own Ethereum node using Web3 Pi or any other method, it's important to understand what a node is and its role in the Ethereum network.

+

The Basics

+

An Ethereum node is simply a piece of software that connects to the Ethereum network. It downloads a copy of the Ethereum blockchain and follows the network's consensus rules to verify transactions and blocks. Running a node contributes to the decentralization, security, and resilience of the Ethereum network.

+

For a comprehensive overview, start by reading the documentation provided by the Ethereum Foundation:

+ +

Node Components: Execution and Consensus Clients

+

A modern Ethereum node consists of two main software components running together:

+
    +
  1. Execution Client (EL): Sometimes called the Execution Engine or formerly Eth1 client, this software listens for new transactions broadcasted to the network, executes them in the Ethereum Virtual Machine (EVM), and holds the latest state and database of all Ethereum data. It handles the "computation" part of the network.
  2. +
  3. Consensus Client (CL): Also known as the Beacon Node or formerly Eth2 client, this software implements the proof-of-stake consensus algorithm. It enables the network to agree on the state of the blockchain based on the validated data received from the Execution Client. It handles the "agreement" part of the network.
  4. +
+

These two clients work in tandem to keep the node synchronized with the head of the Ethereum chain and allow users to interact with the network.

+

Learn more about clients and nodes:

+ +

Why Run a Node?

+

People run Ethereum nodes for various reasons:

+
    +
  • Support the Network: Increase the decentralization and security of Ethereum.
  • +
  • Trustless & Uncensored Access: Interact directly with the Ethereum blockchain without relying on third-party services, ensuring privacy and avoiding potential censorship or rate limits.
  • +
  • High-Performance RPC: Gain fast, local, and unlimited access to the Ethereum RPC API for wallets and applications.
  • +
  • Development: Provide a local endpoint for developing and testing decentralized applications (dApps).
  • +
  • Foundation for Staking: Provide the necessary infrastructure (EL/CL clients) that could be configured for staking.
  • +
+

It is important to clarify: running an Ethereum node does not require staking 32 ETH. Anyone can run a full node using Web3 Pi to verify transactions, interact privately with the blockchain, and contribute to network health without any ETH deposit. Staking, which does require 32 ETH per validator, is a separate activity that involves proposing and attesting to new blocks to earn rewards.

+
+

What about staking?

+

While running a node is a prerequisite for staking, configuring it specifically for staking duties is a complex process that goes beyond the standard Web3 Pi setup. You can learn more about staking here.

+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/introduction/next-steps/index.html b/introduction/next-steps/index.html new file mode 100644 index 0000000..6e4a111 --- /dev/null +++ b/introduction/next-steps/index.html @@ -0,0 +1,2449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Next Steps - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Next Steps

+

Now that you have a basic understanding of Web3 Pi and Ethereum nodes, here's how to proceed:

+

1. Make Key Decisions

+

Before you begin the setup process, you need to decide on a few things:

+

Single or Dual Device Mode?

+

An Ethereum node requires both an Execution Client (EL) and a Consensus Client (CL).

+
    +
  • Single Device: Run both EL and CL clients on one Raspberry Pi. This is cheaper but shares resources. A Raspberry Pi 5 with 8GB RAM is recommended for this configuration.
  • +
  • Dual Device: Run the EL client on one Raspberry Pi and the CL client on a separate Raspberry Pi. This allows you to use two, weaker devices in place of one, more powerful one, at the cost of increased complexity.
  • +
+

If you're unsure, we recommend starting with a single device setup.

+

2. Prepare Your Hardware

+

Ensure you have all the necessary hardware components (Raspberry Pi, power supply, storage, network cable, etc.).

+

Use Your Own Hardware or the WelcomeBox?

+
    +
  • WelcomeBox: Web3 Pi offers a WelcomeBox — an all-in-one solution with pre-selected, compatible hardware to help you get started quickly.
  • +
  • Own Hardware: You can use your existing Raspberry Pi (Model 4, 5, or CM4) and compatible peripherals. Ensure you have everything required by checking the Prerequisites Guide and the Hardware Recommendations document.
  • +
+

Optional LCD Display?

+
    +
  • You can add an optional LCD display to your setup for at-a-glance monitoring. It's included in the WelcomeBox and natively supported by the Web3 Pi system — just plug it in, no installation or configuration needed.
    +Details are in the LCD Monitoring Guide.
  • +
+

3. Set Up Your Node

+
    +
  • Follow the Full Setup Guide relevant to your configuration for detailed, step-by-step instructions assembling the hardware and booting your Raspberry Pi for the first time.
  • +
+

4. Manage Your Node

+

Web3 Pi includes a set of tools to help you manage and monitor your node:

+
    +
  • The Grafana Dashboard provides real-time monitoring of your node's performance.
  • +
  • The Cockpit Dashboard provides an overview of your node's status and allows you to manage basic settings.
  • +
  • The Web3 Pi Updater allows you to upgrade your node's ethereum dependencies and other software.
  • +
  • The Web3 Pi Link allows you to access your node from anywhere in the world.
  • +
+
+

Warning

+

Remember: Staking configuration is currently considered an advanced, unsupported procedure. Proceed with extreme caution if exploring this path independently.

+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/introduction/staking/index.html b/introduction/staking/index.html new file mode 100644 index 0000000..bb3dd22 --- /dev/null +++ b/introduction/staking/index.html @@ -0,0 +1,2232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Can I earn with Web3 Pi? - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Can I Earn with Web3 Pi?

+
+

Warning

+

Running an Ethereum validator involves significant financial risk and technical responsibility. You do so entirely at your own risk.

+
+

Running a standard Ethereum node with Web3 Pi does not directly generate income by itself. Its primary purpose is providing secure, private, and performant access to the blockchain. However, Web3 Pi serves as a foundation for potential earning opportunities:

+
    +
  • Solo Staking: While running a node doesn't require 32 ETH, staking to secure the network and earn rewards does. Web3 Pi hardware (like Raspberry Pi 5) is capable of handling staking duties (proven in tests running 200 validators), although the setup currently requires manual configuration. We aim to simplify this process significantly in the future.
  • +
  • RPC Monetization: Through collaborations like the one with Golem Network, there are plans to allow Web3 Pi node operators to share RPC endpoint and potentially earn rewards (e.g., in GLM tokens) for serving requests. This feature is not yet available.
  • +
+

If you are considering staking, it is crucial that you thoroughly research and understand the process, responsibilities, and significant risks involved before attempting it.

+

Learn more about staking fundamentals: - Ethereum Staking Guide

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/introduction/web3-pi-project/index.html b/introduction/web3-pi-project/index.html new file mode 100644 index 0000000..3960bee --- /dev/null +++ b/introduction/web3-pi-project/index.html @@ -0,0 +1,2351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + About Web3 Pi - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Web3 Pi: Before you start

+

The Web3 Pi Project

+

Web3 Pi is an open-source project simplifying the operation of a personal Ethereum node using Raspberry Pi devices. We provide a ready-to-use, optimized operating system image and automated tools, making node ownership accessible even without deep technical knowledge. Leveraging the Raspberry Pi's efficient ARM architecture, Web3 Pi ensures reliable blockchain access with very low energy consumption (around 8W), allowing for cost-effective, 24/7 operation. Our goal is to lower the barrier to entry for direct participation in the Ethereum network.

+

Currently supported devices (at least 8GB RAM required):

+ +

Why Web3 Pi?

+

Web3 Pi images offer several advantages for setting up your Ethereum node:

+
    +
  • Plug & Play: Simply flash the image to your storage and power up the Raspberry Pi.
  • +
  • Solid Foundation: Based on Ubuntu 24.04 LTS for ARM64, providing long-term support.
  • +
  • Automated Setup: Handles initial configuration for network, user accounts, etc.
  • +
  • Disk Management: Automatically partitions and formats attached storage.
  • +
  • Auto-Sync: Automatically starts the Ethereum sync process.
  • +
  • Easy Updates: Includes an APT repository for straightforward installation and upgrades.
  • +
  • Monitoring Included: Comes with pre-configured monitoring dashboards.
  • +
  • Security Focused: Includes the UFW firewall configured for basic security.
  • +
+

Why Raspberry Pi?

+

Raspberry Pi devices are an excellent platform for running an Ethereum node (Full or Archive) due to several key factors:

+
    +
  • Affordable: Set up a Full Ethereum node (EL/CL) for under $350.
  • +
  • Efficient: As a dedicated device, resources are focused solely on running the node.
  • +
  • Low Power Consumption: Typically consumes around 8W.
  • +
  • Small Form Factor: Its compact size makes it easy to place anywhere in your home.
  • +
  • Ideal for 24/7 Operation: The combination of low cost, low power, and small size makes it perfect for continuous operation.
  • +
+ + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/management/cockpit/dashboard/index.html b/management/cockpit/dashboard/index.html new file mode 100644 index 0000000..ee7c282 --- /dev/null +++ b/management/cockpit/dashboard/index.html @@ -0,0 +1,2374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Cockpit Dashboard - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + +

Cockpit Dashboard

+

cockpit dashboard main page

+

What is Cockpit?

+

Web3 Pi comes with Cockpit pre-installed. Cockpit is a free, open-source, web-based graphical interface for servers and Linux systems. It allows you to monitor and administer your Raspberry Pi's underlying operating system (Ubuntu) directly from your web browser.

+

Think of it as a user-friendly dashboard for managing the system itself.

+

What Can You Do with Cockpit on Web3 Pi?

+

While Grafana dashboards focus specifically on monitoring your Ethereum node clients (EL/CL), Cockpit focuses on the operating system level. Common uses include:

+
    +
  • Checking CPU, RAM, and network usage trends.
  • +
  • Viewing available disk space on your system drive and attached USB drive.
  • +
  • Inspecting system logs (journald).
  • +
  • Checking the status of system services (be cautious about stopping essential services!).
  • +
  • Viewing network interface configurations.
  • +
+

How to Access Cockpit

+
    +
  1. Find the IP address or hostname of your Raspberry Pi. +
  2. +
  3. Navigate to http://<your-pi-ip-address>:9090 (replace <your-pi-ip-address> with the actual IP address or hostname).
  4. +
  5. You may see a security warning because Cockpit uses a self-signed SSL certificate by default. It's safe to proceed (usually under an "Advanced" or "Proceed anyway" option).
  6. +
  7. Log in using the username ethereum and password you selected. If this is your first time accessing your Web3 Pi, use the default password. You will be required to change the password upon first login.
  8. +
+
+

Note

+

The default password is ethereum. Change it as soon as possible to prevent unauthorized access.

+
+

Now you can explore the Cockpit interface and monitor your Web3 Pi's system health!

+

Official Web3 Pi Plugins

+

The Web3 Pi project maintains a number of official plugins for Cockpit.

+ + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/management/cockpit/web3-pi-link/index.html b/management/cockpit/web3-pi-link/index.html new file mode 100644 index 0000000..9b2337e --- /dev/null +++ b/management/cockpit/web3-pi-link/index.html @@ -0,0 +1,2487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Web3 Pi Link - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi Link

+

Browser window with "my-pi.web3pi.link" in the search bar

+

Web3 Pi Link is a Cockpit plugin enabling secure exposure of Raspberry Pi services to the internet. It functions as a reverse proxy tunnel, eliminating the need for manual port forwarding. This document provides technical details for developers using Web3 Pi Link.

+

Core Functionality

+

Web3 Pi Link establishes a persistent, encrypted tunnel between your Raspberry Pi and a Web3 Pi managed server. This tunnel allows external access to services running on your Pi without direct exposure or complex network configuration. The plugin manages the tunnel lifecycle and provides a simplified configuration interface within Cockpit.

+

Key Features

+
    +
  • HTTP/WebSocket Proxy: Supports forwarding HTTP and WebSocket traffic from the Raspberry Pi to a public-facing address. Note: Other protocols like SSH are currently not supported.
  • +
  • Automatic HTTPS: All tunnels are automatically secured with HTTPS, even if the service on the Raspberry Pi uses HTTP internally.
  • +
  • Automatic Reconnection: The plugin automatically re-establishes the tunnel in case of network interruptions.
  • +
  • Cockpit Integration: Configuration is managed entirely through the Cockpit web interface.
  • +
  • Subdomain Routing: Each service is assigned a unique subdomain (yourname.web3pi.link) for easy access.
  • +
+

Technical Architecture

+

The plugin creates a reverse proxy tunnel. When a request arrives at yourname.web3pi.link, it's routed through the Web3 Pi infrastructure, through the secure tunnel to the specified port on your Raspberry Pi. The tunnel client, running on the Raspberry Pi, establishes an outbound connection to the Web3 Pi servers, so there is no need to open any specific ports on your Pi.

+

Installation and Configuration

+

Prerequisites

+
    +
  • A Raspberry Pi with Web3 Pi installed and operational.
  • +
  • Cockpit web interface access.
  • +
  • Network connectivity allowing outbound connections. Note: No specific firewall rules are required as the connection is initiated from the Raspberry Pi.
  • +
+

Installation Steps

+
    +
  1. +

    Install via Cockpit: Access the Cockpit web interface and navigate to the "Web3 Pi Updater" section. Search for "Web3 Pi Link" and install the plugin. Make sure to refresh the page after the installation is complete. Newer Web3 Pi versions may have this plugin already installed by default.

    +

    Web3 Pi Link in Cockpit

    +
  2. +
  3. +

    Configuration: The Web3 Pi Link plugin will appear in the Cockpit navigation.

    +
      +
    • Local Port: Specify the TCP port on your Raspberry Pi that you want to expose (e.g., 3000, 8545, 9090). This is the port your HTTP service is listening on.
    • +
    • Subdomain: Choose a unique subdomain name. This will be used to construct the public-facing URL (yourname.web3pi.link). The subdomain must be globally unique. A valid subdomain must begin with a letter or number and can contain lowercase letters, numbers, and hyphens.
    • +
    +
  4. +
+

Create new Link form

+

Troubleshooting and Debugging

+
    +
  • Verify Service Status: Check that the Web3 Pi Link service is enabled and running. You can do this by navigating to the "Services" section in the Cockpit interface and searching for "web3-pi-link".
  • +
  • Network Connectivity: Ensure the Raspberry Pi has outbound internet access.
  • +
  • HTTPS Issues: While Web3 Pi Link automatically provides HTTPS, ensure your application handles redirects correctly if it expects HTTPS.
  • +
+

Security Considerations

+
    +
  • Secure Your Application: Web3 Pi Link provides a secure tunnel with automatic HTTPS, but it's crucial to secure your application. Implement proper authentication, authorization, and input validation to prevent vulnerabilities.
  • +
  • Regular Updates: Make sure to always keep your Web3 Pi software up to date with the latest security patches. You can download the latest updates to each plugin from the "Web3 Pi Updater" section in the Cockpit interface.
  • +
  • Monitor Logs: Regularly monitor application logs for suspicious activity.
  • +
+

Support

+

For support and further assistance, join the Web3 Pi Discord community.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/management/cockpit/web3-pi-script-runner/index.html b/management/cockpit/web3-pi-script-runner/index.html new file mode 100644 index 0000000..2afc139 --- /dev/null +++ b/management/cockpit/web3-pi-script-runner/index.html @@ -0,0 +1,2386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Web3 Pi Script Runner - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Script Runner

+

List of scripts in Script Runner

+

Script Runner is a Cockpit plugin that provides a user-friendly interface for executing pre-installed scripts on your Web3 Pi. This allows for easy access to common utilities and diagnostic tools directly from the Cockpit web interface.

+

Installation

+

The Script Runner plugin is installed via the "Web3 Pi Updater" section in the Cockpit interface. Search for "Script Runner" and install the plugin. Make sure to refresh the page after the installation is complete. On newer Web3 Pi releases, this plugin might be pre-installed by default.

+

Usage

+

The Script Runner plugin is located in the Cockpit navigation menu. Upon opening, you will see a list of available scripts.

+

Script Runner in Cockpit

+

Running a Script

+
    +
  1. Select a Script: Click on the desired script from the list.
  2. +
  3. View Script (Optional): Click on the "Read File Contents" button to inspect the script's contents.
  4. +
  5. Run Script: Click the "Run" button to execute the script.
  6. +
  7. View Output: The script's output will be displayed in a terminal window within the plugin.
  8. +
+

Troubleshooting

+
    +
  • Script Execution Errors: Check the script's output for error messages.
  • +
  • Missing Scripts: If a script is missing, ensure your Web3 Pi installation is up-to-date. Navigate to the "Web3 Pi Updater" section in the Cockpit interface and search for "Script Runner".
  • +
+

Support

+

For support and further assistance, join the Web3 Pi Discord community.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/management/cockpit/web3-pi-updater/index.html b/management/cockpit/web3-pi-updater/index.html new file mode 100644 index 0000000..929d2ab --- /dev/null +++ b/management/cockpit/web3-pi-updater/index.html @@ -0,0 +1,2412 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Web3 Pi Updater - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Web3 Pi Updater

+

The Web3 Pi Updater plugin provides a centralized interface for managing Web3 Pi packages and essential Ethereum node dependencies. This tool allows you to easily list, update, and install software directly from the Cockpit web interface.

+

Usage

+

The Web3 Pi Updater plugin is located in the Cockpit navigation menu.

+

Web3 Pi Updater in Cockpit

+

Packages List

+

Packages are separated into two categories: Web3 Pi Official Packages and Ethereum Packages. The former are maintained by the Web3 Pi team and provide additional functionality for your device, while the latter are maintained by their respective developers and are essential for running a node.

+

Packages split into categories

+

Refresh Packages List

+

Web3 Pi packages are distributed from our apt repository. To ensure that your device is up to date with the latest packages, click the Refresh packages list button. This will update the list of available packages and give you the option to install or update them.

+

Refresh Packages List button

+
+

Note

+

The Refresh packages list button has the same effect as running sudo apt update on the command line.

+
+

Install or Update Packages

+

To install a package, click the Install button next to the package name.

+

To update an existing package, click the Update button next to the package name.

+
+

Note

+

After installing a new cockpit plugin, you may need to refresh your browser tab to see the new plugin in the navigation menu.

+
+

Notes

+
    +
  • Package installations and updates may require administrative privileges.
  • +
  • Ensure your Web3 Pi has a stable internet connection during package operations.
  • +
  • Regularly check for updates to maintain optimal performance and security.
  • +
+

Support

+

For support and further assistance, join the Web3 Pi Discord community.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/management/index.html b/management/index.html new file mode 100644 index 0000000..79cf39f --- /dev/null +++ b/management/index.html @@ -0,0 +1,2223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Managing Your Node - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Managing Your Node

+

Once your Web3 Pi node is up and running, you'll occasionally need to interact with it for monitoring, updates, or configuration changes. There are two primary ways to manage the underlying system:

+
    +
  1. +

    SSH (Secure Shell): + Provides direct command-line access to the Ubuntu operating system. This is the most powerful method, suitable for advanced configuration, troubleshooting, and running specific commands.

    +
  2. +
  3. +

    Cockpit Web Interface: + Offers a user-friendly, web-based dashboard for monitoring system resources (CPU, RAM, disk), viewing logs, and performing basic administrative tasks directly from your browser.

    +
  4. +
+

Choose the tool that best suits the task at hand. For routine checks and system overview, Cockpit is often sufficient. For more advanced tasks, SSH is the way to go.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/management/ssh/index.html b/management/ssh/index.html new file mode 100644 index 0000000..6cd12a4 --- /dev/null +++ b/management/ssh/index.html @@ -0,0 +1,2435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + SSH - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Connecting via SSH

+

What is SSH?

+

SSH stands for Secure Shell. It is a network protocol that allows you to securely access the command-line interface (also known as the terminal or shell) of a remote computer over an unsecured network. All communication between your computer and the Raspberry Pi via SSH is encrypted.

+

Why Use SSH with Web3 Pi?

+

While Cockpit provides a web-based graphical interface for basic system administration, SSH gives you direct, powerful command-line access to your Web3 Pi's underlying Ubuntu operating system.

+

How to Connect via SSH

+

Before connecting, you will need:

+
    +
  1. An SSH Client: This depends on your operating system (see tabs below).
  2. +
  3. Your Web3 Pi's IP Address or Hostname: +
  4. +
+

Now, follow the steps for your specific operating system:

+
+
+
+
    +
  1. Open your SSH Client:
      +
    • On macOS or Linux, open the Terminal application.
    • +
    • On Windows 10/11, open PowerShell or Command Prompt. The ssh client is typically built-in.
    • +
    +
  2. +
  3. Initiate the Connection:
      +
    • Type the following command, replacing <your-pi-address> with your Pi's actual IP address or hostname: +
      ssh ethereum@<your-pi-address>
      +
      + Example using hostname: ssh ethereum@eop-1.local + Example using IP: ssh ethereum@192.168.1.123
    • +
    • Press Enter.
    • +
    +
  4. +
  5. Accept the Host Key (First Connection):
      +
    • The very first time you connect from your computer to the Web3 Pi, your SSH client will show a security alert asking you to verify the host's key fingerprint: +
      The authenticity of host 'eop-1.local (192.168.1.123)' can't be established.
      +ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
      +Are you sure you want to continue connecting (yes/no/[fingerprint])?
      +
    • +
    • This is normal. Type yes and press Enter to continue and store the key.
    • +
    +
  6. +
  7. Enter the Password:
      +
    • You will be prompted for the password for the ethereum user: +
      ethereum@<your-pi-address>'s password:
      +
    • +
    • Default Username: ethereum
    • +
    • Default Password: ethereum
    • +
    • (Security Note: When typing the password, you won't see any characters or dots appear. This is expected. Type the password carefully and press Enter.)
    • +
    +
  8. +
  9. Change the Password (Mandatory First Login):
      +
    • If this is the first time you are logging in with the default ethereum password, the system will force you to change it immediately: +
      WARNING: Your password has expired.
      +You must change your password now and login again!
      +Changing password for ethereum.
      +(Current) UNIX password:
      +
    • +
    • Enter the current password (ethereum) and press Enter.
    • +
    • Then, you will be prompted to enter a New password: Type your chosen strong, unique password and press Enter.
    • +
    • You will be asked to Retype new password: Type the same new password again and press Enter.
    • +
    • Choose a strong password! This is crucial for security.
    • +
    +
  10. +
  11. Login Successful:
      +
    • After changing the password (on first login) or entering the correct password (on subsequent logins), you will be logged in and see the Web3 Pi command prompt: +
      ethereum@<your-pi-address>:~$
      +
    • +
    +
  12. +
+
+
+
    +
  1. Open PuTTY:
      +
    • Download and launch the PuTTY application.
    • +
    +
  2. +
  3. Configure the Session:
      +
    • In the "Session" category (the default view):
    • +
    • Enter your Pi's IP address or hostname in the "Host Name (or IP address)" field.
    • +
    • Ensure "Port" is set to 22.
    • +
    • Ensure "Connection type" is set to SSH.
    • +
    • Click the "Open" button.
    • +
    +
  4. +
  5. Accept the Host Key (First Connection):
      +
    • The very first time you connect, a "PuTTY Security Alert" window will appear, showing the server's host key fingerprint.
    • +
    • This is normal. Verify the key if desired, then click "Accept" (or "Yes") to trust the key and continue.
    • +
    +
  6. +
  7. Enter the Username and Password:
      +
    • A terminal window will open, prompting login as:. Type ethereum and press Enter.
    • +
    • Next, it will prompt ethereum@<your-pi-address>'s password:.
    • +
    • Default Password: ethereum
    • +
    • (Security Note: When typing the password, you won't see any characters. Type carefully and press Enter.)
    • +
    +
  8. +
  9. Change the Password (Mandatory First Login):
      +
    • If this is the first time you are logging in with the default ethereum password, the system will force you to change it immediately after entering the default password: +
      WARNING: Your password has expired.
      +You must change your password now and login again!
      +Changing password for ethereum.
      +(Current) UNIX password:
      +
    • +
    • Enter the current password (ethereum) and press Enter.
    • +
    • Then, you will be prompted to enter a New password: Type your chosen strong, unique password and press Enter.
    • +
    • You will be asked to Retype new password: Type the same new password again and press Enter.
    • +
    • Choose a strong password! This is crucial for security.
    • +
    +
  10. +
  11. Login Successful:
      +
    • After changing the password (on first login) or entering the correct password (on subsequent logins), you will be logged in and see the Web3 Pi command prompt: +
      ethereum@<your-pi-address>:~$
      +
    • +
    +
  12. +
+
+
+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index aa02ae9..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,135 +0,0 @@ -# Project information -site_name: Web3 Pi -site_url: https://docs.web3pi.io/ - -# Repository -repo_name: Source-Code -repo_url: https://github.com/Web3-Pi/Ethereum-On-Raspberry-Pi - -extra_css: - - stylesheets/extra.css -extra_javascript: - - stylesheets/extra.js - -theme: - name: material - custom_dir: overrides - logo: assets/color.svg - favicon: assets/favicon.ico - features: - - navigation.tabs - - navigation.tabs.sticky - - navigation.indexes - # - navigation.path - # - toc.integrate - - navigation.top - - search.suggest - - search.highlight - - content.tabs.link - - content.code.annotation - - content.code.copy - - navigation.footer - icon: - repo: fontawesome/brands/github - language: en - palette: - primary: black - scheme: slate - -plugins: - - search - -extra: - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/Web3-Pi/Ethereum-On-Raspberry-Pi - type: github - repo: Web3-Pi/Ethereum-On-Raspberry-Pi - - icon: fontawesome/brands/x-twitter - link: https://x.com/_Web3Pi_ - - icon: fontawesome/brands/discord - link: https://discord.gg/aDMw5zeUZ4 - -markdown_extensions: - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.inlinehilite - - pymdownx.snippets - - admonition - - pymdownx.arithmatex: - generic: true - - footnotes - - pymdownx.details - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - - pymdownx.mark - - attr_list - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - - md_in_html - - pymdownx.tabbed: - alternate_style: true - - -copyright: | - © 2025 Web3 Pi - -nav: - - Home: 'index.md' - - Introduction: - - About Web3 Pi: 'introduction/web3-pi-project.md' - - What is an Ethereum Node?: 'introduction/ethereum-node.md' - - Can I earn with Web3 Pi?: 'introduction/staking.md' - - Next Steps: 'introduction/next-steps.md' - - Setup: - - Prerequisites: 'setup/prerequisites.md' - - Supported Configurations: 'setup/supported-configurations.md' - - Single Mode Setup: - - Required Hardware: 'setup/single-mode/hardware-checklist.md' - - Recommended Hardware: 'setup/single-mode/hardware-recommendations.md' - - Software Setup: 'setup/single-mode/software-setup.md' - - Hardware Assembly: 'setup/single-mode/hardware-assembly.md' - - Installation Monitoring: 'setup/single-mode/installation-monitoring.md' - - Dual Mode Setup: - - Required Hardware: 'setup/dual-mode/hardware-checklist.md' - - Recommended Hardware: 'setup/dual-mode/hardware-recommendations.md' - - Software Setup: 'setup/dual-mode/software-setup.md' - - Hardware Assembly: 'setup/dual-mode/hardware-assembly.md' - - Installation Monitoring: 'setup/dual-mode/installation-monitoring.md' - - Next Steps: 'setup/next-steps.md' - - Advanced Setup: - - 'advanced-setup/index.md' - - Two-Factor Authentication (2FA): 'advanced-setup/2fa.md' - - Backup Power: 'advanced-setup/ups.md' - - Power over Ethernet: 'advanced-setup/poe.md' - - Firewall Configuration (UFW): 'advanced-setup/ufw.md' - - The config.txt File: 'advanced-setup/config.md' - - Overclocking: 'advanced-setup/OC.md' - - Monitoring: - - 'monitoring/index.md' - - Installation Monitor: 'monitoring/installation-monitor.md' - - Grafana: 'monitoring/grafana.md' - - LCD: 'monitoring/lcd.md' - - HTTP API: 'monitoring/system-monitor.md' - - Management: - - 'management/index.md' - - SSH: 'management/ssh.md' - - Cockpit: - - 'management/cockpit/dashboard.md' - - Web3 Pi Updater: 'management/cockpit/web3-pi-updater.md' - - Web3 Pi Link: 'management/cockpit/web3-pi-link.md' - - Web3 Pi Script Runner: 'management/cockpit/web3-pi-script-runner.md' - - WelcomeBox: 'welcome-box/index.md' - - Use Cases: - - Install in your wallet: 'use-cases/wallet.md' - - Transaction Firewall: 'use-cases/transaction-firewall.md' - - Solo Staking (coming soon): 'use-cases/solo-staking.md' - - Support: - - Troubleshooting: 'support/troubleshooting.md' - - Cheatsheet: 'support/cheatsheet.md' - - Contact: 'support/contact.md' - - Downloads: 'downloads/index.md' \ No newline at end of file diff --git a/monitoring/grafana/index.html b/monitoring/grafana/index.html new file mode 100644 index 0000000..95ddde0 --- /dev/null +++ b/monitoring/grafana/index.html @@ -0,0 +1,2323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Grafana - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Basic Ethereum Node Monitor

+

The Basic Ethereum Node Monitor plugin is responsible for monitoring the health and status of your Ethereum node. That data is then pushed to the InfluxDB database, which can be visualized in Grafana.

+

Installation

+

To install the Basic Ethereum Node Monitor plugin, navigate to the "Web3 Pi Updater" section in the Cockpit interface. Search for "Basic Ethereum Node Monitor" and install the plugin. On newer Web3 Pi releases, this plugin might be pre-installed by default.

+

Usage

+

Once installed, the Basic Ethereum Node Monitor plugin will start automatically. Open Grafana in your web browser (port 3000 by default) to view the collected data. The default username is 'admin', and the password is 'admin'. It is highly recommended to change the password after the first login.

+

Grafana Dashboard

+

Support

+

For support and further assistance, join the Web3 Pi Discord community.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/monitoring/index.html b/monitoring/index.html new file mode 100644 index 0000000..eda2648 --- /dev/null +++ b/monitoring/index.html @@ -0,0 +1,2231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Monitoring Your Node - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Monitoring Your Node

+

Keeping an eye on your Ethereum node is crucial for ensuring it runs smoothly, stays synced, and uses resources efficiently. Web3 Pi includes several built-in tools to help you monitor different aspects of your setup:

+
    +
  1. +

    Installation Monitor: + A simple web page exposed by your Pi that displays information about the system's setup status. While always available, it's most useful during the initial installation to track the progress of automated steps in real-time.

    +
  2. +
  3. +

    Grafana Dashboards: + Provides detailed, graphical dashboards visualizing the performance and status of your Ethereum clients. Ideal for in-depth performance monitoring once the node is running.

    +
  4. +
  5. +

    LCD Display: + If you've installed the optional LCD hardware, this small screen provides an at-a-glance view of key system information like IP address, hostname, CPU usage, and memory, directly on the device.

    +
  6. +
  7. +

    HTTP API: + Offers a programmatic way to query node status and metrics. Useful for integration with custom scripts or external monitoring systems.

    +
  8. +
+

Refer to the specific pages linked above for details on how to access and use each monitoring tool.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/monitoring/installation-monitor/index.html b/monitoring/installation-monitor/index.html new file mode 100644 index 0000000..e726800 --- /dev/null +++ b/monitoring/installation-monitor/index.html @@ -0,0 +1,2363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Installation Monitor - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Installation Status Page

+

Installation status page

+

The Installation Status plugin provides a comprehensive overview of the Web3 Pi installation process. It displays the status of each installation stage as well as any errors or warnings encountered during the process.

+

Installation

+

To install the Installation Status plugin, navigate to the "Web3 Pi Updater" section in the Cockpit interface. Search for "Installation Status" and install the plugin. On newer Web3 Pi releases, this plugin might be pre-installed by default.

+

Usage

+

Once installed, the Installation Status plugin will be accessible on port 80 of your Raspberry Pi. Simply open the URL in your web browser to view the status page.

+

Disabling the Plugin

+

If you no longer require the Installation Status plugin, you can disable it by stopping the w3p_installation-status service. To do this, navigate to the "Services" section in the Cockpit interface and stop the service.

+

Source Code

+

The source code for the Installation Status plugin can be found on GitHub.

+

Support

+

For support and further assistance, join the Web3 Pi Discord community.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/monitoring/lcd/index.html b/monitoring/lcd/index.html new file mode 100644 index 0000000..3bc0236 --- /dev/null +++ b/monitoring/lcd/index.html @@ -0,0 +1,2344 @@ + + + + + + + + + + + + + + + + + + + + + + + + + LCD - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

LCD Dashboard

+

+

To add an LCD dashboard to your Raspberry Pi, you will need:

+
    +
  • +

    A dashboard cover for mounting;

    +
  • +
  • +

    A suitable LCD display;

    +
  • +
  • +

    Software to run the LCD.

    +
  • +
+

Dashboard Cover

+

The Web3 Pi LCD Dashboard project offers a suitable cover available for 3D printing.

+

This is designed for installation of a colorful LCD in the Argon Neo 5 enclosure. We have designed our own 3D model of the enclosure cover with a space for the display. The assembly is simple, using snap-fits, with no tools required. The models are open-source, so anyone can print them on a 3D printer. The source code is also open-source, allowing users to add new functionalities, customize it to their needs, or add support for new displays.

+

For more information, see Web 3 Pi Dashboard documentation.

+

LCD Display

+

The LCD display should be 1.69" with ST7789V2 Driver. Suitable models are:

+ +

Software

+

The software is included in the Web3 Pi image, and can also be downloaded separately from the Web 3 Pi Dashboard Project.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/monitoring/system-monitor/index.html b/monitoring/system-monitor/index.html new file mode 100644 index 0000000..b8b584a --- /dev/null +++ b/monitoring/system-monitor/index.html @@ -0,0 +1,2341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + HTTP API - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Basic System Monitor (monitoring via HTTP API)

+

The Basic System Monitor plugin is responsible for monitoring the health and status of your Web3 Pi. It exposes a simple HTTP API that can be queried to retrieve information about your device.

+

Installation

+

To install the Basic System Monitor plugin, navigate to the "Web3 Pi Updater" section in the Cockpit interface. Search for "Basic System Monitor" and install the plugin. On newer Web3 Pi releases, this plugin might be pre-installed by default.

+

Usage

+

Once installed, the Basic System Monitor plugin will start automatically. You can query the status of your Web3 Pi by calling the HTTP API endpoint on port 7197 (by default).

+

The output should look similar to this:

+
{
+  "host_name": "eop-1",
+  "num_cores": 4,
+  "cpu_percent": 30,
+  "mem_total": 8323276800,
+  "mem_used": 6859583488,
+  "mem_free": 386887680,
+  "mem_percent": 85.4,
+  "swap_total": 17179865088,
+  "swap_used": 7224090624,
+  "swap_free": 9955774464,
+  "swap_percent": 42,
+  "disk_used": 1359481831424,
+  "cpu_temp": 58.95,
+  "net_upload": 510982.969251317,
+  "net_download": 702530.988724869
+}
+
+

Support

+

For support and further assistance, join the Web3 Pi Discord community.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/overrides/main.html b/overrides/main.html deleted file mode 100644 index 2370886..0000000 --- a/overrides/main.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} {% block scripts %} {{ super() }} - - -{% endblock %} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 284e50e..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -mkdocs>=1.5.3,<2.0 -mkdocs-material>=9.4.2,<10.0 -mkdocs-material-extensions>=1.1,<2.0 -pymdown-extensions>=10.0,<11.0 \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..d592b48 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"Web3 Pi documentation

This documentation provides everything you need to get started with Web3 Pi.Find step-by-step guides for setup, detailed explanations of features, monitoring tools, troubleshooting and tips for running your Ethereum node smoothly on Raspberry Pi.

Get Started"},{"location":"#learn","title":"Learn","text":"

Learn more about the Web3 Pi project and benefits of running an Ethereum node on a Raspberry Pi.

  • About Web3 Pi

    Essential information before starting your Web3 Pi journey

    The Web3 Pi Project

  • Running an Ethereum Node

    Learn about the benefits of running your very own Ethereum node on a Raspberry Pi

    What is an Ethereum Node?

"},{"location":"#setup-configuration","title":"Setup & Configuration","text":"

Get started with running your very own Ethereum node on a Raspberry Pi.

  • Prerequisites

    Hardware and software requirements to run an Ethereum node on a Raspberry Pi

    Prerequisites

  • Setup Guide

    A complete step-by-step guide to setting up your own Ethereum node on a Raspberry Pi using Web3 Pi

    Setup guide

"},{"location":"#monitoring-management","title":"Monitoring & Management","text":"

Tips and tricks to keep your node running smoothly.

  • Monitoring

    Learn how to monitor your node

    Managing your node

  • Management

    Learn how to manage and maintain your node

    Managing your node

  • Troubleshooting

    Learn how to troubleshoot common issues

    Troubleshooting

"},{"location":"advanced-setup/","title":"Advanced Setup","text":"

This section provides advanced setup guides for your Web3 Pi node that aim to optimize your node's performance and reliability. None of these steps are required, but they can help you achieve better uptime and reduce maintenance burden.

"},{"location":"advanced-setup/#table-of-contents","title":"Table of Contents","text":"
  • Two-Factor Authentication (2FA) - Add an extra layer of security to your Cockpit login.
  • Backup Power - Consider adding a backup power source to your node, in case of power outages.
  • Power over Ethernet - Use PoE (Power over Ethernet) instead of the included power supply.
  • Firewall Configuration (UFW) - Configure UFW to control incoming and outgoing network traffic.
  • The config.txt File - Learn how to configure boot options, hardware interfaces, and more.
  • Overclocking - Learn how to overclock your Raspberry Pi to improve performance.
"},{"location":"advanced-setup/2fa/","title":"Two-Factor Authentication (2FA) for Cockpit","text":"

Adding Two-Factor Authentication (2FA) to Cockpit increases the security of your server by requiring a time-based one-time password (TOTP) in addition to your regular credentials.

The Web3 Pi team developed a plugin to easily enable 2FA in Cockpit using a graphical interface. Alternatively, you can manually configure 2FA using the libpam-google-authenticator package.

Using the official pluginInstalling manually"},{"location":"advanced-setup/2fa/#step-1-install-required-packages","title":"Step 1: Install Required Packages","text":"

Navigate to the Web3 Pi Updater section in Cockpit and install Two Factor Authentication Plugin for Cockpit:

Alternatively, you can install the plugin manually by opening a terminal and running the following command:

sudo apt-get install w3p-two-factor-auth\n

Note

Cockpit will restart after the installation of the plugin, so you may need to refresh the page and log in again.

"},{"location":"advanced-setup/2fa/#step-2-configure-2fa","title":"Step 2: Configure 2FA","text":"

After installing the plugin, navigate to the Web3 Pi 2FA section in Cockpit:

Click Enable Two-Factor Authentication to start the setup process.

Follow the on-screen instructions to set up 2FA:

  1. Scan the QR code with your preferred authenticator app (e.g., Google Authenticator, Authy).
  2. Enter the verification code from your authenticator app to complete the setup.
  3. Save the emergency scratch codes in a safe place.

Note

Scratch codes are one-time use only. If you lose access to your authenticator app, enter one of these codes to log in and recreate your 2FA setup.

"},{"location":"advanced-setup/2fa/#step-3-test-your-setup","title":"Step 3: Test Your Setup","text":"
  1. Log out of Cockpit.
  2. Log back in. You should be prompted for a verification code from your authenticator app.
"},{"location":"advanced-setup/2fa/#uninstalling-2fa","title":"Uninstalling 2FA","text":"

To remove 2FA from Cockpit, navigate back to the Web3 Pi 2FA section and click Disable Two-Factor Authentication.

"},{"location":"advanced-setup/2fa/#step-1-install-required-packages_1","title":"Step 1: Install Required Packages","text":"

Open a terminal and run:

sudo apt-get install libpam-google-authenticator -y\n

This installs the PAM module for Google Authenticator.

"},{"location":"advanced-setup/2fa/#step-2-configure-google-authenticator-for-your-user","title":"Step 2: Configure Google Authenticator for Your User","text":"

Run the following command to set up Google Authenticator with recommended options:

google-authenticator -t -d -f -r 3 -R 30 -W -Q UTF8\n

Note

  • -t use TOTP instead of HOTP (recommended).
  • -d disable reuse of previously used TOTP tokens.
  • -f disable confirmation before writing the ~/.google_authenticator file.
  • -r 3 -R 30 limits the number of login attempts to 3 every 30 seconds.
  • -W by default google-authenticator allows the use of codes that were generated a little before or a little after the current time. This option disables that feature (recommended for security).
  • -Q UTF8 specifies the encoding for the QR code. Change to -Q ANSI if you're having issues with viewing the QR code.
  • This will generate a secret key, QR code, and emergency scratch codes.
  • Scan the QR code with your preferred authenticator app (e.g., Google Authenticator, Authy).
  • Enter the verification code from your authenticator app to complete the setup.
  • Save the emergency scratch codes in a safe place.

Note

Scratch codes are one-time use only. If you lose access to your authenticator app, enter one of these codes to log in and recreate your 2FA setup.

"},{"location":"advanced-setup/2fa/#step-3-enable-2fa-for-cockpit","title":"Step 3: Enable 2FA for Cockpit","text":"

Use the following command to add the Google Authenticator PAM module to the Cockpit PAM configuration:

sudo bash -c 'echo \"auth required pam_google_authenticator.so nullok\" >> /etc/pam.d/cockpit'\n

This tells Cockpit to require a TOTP code during login.

Note

  • The nullok option disables 2FA for users that do not have a ~/.google_authenticator file.
"},{"location":"advanced-setup/2fa/#step-4-restart-cockpit","title":"Step 4: Restart Cockpit","text":"

Restart the Cockpit service to apply the changes:

sudo systemctl restart cockpit\n
"},{"location":"advanced-setup/2fa/#step-5-test-your-setup","title":"Step 5: Test Your Setup","text":"
  1. Log out of Cockpit.
  2. Log back in. You should be prompted for a verification code from your authenticator app.
"},{"location":"advanced-setup/2fa/#uninstalling-2fa_1","title":"Uninstalling 2FA","text":"

To remove 2FA from Cockpit, simply delete the line you added to the PAM configuration:

sudo bash -c 'sed -i \"/pam_google_authenticator.so nullok/d\" /etc/pam.d/cockpit'\n

Then restart the Cockpit service:

sudo systemctl restart cockpit\n

You can also remove the generated ~/.google_authenticator file and the installed packages if you no longer need 2FA:

sudo apt remove libpam-google-authenticator -y\nrm ~/.google_authenticator\n
"},{"location":"advanced-setup/OC/","title":"Overclocking","text":""},{"location":"advanced-setup/OC/#raspberry-pi-5","title":"Raspberry Pi 5","text":"

There are two things that can be tweaked on the Raspberry Pi 5 to increase Ethereum Node performance.

  • CPU frequency
  • PCIe generation

Note about PCIe generation settings

This makes sense only if using a PCIe to m.2 adapter for storage.

"},{"location":"advanced-setup/OC/#cpu-overclocking","title":"CPU Overclocking","text":"

By default, the Raspberry Pi 5 CPU clock is set to 2.4 GHz, but it is relatively easy to overclock. An overclocked CPU with a significant load will require an active cooling solution or a high-quality cooling case.

The maximum stable clock that can be achieved depends on a particular device.

Safe for all devices is 2.6 GHz. The reasonable top is 3.0 GHz.

The Raspberry Pi has enough power to handle an Ethereum node without OC, so our recommendation is to keep stable settings like 2.6 GHz.

"},{"location":"advanced-setup/OC/#how-to-overclock-the-cpu","title":"How to overclock the CPU","text":"
  • Edit the /boot/firmware/config.txt file:
    sudo nano /boot/firmware/config.txt\n
  • Find the last [pi5] section, almost at the end of the file. Look for this comment:
     [rpi5]\n # Overclocking for Raspberry Pi 5\n # Active cooling is required\n over_voltage_delta=50000\n arm_freq=2800\n #2400MHz is default\n #3000MHz is max (not all boards will work stable)\n #2800MHz is reasonable OC\n
  • Exit the editor by pressing Ctrl+x and save the changes.
  • Restart the device:
    sudo reboot\n
  • After reboot, you can check if the frequency is correctly recognized by the OS.
sudo cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq\n

It should output 2600000

If the procedure is successful, the device should be up and running with the updated OC settings.

For more information about overclocking the Raspberry Pi 5, please visit:

https://www.jeffgeerling.com/blog/2023/overclocking-and-underclocking-raspberry-pi-5

"},{"location":"advanced-setup/OC/#pcie-generation-select","title":"PCIe generation select","text":"

The Raspberry Pi by default uses PCIe gen 2. But the Broadcom BCM2712 offers PCIe generation 3, which is twice as fast. By default, it is set to gen 2 because of compatibility reasons with different adapters. In most cases, you can safely set it to gen 3, which can double the performance of the NVMe drive.

  • Edit the /boot/firmware/config.txt file:
    sudo nano /boot/firmware/config.txt\n
  • Find the last [pi5] section, almost at the end of the file. Look for this comment:
     #Enable PCIe\n dtparam=pciex1\n #Enable PCIe gen.3 (default is gen.2)\n dtparam=pciex1_gen=3\n
  • Exit the editor by pressing Ctrl+x and save the changes.
  • Restart the device:
    sudo reboot\n

For more information, please visit: https://www.jeffgeerling.com/blog/2023/nvme-ssd-boot-raspberry-pi-5

"},{"location":"advanced-setup/OC/#raspberry-pi-4","title":"Raspberry Pi 4","text":"

To overclock the Raspberry Pi 4, you need to edit the config.txt file located in the /boot/firmware/ directory.

  1. Access the File:

  2. Open a terminal on your Raspberry Pi.

  3. Edit the config.txt file using a text editor such as nano.
sudo nano /boot/firmware/config.txt\n
  1. Add Overclocking Settings:

  2. Uncomment the following lines near the end of the config.txt file. Adjust the values based on your desired overclock settings and the stability of your system.

[pi4]\nover_voltage=6\narm_freq=1800\ngpu_freq=600\n

Explanation:

  • over_voltage=6: Increases the core voltage. Values range from 0 to 8. Higher values increase stability but also generate more heat.
  • arm_freq=1800: Sets the CPU frequency to 1800 MHz (1.8 GHz).
    • The default is 1500 MHz
    • Moderate OC is 1800 MHz
    • A high overclock is 2000 MHz
  • gpu_freq=600: Sets the GPU frequency to 600 MHz.

    • The default is 500 MHz
    • Moderate OC is 600 MHz
    • A high overclock is 750 MHz
  • Save and Reboot:

  • Save the file (Ctrl+O and Enter in nano) and exit the text editor (Ctrl+X in nano).

  • Reboot the Raspberry Pi to apply the changes.
sudo reboot\n
"},{"location":"advanced-setup/OC/#raspberry-pi-cm4","title":"Raspberry Pi CM4","text":"

Overclocking the Raspberry Pi Compute Module 4 (CM4) is similar to overclocking the Raspberry Pi 4, but there are a few key differences to consider due to the form factor and intended use cases of the CM4.

"},{"location":"advanced-setup/OC/#similarities","title":"Similarities","text":"
  1. Configuration File:
  2. Both use the config.txt file located in the /boot directory for overclocking settings.
  3. Overclocking Parameters:

  4. Parameters such as over_voltage, arm_freq, and gpu_freq are used in the same way to adjust voltage, CPU frequency, and GPU frequency.

  5. Monitoring and Testing:

  6. Tools and methods for monitoring temperature, checking for throttling, and stress testing are the same.
"},{"location":"advanced-setup/OC/#differences","title":"Differences","text":"
  1. Form Factor and Cooling:

  2. The CM4 is designed to be used with custom carrier boards, which may affect cooling solutions. Ensure your carrier board design allows for adequate cooling, especially when overclocking.

  3. Power Supply:

  4. The power supply and power delivery to the CM4 might be different depending on the carrier board. Ensure that the carrier board can supply sufficient power for overclocking.
"},{"location":"advanced-setup/OC/#monitoring-and-stability","title":"Monitoring and Stability","text":"
  1. Monitor Temperatures

  2. Use tools like vcgencmd to monitor the temperature of your Raspberry Pi.

vcgencmd measure_temp\n
  • Ideally, temperatures should remain below 85\u00b0C. If temperatures are higher, consider improving your cooling solution.

  • Stress Test

  • Run stress tests to ensure stability. The stress tool can be used for this purpose.

sudo apt install stress\nstress --cpu 4 --timeout 600\n
  1. Check for Throttling

  2. Use vcgencmd to check if the Raspberry Pi is throttling due to high temperatures or insufficient power.

vcgencmd get_throttled\n
  • A result of 0x0 indicates no throttling.
"},{"location":"advanced-setup/OC/#safety-tips","title":"Safety Tips","text":"
  1. Incremental Changes: Start with small increments and gradually increase the values. Monitor stability and temperatures at each step.
  2. Cooling: Ensure you have sufficient cooling. Consider adding a fan or better heatsinks if necessary.
  3. Power Supply: Use a high-quality power supply that can handle the increased power demands.
  4. Testing: Perform extensive testing to ensure that your system remains stable under load.
"},{"location":"advanced-setup/OC/#conclusion","title":"Conclusion","text":"

Overclocking the Raspberry Pi can provide significant performance improvements, making it more capable for an Ethereum node. However, it is crucial to approach overclocking with caution, ensuring adequate cooling and power supply, and thoroughly testing for stability. By following these guidelines, you can safely and effectively overclock your Raspberry Pi to meet your performance needs.

"},{"location":"advanced-setup/config/","title":"Advanced Setup: The config.txt File","text":""},{"location":"advanced-setup/config/#overview","title":"Overview","text":"

Web3 Pi utilizes a central configuration file, /boot/firmware/config.txt, to manage both its own settings (like which clients to run and network selection) and the underlying Raspberry Pi hardware configuration (like boot options, hardware interfaces, and overclocking).

This file is read by the Raspberry Pi firmware during the early boot process.

For most users, the default settings generated by the Web3 Pi Imager are sufficient and should not need modification. This page is intended for advanced users who understand the implications of changing these parameters.

\u26a0\ufe0f WARNING: Incorrectly editing /boot/firmware/config.txt can prevent your Raspberry Pi from booting correctly or cause your Ethereum node clients to malfunction. Always back up the file before making changes.

"},{"location":"advanced-setup/config/#location-and-editing","title":"Location and Editing","text":"

The configuration file is located at:

/boot/firmware/config.txt

You will need root privileges to edit this file. Connect via SSH and use a text editor like nano:

# First, create a backup\nsudo cp /boot/firmware/config.txt /boot/firmware/config.txt.backup\n\n# Then, edit the file\nsudo nano /boot/firmware/config.txt\n

After saving changes, you must reboot your Raspberry Pi for them to take effect:

sudo reboot\n
"},{"location":"advanced-setup/config/#web3-pi-specific-sections","title":"Web3 Pi Specific Sections","text":"

These sections control the behavior of the Web3 Pi software suite.

"},{"location":"advanced-setup/config/#web3pi-section","title":"[web3pi] Section","text":"
  • geth=true|false: Enables or disables the Geth execution client service (w3p_geth).
  • nimbus=true|false: Enables or disables the Nimbus consensus client service (w3p_nimbus-beacon).
  • lighthouse=true|false: Enables or disables the Lighthouse consensus client service (w3p_lighthouse-beacon). (Note: Typically, only one consensus client should be enabled).
  • influxdb=true|false: Enables or disables the InfluxDB time-series database service (for monitoring).
  • grafana=true|false: Enables or disables the Grafana dashboard service (for monitoring).
  • bsm=true|false: Enables or disables the Basic System Monitor service.
  • bnm=true|false: Enables or disables the Basic Eth2 Node Monitor service.
  • exec_url=http://localhost:8551: Specifies the URL the consensus client uses to connect to the execution client's Engine API. The default assumes both clients are on the same machine.
  • eth_network=mainnet|sepolia|hoodi|...: Crucially, sets the target Ethereum network for all clients. Must match the network you intend to sync.
"},{"location":"advanced-setup/config/#client-port-sections-geth-nimbus-lighthouse","title":"Client Port Sections ([geth], [nimbus], [lighthouse]):","text":"
  • geth_port=30303: Sets the P2P port Geth uses for peer discovery and communication.
  • nimbus_port=9000: Sets the P2P port Nimbus uses.
  • lighthouse_port=9000: Sets the P2P port Lighthouse uses.

Make sure to update the UFW Firewall after changing these ports.

"},{"location":"advanced-setup/config/#standard-raspberry-pi-sections","title":"Standard Raspberry Pi Sections","text":"

The rest of the config.txt file contains standard Raspberry Pi configuration directives, grouped under conditional filters like [all], [pi4], [pi5], [cm4], etc. These control hardware aspects:

  • Boot Options: (kernel, cmdline, initramfs) Defines how the Linux kernel is loaded.
  • Hardware Interfaces: (dtparam=audio, i2c_arm, spi, enable_uart) Enables or disables onboard hardware like audio, I2C, SPI, and serial ports.
  • Display Settings: (disable_overscan, hdmi_drive, hdmi_force_hotplug, display_auto_detect) Configures HDMI output behavior.
  • Graphics: (dtoverlay=vc4-kms-v3d) Configures the graphics driver.
  • Camera: (camera_auto_detect) Auto-detects connected cameras.
  • USB: (dtoverlay=dwc2, usb_max_current_enable) Configures USB ports, including enabling higher current output on Pi 5.
  • PCIe (Pi 5 / CM4): (dtparam=pciex1, dtparam=pciex1_gen=3) Important for NVMe drives. Enables the PCIe interface and sets its speed (Gen 2 or Gen 3). Web3 Pi typically enables Gen 3 for better NVMe performance.
  • Overclocking: (Lines often starting with # over_voltage, # arm_freq) These settings (commented out by default) allow advanced users to potentially increase CPU performance. Requires active cooling and carries risks of instability or hardware damage if done improperly.
"},{"location":"advanced-setup/config/#further-reading","title":"Further Reading","text":"

For an exhaustive explanation of all standard Raspberry Pi config.txt options, refer to the official documentation:

  • Official Raspberry Pi config.txt Documentation

Remember to exercise caution when editing this file. Stick to the defaults unless you have a specific need and understand the parameter you are changing.

"},{"location":"advanced-setup/poe/","title":"Advanced Setup: Power over Ethernet (PoE)","text":"

Power over Ethernet (PoE) is a networking feature that allows network cables to carry electrical power in addition to data. By using PoE, you can power your Web3 Pi Raspberry Pi using the same Ethernet cable that provides its network connection, eliminating the need for a separate USB-C power adapter.

This can simplify cable management and potentially allow for centralized power backup if your network switch is connected to a UPS.

"},{"location":"advanced-setup/poe/#benefits-of-using-poe","title":"Benefits of Using PoE","text":"
  • Simplified Wiring: Reduces cable clutter by combining power and data into a single Ethernet cable run to the Raspberry Pi.
  • Flexible Placement: Allows placing the Raspberry Pi further from power outlets, as long as an Ethernet cable can reach it.
  • Centralized Power Backup: If your PoE-providing network switch or injector is connected to a UPS (Uninterruptible Power Supply), your Raspberry Pi will also benefit from backup power.
"},{"location":"advanced-setup/ufw/","title":"Advanced Setup: Firewall Configuration (UFW)","text":""},{"location":"advanced-setup/ufw/#understanding-the-firewall","title":"Understanding the Firewall","text":"

Web3 Pi includes and enables UFW (Uncomplicated Firewall) by default to provide a baseline level of network security for your node. UFW is a user-friendly frontend for managing the underlying iptables firewall rules on Linux systems like Ubuntu.

Its primary purpose is to control incoming and outgoing network traffic, ensuring that only necessary connections are allowed, thus reducing the potential attack surface of your device.

"},{"location":"advanced-setup/ufw/#default-status-and-policy","title":"Default Status and Policy","text":"
  • Enabled by Default: UFW is installed and enabled at the end of the Web3 Pi setup process.
  • Default Incoming Policy: DENY - All incoming connections are blocked unless explicitly allowed by a specific rule.
  • Default Outgoing Policy: ALLOW - All outgoing connections initiated by the Raspberry Pi are permitted.
"},{"location":"advanced-setup/ufw/#default-allowed-incoming-ports","title":"Default Allowed Incoming Ports","text":"

The Web3 Pi installation script configures UFW to allow incoming traffic on the specific ports required for node operation, management, and monitoring based on your configuration choices during setup. The standard ports opened are:

Port Protocol Service Purpose 22 TCP SSH Secure remote command-line access 80 TCP Installation Monitor / Status Page Viewing setup progress and basic status 3000 TCP Grafana Dashboard Viewing node performance and health 5353 UDP mDNS (Avahi Daemon) Hostname discovery (e.g., web3pi.local) 7197 TCP Basic System Monitor JSON API Programmatic access to monitoring data 8545 TCP Execution Client JSON-RPC (Geth) Wallet connections 8546 TCP Execution Client WebSocket RPC (Geth) WebSocket connections for dApps/tools 8551 TCP Execution Client Engine API (Geth) Communication between EL & CL clients 9090 TCP Cockpit System Dashboard Web-based system management 9000 (default) TCP & UDP Consensus Client P2P (Lighthouse/Nimbus) Peer discovery and communication 30303 (default) TCP & UDP Execution Client P2P (Geth) Peer discovery and communication"},{"location":"advanced-setup/ufw/#checking-firewall-status-and-rules","title":"Checking Firewall Status and Rules","text":"

You can view the current UFW status and the list of active rules by connecting via SSH and running the following commands:

sudo ufw status numbered\n
"},{"location":"advanced-setup/ufw/#adding-or-removing-ports","title":"Adding or Removing Ports","text":"

To add a new port, use the ufw allow command. For example, to allow incoming TPC traffic on port 12345, run the following command:

sudo ufw allow 12345/tcp comment 'This port is used by XYZ service'\n

To remove a port, use the ufw delete command. It's recommended to use the ufw status numbered command to identify the rule number before deleting it. For example, to delete the rule with the number 100, run the following command:

sudo ufw delete 100\n
"},{"location":"advanced-setup/ups/","title":"Power Backup Solutions for Raspberry Pi 5 and Raspberry Pi 4","text":""},{"location":"advanced-setup/ups/#why-use-power-backup-for-an-ethereum-full-node","title":"Why Use Power Backup for an Ethereum Full Node?","text":"

Running a full Ethereum node on Raspberry Pi, especially in projects like Web3 Pi, requires consistent and stable power. A power outage or fluctuation can result in corrupted data since the node continuously writes to storage. In addition, even if you live in a region with seemingly stable electricity, brief voltage dips or surges can occur unnoticed, potentially causing instability, freezing, or unexpected reboots of your Raspberry Pi.

For optimal reliability, power backup systems should ideally cover not just the Raspberry Pi but the entire network path (e.g., routers and switches). However, even just powering the Raspberry Pi can significantly enhance stability.

"},{"location":"advanced-setup/ups/#backup-power-options-for-raspberry-pi","title":"Backup Power Options for Raspberry Pi","text":"

There are two primary solutions for providing backup power to Raspberry Pi:

  1. Conventional 230/110V UPS
  2. Dedicated UPS for Raspberry Pi SBC
"},{"location":"advanced-setup/ups/#conventional-230110v-ups","title":"Conventional 230/110V UPS","text":""},{"location":"advanced-setup/ups/#advantages","title":"Advantages:","text":"
  • Widely available globally in various models and capacities.
  • Can power multiple devices, such as routers, switches, or multiple Raspberry Pis.
"},{"location":"advanced-setup/ups/#disadvantages","title":"Disadvantages:","text":"
  • Larger in size and often equipped with fans, which may produce noise.
"},{"location":"advanced-setup/ups/#recommended-model","title":"Recommended Model:","text":"
  • Legrand UPS KEOR PDU (EAN: 3414971529380)
    • Silent operation and tested for reliability.
    • More details.
"},{"location":"advanced-setup/ups/#dedicated-raspberry-pi-ups","title":"Dedicated Raspberry Pi UPS","text":""},{"location":"advanced-setup/ups/#advantages_1","title":"Advantages:","text":"
  • Compact and silent.
  • Designed specifically for Raspberry Pi, often as a HAT or a small external device.
  • Equipped with popular 18650 cells for longer battery life, depending on the number of cells.
  • Can interface with Raspberry Pi to detect low battery levels and initiate safe shutdowns.
"},{"location":"advanced-setup/ups/#disadvantages_1","title":"Disadvantages:","text":"
  • Less commonly available but can be ordered online.
"},{"location":"advanced-setup/ups/#recommendations","title":"Recommendations:","text":""},{"location":"advanced-setup/ups/#for-raspberry-pi-5","title":"For Raspberry Pi 5:","text":"
  • Geekworm X1200 2-Cell 18650 5.1V 5A UPS HAT

    • Designed specifically for Raspberry Pi 5.
    • Features:
      • Supports two 18650 batteries for extended runtime.
      • Output: 5.1V/5A for consistent power delivery.
      • Compact HAT form factor, easy to install.
    • More information.
  • Waveshare UPS Module 3S

    • Features:
      • Supports three 18650 batteries.
      • Smart power management with low-battery alert.
      • Compact external unit, easy to integrate.
    • More information.
"},{"location":"advanced-setup/ups/#for-raspberry-pi-4","title":"For Raspberry Pi 4:","text":"
  • Geekworm Raspberry Pi X728
    • Features:
      • Supports three 18650 batteries.
      • Integrated power management for safe shutdown.
      • Output: 5V/6A.
      • Compact design that mounts directly onto Raspberry Pi.
    • More information.
"},{"location":"advanced-setup/ups/#installation-and-setup","title":"Installation and Setup","text":"

The installation and configuration processes for these UPS devices are detailed on their respective product pages. Refer to the manufacturer's guides for precise instructions.

"},{"location":"advanced-setup/ups/#recommendation","title":"Recommendation","text":"

We highly recommend using a power backup solution to enhance the stability of your Ethereum node and reduce potential problems caused by power interruptions.

For Ethereum Solo Staking, power backup is critical. A reliable power supply minimizes downtime, ensures data integrity, and helps avoid penalties related to missed attestations or blocks.

"},{"location":"downloads/","title":"Downloads","text":""},{"location":"downloads/#web3-pi-image","title":"Web3 Pi Image","text":"

A comprehensive image packed with tools and references for creating and hosting Ethereum Nodes on Raspberry Pi devices. Ethereum on Raspberry Pi \u2192

"},{"location":"downloads/#web3-pi-imager","title":"Web3 Pi Imager","text":"

An intuitive application designed to streamline the process of burning Web3 Pi images effortlessly. Web3 Pi Imager \u2192

"},{"location":"introduction/ethereum-node/","title":"What is an Ethereum Node?","text":"

Before running your own Ethereum node using Web3 Pi or any other method, it's important to understand what a node is and its role in the Ethereum network.

"},{"location":"introduction/ethereum-node/#the-basics","title":"The Basics","text":"

An Ethereum node is simply a piece of software that connects to the Ethereum network. It downloads a copy of the Ethereum blockchain and follows the network's consensus rules to verify transactions and blocks. Running a node contributes to the decentralization, security, and resilience of the Ethereum network.

For a comprehensive overview, start by reading the documentation provided by the Ethereum Foundation:

  • Running an Ethereum Node Overview
"},{"location":"introduction/ethereum-node/#node-components-execution-and-consensus-clients","title":"Node Components: Execution and Consensus Clients","text":"

A modern Ethereum node consists of two main software components running together:

  1. Execution Client (EL): Sometimes called the Execution Engine or formerly Eth1 client, this software listens for new transactions broadcasted to the network, executes them in the Ethereum Virtual Machine (EVM), and holds the latest state and database of all Ethereum data. It handles the \"computation\" part of the network.
  2. Consensus Client (CL): Also known as the Beacon Node or formerly Eth2 client, this software implements the proof-of-stake consensus algorithm. It enables the network to agree on the state of the blockchain based on the validated data received from the Execution Client. It handles the \"agreement\" part of the network.

These two clients work in tandem to keep the node synchronized with the head of the Ethereum chain and allow users to interact with the network.

Learn more about clients and nodes:

  • Nodes and Clients Documentation
"},{"location":"introduction/ethereum-node/#why-run-a-node","title":"Why Run a Node?","text":"

People run Ethereum nodes for various reasons:

  • Support the Network: Increase the decentralization and security of Ethereum.
  • Trustless & Uncensored Access: Interact directly with the Ethereum blockchain without relying on third-party services, ensuring privacy and avoiding potential censorship or rate limits.
  • High-Performance RPC: Gain fast, local, and unlimited access to the Ethereum RPC API for wallets and applications.
  • Development: Provide a local endpoint for developing and testing decentralized applications (dApps).
  • Foundation for Staking: Provide the necessary infrastructure (EL/CL clients) that could be configured for staking.

It is important to clarify: running an Ethereum node does not require staking 32 ETH. Anyone can run a full node using Web3 Pi to verify transactions, interact privately with the blockchain, and contribute to network health without any ETH deposit. Staking, which does require 32 ETH per validator, is a separate activity that involves proposing and attesting to new blocks to earn rewards.

What about staking?

While running a node is a prerequisite for staking, configuring it specifically for staking duties is a complex process that goes beyond the standard Web3 Pi setup. You can learn more about staking here.

"},{"location":"introduction/next-steps/","title":"Next Steps","text":"

Now that you have a basic understanding of Web3 Pi and Ethereum nodes, here's how to proceed:

"},{"location":"introduction/next-steps/#1-make-key-decisions","title":"1. Make Key Decisions","text":"

Before you begin the setup process, you need to decide on a few things:

"},{"location":"introduction/next-steps/#single-or-dual-device-mode","title":"Single or Dual Device Mode?","text":"

An Ethereum node requires both an Execution Client (EL) and a Consensus Client (CL).

  • Single Device: Run both EL and CL clients on one Raspberry Pi. This is cheaper but shares resources. A Raspberry Pi 5 with 8GB RAM is recommended for this configuration.
  • Dual Device: Run the EL client on one Raspberry Pi and the CL client on a separate Raspberry Pi. This allows you to use two, weaker devices in place of one, more powerful one, at the cost of increased complexity.

If you're unsure, we recommend starting with a single device setup.

"},{"location":"introduction/next-steps/#2-prepare-your-hardware","title":"2. Prepare Your Hardware","text":"

Ensure you have all the necessary hardware components (Raspberry Pi, power supply, storage, network cable, etc.).

"},{"location":"introduction/next-steps/#use-your-own-hardware-or-the-welcomebox","title":"Use Your Own Hardware or the WelcomeBox?","text":"
  • WelcomeBox: Web3 Pi offers a WelcomeBox \u2014 an all-in-one solution with pre-selected, compatible hardware to help you get started quickly.
  • Own Hardware: You can use your existing Raspberry Pi (Model 4, 5, or CM4) and compatible peripherals. Ensure you have everything required by checking the Prerequisites Guide and the Hardware Recommendations document.
"},{"location":"introduction/next-steps/#optional-lcd-display","title":"Optional LCD Display?","text":"
  • You can add an optional LCD display to your setup for at-a-glance monitoring. It's included in the WelcomeBox and natively supported by the Web3 Pi system \u2014 just plug it in, no installation or configuration needed. Details are in the LCD Monitoring Guide.
"},{"location":"introduction/next-steps/#3-set-up-your-node","title":"3. Set Up Your Node","text":"
  • Follow the Full Setup Guide relevant to your configuration for detailed, step-by-step instructions assembling the hardware and booting your Raspberry Pi for the first time.
"},{"location":"introduction/next-steps/#4-manage-your-node","title":"4. Manage Your Node","text":"

Web3 Pi includes a set of tools to help you manage and monitor your node:

  • The Grafana Dashboard provides real-time monitoring of your node's performance.
  • The Cockpit Dashboard provides an overview of your node's status and allows you to manage basic settings.
  • The Web3 Pi Updater allows you to upgrade your node's ethereum dependencies and other software.
  • The Web3 Pi Link allows you to access your node from anywhere in the world.

Warning

Remember: Staking configuration is currently considered an advanced, unsupported procedure. Proceed with extreme caution if exploring this path independently.

"},{"location":"introduction/staking/","title":"Can I Earn with Web3 Pi?","text":"

Warning

Running an Ethereum validator involves significant financial risk and technical responsibility. You do so entirely at your own risk.

Running a standard Ethereum node with Web3 Pi does not directly generate income by itself. Its primary purpose is providing secure, private, and performant access to the blockchain. However, Web3 Pi serves as a foundation for potential earning opportunities:

  • Solo Staking: While running a node doesn't require 32 ETH, staking to secure the network and earn rewards does. Web3 Pi hardware (like Raspberry Pi 5) is capable of handling staking duties (proven in tests running 200 validators), although the setup currently requires manual configuration. We aim to simplify this process significantly in the future.
  • RPC Monetization: Through collaborations like the one with Golem Network, there are plans to allow Web3 Pi node operators to share RPC endpoint and potentially earn rewards (e.g., in GLM tokens) for serving requests. This feature is not yet available.

If you are considering staking, it is crucial that you thoroughly research and understand the process, responsibilities, and significant risks involved before attempting it.

Learn more about staking fundamentals: - Ethereum Staking Guide

"},{"location":"introduction/web3-pi-project/","title":"Web3 Pi: Before you start","text":""},{"location":"introduction/web3-pi-project/#the-web3-pi-project","title":"The Web3 Pi Project","text":"

Web3 Pi is an open-source project simplifying the operation of a personal Ethereum node using Raspberry Pi devices. We provide a ready-to-use, optimized operating system image and automated tools, making node ownership accessible even without deep technical knowledge. Leveraging the Raspberry Pi's efficient ARM architecture, Web3 Pi ensures reliable blockchain access with very low energy consumption (around 8W), allowing for cost-effective, 24/7 operation. Our goal is to lower the barrier to entry for direct participation in the Ethereum network.

Currently supported devices (at least 8GB RAM required):

  • Raspberry Pi 5
  • Raspberry Pi 4
  • Raspberry Pi CM4/CM5
"},{"location":"introduction/web3-pi-project/#why-web3-pi","title":"Why Web3 Pi?","text":"

Web3 Pi images offer several advantages for setting up your Ethereum node:

  • Plug & Play: Simply flash the image to your storage and power up the Raspberry Pi.
  • Solid Foundation: Based on Ubuntu 24.04 LTS for ARM64, providing long-term support.
  • Automated Setup: Handles initial configuration for network, user accounts, etc.
  • Disk Management: Automatically partitions and formats attached storage.
  • Auto-Sync: Automatically starts the Ethereum sync process.
  • Easy Updates: Includes an APT repository for straightforward installation and upgrades.
  • Monitoring Included: Comes with pre-configured monitoring dashboards.
  • Security Focused: Includes the UFW firewall configured for basic security.
"},{"location":"introduction/web3-pi-project/#why-raspberry-pi","title":"Why Raspberry Pi?","text":"

Raspberry Pi devices are an excellent platform for running an Ethereum node (Full or Archive) due to several key factors:

  • Affordable: Set up a Full Ethereum node (EL/CL) for under $350.
  • Efficient: As a dedicated device, resources are focused solely on running the node.
  • Low Power Consumption: Typically consumes around 8W.
  • Small Form Factor: Its compact size makes it easy to place anywhere in your home.
  • Ideal for 24/7 Operation: The combination of low cost, low power, and small size makes it perfect for continuous operation.

What now?

  • Learn what exactly is an Ethereum node and how it works (next step).
  • Get started with running your own Ethereum node on a Raspberry Pi.
"},{"location":"management/","title":"Managing Your Node","text":"

Once your Web3 Pi node is up and running, you'll occasionally need to interact with it for monitoring, updates, or configuration changes. There are two primary ways to manage the underlying system:

  1. SSH (Secure Shell): Provides direct command-line access to the Ubuntu operating system. This is the most powerful method, suitable for advanced configuration, troubleshooting, and running specific commands.

  2. Cockpit Web Interface: Offers a user-friendly, web-based dashboard for monitoring system resources (CPU, RAM, disk), viewing logs, and performing basic administrative tasks directly from your browser.

Choose the tool that best suits the task at hand. For routine checks and system overview, Cockpit is often sufficient. For more advanced tasks, SSH is the way to go.

"},{"location":"management/ssh/","title":"Connecting via SSH","text":""},{"location":"management/ssh/#what-is-ssh","title":"What is SSH?","text":"

SSH stands for Secure Shell. It is a network protocol that allows you to securely access the command-line interface (also known as the terminal or shell) of a remote computer over an unsecured network. All communication between your computer and the Raspberry Pi via SSH is encrypted.

"},{"location":"management/ssh/#why-use-ssh-with-web3-pi","title":"Why Use SSH with Web3 Pi?","text":"

While Cockpit provides a web-based graphical interface for basic system administration, SSH gives you direct, powerful command-line access to your Web3 Pi's underlying Ubuntu operating system.

"},{"location":"management/ssh/#how-to-connect-via-ssh","title":"How to Connect via SSH","text":"

Before connecting, you will need:

  1. An SSH Client: This depends on your operating system (see tabs below).
  2. Your Web3 Pi's IP Address or Hostname:
    • How to find your node's IP address or hostname

Now, follow the steps for your specific operating system:

macOS / Linux / Windows TerminalWindows (PuTTY)
  1. Open your SSH Client:
    • On macOS or Linux, open the Terminal application.
    • On Windows 10/11, open PowerShell or Command Prompt. The ssh client is typically built-in.
  2. Initiate the Connection:
    • Type the following command, replacing <your-pi-address> with your Pi's actual IP address or hostname:
      ssh ethereum@<your-pi-address>\n
      Example using hostname: ssh ethereum@eop-1.local Example using IP: ssh ethereum@192.168.1.123
    • Press Enter.
  3. Accept the Host Key (First Connection):
    • The very first time you connect from your computer to the Web3 Pi, your SSH client will show a security alert asking you to verify the host's key fingerprint:
      The authenticity of host 'eop-1.local (192.168.1.123)' can't be established.\nED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.\nAre you sure you want to continue connecting (yes/no/[fingerprint])?\n
    • This is normal. Type yes and press Enter to continue and store the key.
  4. Enter the Password:
    • You will be prompted for the password for the ethereum user:
      ethereum@<your-pi-address>'s password:\n
    • Default Username: ethereum
    • Default Password: ethereum
    • (Security Note: When typing the password, you won't see any characters or dots appear. This is expected. Type the password carefully and press Enter.)
  5. Change the Password (Mandatory First Login):
    • If this is the first time you are logging in with the default ethereum password, the system will force you to change it immediately:
      WARNING: Your password has expired.\nYou must change your password now and login again!\nChanging password for ethereum.\n(Current) UNIX password:\n
    • Enter the current password (ethereum) and press Enter.
    • Then, you will be prompted to enter a New password: Type your chosen strong, unique password and press Enter.
    • You will be asked to Retype new password: Type the same new password again and press Enter.
    • Choose a strong password! This is crucial for security.
  6. Login Successful:
    • After changing the password (on first login) or entering the correct password (on subsequent logins), you will be logged in and see the Web3 Pi command prompt:
      ethereum@<your-pi-address>:~$\n
  1. Open PuTTY:
    • Download and launch the PuTTY application.
  2. Configure the Session:
    • In the \"Session\" category (the default view):
    • Enter your Pi's IP address or hostname in the \"Host Name (or IP address)\" field.
    • Ensure \"Port\" is set to 22.
    • Ensure \"Connection type\" is set to SSH.
    • Click the \"Open\" button.
  3. Accept the Host Key (First Connection):
    • The very first time you connect, a \"PuTTY Security Alert\" window will appear, showing the server's host key fingerprint.
    • This is normal. Verify the key if desired, then click \"Accept\" (or \"Yes\") to trust the key and continue.
  4. Enter the Username and Password:
    • A terminal window will open, prompting login as:. Type ethereum and press Enter.
    • Next, it will prompt ethereum@<your-pi-address>'s password:.
    • Default Password: ethereum
    • (Security Note: When typing the password, you won't see any characters. Type carefully and press Enter.)
  5. Change the Password (Mandatory First Login):
    • If this is the first time you are logging in with the default ethereum password, the system will force you to change it immediately after entering the default password:
      WARNING: Your password has expired.\nYou must change your password now and login again!\nChanging password for ethereum.\n(Current) UNIX password:\n
    • Enter the current password (ethereum) and press Enter.
    • Then, you will be prompted to enter a New password: Type your chosen strong, unique password and press Enter.
    • You will be asked to Retype new password: Type the same new password again and press Enter.
    • Choose a strong password! This is crucial for security.
  6. Login Successful:
    • After changing the password (on first login) or entering the correct password (on subsequent logins), you will be logged in and see the Web3 Pi command prompt:
      ethereum@<your-pi-address>:~$\n
"},{"location":"management/cockpit/dashboard/","title":"Cockpit Dashboard","text":""},{"location":"management/cockpit/dashboard/#what-is-cockpit","title":"What is Cockpit?","text":"

Web3 Pi comes with Cockpit pre-installed. Cockpit is a free, open-source, web-based graphical interface for servers and Linux systems. It allows you to monitor and administer your Raspberry Pi's underlying operating system (Ubuntu) directly from your web browser.

Think of it as a user-friendly dashboard for managing the system itself.

"},{"location":"management/cockpit/dashboard/#what-can-you-do-with-cockpit-on-web3-pi","title":"What Can You Do with Cockpit on Web3 Pi?","text":"

While Grafana dashboards focus specifically on monitoring your Ethereum node clients (EL/CL), Cockpit focuses on the operating system level. Common uses include:

  • Checking CPU, RAM, and network usage trends.
  • Viewing available disk space on your system drive and attached USB drive.
  • Inspecting system logs (journald).
  • Checking the status of system services (be cautious about stopping essential services!).
  • Viewing network interface configurations.
"},{"location":"management/cockpit/dashboard/#how-to-access-cockpit","title":"How to Access Cockpit","text":"
  1. Find the IP address or hostname of your Raspberry Pi.
    • How to find your node's IP address or hostname
  2. Navigate to http://<your-pi-ip-address>:9090 (replace <your-pi-ip-address> with the actual IP address or hostname).
  3. You may see a security warning because Cockpit uses a self-signed SSL certificate by default. It's safe to proceed (usually under an \"Advanced\" or \"Proceed anyway\" option).
  4. Log in using the username ethereum and password you selected. If this is your first time accessing your Web3 Pi, use the default password. You will be required to change the password upon first login.

Note

The default password is ethereum. Change it as soon as possible to prevent unauthorized access.

Now you can explore the Cockpit interface and monitor your Web3 Pi's system health!

"},{"location":"management/cockpit/dashboard/#official-web3-pi-plugins","title":"Official Web3 Pi Plugins","text":"

The Web3 Pi project maintains a number of official plugins for Cockpit.

  • Web3 Pi Updater - Manages Web3 Pi and Ethereum packages.
  • Web3 Pi Link - Allows you to expose your Ethereum node (and more) to the internet.
  • Script Runner - Allows you to run pre-installed scripts on your Web3 Pi.
  • Basic Ethereum Node Monitor - Monitors your Ethereum node (EL/CL) and pushes data to InfluxDB.
  • Basic System Monitor - Exposes a simple HTTP API for monitoring your Web3 Pi.
  • LCD Dashboard - Adds an LCD display to your Web3 Pi.
  • Installation Status - Provides a comprehensive overview of your Web3 Pi installation.
"},{"location":"management/cockpit/web3-pi-link/","title":"Web3 Pi Link","text":"

Web3 Pi Link is a Cockpit plugin enabling secure exposure of Raspberry Pi services to the internet. It functions as a reverse proxy tunnel, eliminating the need for manual port forwarding. This document provides technical details for developers using Web3 Pi Link.

"},{"location":"management/cockpit/web3-pi-link/#core-functionality","title":"Core Functionality","text":"

Web3 Pi Link establishes a persistent, encrypted tunnel between your Raspberry Pi and a Web3 Pi managed server. This tunnel allows external access to services running on your Pi without direct exposure or complex network configuration. The plugin manages the tunnel lifecycle and provides a simplified configuration interface within Cockpit.

"},{"location":"management/cockpit/web3-pi-link/#key-features","title":"Key Features","text":"
  • HTTP/WebSocket Proxy: Supports forwarding HTTP and WebSocket traffic from the Raspberry Pi to a public-facing address. Note: Other protocols like SSH are currently not supported.
  • Automatic HTTPS: All tunnels are automatically secured with HTTPS, even if the service on the Raspberry Pi uses HTTP internally.
  • Automatic Reconnection: The plugin automatically re-establishes the tunnel in case of network interruptions.
  • Cockpit Integration: Configuration is managed entirely through the Cockpit web interface.
  • Subdomain Routing: Each service is assigned a unique subdomain (yourname.web3pi.link) for easy access.
"},{"location":"management/cockpit/web3-pi-link/#technical-architecture","title":"Technical Architecture","text":"

The plugin creates a reverse proxy tunnel. When a request arrives at yourname.web3pi.link, it's routed through the Web3 Pi infrastructure, through the secure tunnel to the specified port on your Raspberry Pi. The tunnel client, running on the Raspberry Pi, establishes an outbound connection to the Web3 Pi servers, so there is no need to open any specific ports on your Pi.

"},{"location":"management/cockpit/web3-pi-link/#installation-and-configuration","title":"Installation and Configuration","text":""},{"location":"management/cockpit/web3-pi-link/#prerequisites","title":"Prerequisites","text":"
  • A Raspberry Pi with Web3 Pi installed and operational.
  • Cockpit web interface access.
  • Network connectivity allowing outbound connections. Note: No specific firewall rules are required as the connection is initiated from the Raspberry Pi.
"},{"location":"management/cockpit/web3-pi-link/#installation-steps","title":"Installation Steps","text":"
  1. Install via Cockpit: Access the Cockpit web interface and navigate to the \"Web3 Pi Updater\" section. Search for \"Web3 Pi Link\" and install the plugin. Make sure to refresh the page after the installation is complete. Newer Web3 Pi versions may have this plugin already installed by default.

  2. Configuration: The Web3 Pi Link plugin will appear in the Cockpit navigation.

    • Local Port: Specify the TCP port on your Raspberry Pi that you want to expose (e.g., 3000, 8545, 9090). This is the port your HTTP service is listening on.
    • Subdomain: Choose a unique subdomain name. This will be used to construct the public-facing URL (yourname.web3pi.link). The subdomain must be globally unique. A valid subdomain must begin with a letter or number and can contain lowercase letters, numbers, and hyphens.

"},{"location":"management/cockpit/web3-pi-link/#troubleshooting-and-debugging","title":"Troubleshooting and Debugging","text":"
  • Verify Service Status: Check that the Web3 Pi Link service is enabled and running. You can do this by navigating to the \"Services\" section in the Cockpit interface and searching for \"web3-pi-link\".
  • Network Connectivity: Ensure the Raspberry Pi has outbound internet access.
  • HTTPS Issues: While Web3 Pi Link automatically provides HTTPS, ensure your application handles redirects correctly if it expects HTTPS.
"},{"location":"management/cockpit/web3-pi-link/#security-considerations","title":"Security Considerations","text":"
  • Secure Your Application: Web3 Pi Link provides a secure tunnel with automatic HTTPS, but it's crucial to secure your application. Implement proper authentication, authorization, and input validation to prevent vulnerabilities.
  • Regular Updates: Make sure to always keep your Web3 Pi software up to date with the latest security patches. You can download the latest updates to each plugin from the \"Web3 Pi Updater\" section in the Cockpit interface.
  • Monitor Logs: Regularly monitor application logs for suspicious activity.
"},{"location":"management/cockpit/web3-pi-link/#support","title":"Support","text":"

For support and further assistance, join the Web3 Pi Discord community.

"},{"location":"management/cockpit/web3-pi-script-runner/","title":"Script Runner","text":"

Script Runner is a Cockpit plugin that provides a user-friendly interface for executing pre-installed scripts on your Web3 Pi. This allows for easy access to common utilities and diagnostic tools directly from the Cockpit web interface.

"},{"location":"management/cockpit/web3-pi-script-runner/#installation","title":"Installation","text":"

The Script Runner plugin is installed via the \"Web3 Pi Updater\" section in the Cockpit interface. Search for \"Script Runner\" and install the plugin. Make sure to refresh the page after the installation is complete. On newer Web3 Pi releases, this plugin might be pre-installed by default.

"},{"location":"management/cockpit/web3-pi-script-runner/#usage","title":"Usage","text":"

The Script Runner plugin is located in the Cockpit navigation menu. Upon opening, you will see a list of available scripts.

"},{"location":"management/cockpit/web3-pi-script-runner/#running-a-script","title":"Running a Script","text":"
  1. Select a Script: Click on the desired script from the list.
  2. View Script (Optional): Click on the \"Read File Contents\" button to inspect the script's contents.
  3. Run Script: Click the \"Run\" button to execute the script.
  4. View Output: The script's output will be displayed in a terminal window within the plugin.
"},{"location":"management/cockpit/web3-pi-script-runner/#troubleshooting","title":"Troubleshooting","text":"
  • Script Execution Errors: Check the script's output for error messages.
  • Missing Scripts: If a script is missing, ensure your Web3 Pi installation is up-to-date. Navigate to the \"Web3 Pi Updater\" section in the Cockpit interface and search for \"Script Runner\".
"},{"location":"management/cockpit/web3-pi-script-runner/#support","title":"Support","text":"

For support and further assistance, join the Web3 Pi Discord community.

"},{"location":"management/cockpit/web3-pi-updater/","title":"Web3 Pi Updater","text":"

The Web3 Pi Updater plugin provides a centralized interface for managing Web3 Pi packages and essential Ethereum node dependencies. This tool allows you to easily list, update, and install software directly from the Cockpit web interface.

"},{"location":"management/cockpit/web3-pi-updater/#usage","title":"Usage","text":"

The Web3 Pi Updater plugin is located in the Cockpit navigation menu.

"},{"location":"management/cockpit/web3-pi-updater/#packages-list","title":"Packages List","text":"

Packages are separated into two categories: Web3 Pi Official Packages and Ethereum Packages. The former are maintained by the Web3 Pi team and provide additional functionality for your device, while the latter are maintained by their respective developers and are essential for running a node.

"},{"location":"management/cockpit/web3-pi-updater/#refresh-packages-list","title":"Refresh Packages List","text":"

Web3 Pi packages are distributed from our apt repository. To ensure that your device is up to date with the latest packages, click the Refresh packages list button. This will update the list of available packages and give you the option to install or update them.

Note

The Refresh packages list button has the same effect as running sudo apt update on the command line.

"},{"location":"management/cockpit/web3-pi-updater/#install-or-update-packages","title":"Install or Update Packages","text":"

To install a package, click the Install button next to the package name.

To update an existing package, click the Update button next to the package name.

Note

After installing a new cockpit plugin, you may need to refresh your browser tab to see the new plugin in the navigation menu.

"},{"location":"management/cockpit/web3-pi-updater/#notes","title":"Notes","text":"
  • Package installations and updates may require administrative privileges.
  • Ensure your Web3 Pi has a stable internet connection during package operations.
  • Regularly check for updates to maintain optimal performance and security.
"},{"location":"management/cockpit/web3-pi-updater/#support","title":"Support","text":"

For support and further assistance, join the Web3 Pi Discord community.

"},{"location":"monitoring/","title":"Monitoring Your Node","text":"

Keeping an eye on your Ethereum node is crucial for ensuring it runs smoothly, stays synced, and uses resources efficiently. Web3 Pi includes several built-in tools to help you monitor different aspects of your setup:

  1. Installation Monitor: A simple web page exposed by your Pi that displays information about the system's setup status. While always available, it's most useful during the initial installation to track the progress of automated steps in real-time.

  2. Grafana Dashboards: Provides detailed, graphical dashboards visualizing the performance and status of your Ethereum clients. Ideal for in-depth performance monitoring once the node is running.

  3. LCD Display: If you've installed the optional LCD hardware, this small screen provides an at-a-glance view of key system information like IP address, hostname, CPU usage, and memory, directly on the device.

  4. HTTP API: Offers a programmatic way to query node status and metrics. Useful for integration with custom scripts or external monitoring systems.

Refer to the specific pages linked above for details on how to access and use each monitoring tool.

"},{"location":"monitoring/grafana/","title":"Basic Ethereum Node Monitor","text":"

The Basic Ethereum Node Monitor plugin is responsible for monitoring the health and status of your Ethereum node. That data is then pushed to the InfluxDB database, which can be visualized in Grafana.

"},{"location":"monitoring/grafana/#installation","title":"Installation","text":"

To install the Basic Ethereum Node Monitor plugin, navigate to the \"Web3 Pi Updater\" section in the Cockpit interface. Search for \"Basic Ethereum Node Monitor\" and install the plugin. On newer Web3 Pi releases, this plugin might be pre-installed by default.

"},{"location":"monitoring/grafana/#usage","title":"Usage","text":"

Once installed, the Basic Ethereum Node Monitor plugin will start automatically. Open Grafana in your web browser (port 3000 by default) to view the collected data. The default username is 'admin', and the password is 'admin'. It is highly recommended to change the password after the first login.

"},{"location":"monitoring/grafana/#support","title":"Support","text":"

For support and further assistance, join the Web3 Pi Discord community.

"},{"location":"monitoring/installation-monitor/","title":"Installation Status Page","text":"

The Installation Status plugin provides a comprehensive overview of the Web3 Pi installation process. It displays the status of each installation stage as well as any errors or warnings encountered during the process.

"},{"location":"monitoring/installation-monitor/#installation","title":"Installation","text":"

To install the Installation Status plugin, navigate to the \"Web3 Pi Updater\" section in the Cockpit interface. Search for \"Installation Status\" and install the plugin. On newer Web3 Pi releases, this plugin might be pre-installed by default.

"},{"location":"monitoring/installation-monitor/#usage","title":"Usage","text":"

Once installed, the Installation Status plugin will be accessible on port 80 of your Raspberry Pi. Simply open the URL in your web browser to view the status page.

"},{"location":"monitoring/installation-monitor/#disabling-the-plugin","title":"Disabling the Plugin","text":"

If you no longer require the Installation Status plugin, you can disable it by stopping the w3p_installation-status service. To do this, navigate to the \"Services\" section in the Cockpit interface and stop the service.

"},{"location":"monitoring/installation-monitor/#source-code","title":"Source Code","text":"

The source code for the Installation Status plugin can be found on GitHub.

"},{"location":"monitoring/installation-monitor/#support","title":"Support","text":"

For support and further assistance, join the Web3 Pi Discord community.

"},{"location":"monitoring/lcd/","title":"LCD Dashboard","text":"

To add an LCD dashboard to your Raspberry Pi, you will need:

  • A dashboard cover for mounting;

  • A suitable LCD display;

  • Software to run the LCD.

"},{"location":"monitoring/lcd/#dashboard-cover","title":"Dashboard Cover","text":"

The Web3 Pi LCD Dashboard project offers a suitable cover available for 3D printing.

This is designed for installation of a colorful LCD in the Argon Neo 5 enclosure. We have designed our own 3D model of the enclosure cover with a space for the display. The assembly is simple, using snap-fits, with no tools required. The models are open-source, so anyone can print them on a 3D printer. The source code is also open-source, allowing users to add new functionalities, customize it to their needs, or add support for new displays.

For more information, see Web 3 Pi Dashboard documentation.

"},{"location":"monitoring/lcd/#lcd-display","title":"LCD Display","text":"

The LCD display should be 1.69\" with ST7789V2 Driver. Suitable models are:

  • Waveshare 24382 -\u00a0See product page

  • Seeed Studio 104990802 -\u00a0See product page

"},{"location":"monitoring/lcd/#software","title":"Software","text":"

The software is included in the Web3 Pi image, and can also be downloaded separately from the Web 3 Pi Dashboard Project.

"},{"location":"monitoring/system-monitor/","title":"Basic System Monitor (monitoring via HTTP API)","text":"

The Basic System Monitor plugin is responsible for monitoring the health and status of your Web3 Pi. It exposes a simple HTTP API that can be queried to retrieve information about your device.

"},{"location":"monitoring/system-monitor/#installation","title":"Installation","text":"

To install the Basic System Monitor plugin, navigate to the \"Web3 Pi Updater\" section in the Cockpit interface. Search for \"Basic System Monitor\" and install the plugin. On newer Web3 Pi releases, this plugin might be pre-installed by default.

"},{"location":"monitoring/system-monitor/#usage","title":"Usage","text":"

Once installed, the Basic System Monitor plugin will start automatically. You can query the status of your Web3 Pi by calling the HTTP API endpoint on port 7197 (by default).

The output should look similar to this:

{\n  \"host_name\": \"eop-1\",\n  \"num_cores\": 4,\n  \"cpu_percent\": 30,\n  \"mem_total\": 8323276800,\n  \"mem_used\": 6859583488,\n  \"mem_free\": 386887680,\n  \"mem_percent\": 85.4,\n  \"swap_total\": 17179865088,\n  \"swap_used\": 7224090624,\n  \"swap_free\": 9955774464,\n  \"swap_percent\": 42,\n  \"disk_used\": 1359481831424,\n  \"cpu_temp\": 58.95,\n  \"net_upload\": 510982.969251317,\n  \"net_download\": 702530.988724869\n}\n
"},{"location":"monitoring/system-monitor/#support","title":"Support","text":"

For support and further assistance, join the Web3 Pi Discord community.

"},{"location":"setup/next-steps/","title":"Post-Installation: Next Steps","text":"

Congratulations! You've successfully assembled your hardware, flashed the Web3 Pi image, and completed the initial automated setup process. Your Ethereum node(s) should now be running and synchronizing with the network.

Here\u2019s what to do next to manage, monitor, and utilize your new node:

"},{"location":"setup/next-steps/#1-verify-synchronization-status","title":"1. Verify Synchronization Status","text":"

The most crucial step after installation is blockchain synchronization. This process can take many hours, sometimes even days, depending on your hardware, network speed, and the chosen Ethereum network (Mainnet takes the longest).

  • Check Grafana: The primary way to monitor sync progress is through the Grafana dashboard.
"},{"location":"setup/next-steps/#2-access-your-nodes","title":"2. Access Your Node(s)","text":"

You have several ways to interact with the underlying system(s):

  • SSH (Command Line): For direct terminal access, advanced configuration, and troubleshooting.

    • Guide: Connecting via SSH
    • Default Credentials: Username ethereum, Password ethereum
    • Action Required: You must change this password on your first SSH login.
  • Cockpit (Web Interface): For a graphical overview of system resources, logs, services, and basic management tasks.

    • Guide: Cockpit Dashboard
    • Access: http://<your-pi-hostname-or-ip>:9090
    • Login: Use the ethereum username and the password you set via SSH (or the default ethereum if you haven't logged in via SSH yet).

Dual-Mode Access

If you set up a dual-device node, remember that you have two separate systems. You need to use the specific hostname for each device when connecting via SSH or Cockpit (e.g., ssh ethereum@eop-1-exec.local and ssh ethereum@eop-1-cons.local).

"},{"location":"setup/next-steps/#3-explore-monitoring-tools","title":"3. Explore Monitoring Tools","text":"

Beyond checking sync status, familiarize yourself with the monitoring tools:

  • Grafana Dashboards: Detailed performance graphs for EL/CL clients, system resources.
  • Cockpit Dashboard: System-level monitoring (CPU, RAM, Disk, Network).
  • LCD Display: (Optional Hardware) At-a-glance status directly on the device.
  • HTTP API: Programmatic access to system metrics.
"},{"location":"setup/next-steps/#4-learn-about-management-tools","title":"4. Learn About Management Tools","text":"

Web3 Pi includes tools within Cockpit to help manage your node:

  • Web3 Pi Updater: Keep your Ethereum clients and Web3 Pi components up-to-date. Check this periodically.
  • Web3 Pi Link: Securely expose services (like your node's RPC endpoint) to the internet without complex firewall rules.
  • Script Runner: Execute useful pre-installed utility and diagnostic scripts.
"},{"location":"setup/next-steps/#5-utilize-your-node","title":"5. Utilize Your Node","text":"

Now that your node is running, you can start using it:

  • Connect Your Wallet: Point wallets like MetaMask to your own node's RPC endpoint (http://<your-pi-hostname-or-ip>:8545) for enhanced privacy and reliability. Use Web3 Pi Link for access outside your home network.
  • Transaction Firewall: Add an extra layer of security by manually approving transactions initiated from your wallet.
  • Development: Use the local RPC endpoint for developing and testing decentralized applications (dApps).
  • Staking (Advanced/Unsupported): While Web3 Pi provides the foundation, configuring for staking is complex and high-risk. Read the Staking Considerations carefully before proceeding independently.
"},{"location":"setup/next-steps/#6-bookmark-support-resources","title":"6. Bookmark Support Resources","text":"

Keep these pages handy for future reference:

  • Cheatsheet: Quick reference for commands, ports, and file locations.
  • Troubleshooting: Guidance for common issues (needs expansion!).
  • Contact: How to reach the Web3 Pi community (Discord, GitHub).
"},{"location":"setup/next-steps/#7-consider-advanced-setup-optional","title":"7. Consider Advanced Setup (Optional)","text":"

Explore options to enhance reliability and performance:

  • Backup Power (UPS): Protect against power outages and data corruption.
  • Firewall Configuration (UFW): Understand and customize network security rules.
  • Overclocking: Potentially increase performance (requires caution and cooling).

You've taken a significant step towards supporting the Ethereum network and gaining more control over your web3 experience. Keep exploring the documentation, join the community if you have questions, and enjoy running your own node!

"},{"location":"setup/prerequisites/","title":"Web3 Pi: Prerequisites","text":""},{"location":"setup/prerequisites/#internet-requirements","title":"Internet Requirements","text":"
  • Speed: A download speed of at least 160 Mb/s (20 MB/s) is required, with 240+ Mb/s (30+ MB/s) recommended for optimal synchronization performance (allowing sync completion in under 24 hours on a Raspberry Pi 5). Slower connections will still work but will result in significantly longer synchronization times. Upload speed requirements are much lower.
  • Stability: A stable, low-latency (ping) connection is important for reliable node operation.
  • Data: Initial synchronization requires downloading approximately 1.4 TB (May 2025) of data from the Ethereum network. An unmetered internet connection is essential to avoid unexpected charges. Do not use connections with data caps.
"},{"location":"setup/prerequisites/#lan-requirements","title":"Lan Requirements","text":"

You'll need a gigabit LAN, including a gigabit network switch and Ethernet cables rated Cat5e or higher. The network must support automatic DHCP configuration and have internet access.

"},{"location":"setup/prerequisites/#wifi-connection","title":"Wifi Connection","text":"

The default and recommended method for connecting the Raspberry Pi in the Web3 Pi project is via a wired Ethernet connection with automatic DHCP configuration.

However, you can also connect Raspberry Pi 4/5 to the internet using the built-in WiFi module. You'd need to provide the SSID and password for your WiFi network during setup. This is documented in the setup guide.

Although using WiFi is possible, we strongly recommend using a wired connection. Over time, WiFi may lead to issues with connection stability and bandwidth performance.

Note: If you're using WiFi, do not connect the Ethernet cable.

"},{"location":"setup/prerequisites/#uninterruptible-power-supply-ups","title":"Uninterruptible Power Supply (UPS)","text":"

We strongly recommend connecting your Web3 Pi device and essential networking equipment (such as a router/modem/switch) to an uninterruptible power supply (UPS). A UPS protects against voltage fluctuations and short power outages, which are common causes of node downtime, potential disk data corruption, and may lead to penalties for validator inactivity (in Solo Staking setups).

See UPS Recommendations

"},{"location":"setup/prerequisites/#hardware-requirements","title":"Hardware Requirements","text":"

The Easiest Start: The Web3 Pi WelcomeBox For those who prefer a guaranteed-compatible, all-in-one solution, the Web3 Pi WelcomeBox is the recommended starting point. This kit contains all the hardware you need to run a single node.

If you would prefer to use your own existing Raspberry Pi, please read the Hardware Checklist to make sure you have everything you need. If you need to purchase anything further, the Hardware Recommendations document will help you choose suitable additions to your existing setup.

Optionally, you can also purchase an LCD screen for the Pi. This gives you a quick way of checking that your node is functional. The LCD screen is included in the WelcomeBox.

"},{"location":"setup/supported-configurations/","title":"Supported Configurations","text":"

Web3 Pi provides a custom image file that contains all the software needed to run a full Ethereum node on Raspberry Pi single-board computers.

  • Execution client
  • Consensus client
  • Monitoring tools
"},{"location":"setup/supported-configurations/#available-configurations","title":"Available Configurations","text":"

The Web3 Pi node can be deployed in either a single-device or on dual-devices.

"},{"location":"setup/supported-configurations/#single-device-mode","title":"Single-Device Mode","text":"

Single device mode is the simplest configuration. For optimal performance, it is recommended to use a Raspberry Pi 5.

Single Mode - Hardware Checklist

"},{"location":"setup/supported-configurations/#dual-devices-mode","title":"Dual-Devices Mode","text":"

Dual-devices mode requires additional configuration, but a single device may start running low on resources for a Raspberry Pi 4. In dual-devices mode, one Raspberry Pi acts as the consensus client and the other as the execution client. The solution maintains performance and stability by splitting tasks across two devices.

The following combinations are supported:

  • Two Raspberry Pi 5
  • Two Raspberry Pi 4
  • One Raspberry Pi 5 and one Raspberry Pi 4
  • Any combination including Compute Module 4 (CM4)

Dual-Devices - Hardware Checklist

"},{"location":"setup/dual-mode/hardware-assembly/","title":"Web3 Pi: Hardware Assembly (Dual-Device Mode)","text":"

Assembling the hardware for a dual-device Web3 Pi node involves building two separate Raspberry Pi systems: one designated as the Execution Layer (EL) Node and the other as the Consensus Layer (CL) Node.

While the assembly steps for each individual device are identical to the Single-Device Hardware Assembly Guide, managing two builds requires careful attention, especially regarding component placement (SSDs) and labeling.

Follow Instructions Carefully

Read through these instructions and the single-device guide before starting. Ensure you have all components for both devices laid out and organized.

"},{"location":"setup/dual-mode/hardware-assembly/#before-you-start","title":"Before You Start","text":"
  1. Gather Components: Ensure you have all the necessary hardware components for two complete Raspberry Pi setups as outlined in the Dual-Device Hardware Checklist and Recommendations. This includes two Pis, two power supplies, two enclosures/cooling solutions, two microSD cards, and two SSDs (one 2TB+, one 256GB+).
  2. Prepare SD Cards: You should have already flashed two separate microSD cards using the Web3 Pi Imager in Dual-Device mode \u2013 one configured for the EL node and one for the CL node. Keep them clearly identifiable.
  3. Reference Guide: Keep the Single-Device Hardware Assembly Guide open in another tab or window. You will follow those detailed steps twice, once for each device.
"},{"location":"setup/dual-mode/hardware-assembly/#assembly-steps","title":"Assembly Steps","text":""},{"location":"setup/dual-mode/hardware-assembly/#step-1-decide-and-label-your-devices-crucial","title":"Step 1: Decide and Label Your Devices (Crucial!)","text":"

Before assembling anything, decide which Raspberry Pi will be your EL Node and which will be your CL Node.

Label Everything Clearly

Use sticky notes, labels, or markers to clearly label each Raspberry Pi board, enclosure/case, and the corresponding EL and CL microSD cards. This labeling will prevent you from inserting the wrong SD card or SSD into the wrong device.

"},{"location":"setup/dual-mode/hardware-assembly/#step-2-assemble-the-execution-layer-el-node","title":"Step 2: Assemble the Execution Layer (EL) Node","text":"
  1. Take the Raspberry Pi and enclosure you designated as the EL Node.
  2. Follow the detailed steps outlined in the Single-Device Hardware Assembly Guide precisely.
  3. Critical Check - SSD: When you reach the step to install the storage drive, ensure you install the larger (2TB or greater) SSD into this EL Node device.
  4. Critical Check - SD Card: When you reach the step to insert the microSD card, ensure you insert the card specifically flashed and labeled for the EL Node.
  5. Complete the assembly for the EL Node as per the single-device guide.
"},{"location":"setup/dual-mode/hardware-assembly/#step-3-assemble-the-consensus-layer-cl-node","title":"Step 3: Assemble the Consensus Layer (CL) Node","text":"
  1. Take the Raspberry Pi and enclosure you designated as the CL Node.
  2. Follow the detailed steps outlined in the Single-Device Hardware Assembly Guide precisely, just as you did for the EL node.
  3. Critical Check - SSD: When you reach the step to install the storage drive, ensure you install the smaller (256GB or greater) SSD into this CL Node device.
  4. Critical Check - SD Card: When you reach the step to insert the microSD card, ensure you insert the card specifically flashed and labeled for the CL Node.
  5. Complete the assembly for the CL Node as per the single-device guide.
"},{"location":"setup/dual-mode/hardware-assembly/#step-4-final-check","title":"Step 4: Final Check","text":"

You should now have two fully assembled Raspberry Pi devices:

  • One labeled \"EL Node\" containing the larger SSD and the EL microSD card.
  • One labeled \"CL Node\" containing the smaller SSD and the CL microSD card.

Double-check your labels and component placement one last time.

"},{"location":"setup/dual-mode/hardware-assembly/#hardware-connections","title":"Hardware connections","text":"

Once you have both devices assembled, connect them as follows:

Warning

For the mDNS mechanism to work, both devices must be connected to the same local network. It's recommended to use one network switch for both devices.

Refer to the following image to verify your setup:

Ensure all cables and storage devices are securely connected before proceeding.

"},{"location":"setup/dual-mode/hardware-assembly/#next-steps","title":"Next Steps","text":"

With both devices assembled correctly, you are ready to connect them to your network and begin the software installation and synchronization process.

"},{"location":"setup/dual-mode/hardware-checklist/","title":"Required Hardware","text":""},{"location":"setup/dual-mode/hardware-checklist/#dual-device-node-requirements","title":"Dual Device Node Requirements","text":"

Suggested configurations:

  • 2 X Raspberry Pi 5
  • 2 X Raspberry Pi 4
  • 1 X Raspberry Pi 5 + 1 X Raspberry Pi 4

or any other combination, including CM4.

The default setup requires the following hardware components:

  • 2 x Raspberry Pi (8GB) starter kits
  • 2 x SSD (one for each device)
  • 1 x SD Card reader/writer
  • 2 x Fast microSD Card

Optionally, you can add an LCD display to each Raspberry Pi for monitoring purposes.

A device with an execution client needs 2TB+ fast storage. A device with a consensus client needs 256GB+ fast storage.

"},{"location":"setup/dual-mode/hardware-checklist/#raspberry-pi","title":"Raspberry Pi","text":"

You can use:

  • Raspberry Pi 5\u00a0with\u00a0Active cooling
  • Raspberry Pi 4\u00a0with\u00a0Active cooling
  • Raspberry Pi CM4\u00a0with\u00a0Active cooling\u00a0and motherboard

Note

8GB\u00a0RAM is required.

"},{"location":"setup/dual-mode/hardware-recommendations/","title":"Web3 Pi: Recommended Hardware","text":"

In a dual-device setup, the workload of running an Ethereum node is split between two Raspberry Pi devices. One device runs the Execution Layer (EL) client (like Geth), which requires significant storage, while the other runs the Consensus Layer (CL) client (like Nimbus or Lighthouse), which has much lower storage needs but still benefits from fast access.

This setup requires two complete Raspberry Pi systems, each with its own Pi, power supply, storage, SD card, and cooling/enclosure.

Supported combinations include:

  • Two Raspberry Pi 5
  • Two Raspberry Pi 4
  • One Raspberry Pi 5 and one Raspberry Pi 4
  • Any combination including Compute Module 4/5
"},{"location":"setup/dual-mode/hardware-recommendations/#raspberry-pi-models","title":"Raspberry Pi Models","text":"

You will need two Raspberry Pi devices. The following models are supported:

  • Raspberry Pi 5: Recommended for best performance, especially for the EL client.
  • Raspberry Pi 4 Model B: A viable option, particularly suitable for the CL client or if cost is a major factor. Can also run the EL client, though sync times may be longer than Pi 5.
  • Raspberry Pi Compute Module 4 (CM4): Requires a compatible carrier board with necessary ports (Ethernet, USB, potentially PCIe/M.2) and a cooling solution.

RAM Requirement

Regardless of the model chosen, both Raspberry Pi devices must have at least 8GB of RAM.

Refer to the hardware checklists for specific model links:

  • Single Device Checklist (useful for Pi links)
  • Dual Device Checklist
"},{"location":"setup/dual-mode/hardware-recommendations/#ssd-drive-requirements","title":"SSD Drive Requirements","text":"
  • Execution Client (EL) Device: Needs a 2 TB or larger fast SSD (NVMe or USB 3.0) to store the growing Ethereum blockchain state data.
  • Consensus Client (CL) Device: Needs a 500 GB or larger fast SSD. While 500GB is sufficient currently. NVMe or a reliable USB 3.0 SSD is recommended.
"},{"location":"setup/dual-mode/hardware-recommendations/#connection-options","title":"Connection Options","text":"
  • Raspberry Pi 5:

    • External USB 3.0 SSD drive.
    • M.2 NVMe drive with an NVMe HAT (connects via PCIe).
    • M.2 NVMe drive with a USB 3.0 to M.2 adapter.
  • Raspberry Pi 4 / CM4 (with appropriate carrier board):

    • External USB 3.0 SSD drive.
    • M.2 NVMe drive with a USB 3.0 to M.2 adapter.
    • (Some CM4 carrier boards may offer direct M.2 slots).

Use USB 3.0 Ports

If using USB-connected storage, always use the blue USB 3.0 ports on the Raspberry Pi for maximum speed.

"},{"location":"setup/dual-mode/hardware-recommendations/#recommendations-for-execution-client-ssd-2tb","title":"Recommendations for Execution Client SSD (2TB+)","text":"

This device requires a large, fast drive. The recommendations are the same as for the single-device mode.

"},{"location":"setup/dual-mode/hardware-recommendations/#usb-drive-el","title":"USB Drive (EL)","text":"Brand/Model Comment Link Samsung T7 2TB USB 3.2 Recommended for RPi4 users. Compatible with Raspberry Pi 4 and 5 More Info

Tip

Some external disks consume more power than Raspberry Pi can deliver via USB. For Raspberry Pi 5, the max power output of the USB ports is 600mA if you're using a 3A supply, and 1600mA if you're using a 5A supply. You can edit /boot/firmware/config.txt and add usb_max_current_enable=1 to disable the current limit. Please read the documentation: Link

"},{"location":"setup/dual-mode/hardware-recommendations/#nvme-drive-el","title":"NVMe Drive (EL)","text":"

These drives need adapters (HAT or USB). See below.

Brand/Model Controller Comment Link Lexar NM7902TB m.2 2280 MaxiotekMAP1602A single side design 4TB available Product pageMore Info Goodram PX7002TB m.2 2280 MaxiotekMAP1602A single side design 4TB available Product page Micron 24002TB m.2 2230 Silicon MotionSM2269XT single side design ~4 W (Max)small 2230 form factorlow power consumptionlow heat Product pageMore Info Samsung 980 Pro2TB m.2 2280 SamsungElpis (S4LV003) single side design 7.2 W (Max) Product pageMore Info

Note

Double-sided NVMe m.2 memory modules (with memory chips on both sides of the PCB) may not be fully compatible with every enclosure due to physical dimensions, specifically the height of the m.2 slot in the adapter/enclosure.

"},{"location":"setup/dual-mode/hardware-recommendations/#nvme-hat-pi-5-el","title":"NVMe Hat (Pi 5) (EL)","text":"Brand Link Pimoroni NVMe Base for Raspberry Pi 5 Raspberry Pi m.2 Hat https://www.raspberrypi.com/products/m2-hat-plus Pineboards HatDrive: Bottom https://pineberrypi.com/products/hatdrive-bottom-2230-2242-2280-for-rpi5 Pineboards HatDrive: Top https://pineboards.io/products/hat-top-2230-2240-for-rpi5 Waveshare 26583 https://www.waveshare.com/pcie-to-m.2-hat-plus.htm"},{"location":"setup/dual-mode/hardware-recommendations/#recommendations-for-consensus-client-ssd-500gb","title":"Recommendations for Consensus Client SSD (500GB+)","text":"

This device requires a smaller, but still fast and reliable drive.

"},{"location":"setup/dual-mode/hardware-recommendations/#usb-drive-cl","title":"USB Drive (CL)","text":"Brand/Model Comment Link Samsung T7 500GB USB 3.2 Recommended for RPi4 users. Compatible with Raspberry Pi 4 and 5 More Info"},{"location":"setup/dual-mode/hardware-recommendations/#nvme-drive-cl","title":"NVMe Drive (CL)","text":"

These drives need adapters (HAT or USB). Choose a reputable brand. 500GB or more are suitable sizes.

Brand/Model Controller Comment Link Lexar NM7901TB m.2 2280 MaxiotekMAP1602A single side design 4TB available Product pageMore Info Goodram PX7001TB m.2 2280 MaxiotekMAP1602A single side design 4TB available Product page Micron 24001TB m.2 2230 Silicon MotionSM2269XT single side design ~4 W (Max)small 2230 form factorlow power consumptionlow heat Product pageMore Info Samsung 980 Pro1TB m.2 2280 SamsungElpis (S4LV003) single side design 7.2 W (Max) Product pageMore Info"},{"location":"setup/dual-mode/hardware-recommendations/#nvme-hat-pi-5-cl","title":"NVMe Hat (Pi 5) (CL)","text":"

Same HATs as recommended for the EL client can be used here with a smaller NVMe drive. See NVMe Hat (Pi 5) (EL) section above.

"},{"location":"setup/dual-mode/hardware-recommendations/#usb-to-nvme-adapters-cl","title":"USB to NVMe adapters (CL)","text":"

Same adapters as recommended for the EL client can be used here with a smaller NVMe drive. See USB to NVMe adapters (EL) section above.

"},{"location":"setup/dual-mode/hardware-recommendations/#sd-card-reader-and-writer","title":"SD Card Reader and Writer","text":"

You only need one SD card reader/writer to flash the operating system onto both microSD cards.

"},{"location":"setup/dual-mode/hardware-recommendations/#microsd-cards","title":"MicroSD Cards","text":"

You will need two microSD cards, one for each Raspberry Pi.

  • Requirement: 32GB minimum capacity. Faster cards can improve boot times.
  • Recommendations: (Refer to the list in the Single Mode Recommendations) - purchase two cards.
"},{"location":"setup/dual-mode/hardware-recommendations/#power-supplies","title":"Power Supplies","text":"

You will need two power supplies, one appropriate for each Raspberry Pi model you are using.

  • Raspberry Pi 5: Official Raspberry Pi 27W USB-C Power Supply (5.1V/5A) is strongly recommended.
  • Raspberry Pi 4: Official Raspberry Pi 15.3W USB-C Power Supply (5.1V/3A) is strongly recommended.
  • CM4: Depends on the carrier board requirements. Check the carrier board documentation.

Using the official power supplies ensures stability, especially when powering connected peripherals like SSDs.

"},{"location":"setup/dual-mode/hardware-recommendations/#enclosures-and-active-cooling","title":"Enclosures and Active Cooling","text":"

Active cooling is mandatory for both Raspberry Pi devices in a dual-node setup to prevent thermal throttling and ensure stability. You will need two enclosures with active cooling.

  • For Raspberry Pi 5: Choose one of the recommended cases with integrated fan/heatsink. (Refer to Single Mode Recommendations).
  • For Raspberry Pi 4: Choose one of the recommended cases with integrated fan/heatsink. (Refer to Single Mode Recommendations).
  • For CM4: Ensure your chosen carrier board has a suitable active cooling solution attached or available.
"},{"location":"setup/dual-mode/hardware-recommendations/#optional-lcd-display","title":"Optional: LCD Display","text":"

You can add an optional LCD display to either or both Raspberry Pi devices for at-a-glance monitoring.

  • Refer to the LCD Monitoring Guide for hardware and setup details.
"},{"location":"setup/dual-mode/hardware-recommendations/#networking","title":"Networking","text":"
  • You will need two Ethernet cables (Cat5e or better).
  • Ensure your router or network switch has at least two available Gigabit Ethernet ports.
  • Refer to the main Prerequisites Guide for internet speed requirements.

Choosing the right hardware ensures a stable and performant dual-device node setup. Remember to clearly label your devices during assembly to avoid confusion!

"},{"location":"setup/dual-mode/installation-monitoring/","title":"Web3 Pi: Installation Monitoring Guide - Dual Device Node","text":"

Once you have flashed the boot cards and assembled the hardware, you're ready to install and activate Web3 Pi in dual device mode.

"},{"location":"setup/dual-mode/installation-monitoring/#pre-installation-checklist","title":"Pre-installation Checklist","text":"
  • Ensure your Raspberry Pis have active cooling.

  • The Execution Client should have at least 2 TB SSD storage. The Consensus Client needs at least 256 GB.

  • You should have flashed two boot cards. Make sure the boot card for the Execution Client is loaded into the machine with the larger SSD drive, and the Consensus Client boot card is inserted into the smaller one.

  • Make sure the devices are protected against power surges with a UPS

  • Connect the Raspberry Pis using Ethernet cables to your network, and ensure the network is connected to the Internet.

"},{"location":"setup/dual-mode/installation-monitoring/#installation","title":"Installation","text":"

Installation will begin automatically as soon as you connect the power cable.

You can monitor the installation on both devices by entering the corresponding addresses in your browser, based on the previously defined hostnames. The default values are:

  • http://eop-1-exec.local
  • http://eop-1-cons.local
  • The monitoring should start working approximately three minutes after the device is first switched on.

Replace eop-1-exec and eop-1-cons with your hostnames that you entered during the microSD card burning process.

After approximately 3 minutes from powering on the device for the first time, you should see a similar page - for both devices.

Note

Leave the device for about 8-15 minutes to complete the installation process. Do not disconnect power during this time. The time may vary depending on the bandwidth of the internet connection.

"},{"location":"setup/dual-mode/installation-monitoring/#grafana-dashboard-access","title":"Grafana Dashboard Access","text":"

Next, click the link to the Grafana dashboard. If everything has gone smoothly, you should see the login panel. The default username is 'admin', and the password is 'admin'. You will be required to change the password upon first login.

In the Grafana Panel, click on the dashboard named 'Ethereum Nodes Monitor'.

Note

Pay attention to the status of the consensus and execution clients. Initially, both will be 'inactive'

In the next step, the execution client will change to 'waiting'.

Then both will transition to the 'syncing' state.

Grafana URL: http://eop-1-exec.local:3000

"},{"location":"setup/dual-mode/installation-monitoring/#blockchain-synchronization","title":"Blockchain Synchronization","text":"

At this point, the blockchain synchronization process will begin and will take approximately 19 hours.

The CPU load will increase until it reaches its maximum possible value.

Disk usage will grow to around 1.2TB.

"},{"location":"setup/dual-mode/installation-monitoring/#synchronization-complete","title":"Synchronization Complete","text":"

Full synchronization will be achieved when the status of both the execution and consensus clients turns green (\"synced\").

"},{"location":"setup/dual-mode/installation-monitoring/#ssh-access","title":"SSH Access","text":"

After the installation completes successfully, you should have SSH access to the Web3 Pi node.

Username: ethereum Password: ethereum

You can use [your-hostname].local as the SSH address or the IP address if you know it.

If the 'ethereum' user does not exist, it means the installation failed unexpectedly (in such case, please contact support).

By default, the ethereum user is required to change the password during the first login.

"},{"location":"setup/dual-mode/software-setup/","title":"Web3 Pi Image Installation Guide","text":"

This guide will walk you through the process of writing the Web3 Pi image to a microSD card using the Web3 Pi Imager tool.

"},{"location":"setup/dual-mode/software-setup/#getting-started","title":"Getting Started","text":"

Follow the instructions below to write images on the microSD card:

  1. Download and install Web3 Pi Imager
  2. Insert the microSD card into the card reader and connect the reader to your PC
  3. Open the Web3 Pi Imager on your PC
  4. Choose the Single Mode Device

"},{"location":"setup/dual-mode/software-setup/#configuration-options","title":"Configuration Options","text":"

For Dual Mode device, the following settings can be configured. In this mode, we set separate hostnames, clients, and ports for the execution layer device and the consensus layer device:

  • Image version: Default is the latest version of Web3 Pi Image
  • Default Ethereum Network: Choice between Mainnet, Sepolia, or Holesky
  • Execution device hostname: Use a unique hostname for the execution layer device. Default is eop-1-exec.local
  • Consensus device hostname: Use a unique hostname for the consensus layer device. Default is eop-1-cons.local
  • Execution client: Choose between Geth or Disabled for the execution layer device
  • Execution Port for Geth: 30303 for the execution layer device
  • Consensus Client: Choose between Nimbus or Lighthouse for the consensus layer device
  • Consensus Client Port: 9000 for the consensus layer device
  • Enable Grafana Monitoring: Turn on the advanced monitoring system by Grafana
  • Format storage: Option to format external storage during installation

"},{"location":"setup/dual-mode/software-setup/#advanced-options","title":"Advanced Options","text":"

If you click the ADVANCED button, you can configure these additional options. These advanced settings will be applied to both the execution layer device and the consensus layer device:

  • Execution endpoint address: Optional custom endpoint for execution client
  • Locale settings: Including:
    • Time zone selection
    • Keyboard layout
  • Wireless LAN configuration: Including:
    • SSID
    • Password
    • Wireless LAN country selection

Note

Wired Ethernet connection is recommended over Wi-Fi to ensure better synchronization.

"},{"location":"setup/dual-mode/software-setup/#drive-selection","title":"Drive Selection","text":"

After setting up the configuration and clicking NEXT, a dialog box will appear allowing you to select the drive where the image with settings will be saved.

  • Drive selection: The list shows available storage devices
  • Display options:
    • By default, only drives smaller than 300GB are displayed
    • Checking the \"Show large external storage device\" option will display devices larger than 300GB

Note

Make sure you select the correct drive to avoid data loss on other devices

"},{"location":"setup/dual-mode/software-setup/#accepting-terms-of-use","title":"Accepting Terms of Use","text":"

After selecting your target drive, a warning dialog will appear informing you that all data on the selected device will be erased.

  • Confirms that all existing data on the selected drive will be permanently deleted
  • You must accept the terms to proceed (the \"Yes\" button remains disabled until accepted). The full Terms of Use can be found at www.web3pi.io/terms.

Note

This is your final confirmation before the write process begins - ensure you have selected the correct device

"},{"location":"setup/dual-mode/software-setup/#writing-process-execution-device","title":"Writing Process - Execution Device","text":"

After confirmation, the writing process for execution device begins.

  • Writing process: The progress bar displays the current writing status
  • Verification phase: After the writing completes, the verification process automatically starts. This step ensures data integrity and proper image installation.

Note

Do not disconnect or remove the storage device until both the writing and verification processes are complete

"},{"location":"setup/dual-mode/software-setup/#replacing-cards","title":"Replacing cards","text":"

At this point, you need to switch the SD cards. Remove the card that has been written for the execution layer device and insert a new card for the consensus layer device. Select the appropriate storage destination for the consensus device image and confirm by clicking \"Yes\" to proceed with the writing process.

"},{"location":"setup/dual-mode/software-setup/#writing-process-consensus-device","title":"Writing Process - Consensus Device","text":"

Now the process of writing the card for the consensus layer device will begin.

  • Writing process: The progress bar displays the current writing status
  • Verification phase: After the writing completes, the verification process automatically starts. This step ensures data integrity and proper image installation.

Note

Do not disconnect or remove the storage device until both the writing and verification processes are complete

"},{"location":"setup/dual-mode/software-setup/#final-steps","title":"Final Steps","text":"

Now that you have both cards prepared, you will see a message about the possibility of installing the prepared cards in the appropriate devices. At this stage, you can click NEXT and complete the process. In the following steps, you will be able to assemble both devices and install the prepared cards in the corresponding slots.

"},{"location":"setup/single-mode/hardware-assembly/","title":"Web3 Pi: Hardware Assembly","text":"

This assembly guide is primarily aimed at assembling the components included in the Welcome Box, but will also be useful for anyone assembling their own hardware.

Info

To avoid errors during the first setup, please follow the instructions precisely.

Please also see the video instructions for more information.

If you have a Welcome Box, unpack the contents and check them against the components listed here.

"},{"location":"setup/single-mode/hardware-assembly/#components-overview","title":"Components Overview","text":"
  1. Aluminium Top Cover with Screw Points
  2. Aluminium Case
  3. Cooling Fins and Exhaust Vent
  4. Fan Port Access
  5. POE HAT Connection
  6. GPIO Access
  7. MIPI Ports Access
  8. 30mm PWM Blower-type Fan
  9. UART Connector
  10. RTC Battery Connector Access
  11. PCIe Port Access
  12. PCIE Film Strip
  13. Power Button and LED Light
  14. THRML M.2 Heatsink
  15. M.2 NVMe Drive Socket

Some parts are in two zipper bags. Open them and carefully pour out the contents. You will find:

  • Screws (two types)
  • Rubber feet
  • Two ribbon cables. You need one, the other one is a spare
"},{"location":"setup/single-mode/hardware-assembly/#assembly-instructions","title":"Assembly Instructions","text":""},{"location":"setup/single-mode/hardware-assembly/#1-prepare-the-raspberry-pi-5","title":"1. Prepare the Raspberry Pi 5","text":"

Place the thermal pads on the CPU, RP1, RAM and PMIC Chip of the RPi 5.

There are different versions of this case on the market: * If you have four thermal pads, place them in the areas marked in blue. * If you have two thermal pads in the set, place them on the CPU and PMIC (left corner, near the USB-C connector).

"},{"location":"setup/single-mode/hardware-assembly/#2-connect-the-fan","title":"2. Connect the Fan","text":"

Connect the NEO 5 fan to the RPi 5 fan connector as shown in the image. Please pay attention to how the cable is routed.

Note

There may be a small plug inserted in the fan connector. Remove it.

"},{"location":"setup/single-mode/hardware-assembly/#3-connect-pcie-ribbon-cable","title":"3. Connect PCIe Ribbon Cable","text":"

Connect the PCIe flat ribbon cable to the Raspberry Pi 5 PCIe port. Be careful when handling brown PCIe flip/cover. Pull up the brown flip to release the lock.

"},{"location":"setup/single-mode/hardware-assembly/#4-place-raspberry-pi-in-the-case","title":"4. Place Raspberry Pi in the Case","text":"

Drop in the RPi 5 inside the Argon NEO 5 M.2 NVMe Case

Warning

After inserting and pressing the RPi 5 into the central part of the Argon Neo 5 case, they will adhere due to the stickiness of the thermal pads. To ensure good thermal conductivity, do this once and avoid removing the RPi 5 from this part of the case again.

"},{"location":"setup/single-mode/hardware-assembly/#5-route-the-pcie-cable","title":"5. Route the PCIe Cable","text":"

The PCIe flat ribbon cable should be threaded through the hole in the case, as shown in the picture.

"},{"location":"setup/single-mode/hardware-assembly/#6-connect-pcie-cable-to-m2-board","title":"6. Connect PCIe Cable to M.2 Board","text":"

Carefully connect the Raspberry Pi 5 with the PCIe flat ribbon cable with copper facing up to the Argon NEO 5 M.2 NVMe Carrier Board Case. Flip up the cover on the M.2 NVMe Expansion Board.

"},{"location":"setup/single-mode/hardware-assembly/#7-insert-microsd-card","title":"7. Insert MicroSD Card","text":"

Here we want you to insert the PREVIOUSLY flashed microSD card with Web3 Pi image.

"},{"location":"setup/single-mode/hardware-assembly/#8-prepare-m2-nvme-drive-installation","title":"8. Prepare M.2 NVMe Drive Installation","text":"

Connect your M.2 NVMe Drive to the Argon NEO 5 M.2 NVMe Carrier Board. Detailed instructions for this process are described in the following steps.

"},{"location":"setup/single-mode/hardware-assembly/#9-check-nvme-compatibility","title":"9. Check NVMe Compatibility","text":"

Connect your M.2 NVMe Drive to the Argon NEO 5 M.2 NVMe Carrier Board. Detailed instructions for this process are described in the following steps.

This Board will accept M.2 Key M and M.2 Key B+M NVMe Storage Drive.

Warning

This Board will accept M.2 Key M and M.2 Key B+M NVMe Storage Drive.

"},{"location":"setup/single-mode/hardware-assembly/#10-remove-heatsink-cover","title":"10. Remove Heatsink Cover","text":"

Remove the \"THRMK M.2 Heatsink\" cover by unscrewing the four screws at its corners.

"},{"location":"setup/single-mode/hardware-assembly/#11-adjust-screw-mount-position","title":"11. Adjust Screw Mount Position","text":"

Move the screw point on the Board to the appropriate size of your Storage Drive.

"},{"location":"setup/single-mode/hardware-assembly/#12-insert-nvme-drive","title":"12. Insert NVMe Drive","text":"

Insert the NVMe drive into the M.2 slot as shown in the picture.

"},{"location":"setup/single-mode/hardware-assembly/#13-secure-the-nvme-drive","title":"13. Secure the NVMe Drive","text":"

Screw in the NVMe drive as shown in the picture.

"},{"location":"setup/single-mode/hardware-assembly/#14-apply-thermal-pad","title":"14. Apply Thermal Pad","text":"

Mount the thermal pad on the NVMe drive. There is no need to shorten it. Remember to remove the protective film from both sides.

"},{"location":"setup/single-mode/hardware-assembly/#15-mount-metal-cover","title":"15. Mount Metal Cover","text":"

Mount the metal cover and screw it in using four screws with conical heads.

"},{"location":"setup/single-mode/hardware-assembly/#16-secure-top-cover","title":"16. Secure Top Cover","text":"

Secure the Aluminium Top Cover with 2 screws.

"},{"location":"setup/single-mode/hardware-assembly/#lcd-display-option","title":"LCD Display Option","text":"

If you have a plastic cover with an LCD display, connect it according to the diagram instead of the original metal one.

Warning

Pay attention to the positioning of the cables when mounting the cover to ensure they don't mechanically obstruct the fan blades.

"},{"location":"setup/single-mode/hardware-assembly/#final-assembly","title":"Final Assembly","text":"

The final result should look like this:

Note

Pay attention to the positioning of the cables when mounting the cover to ensure they don't mechanically obstruct the fan blades.

"},{"location":"setup/single-mode/hardware-assembly/#hardware-connections","title":"Hardware Connections","text":"

Once you have assembled your device, connect the Raspberry Pi as shown below:

Refer to the following images to verify your setup. These show two typical configurations:

  • With a USB drive:

  • With an NVMe drive:

Ensure all cables and storage devices are securely connected before proceeding.

Note

Before you connect power, make sure that the ethernet cable is connected with DHCP. Internet connection is required during the installation process.

"},{"location":"setup/single-mode/hardware-checklist/","title":"Web3 Pi: Hardware Checklist","text":"

Active cooling is required to avoid throttling and keep sufficient performance/stability of the system.

As a power supply, we recommend using the official Raspberry Pi power supply for your model.

Raspberry Pi + 2TB drive can use a significant amount of power so a sufficient power supply is important for stability.

"},{"location":"setup/single-mode/hardware-checklist/#single-device-node-requirements","title":"Single Device Node Requirements","text":"

You can run a single device node on any of the following.

  • Raspberry Pi 5 - see full details

  • Raspberry Pi 4 - see full details

  • Raspberry Pi CM4/5 - see ful details

Optionally, you can add an LCD display for monitoring purposes.

"},{"location":"setup/single-mode/hardware-checklist/#raspberry-pi-5","title":"Raspberry Pi 5","text":"
  • 1 x Raspberry Pi 5 (8GB)
  • 1 X 2TB SSD drive (external USB SSD or NVMe m.2 with adapter)
  • 1 X SD Card reader/writer
  • 1 x microSD Card
  • 1 x Power supply
  • 1 x Raspberry Pi 5 Active Cooler
"},{"location":"setup/single-mode/hardware-checklist/#raspberry-pi-4","title":"Raspberry Pi 4","text":"
  • 1 x Raspberry Pi 4 (8GB)
  • 1 x 2TB SSD drive (external USB SSD or NVMe with m.2 to USB adapter)
  • 1 x SD Card reader/writer
  • 1 x microSD Card
  • 1 X Raspberry Pi 4 Active Cooler
  • 1 x Power supply
"},{"location":"setup/single-mode/hardware-checklist/#raspberry-pi-compute-module","title":"Raspberry Pi Compute Module","text":"

The CM4/5 module needs a carrier board. There are many on the market.

Minimum requiments are:

  • 1 x Raspberry Pi CM4/CM5 8GB
  • 1 x 2TB fast storage
  • 1 x Power supply
  • 1 x Raspberry Pi CM4 Active Cooler
  • 1 x 32GB+ storage for operating system (microSD or eMMC)
  • 1 x Motherboard

Installing Web3 Pi on the CM4/5 requires more knowledge. CM4/5 modules come with built-in eMMC memory, and in this case, you need to use the rpiboot application before using the Raspberry Pi Imager. If your module does not have built-in memory and uses an SD card, the installation process is similar to a standard Raspberry Pi.

More information can be found on the manufacturer's website: Raspberry Pi Documentation.

In some cases, a bootloader update may be necessary, which is described here: How to Update the Raspberry Pi Compute Module 4 Bootloader EEPROM.

"},{"location":"setup/single-mode/hardware-recommendations/","title":"Web3 Pi: Recommended Hardware","text":""},{"location":"setup/single-mode/hardware-recommendations/#ssd-drive","title":"SSD Drive","text":"

2 TB\u00a0fast drive is required.

For the Raspberry Pi 5, you have three options for storage:

  • external USB SSD drive (wide availability)
  • m.2 NVMe drive with NVMe HAT for Raspberry Pi 5 (max performance)
  • m.2 NVMe drive with USB m.2 adapter

Raspberry Pi 5 has a PCIe x1 connector on board so with a special adapter m.2 NVMe drive can be used. This option gives the maximum possible performance.

For the Raspberry Pi 4,\u00a0you have two options for storage:

  • external USB SSD drive (wide availability)
  • SSD drive with USB adapter

Note

If you use USB always choose USB 3.0 ports (blue)

Warning

Do NOT use HDD drives!

"},{"location":"setup/single-mode/hardware-recommendations/#recommended-ssd-hardware","title":"Recommended SSD Hardware","text":""},{"location":"setup/single-mode/hardware-recommendations/#usb-drive","title":"USB Drive","text":"Brand/Model Comment Link Samsung T7 2TB USB 3.2 Recommended for RPi4 users. Compatible with Raspberry Pi 4 and 5 More Info

Tip

Some external disks consume more power than Raspberry Pi can deliver via USB. For Raspberry Pi 5, the max power output of the USB ports is 600mA if you're using a 3A supply, and 1600mA if you're using a 5A supply. You can edit /boot/firmware/config.txt and add usb_max_current_enable=1 to disable the current limit. Please read the documentation: Link

"},{"location":"setup/single-mode/hardware-recommendations/#nvme-drive","title":"NVMe Drive","text":"

These drives need adapters.

Brand/Model Controller Comment Link Lexar NM7902TB m.2 2280 MaxiotekMAP1602A single side design 4TB available Product pageMore Info Goodram PX7002TB m.2 2280 MaxiotekMAP1602A single side design 4TB available Product page Micron 24002TB m.2 2230 Silicon MotionSM2269XT single side design ~4 W (Max)small 2230 form factorlow power consumptionlow heat Product pageMore Info Samsung 980 Pro2TB m.2 2280 SamsungElpis (S4LV003) single side design 7.2 W (Max) Product pageMore Info

Note

Double-sided NVMe m.2 memory modules (with memory chips on both sides of the PCB) may not be fully compatible with every enclosure due to physical dimensions, specifically the height of the m.2 slot in the adapter/enclosure.

"},{"location":"setup/single-mode/hardware-recommendations/#nvme-hat","title":"NVMe Hat","text":""},{"location":"setup/single-mode/hardware-recommendations/#nvme-hat-for-raspberry-pi-5","title":"NVMe HAT for Raspberry Pi 5","text":"Brand Link Pimoroni Product page Raspberry Pi m.2 Hat Product page Pineboards HatDrive: Bottom Product page Pineboards HatDrive: Top Product page Pineboards HatDrive: Nano Product page Waveshare 26583 Product page

We do not recommend the following:

  • KAmodRPi5 PCIe-M.2

  • Geekworm X1001

"},{"location":"setup/single-mode/hardware-recommendations/#sd-card-reader-and-writer","title":"SD Card Reader and Writer","text":"

You will use this on your PC for flashing the boot card. Since this operation takes time, we recommend a high-speed device.

"},{"location":"setup/single-mode/hardware-recommendations/#microsd-card","title":"MicroSD Card","text":"

Flashing a microSD card takes time, but it can be reduced by using a fast device. Additionally, using a fast micro SD card results in a shorter booting time.

You will require at least 32GB capacity.

A few examples:

  • Silicon Power 3D NAND
  • SanDisk Extreme Pro
  • SanDisk Ultra
  • SanDisk Max Endurance
  • Kingston Canvas React
  • Samsung 64 Evo Plus
  • Lexar Professional 1000X

More Information

"},{"location":"setup/single-mode/hardware-recommendations/#enclosures","title":"Enclosures","text":""},{"location":"setup/single-mode/hardware-recommendations/#enclosures-for-raspberry-pi-5","title":"Enclosures for Raspberry Pi 5","text":"Brand/Model Comment Link Argon NEO 5 M.2 NVMe +good cooling+metal-inconvenient access to the microSD card-m.2 slot not compatible with double side NVMe(easy modification possible with utility knife) Product pageShop Argon NEO 5 +easy acces to microSD card+good cooling+metal Product pageShop Argon ONE V3 M.2 NVMe +good cooling+metal-m.2 slot not compatible with double side NVMe(easy modification possible with utility knife) Product pageShop Argon ONE V3 +good cooling+metal Product pageShop"},{"location":"setup/single-mode/hardware-recommendations/#enclosures-for-raspberry-pi-4b","title":"Enclosures for Raspberry Pi 4B","text":"Brand/Model Comment Link Case justPi assembly instructions Product pageShop Argon One V2 Fan control needs additional configurationas described by the manufacturer manual Product pageShop Argon One V2 m.2 Fan control needs additional configurationas described by the manufacturer manual Product pageShop"},{"location":"setup/single-mode/installation-monitoring/","title":"Web3 Pi: Installation Monitoring Guide - Single Device Node","text":"

Now that the device is assembled and the card is inside, go back to the Web3 Pi Imager and follow the instructions:

  1. Connect the necessary cables.
  2. Ensure Internet connection (via DHCP) is available.
  3. Turn on the device and then press the NEXT button.

"},{"location":"setup/single-mode/installation-monitoring/#sd-card-installation-complete","title":"SD Card Installation Complete","text":"
  • Web3 Pi installer has been successfully written to the SD card.
  • At this point, the card is ready, and the installation process on the device begins.
  • Now the device is being searched on the network - this may take about 2 minutes, after which the user will be able to monitor the further installation process.
"},{"location":"setup/single-mode/installation-monitoring/#track-installation-progress","title":"Track Installation Progress","text":"

Now you can click the \"Track\" button - a page with the software installation process on the device will open.

Note

From this point, the user can log in via SSH using the credentials ethereum:ethereum

"},{"location":"setup/single-mode/installation-monitoring/#monitor-installation","title":"Monitor Installation","text":"

You can monitor the installation process through a dedicated website: http://eop-1.local

The monitoring should start working approximately three minutes after the device is first switched on.

Replace eop-1 with your hostname that you entered during the microSD card burning process in Web3 Pi Imager, if you used a name other than eop-1.

After approximately 3 minutes from powering on the device for the first time, you should see a similar page.

Note

Leave the device for about 8-15 minutes to complete the installation process. Do not disconnect power during this time. The time may vary depending on the bandwidth of the internet connection.

"},{"location":"setup/single-mode/installation-monitoring/#installation-web-interface","title":"Installation Web Interface","text":"

The Raspberry Pi with the Web3 Pi image on port 80 hosts an HTTP server that continuously displays the following in the web browser:

  • The installation stage
  • The hostname and IP address of the device
  • The full installation log and uptime
  • A link to the Grafana dashboard and a JSON status file

The installation is divided into stages. The installation is complete when you see: \"STAGE 100: Installation completed.\" This status is shown in the following screenshot.

"},{"location":"setup/single-mode/installation-monitoring/#grafana-dashboard-access","title":"Grafana Dashboard Access","text":"

Next, click the link to the Grafana dashboard. If everything has gone smoothly, you should see the login panel. The default username is 'admin', and the password is 'admin'. You will be required to change the password upon first login.

In the Grafana Panel, click on the dashboard named 'Ethereum Nodes Monitor'.

Note

Pay attention to the status of the consensus and execution clients. Initially, both will be 'inactive'

In the next step, the execution client will change to 'waiting'.

Then both will transition to the 'syncing' state.

Grafana URL: http://eop-1.local:3000

"},{"location":"setup/single-mode/installation-monitoring/#blockchain-synchronization","title":"Blockchain Synchronization","text":"

At this point, the blockchain synchronization process will begin and will take approximately 19 hours.

The CPU load will increase until it reaches its maximum possible value.

Disk usage will grow to around 1.2TB.

"},{"location":"setup/single-mode/installation-monitoring/#synchronization-complete","title":"Synchronization Complete","text":"

Full synchronization will be achieved when the status of both the execution and consensus clients turns green (\"synced\").

"},{"location":"setup/single-mode/installation-monitoring/#ssh-access","title":"SSH Access","text":"

After the installation completes successfully, you should have SSH access to the Web3 Pi node.

Username: ethereum Password: ethereum

You can use [your-hostname].local as the SSH address or the IP address if you know it.

If the 'ethereum' user does not exist, it means the installation failed unexpectedly (in such case, please contact support).

By default, the ethereum user is required to change the password during the first login.

"},{"location":"setup/single-mode/installation-monitoring/#network-configuration-verification","title":"Network Configuration Verification","text":"

To check that the network is working correctly, SSH into the Rasberry Pi and run a ping command:

ping -c 4 google.com\n

You should see similar response:

PING google.com (142.250.186.206) 56(84) bytes of data.\n64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=1 ttl=59 time=2.83 ms\n64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=2 ttl=59 time=3.62 ms\n64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=3 ttl=59 time=2.23 ms\n64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=4 ttl=59 time=3.73 ms\n\n--- google.com ping statistics ---\n4 packets transmitted, 4 received, 0% packet loss, time 3005ms\nrtt min/avg/max/mdev = 2.229/3.102/3.734/0.614 ms\n

You now have a fully operational Ethereum node running Geth and Nimbus.

For more information on managing, configuring and troubleshooting your node, please refer to the Managing Your Node menu.

"},{"location":"setup/single-mode/software-setup/","title":"Web3 Pi Image Installation Guide","text":"

This guide will walk you through the process of writing the Web3 Pi image to a microSD card using the Web3 Pi Imager tool.

"},{"location":"setup/single-mode/software-setup/#getting-started","title":"Getting Started","text":"

Follow the instructions below to write images on the microSD card:

  1. Download and install Web3 Pi Imager
  2. Insert the microSD card into the card reader and connect the reader to your PC
  3. Open the Web3 Pi Imager on your PC
  4. Choose the Single Mode Device

"},{"location":"setup/single-mode/software-setup/#configuration-options","title":"Configuration Options","text":"

For Single Mode device, the following settings can be configured:

  • Image version: Default is the latest version of Web3 Pi Image
  • Default Ethereum Network: Choice between Mainnet, Sepolia, or Holesky
  • Hostname for Raspberry Pi: Use a unique hostname. Default is eop-1.local
  • Execution client: Choose between Geth or Disabled
  • Execution Port for Geth: 30303
  • Consensus Client: Choose between Nimbus or Lighthouse
  • Consensus Client Port: 9000
  • Enable Grafana Monitoring: Turn on the advanced monitoring system by Grafana
  • Format storage: Option to format external storage during installation

"},{"location":"setup/single-mode/software-setup/#advanced-options","title":"Advanced Options","text":"

If you click the ADVANCED button, you can configure these additional options:

  • Execution endpoint address: Optional custom endpoint for execution client
  • Locale settings: Including:
    • Time zone selection
    • Keyboard layout
  • Wireless LAN configuration: Including:
    • SSID
    • Password
    • Wireless LAN country selection

Note

Wired Ethernet connection is recommended over Wi-Fi to ensure better synchronization.

"},{"location":"setup/single-mode/software-setup/#drive-selection","title":"Drive Selection","text":"

After setting up the configuration and clicking NEXT, a dialog box will appear allowing you to select the drive where the image with settings will be saved.

  • Drive selection: The list shows available storage devices
  • Display options:
    • By default, only drives smaller than 300GB are displayed
    • Checking the \"Show large external storage device\" option will display devices larger than 300GB

Note

Make sure you select the correct drive to avoid data loss on other devices

"},{"location":"setup/single-mode/software-setup/#accepting-terms-of-use","title":"Accepting Terms of Use","text":"

After selecting your target drive, a warning dialog will appear informing you that all data on the selected device will be erased.

  • Confirms that all existing data on the selected drive will be permanently deleted
  • You must accept the terms to proceed (the \"Yes\" button remains disabled until accepted). The full Terms of Use can be found at www.web3pi.io/terms.

Note

This is your final confirmation before the write process begins - ensure you have selected the correct device

"},{"location":"setup/single-mode/software-setup/#writing-process","title":"Writing Process","text":"

After confirmation, the writing process to the selected storage device begins.

  • Writing process: The progress bar displays the current writing status
  • Verification phase: After the writing completes, the verification process automatically starts. This step ensures data integrity and proper image installation.

Note

Do not disconnect or remove the storage device until both the writing and verification processes are complete

"},{"location":"setup/single-mode/software-setup/#final-steps","title":"Final Steps","text":"

Now after the card has finished writing you will see a screen informing you about installing the prepared card to the device.

Keep Web3 Pi Imager open at this step and now we will start assembling the device. We will come back to this step after we finish assembling.

"},{"location":"support/cheatsheet/","title":"Cheatsheet","text":"

This cheatsheet provides a quick reference for commonly used commands, ports, credentials, and configurations for your Web3 Pi node.

"},{"location":"support/cheatsheet/#default-credentials","title":"Default Credentials","text":"

SSH Access:

Username Password ethereum ethereum

Note: You are required to change this password upon first SSH login.

Grafana Dashboard:

Username Password admin admin

Note: You will be prompted to change this password upon first Grafana login. It is highly recommended to do so.

"},{"location":"support/cheatsheet/#common-network-ports","title":"Common Network Ports","text":"Port Service / Description Access Method 22 SSH SSH 80 Installation Monitor / Status Page HTTP 3000 Grafana Dashboard HTTP 5052 Lighthouse HTTP REST API HTTP 5353 mDNS (Avahi Daemon) mDNS 7197 Basic System Monitor JSON API HTTP 8008 InfluxDB HTTP API HTTP 8545 Execution Client JSON-RPC (Geth) HTTP 8546 Execution Client WebSocket RPC (Geth) HTTP 8551 Execution Client Engine API (Geth) HTTP 9000 Consensus Client P2P (Lighthouse) TCP/UDP 9000 Consensus Client P2P (Nimbus) TCP/UDP 9090 Cockpit System Dashboard HTTPS 30303 Execution Client P2P (Geth) TCP/UDP"},{"location":"support/cheatsheet/#how-to-find-your-nodes-ip-address-or-hostname","title":"How to find your nodes ip address or hostname","text":"
  • Hostname: Use the hostname you chose during image creation (e.g., web3pi.local, eop-1.local). Check via SSH with hostname.
  • IP Address: Check your router's admin panel or use network scanning tools to find the IP address assigned to your Raspberry Pi.
  • LCD Display: If installed, the LCD Dashboard shows the current IP address and hostname.
"},{"location":"support/cheatsheet/#log-files","title":"Log files","text":"
  • Web3 Pi Logs: /var/log/web3pi.log
  • rc.local logs: /root/first-run.flag
"},{"location":"support/cheatsheet/#internet-connectivity","title":"Internet connectivity","text":""},{"location":"support/cheatsheet/#bandwidth","title":"Bandwidth","text":"

To achieve optimal synchronization performance, your internet connection should have a download bandwidth of at least 160 Mb/s (20 MB/s). The upload requirement, however, is significantly lower. The synchronization process with the Ethereum mainnet requires downloading approximately 1.2 TB of data. [1.1 TB download, 25 GB upload - October 2024] So please be cautious if your internet connection is metered. A slower internet connection will still function, though the synchronization process will take longer. While upload and download speeds are important, they are only one factor in determining the quality of your connection. Ideally, a stable connection with low latency (ping) is recommended. For optimal performance, having a static public IP address is beneficial, but it is not strictly necessary.

"},{"location":"support/cheatsheet/#wifi","title":"WIFI","text":"

The default and recommended method for connecting the Raspberry Pi in the Web3 Pi project is via a wired Ethernet connection with automatic DHCP configuration. However, you can also connect Raspberry Pi 4/5 to the internet using the built-in WiFi module. To do so, in Raspberry Pi Imager, you must provide the SSID and password for your WiFi network.

Although using WiFi is possible, we strongly recommend using a wired connection. Over time, WiFi may lead to issues with connection stability and bandwidth performance.

Note

If you are using WIFI, do not connect the Ethernet cable to the Raspberry Pi.

"},{"location":"support/cheatsheet/#common-monitoring-management-commands-via-ssh","title":"Common Monitoring & Management Commands (via SSH)","text":"

Many system management and logging commands require elevated privileges. Use sudo before the command as shown below. You will be prompted for the ethereum user's password the first time you use sudo in a session.

Replace <service_name> with the actual service: w3p_geth, w3p_lighthouse-beacon, or w3p_nimbus-beacon.

"},{"location":"support/cheatsheet/#service-management-systemd","title":"Service Management (systemd)","text":"
  • Check Status: See if a service is running and view recent log snippets.
    sudo systemctl status <service_name>\n# Example: Check Geth status\nsudo systemctl status w3p_geth\n
  • Start Service:
    sudo systemctl start <service_name>\n
  • Stop Service:
    sudo systemctl stop <service_name>\n
  • Restart Service: (Stop and then start)
    sudo systemctl restart <service_name>\n
  • Enable Service: (Start automatically on boot - usually pre-configured)
    sudo systemctl enable <service_name>\n
  • Disable Service: (Prevent starting automatically on boot)
    sudo systemctl disable <service_name>\n
"},{"location":"support/cheatsheet/#viewing-logs-journald","title":"Viewing Logs (journald)","text":"
  • View Full Logs: Show all logs for a service (press q to exit).
    sudo journalctl -u <service_name>\n# Example: View all Lighthouse logs\nsudo journalctl -u w3p_lighthouse-beacon\n
  • Follow Logs (Live): Watch logs as they are generated (press Ctrl+C to exit).
    sudo journalctl -f -u <service_name>\n# Example: Follow Geth logs\nsudo journalctl -f -u w3p_geth\n
  • View Last N Lines: Show only the most recent log entries.
    sudo journalctl -n 50 -u <service_name> # Shows last 50 lines\n
  • View Logs Since Time: Show logs since a specific time (e.g., \"1 hour ago\", \"2025-04-24 10:00:00\").
    sudo journalctl --since \"1 hour ago\" -u <service_name>\n
"},{"location":"support/cheatsheet/#system-resource-monitoring","title":"System Resource Monitoring","text":"
  • Interactive Process Viewer: Shows CPU, Memory usage, tasks.
    htop\n
  • Disk Usage (Overall): Show usage for all mounted filesystems. (No sudo needed)
    df -h\n
  • Memory Usage: Show free and used RAM and Swap. (No sudo needed)
    free -h\n
  • System Uptime & Load: (No sudo needed)
    uptime\n
"},{"location":"support/cheatsheet/#node-specific-checks","title":"Node Specific Checks","text":"
  • Geth Console (Sync Status): Attach to the Geth console. (No sudo needed)
    geth attach http://localhost:8545\n# Inside the console, type:\neth.syncing\n# (Returns 'false' when synced, or sync progress object if syncing)\neth.blockNumber\n# (Shows the latest block number Geth knows)\nexit\n# (To leave the console)\n
"},{"location":"support/cheatsheet/#system-updates","title":"System Updates","text":"
  • Check for Available Updates: Refreshes the package list.
    sudo apt update\n
  • List Upgradable Packages: See what packages have updates available. (No sudo needed)
    apt list --upgradable\n
  • (Use with Caution) Apply Updates: Upgrades installed packages. Refer to specific Web3 Pi update guides if available.
    sudo apt upgrade\n
"},{"location":"support/cheatsheet/#influxdb","title":"InfluxDB","text":"

InfluxDB stores device status measurements and serves as the data source for Grafana.

The Influx database is fed by the basic-eth2-node-monitor application, which collects data from Ethereum clients. It also receives input from the basic-system-monitor application, which gathers operating system statistics and serves them as JSON over HTTP.

"},{"location":"support/cheatsheet/#clearing-the-database","title":"Clearing the Database","text":"

To clear the database, run the following commands:

influx\nUSE ethonrpi\nDROP SERIES FROM /.*/\nexit\n
"},{"location":"support/cheatsheet/#nimbus","title":"Nimbus","text":"
  • Service name: w3p_nimbus-beacon
  • Default directory: /mnt/storage/.nimbus/data/shared_mainnet_0
  • Startup script: /home/ethereum/clients/nimbus/nimbus.sh
"},{"location":"support/cheatsheet/#clear-saved-data","title":"Clear saved data","text":"
sudo rm -r /mnt/storage/.nimbus/data/shared_mainnet_0\n
"},{"location":"support/cheatsheet/#geth","title":"Geth","text":"
  • Service name: w3p_geth
  • Default directory: /mnt/storage/.ethereum
  • Startup script: /home/ethereum/clients/geth/geth.sh
"},{"location":"support/cheatsheet/#clear-saved-data_1","title":"Clear saved data","text":"
sudo rm -r /mnt/storage/.ethereum\n
"},{"location":"support/cheatsheet/#lighthouse","title":"Lighthouse","text":"
  • Service name: w3p_lighthouse-beacon
  • Default directory: /mnt/storage/.lighthouse
  • Startup script: /home/ethereum/clients/lighthouse/lighthouse.sh
"},{"location":"support/cheatsheet/#clear-saved-data_2","title":"Clear saved data","text":"
sudo rm -r /mnt/storage/.lighthouse\n
"},{"location":"support/cheatsheet/#jwt-secret","title":"JWT Secret","text":"

The jwt.hex file contains the JWT secret used to enable authenticated communication between the execution client and the consensus client. This file is generated by the installation script.

  • Location: /home/ethereum/clients/secrets/jwt.hex
"},{"location":"support/cheatsheet/#storage","title":"Storage","text":""},{"location":"support/cheatsheet/#pcie-generation","title":"PCIe Generation","text":"

By default, Web3 Pi uses PCIe Gen 2, which is the officially supported version. However, if you have a PCIe Gen 3 capable device, you can enable it by editing the config.txt file.

Benefits of PCIe Gen 3:

  • Higher Bandwidth: PCIe Gen 3 offers double the bandwidth compared to PCIe Gen 2. This means faster data transfer rates, which can be especially beneficial for high-speed storage devices like NVMe SSDs or network cards.
  • Improved Performance: For applications that are bottlenecked by PCIe bandwidth, enabling Gen 3 can significantly improve performance.

While Raspberry Pi 5 is designed for PCIe Gen 2, upgrading to Gen 3 can unlock more potential in compatible devices.

Warning

Enabling PCIe Gen 3 on a Raspberry Pi 5 can cause instability. Only proceed if you know what you're doing and have a compatible device.

"},{"location":"support/cheatsheet/#scripts","title":"Scripts","text":"

The Web3 Pi image comes with several useful scripts pre-installed. These can be found in the home directory of the ethereum user at /home/ethereum/scripts.

Below is a description of each script and how to execute them:

  • sudo ./scripts/shutdown.sh This script gracefully shuts down the device. It first stops the services, allowing them time to finish their tasks, and then powers off the system.

  • sudo ./scripts/reboot.sh This script gracefully reboots the device. It first stops the services, giving them time to complete their tasks, and then restarts the system.

  • sudo ./scripts/formatMe.sh This script marks the mapped storage as \"to be formatted\" during the next installation. It\u2019s useful when reinstalling the Web3 Pi image.

  • sudo ./scripts/versions.sh This script checks the versions of the currently installed applications and compares them to the latest available online. It covers applications such as Geth, Nimbus, and Lighthouse.

  • sudo ./scripts/update_geth.sh This script updates the Geth application to the latest available version. It stops the service, installs the new version, and then restarts the service.

  • sudo ./scripts/update_nimbus.sh This script updates the Nimbus application to the latest available version. It stops the service, installs the new version, and then restarts the service.

  • sudo ./scripts/check_install.sh This script checks the installation and configuration of Web3 Pi. It verifies installed packages, active services, disk and swap usage, network connectivity, and other important aspects. The output is formatted and color-coded for better readability.

You can also view and execute scripts from the Cockpit Web3 Pi Script Runner plugin.

"},{"location":"support/cheatsheet/#ccze","title":"CCZE","text":"

Newer version of the Web3 Pi image include CCZE, which enables automatic colorization of logs from applications such as Geth and Nimbus, significantly improving readability.

An example how to use CCZE:

sudo journalctl -xfu w3p_geth.service | ccze -A\n\nsudo journalctl -xfu w3p_nimbus-beacon.service | ccze -A\n\ncat /var/log/web3pi.log | ccze\n
"},{"location":"support/contact/","title":"Contact","text":""},{"location":"support/contact/#discord","title":"Discord","text":"

You can reach us on our Discord server: Join Discord

"},{"location":"support/contact/#contact-form","title":"Contact Form","text":"

Fill out our contact form to ask a question: Contact Form

"},{"location":"support/contact/#github","title":"GitHub","text":"

For issues, projects, or contributions, visit our GitHub: GitHub Repository

"},{"location":"support/contact/#x-twitter","title":"X (Twitter)","text":"

Follow us or send us a message on X: Follow us on X

"},{"location":"support/troubleshooting/","title":"Troubleshooting","text":""},{"location":"support/troubleshooting/#my-node-cannot-synchronize","title":"My node cannot synchronize","text":"

Synchronization issues can stem from several causes. First, check your internet connection quality - unstable or slow connections significantly delay the synchronization process. Also ensure your NVMe SSD has sufficient capacity (minimum 2TB) and is functioning properly. Overheating can also slow down or interrupt synchronization, so verify that your CPU cooling is working correctly.

If your Raspberry Pi 4 is synchronizing too slowly, remember this is normal - the process is much faster on Raspberry Pi 5. You can check synchronization progress in the Web3 Pi panel and verify if the node is actually downloading new blocks.

If the problem persists, check: - Whether your firewall is blocking required ports - If the SSD is properly mounted and recognized by the system - If you have the latest version of Web3 Pi software

For further assistance, join our Discord channel where you can get help from our community and team.

"},{"location":"use-cases/solo-staking/","title":"Solo Staking with Web3 Pi","text":"

Under Development & High Risk

Web3 Pi does not currently offer official support for staking configurations. Proceed at your own risk.

While Web3 Pi provides the necessary Execution and Consensus client foundation, configuring it securely and reliably for staking requires significant technical expertise beyond the standard setup.

You are welcome to explore configuring your node for staking independently, but please be aware:

  • This is an advanced procedure.
  • Web3 Pi does not currently offer official support for staking configurations.
  • You proceed entirely at your own risk. Mistakes can lead to financial penalties (slashing).

We strongly recommend thoroughly understanding the responsibilities and substantial risks involved before attempting solo staking.

\u27a1\ufe0f Review Staking Risks and Considerations

"},{"location":"use-cases/transaction-firewall/","title":"Web3 Pi Transactions Firewall","text":"

TxFirewall is a tool that enhances the security of interactions with the Ethereum network through local RPC endpoints. It is designed to work with the Ethereum On Raspberry Pi suite.

At its core, TxFirewall functions as a local transaction-intercepting proxy server. It acts as an intermediary between the user's wallet (e.g., MetaMask) and the RPC endpoint of their own W3P Ethereum node.

Technically, TxFirewall listens on a specific local port configured in the wallet. When the wallet sends an RPC request, TxFirewall intercepts it. Simple data read calls (like eth_getBalance) can be passed directly to the W3P node. However, critical calls like eth_sendTransaction are halted and subjected to a verification process. Crucially, this entire process happens locally on the W3P device or the user's machine, before the transaction is broadcast to the Ethereum network.

"},{"location":"use-cases/transaction-firewall/#installation","title":"Installation","text":"

The firewall can be installed manually on any computer by following the instructions available at Github repository: Web3 Pi Transaction Firewall.

However, Web3 Pi Transaction Firewall is part of the Web3 Pi ecosystem, and the recommended installation method is through the Cockpit management system, accessible via the Web3 Pi Updater plugin. This installation approach automatically sets up the firewall along with the cockpit plugin, providing a complete graphical interface for firewall management.

You can easily install Transaction Firewall on your Raspberry Pi by clicking Install in the Web3 Pi Updater plugin.

In this version, the entire package is installed with initial configuration. The firewall is installed as a systemd service with appropriate permissions and configuration paths to ensure it uses the local Ethereum RPC client on your Web3 Pi by default. Additionally, the package includes a Cockpit plugin for complete management and control of the firewall.

"},{"location":"use-cases/transaction-firewall/#configuration","title":"Configuration","text":"

After installation, a new item labeled Web3 Pi Tx Firewall will appear in the Cockpit Menu. The firewall configuration window looks as follows:

Note

You must have administrator privileges enabled to use the Web3 Pi Tx Firewall. Otherwise, you will see a warning message:

"},{"location":"use-cases/transaction-firewall/#main-settings","title":"Main settings","text":"

The top menu of the panel provides options to stop and start the firewall service, as well as configure basic settings:

  • Server Port: Port number where the main application serves the web interface for users to verify transactions. Default: 8454

  • Proxy Port: Port number used for the proxy service. Used by RPC clients such as Metamask. Default: 18500

  • WSS Port: Port number dedicated for WebSocket connections used between the web application and the firewall. Default: 18501

  • RPC Endpoint: The RPC endpoint used to communicate with your Ethereum or blockchain node. Default: http://localhost:8545 - local Ethereum node in Web3 Pi

  • Interactive Mode Timeout: Timeout duration for user decision in interactive mode (in seconds). Default: 60

Note

Changing these settings requires a restart of the firewall service.

"},{"location":"use-cases/transaction-firewall/#opening-ports-in-os-firewall","title":"Opening ports in OS firewall","text":"

The default firewall application ports are blocked by the operating system firewall (USW). This is indicated by an icon next to the listed ports.

To use the Tx firewall, you need to unblock these ports. You can do this manually via the USW CLI or by clicking the lock icon next to each port.

Warning

If the ports are not open, attempting to access the firewall's frontend application will result in the following message:

"},{"location":"use-cases/transaction-firewall/#authorized-address","title":"Authorized address","text":"

In this table, you can add, edit or delete addresses that you recognize and set an appropriate name for them. This name will be displayed in the transaction verification window. It can be either an Ethereum account address or a contract address.

"},{"location":"use-cases/transaction-firewall/#known-contracts","title":"Known contracts","text":"

In this table, you can define contracts that you recognize, including both their names and ABIs. This configuration will be used to decode transaction parameters. In the verification window, the name of the smart contract function being called and its argument names will be displayed. Additionally, if the argument type is address, it will be displayed with the appropriate name if it is found as an authorized address.

"},{"location":"use-cases/transaction-firewall/#client-configuration-metamask","title":"Client configuration (Metamask)","text":"

You can configure any Ethereum RPC client - any wallet (including hardware wallets), or it can be an application using blockchain. This example demonstrates configuration instructions for the popular Metamask wallet.

For this purpose, you need to:

To navigate to the network configuration window in Metamask, follow the official Metamask instructions and then fill in the required fields.

Note

You should use as Default RPC URL a URL consisting of your Pi's hostname or IP address and the port defined in the configuration as Proxy Port

"},{"location":"use-cases/transaction-firewall/#usage","title":"Usage","text":"

To use the Tx Firewall, open the frontend application by clicking the Open Firewall App button in the top menu.

Warning

The Firewall in its current version works exclusively in interactive mode, which means that transactions will only be verified and rejected when the frontend application window is active. If the window is not running, all (!) transactions will be forwarded to the specified RPC node.

"},{"location":"use-cases/transaction-firewall/#frontend-app","title":"Frontend App","text":"

After opening the application, you can monitor transactions in the window:

If for any reason you lose the connection, you should see a notification message.

Warning

Remember! In such a situation, the Firewall operates in mode: Accepts everything!

"},{"location":"use-cases/transaction-firewall/#validating-transaction","title":"Validating transaction","text":""},{"location":"use-cases/transaction-firewall/#simple-transfer","title":"Simple transfer","text":"

During a simple funds transfer from one account to another, the transfer transaction will be displayed in the application window with properly tagged addresses if they have been defined as Authorized Addresses.

You can accept or reject the displayed transaction within the time shown on the screen.

Note

If you don't make a decision within the specified time, the transaction will be automatically rejected! The decision time is set in the Interactive Mode Timeout parameter.

"},{"location":"use-cases/transaction-firewall/#contract-call","title":"Contract call","text":"

If you execute a transaction on a smart contract - for example, an ERC-20 token transfer like Golem Network Token - by calling the transfer function on a specific smart contract, it will be decoded and displayed in the application window.

Note

Pay attention that the address fields have been tagged with names defined in the Authorized Addresses and Known Contract tables. If these definitions weren't present, the labels would display as Unknown.

If you make a transaction on a contract that hasn't been defined in the Known Contracts table, a data field with undecoded transaction data in hexadecimal format will appear in the verification window.

Note

That's why it's important to define contract ABIs for proper transaction verification!

"},{"location":"use-cases/transaction-firewall/#predefined-standard-interfaces","title":"Predefined Standard Interfaces","text":"

If a contract address is not matched with any entries in the Known Contracts table, the firewall attempts to recognize and match the contract data against a set of predefined standard interfaces:

  • Main standards:

    • ERC20 - Standard interface for fungible tokens
    • ERC721 - Standard interface for non-fungible tokens (NFTs)
    • ERC1155 - Multi-token standard
    • ERC4626 - Tokenized vault standard
  • Popular extensions:

    • ERC20Burnable - ERC20 extension allowing token burning
  • Popular utility contracts:

    • Ownable - Contracts with ownership functionality
    • AccessControl - Contracts with role-based access control

All these interfaces are imported from the OpenZeppelin library, which provides secure and community-vetted implementations of common smart contract standards. This automatic detection allows the firewall to correctly parse and display transaction data. Contract will be tagged as Possible Interface: ...

"},{"location":"use-cases/transaction-firewall/#logs","title":"Logs","text":"

While operating and using the firewall, you can view service logs in the Cockpit panel, where firewall activities are recorded:

By clicking on a specific log entry, you can see its details. Logs are stored in JSON format:

{\n  \"level\": 30,\n  \"time\": \"2025-04-25T16:54:57.819Z\",\n  \"pid\": 33525,\n  \"hostname\": \"eop-1\",\n  \"transaction\": {\n    \"id\": \"0xd1490ab481cb7c2d7713a98fa52878300d04ae750b73ed42e28724387aa840a1\",\n    \"from\": \"0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119\",\n    \"to\": \"0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429\",\n    \"value\": \"0\",\n    \"data\": \"0xa9059cbb000000000000000000000000de07073781cadad26053b6d36d8768f0bd283751000000000000000000000000000000000000000000000000001c6bf526340000\",\n    \"labelFrom\": \"Bob\",\n    \"labelTo\": \"GLM Token Contract\",\n    \"txType\": \"contract-call\",\n    \"contractInfo\": {\n      \"address\": \"0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429\",\n      \"labelAddress\": \"Golem Contract\",\n      \"functionName\": \"transfer\",\n      \"args\": [\n        {\n          \"name\": \"recipient\",\n          \"type\": \"address\",\n          \"value\": \"0xdE07073781CADaD26053b6d36D8768f0bD283751\",\n          \"label\": \"Alice\"\n        },\n        {\n          \"name\": \"amount\",\n          \"type\": \"uint256\",\n          \"value\": \"8000000000000000\"\n        }\n      ]\n    }\n  },\n  \"msg\": \"Transaction accepted\"\n}\n
"},{"location":"use-cases/transaction-firewall/#security","title":"Security","text":""},{"location":"use-cases/transaction-firewall/#limitations","title":"Limitations","text":"

Currently, only the Interactive Mode of the Firewall is available. This means that transaction verification can only be performed when the frontend application is open.

  • If the web page is not open, the service automatically forwards all requests to the configured RPC endpoint
  • Only one web page instance may be opened at a time
  • Opening an additional webpage instance drops the old connection and redirects all queries to the current page
  • This is an asynchronous service, but it serves only one request at a time
  • Requests are not queued
  • New requests sent during the processing of a previous one are automatically forwarded to the configured RPC endpoint
"},{"location":"use-cases/transaction-firewall/#more-about-threats","title":"More about threats...","text":"

You can read more about the need to use the Firewall together with Web3 Pi on our blog Fortify Your Ethereum Journey Web3 Pi Transaction Firewall

"},{"location":"use-cases/wallet/","title":"Connect Your Wallet to Your Node","text":"

One of the significant advantages of running your own Ethereum node with Web3 Pi is the ability to use it as a private and trusted backend for your crypto wallets, such as MetaMask. Instead of relying on default public RPC endpoints provided by wallet developers or third parties, you can point your wallet directly to your own node running on your local network.

"},{"location":"use-cases/wallet/#why-connect-your-wallet-to-your-own-node","title":"Why Connect Your Wallet to Your Own Node?","text":"
  • Enhanced Privacy: When you use public RPC endpoints, the provider could potentially log your IP address and the wallet addresses you query. By using your own node, your transaction lookups and balance checks stay within your local network, significantly improving your privacy.
  • Increased Reliability: Public endpoints can sometimes become congested or experience downtime. Your own node provides a dedicated resource that you control, potentially offering more consistent availability (assuming your node and internet connection are stable).
  • Trust Minimization: You are directly querying the Ethereum blockchain state as validated by your node, rather than trusting a third-party provider's node. This aligns with the core principles of decentralization.
  • No Rate Limiting: Public RPCs often have rate limits to prevent abuse. Your own node doesn't impose such external limits (though it's still bound by its own processing capabilities).
"},{"location":"use-cases/wallet/#prerequisites","title":"Prerequisites","text":"
  1. Web3 Pi Node Running: Your Raspberry Pi must be powered on, connected to your network, and the Ethereum Execution Client must be running and fully synced with the network you intend to use (e.g., Mainnet, Sepolia).
  2. Wallet Installed: You need a wallet that supports custom RPC endpoints (MetaMask is a common example).
  3. Node's Local IP Address or Hostname: You need to know the IP address (e.g., 192.168.1.123) or hostname (e.g., web3pi.local) of your Raspberry Pi on your local network. Consult the Cheatsheet if you're not sure how to find it.
"},{"location":"use-cases/wallet/#finding-your-nodes-rpc-address","title":"Finding Your Node's RPC Address","text":"

Your Execution Client exposes an RPC endpoint that wallets can connect to. You can access it in a few ways:

  1. Local IP Address: Use your node's IP address on your local network.

    • Format: http://<your-pi-ip-address>:<rpc-port>
    • Default Port: 8545
    • Example: http://192.168.1.123:8545
    • Use Case: Connecting from devices on the same local network (Wi-Fi).
  2. Local Hostname: Use the hostname assigned to your node.

    • Format: http://<your-pi-hostname>:<rpc-port>
    • Default Port: 8545
    • Example: http://web3pi.local:8545
    • Use Case: Connecting from devices on the same local network (Wi-Fi). Requires local DNS resolution (mDNS/Bonjour) to work.
  3. Web3 Pi Link Address (Remote Access): If you have set up Web3 Pi Link, you can use the secure public HTTPS URL provided by the service.

    • Format: https://<your-chosen-name>.web3pi.link
    • (Note: Web3 Pi Link handles the port mapping and provides a standard HTTPS port 443 endpoint).
    • Example: https://my-awesome-node.web3pi.link
    • Use Case: Connecting from anywhere, including outside your local network (e.g., when you're not home).

Choose the appropriate address based on how and where you want to connect to your node.

"},{"location":"use-cases/wallet/#configuration-steps-metamask-example","title":"Configuration Steps (MetaMask Example)","text":"

MetaMask doesn't allow adding a new network with the same Chain ID as an existing one (like Mainnet 1, Sepolia 11155111, etc.). Therefore, to use your node for these standard networks, you need to edit the existing network settings in MetaMask.

Editing an Existing Network (e.g., Mainnet, Sepolia):

  1. Open MetaMask: Unlock your MetaMask extension.
  2. Network Dropdown: Click on the network selection dropdown menu (top-left).
  3. Open the Edit Network Wizard: Click the \"Edit\" button in the network dropdown.
  4. Update RPC URL: Locate the \"Default RPC URL\" field and add a new RPC URL.

    • For local access: http://web3pi.local:8545 or http://192.168.1.123:8545 (Use http)
    • For remote access via Web3 Pi Link: https://my-awesome-node.web3pi.link (Use https)
  5. Select Your RPC Now that you've added your RPC endpoint, select it from the dropdown menu.

  6. (Optional) Rename the network: You can rename the network to something more descriptive (e.g., Web3 Pi Mainnet or Ethereum Mainnet (Web3 Pi)).

  7. Save: Click the \"Save\" button. Your MetaMask wallet is now configured to communicate directly with your own Web3 Pi Ethereum node using the specified RPC URL.
"},{"location":"use-cases/wallet/#important-considerations","title":"Important Considerations","text":"
  • Local vs. Remote: Remember to use the local http://...:8545 address only when your wallet device is on the same network as the Pi. Use the https://....web3pi.link address for access from anywhere else.
  • Security (Manual Exposure): The default Web3 Pi setup exposes the RPC port (8545) only locally. Do not manually configure your router/firewall to expose port 8545 directly to the public internet unless you implement robust security. Web3 Pi Link is the recommended way to achieve secure remote access.
"},{"location":"welcome-box/","title":"Web3 Pi WelcomeBox","text":""},{"location":"welcome-box/#the-complete-starter-kit-for-ethereum-nodes-on-raspberry-pi","title":"The Complete Starter Kit for Ethereum Nodes on Raspberry Pi","text":"

The Web3 Pi WelcomeBox is designed to be an all-in-one package, providing you with everything you need to launch your own Ethereum node on a Raspberry Pi 5 with a seamless setup experience.

It bundles compatible, tested hardware components to save you the time and effort of sourcing them individually.

"},{"location":"welcome-box/#whats-inside","title":"What\u2019s Inside?","text":"

The WelcomeBox includes the following components:

Photo Product name Store Link Raspberry Pi 5 - 8GB RAMThe heart of Web3 Pi. Powered by an efficient ARM processor, it delivers high performance while maintaining energy efficiency. TME / Kamami / Botland Official Raspberry Pi 5 power supplyOfficial Raspberry Pi 5 power adapter with a USB-C connector. Delivers up to 27W for stable and reliable operation. TME / Kamami / Botland 2TB m.2 storage: Lexar NM790 or Goodram PX700A high-speed 2TB NVMe storage for fast blockchain synchronization. Designed for low power consumption, high performance, and long life. Lexar NM790:Product page / Morele.netGoodram PX700:Product page / Morele.net Argon Neo 5 NVMe enclosureA compact enclosure with active cooling. It looks sleek, provides excellent thermal management, and fits in any corner. Kamami / Botland microSD CardHigh-speed microSD card (preferably 32GB or larger) for storing the operating system and node data files, with plenty of space for future expansions. Kamami / Botland Color LCD DashboardA high-quality LCD screen designed to provide real-time insights into your node and operating system status. Kamami / Botland 3D Printed CoverA custom-designed plastic cover, 3D printed specifically for Web3 Pi. It allows for mounting the LCD display while providing a sleek and functional finish. 3D models Ethernet Cable cat. 5eA 2-meter high-quality Ethernet cable, included to complete the set. TME / Kamami / Botland microSD Card Reader: Ugreen CR127A handy tool for flashing your microSD card with the operating system and node data. Ugreen / Morele.net microHDMI-HDMI cableNot required for running Web3 Pi, but invaluable for troubleshooting and initial setup. TME / Kamami / Botland PliersHandy pliers that make hardware assembly easier. Ideal for gripping small components and included for your convenience. TME / Kamami / Botland Screwdrivers: PH0 and flat 3mmA set of screwdrivers to tighten screws and secure components. Included to ensure you have everything needed to set up Web3 Pi. PH0: TMEflat: TME

(Note: Exact components like storage brand or specific tool types may vary slightly based on availability, but will meet the required specifications.)

"},{"location":"welcome-box/#assembly-and-setup","title":"Assembly and Setup","text":"

Follow the instructions in the Full Setup Guide to assemble and configure your Web3 Pi.

"},{"location":"welcome-box/#availability","title":"Availability","text":"

Coming soon

"}]} \ No newline at end of file diff --git a/setup/dual-mode/hardware-assembly/index.html b/setup/dual-mode/hardware-assembly/index.html new file mode 100644 index 0000000..1dad1e2 --- /dev/null +++ b/setup/dual-mode/hardware-assembly/index.html @@ -0,0 +1,2486 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Hardware Assembly - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi: Hardware Assembly (Dual-Device Mode)

+

Assembling the hardware for a dual-device Web3 Pi node involves building two separate Raspberry Pi systems: one designated as the Execution Layer (EL) Node and the other as the Consensus Layer (CL) Node.

+

While the assembly steps for each individual device are identical to the Single-Device Hardware Assembly Guide, managing two builds requires careful attention, especially regarding component placement (SSDs) and labeling.

+
+

Follow Instructions Carefully

+

Read through these instructions and the single-device guide before starting. Ensure you have all components for both devices laid out and organized.

+
+

Before You Start

+
    +
  1. Gather Components: Ensure you have all the necessary hardware components for two complete Raspberry Pi setups as outlined in the Dual-Device Hardware Checklist and Recommendations. This includes two Pis, two power supplies, two enclosures/cooling solutions, two microSD cards, and two SSDs (one 2TB+, one 256GB+).
  2. +
  3. Prepare SD Cards: You should have already flashed two separate microSD cards using the Web3 Pi Imager in Dual-Device mode – one configured for the EL node and one for the CL node. Keep them clearly identifiable.
  4. +
  5. Reference Guide: Keep the Single-Device Hardware Assembly Guide open in another tab or window. You will follow those detailed steps twice, once for each device.
  6. +
+

Assembly Steps

+

Step 1: Decide and Label Your Devices (Crucial!)

+

Before assembling anything, decide which Raspberry Pi will be your EL Node and which will be your CL Node.

+
+

Label Everything Clearly

+

Use sticky notes, labels, or markers to clearly label each Raspberry Pi board, enclosure/case, and the corresponding EL and CL microSD cards. +This labeling will prevent you from inserting the wrong SD card or SSD into the wrong device.

+
+

Step 2: Assemble the Execution Layer (EL) Node

+
    +
  1. Take the Raspberry Pi and enclosure you designated as the EL Node.
  2. +
  3. Follow the detailed steps outlined in the Single-Device Hardware Assembly Guide precisely.
  4. +
  5. Critical Check - SSD: When you reach the step to install the storage drive, ensure you install the larger (2TB or greater) SSD into this EL Node device.
  6. +
  7. Critical Check - SD Card: When you reach the step to insert the microSD card, ensure you insert the card specifically flashed and labeled for the EL Node.
  8. +
  9. Complete the assembly for the EL Node as per the single-device guide.
  10. +
+

Step 3: Assemble the Consensus Layer (CL) Node

+
    +
  1. Take the Raspberry Pi and enclosure you designated as the CL Node.
  2. +
  3. Follow the detailed steps outlined in the Single-Device Hardware Assembly Guide precisely, just as you did for the EL node.
  4. +
  5. Critical Check - SSD: When you reach the step to install the storage drive, ensure you install the smaller (256GB or greater) SSD into this CL Node device.
  6. +
  7. Critical Check - SD Card: When you reach the step to insert the microSD card, ensure you insert the card specifically flashed and labeled for the CL Node.
  8. +
  9. Complete the assembly for the CL Node as per the single-device guide.
  10. +
+

Step 4: Final Check

+

You should now have two fully assembled Raspberry Pi devices:

+
    +
  • One labeled "EL Node" containing the larger SSD and the EL microSD card.
  • +
  • One labeled "CL Node" containing the smaller SSD and the CL microSD card.
  • +
+

Double-check your labels and component placement one last time.

+

Hardware connections

+

Once you have both devices assembled, connect them as follows:

+

Hardware connections

+
+

Warning

+

For the mDNS mechanism to work, both devices must be connected to the same local network. It's recommended to use one network switch for both devices.

+
+

Refer to the following image to verify your setup:

+

Assembled devices

+

Ensure all cables and storage devices are securely connected before proceeding.

+

Next Steps

+

With both devices assembled correctly, you are ready to connect them to your network and begin the software installation and synchronization process.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/dual-mode/hardware-checklist/index.html b/setup/dual-mode/hardware-checklist/index.html new file mode 100644 index 0000000..7cef80b --- /dev/null +++ b/setup/dual-mode/hardware-checklist/index.html @@ -0,0 +1,2334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Required Hardware - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Required Hardware

+ +

Dual Device Node Requirements

+

Suggested configurations:

+
    +
  • 2 X Raspberry Pi 5
  • +
  • 2 X Raspberry Pi 4
  • +
  • 1 X Raspberry Pi 5 + 1 X Raspberry Pi 4
  • +
+

or any other combination, including CM4.

+

The default setup requires the following hardware components:

+
    +
  • 2 x Raspberry Pi (8GB) starter kits
  • +
  • 2 x SSD (one for each device)
  • +
  • 1 x SD Card reader/writer
  • +
  • 2 x Fast microSD Card
  • +
+

Optionally, you can add an LCD display to each Raspberry Pi for monitoring purposes.

+

A device with an execution client needs 2TB+ fast storage. A device with a consensus client needs 256GB+ fast storage.

+

Raspberry Pi

+

You can use:

+ +
+

Note

+

8GB RAM is required.

+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/dual-mode/hardware-recommendations/index.html b/setup/dual-mode/hardware-recommendations/index.html new file mode 100644 index 0000000..a5529c6 --- /dev/null +++ b/setup/dual-mode/hardware-recommendations/index.html @@ -0,0 +1,2887 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Recommended Hardware - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi: Recommended Hardware

+

In a dual-device setup, the workload of running an Ethereum node is split between two Raspberry Pi devices. One device runs the Execution Layer (EL) client (like Geth), which requires significant storage, while the other runs the Consensus Layer (CL) client (like Nimbus or Lighthouse), which has much lower storage needs but still benefits from fast access.

+

This setup requires two complete Raspberry Pi systems, each with its own Pi, power supply, storage, SD card, and cooling/enclosure.

+

Supported combinations include:

+
    +
  • Two Raspberry Pi 5
  • +
  • Two Raspberry Pi 4
  • +
  • One Raspberry Pi 5 and one Raspberry Pi 4
  • +
  • Any combination including Compute Module 4/5
  • +
+

Raspberry Pi Models

+

You will need two Raspberry Pi devices. The following models are supported:

+
    +
  • Raspberry Pi 5: Recommended for best performance, especially for the EL client.
  • +
  • Raspberry Pi 4 Model B: A viable option, particularly suitable for the CL client or if cost is a major factor. Can also run the EL client, though sync times may be longer than Pi 5.
  • +
  • Raspberry Pi Compute Module 4 (CM4): Requires a compatible carrier board with necessary ports (Ethernet, USB, potentially PCIe/M.2) and a cooling solution.
  • +
+
+

RAM Requirement

+

Regardless of the model chosen, both Raspberry Pi devices must have at least 8GB of RAM.

+
+

Refer to the hardware checklists for specific model links:

+ +

SSD Drive Requirements

+
    +
  • Execution Client (EL) Device: Needs a 2 TB or larger fast SSD (NVMe or USB 3.0) to store the growing Ethereum blockchain state data.
  • +
  • Consensus Client (CL) Device: Needs a 500 GB or larger fast SSD. While 500GB is sufficient currently. NVMe or a reliable USB 3.0 SSD is recommended.
  • +
+

Connection Options

+
    +
  • +

    Raspberry Pi 5: +

    +
      +
    • External USB 3.0 SSD drive.
    • +
    • M.2 NVMe drive with an NVMe HAT (connects via PCIe).
    • +
    • M.2 NVMe drive with a USB 3.0 to M.2 adapter.
    • +
    +
  • +
  • +

    Raspberry Pi 4 / CM4 (with appropriate carrier board): +

    +
      +
    • External USB 3.0 SSD drive.
    • +
    • M.2 NVMe drive with a USB 3.0 to M.2 adapter.
    • +
    • (Some CM4 carrier boards may offer direct M.2 slots).
    • +
    +
  • +
+
+

Use USB 3.0 Ports

+

If using USB-connected storage, always use the blue USB 3.0 ports on the Raspberry Pi for maximum speed.

+
+
+

Recommendations for Execution Client SSD (2TB+)

+

This device requires a large, fast drive. The recommendations are the same as for the single-device mode.

+

USB Drive (EL)

+ + + + + + + + + + + + + + + + + +
Brand/ModelCommentLink
Samsung T7Samsung T7 2TB USB 3.2Recommended for RPi4 users. Compatible with Raspberry Pi 4 and 5More Info
+
+

Tip

+

Some external disks consume more power than Raspberry Pi can deliver via USB. For Raspberry Pi 5, the max power output of the USB ports is 600mA if you're using a 3A supply, and 1600mA if you're using a 5A supply. You can edit /boot/firmware/config.txt and add usb_max_current_enable=1 to disable the current limit. Please read the documentation: Link

+
+

NVMe Drive (EL)

+

These drives need adapters (HAT or USB). See below.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brand/ModelControllerCommentLink
Lexar NM790 2TBLexar NM790
2TB m.2 2280
Maxiotek
MAP1602A
single side design
4TB available
Product page

More Info
Goodram PX700 2TBGoodram PX700
2TB m.2 2280
Maxiotek
MAP1602A
single side design
4TB available
Product page
Micron 2400 2TBMicron 2400
2TB m.2 2230
Silicon Motion
SM2269XT
single side design
~4 W (Max)
small 2230 form factor
low power consumption
low heat
Product page

More Info
Samsung 980 ProSamsung 980 Pro
2TB m.2 2280
Samsung
Elpis (S4LV003)
single side design
7.2 W (Max)
Product page

More Info
+
+

Note

+

Double-sided NVMe m.2 memory modules (with memory chips on both sides of the PCB) may not be fully compatible with every enclosure due to physical dimensions, specifically the height of the m.2 slot in the adapter/enclosure.

+
+

NVMe Hat (Pi 5) (EL)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BrandLink
PimoroniNVMe Base for Raspberry Pi 5
Raspberry Pi m.2 Hathttps://www.raspberrypi.com/products/m2-hat-plus
Pineboards HatDrive: Bottomhttps://pineberrypi.com/products/hatdrive-bottom-2230-2242-2280-for-rpi5
Pineboards HatDrive: Tophttps://pineboards.io/products/hat-top-2230-2240-for-rpi5
Waveshare 26583https://www.waveshare.com/pcie-to-m.2-hat-plus.htm
+

Recommendations for Consensus Client SSD (500GB+)

+

This device requires a smaller, but still fast and reliable drive.

+

USB Drive (CL)

+ + + + + + + + + + + + + + + + + +
Brand/ModelCommentLink
Samsung T7Samsung T7 500GB USB 3.2Recommended for RPi4 users. Compatible with Raspberry Pi 4 and 5More Info
+

NVMe Drive (CL)

+

These drives need adapters (HAT or USB). Choose a reputable brand. 500GB or more are suitable sizes.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brand/ModelControllerCommentLink
Lexar NM790 2TBLexar NM790
1TB m.2 2280
Maxiotek
MAP1602A
single side design
4TB available
Product page

More Info
Goodram PX700 2TBGoodram PX700
1TB m.2 2280
Maxiotek
MAP1602A
single side design
4TB available
Product page
Micron 2400 2TBMicron 2400
1TB m.2 2230
Silicon Motion
SM2269XT
single side design
~4 W (Max)
small 2230 form factor
low power consumption
low heat
Product page

More Info
Samsung 980 ProSamsung 980 Pro
1TB m.2 2280
Samsung
Elpis (S4LV003)
single side design
7.2 W (Max)
Product page

More Info
+

NVMe Hat (Pi 5) (CL)

+

Same HATs as recommended for the EL client can be used here with a smaller NVMe drive. See NVMe Hat (Pi 5) (EL) section above.

+

USB to NVMe adapters (CL)

+

Same adapters as recommended for the EL client can be used here with a smaller NVMe drive. See USB to NVMe adapters (EL) section above.

+
+

SD Card Reader and Writer

+

You only need one SD card reader/writer to flash the operating system onto both microSD cards.

+

MicroSD Cards

+

You will need two microSD cards, one for each Raspberry Pi.

+
    +
  • Requirement: 32GB minimum capacity. Faster cards can improve boot times.
  • +
  • Recommendations: (Refer to the list in the Single Mode Recommendations) - purchase two cards.
  • +
+

Power Supplies

+

You will need two power supplies, one appropriate for each Raspberry Pi model you are using.

+
    +
  • Raspberry Pi 5: Official Raspberry Pi 27W USB-C Power Supply (5.1V/5A) is strongly recommended.
  • +
  • Raspberry Pi 4: Official Raspberry Pi 15.3W USB-C Power Supply (5.1V/3A) is strongly recommended.
  • +
  • CM4: Depends on the carrier board requirements. Check the carrier board documentation.
  • +
+

Using the official power supplies ensures stability, especially when powering connected peripherals like SSDs.

+

Enclosures and Active Cooling

+

Active cooling is mandatory for both Raspberry Pi devices in a dual-node setup to prevent thermal throttling and ensure stability. You will need two enclosures with active cooling.

+
    +
  • For Raspberry Pi 5: Choose one of the recommended cases with integrated fan/heatsink. (Refer to Single Mode Recommendations).
  • +
  • For Raspberry Pi 4: Choose one of the recommended cases with integrated fan/heatsink. (Refer to Single Mode Recommendations).
  • +
  • For CM4: Ensure your chosen carrier board has a suitable active cooling solution attached or available.
  • +
+

Optional: LCD Display

+

You can add an optional LCD display to either or both Raspberry Pi devices for at-a-glance monitoring.

+ +

Networking

+
    +
  • You will need two Ethernet cables (Cat5e or better).
  • +
  • Ensure your router or network switch has at least two available Gigabit Ethernet ports.
  • +
  • Refer to the main Prerequisites Guide for internet speed requirements.
  • +
+
+

Choosing the right hardware ensures a stable and performant dual-device node setup. Remember to clearly label your devices during assembly to avoid confusion!

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/dual-mode/installation-monitoring/index.html b/setup/dual-mode/installation-monitoring/index.html new file mode 100644 index 0000000..3cc7368 --- /dev/null +++ b/setup/dual-mode/installation-monitoring/index.html @@ -0,0 +1,2436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Installation Monitoring - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi: Installation Monitoring Guide - Dual Device Node

+

Once you have flashed the boot cards and assembled the hardware, you're ready to install and activate Web3 Pi in dual device mode.

+

Pre-installation Checklist

+
    +
  • +

    Ensure your Raspberry Pis have active cooling.

    +
  • +
  • +

    The Execution Client should have at least 2 TB SSD storage. The Consensus Client needs at least 256 GB.

    +
  • +
  • +

    You should have flashed two boot cards. Make sure the boot card for the Execution Client is loaded into the machine with the larger SSD drive, and the Consensus Client boot card is inserted into the smaller one.

    +
  • +
  • +

    Make sure the devices are protected against power surges with a UPS

    +
  • +
  • +

    Connect the Raspberry Pis using Ethernet cables to your network, and ensure the network is connected to the Internet.

    +
  • +
+

Installation

+

Installation will begin automatically as soon as you connect the power cable.

+

You can monitor the installation on both devices by entering the corresponding addresses in your browser, based on the +previously defined hostnames. The default values are:

+ +

Replace eop-1-exec and eop-1-cons with your hostnames that you entered during the microSD card burning process.

+

After approximately 3 minutes from powering on the device for the first time, you should see a similar page - for both devices.

+

Installation Monitoring

+
+

Note

+

Leave the device for about 8-15 minutes to complete the installation process. +Do not disconnect power during this time. +The time may vary depending on the bandwidth of the internet connection.

+
+

Grafana Dashboard Access

+

Grafana Dashboard

+

Next, click the link to the Grafana dashboard. If everything has gone smoothly, you should see the login panel. The default username is 'admin', and the password is 'admin'. You will be required to change the password upon first login.

+

In the Grafana Panel, click on the dashboard named 'Ethereum Nodes Monitor'.

+

Grafana Dashboard

+
+

Note

+

Pay attention to the status of the consensus and execution clients. Initially, both will be 'inactive'

+
+

Grafana Dashboard

+

In the next step, the execution client will change to 'waiting'.

+

Then both will transition to the 'syncing' state.

+

Grafana URL: http://eop-1-exec.local:3000

+

Blockchain Synchronization

+

At this point, the blockchain synchronization process will begin and will take approximately 19 hours.

+

The CPU load will increase until it reaches its maximum possible value.

+

Disk usage will grow to around 1.2TB.

+

Synchronization Complete

+

Synced Status

+

Full synchronization will be achieved when the status of both the execution and consensus clients turns green ("synced").

+

SSH Access

+

SSH Access

+

After the installation completes successfully, you should have SSH access to the Web3 Pi node.

+

Username: ethereum
+Password: ethereum

+

You can use [your-hostname].local as the SSH address or the IP address if you know it.

+

If the 'ethereum' user does not exist, it means the installation failed unexpectedly (in such case, please contact support).

+

By default, the ethereum user is required to change the password during the first login.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/dual-mode/software-setup/index.html b/setup/dual-mode/software-setup/index.html new file mode 100644 index 0000000..1837bfd --- /dev/null +++ b/setup/dual-mode/software-setup/index.html @@ -0,0 +1,2528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Software Setup - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi Image Installation Guide

+

This guide will walk you through the process of writing the Web3 Pi image to a microSD card using the Web3 Pi Imager tool.

+

Getting Started

+

Follow the instructions below to write images on the microSD card:

+
    +
  1. Download and install Web3 Pi Imager
  2. +
  3. Insert the microSD card into the card reader and connect the reader to your PC
  4. +
  5. Open the Web3 Pi Imager on your PC
  6. +
  7. Choose the Single Mode Device
  8. +
+

Web3 Pi Imager selection screen

+

Configuration Options

+

For Dual Mode device, the following settings can be configured. In this mode, we set separate hostnames, clients, and ports for the execution layer device and the consensus layer device:

+
    +
  • Image version: Default is the latest version of Web3 Pi Image
  • +
  • Default Ethereum Network: Choice between Mainnet, Sepolia, or Holesky
  • +
  • Execution device hostname: Use a unique hostname for the execution layer device. Default is eop-1-exec.local
  • +
  • Consensus device hostname: Use a unique hostname for the consensus layer device. Default is eop-1-cons.local
  • +
  • Execution client: Choose between Geth or Disabled for the execution layer device
  • +
  • Execution Port for Geth: 30303 for the execution layer device
  • +
  • Consensus Client: Choose between Nimbus or Lighthouse for the consensus layer device
  • +
  • Consensus Client Port: 9000 for the consensus layer device
  • +
  • Enable Grafana Monitoring: Turn on the advanced monitoring system by Grafana
  • +
  • Format storage: Option to format external storage during installation
  • +
+

Configuration options screen

+

Advanced Options

+

If you click the ADVANCED button, you can configure these additional options. These advanced settings will be applied to both the execution layer device and the consensus layer device:

+
    +
  • Execution endpoint address: Optional custom endpoint for execution client
  • +
  • Locale settings: Including:
      +
    • Time zone selection
    • +
    • Keyboard layout
    • +
    +
  • +
  • Wireless LAN configuration: Including:
      +
    • SSID
    • +
    • Password
    • +
    • Wireless LAN country selection
    • +
    +
  • +
+
+

Note

+

Wired Ethernet connection is recommended over Wi-Fi to ensure better synchronization.

+
+

Advanced configuration screen

+

Drive Selection

+

After setting up the configuration and clicking NEXT, a dialog box will appear allowing you to select the drive where the image with settings will be saved.

+
    +
  • Drive selection: The list shows available storage devices
  • +
  • Display options:
      +
    • By default, only drives smaller than 300GB are displayed
    • +
    • Checking the "Show large external storage device" option will display devices larger than 300GB
    • +
    +
  • +
+
+

Note

+

Make sure you select the correct drive to avoid data loss on other devices

+
+

Drive selection screen

+

Accepting Terms of Use

+

After selecting your target drive, a warning dialog will appear informing you that all data on the selected device will be erased.

+
    +
  • Confirms that all existing data on the selected drive will be permanently deleted
  • +
  • You must accept the terms to proceed (the "Yes" button remains disabled until accepted). The full Terms of Use can be found at www.web3pi.io/terms.
  • +
+
+

Note

+

This is your final confirmation before the write process begins - ensure you have selected the correct device

+
+

Data loss warning screen

+

Writing Process - Execution Device

+

After confirmation, the writing process for execution device begins.

+
    +
  • Writing process: The progress bar displays the current writing status
  • +
  • Verification phase: After the writing completes, the verification process automatically starts. This step ensures data integrity and proper image installation.
  • +
+
+

Note

+

Do not disconnect or remove the storage device until both the writing and verification processes are complete

+
+

Writing progress screen

+

Replacing cards

+

At this point, you need to switch the SD cards. Remove the card that has been written for the execution layer device and insert a new card for the consensus layer device. +Select the appropriate storage destination for the consensus device image and confirm by clicking "Yes" to proceed with the writing process.

+

Replacing cards screen

+

Writing Process - Consensus Device

+

Now the process of writing the card for the consensus layer device will begin.

+
    +
  • Writing process: The progress bar displays the current writing status
  • +
  • Verification phase: After the writing completes, the verification process automatically starts. This step ensures data integrity and proper image installation.
  • +
+
+

Note

+

Do not disconnect or remove the storage device until both the writing and verification processes are complete

+
+

Writing progress screen

+

Final Steps

+

Now that you have both cards prepared, you will see a message about the possibility of installing the prepared cards in the appropriate devices. +At this stage, you can click NEXT and complete the process. In the following steps, you will be able to assemble both devices and install the prepared cards in the corresponding slots.

+

Final screen +Final screen

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/setup/img/EditSettings.jpg b/setup/img/EditSettings.jpg similarity index 100% rename from docs/setup/img/EditSettings.jpg rename to setup/img/EditSettings.jpg diff --git a/docs/setup/img/assembly/aluminium.png b/setup/img/assembly/aluminium.png similarity index 100% rename from docs/setup/img/assembly/aluminium.png rename to setup/img/assembly/aluminium.png diff --git a/docs/setup/img/assembly/board.png b/setup/img/assembly/board.png similarity index 100% rename from docs/setup/img/assembly/board.png rename to setup/img/assembly/board.png diff --git a/docs/setup/img/assembly/cable1.png b/setup/img/assembly/cable1.png similarity index 100% rename from docs/setup/img/assembly/cable1.png rename to setup/img/assembly/cable1.png diff --git a/docs/setup/img/assembly/cable2.png b/setup/img/assembly/cable2.png similarity index 100% rename from docs/setup/img/assembly/cable2.png rename to setup/img/assembly/cable2.png diff --git a/docs/setup/img/assembly/case.png b/setup/img/assembly/case.png similarity index 100% rename from docs/setup/img/assembly/case.png rename to setup/img/assembly/case.png diff --git a/docs/setup/img/assembly/cover.png b/setup/img/assembly/cover.png similarity index 100% rename from docs/setup/img/assembly/cover.png rename to setup/img/assembly/cover.png diff --git a/docs/setup/img/assembly/fan.png b/setup/img/assembly/fan.png similarity index 100% rename from docs/setup/img/assembly/fan.png rename to setup/img/assembly/fan.png diff --git a/docs/setup/img/assembly/final.png b/setup/img/assembly/final.png similarity index 100% rename from docs/setup/img/assembly/final.png rename to setup/img/assembly/final.png diff --git a/docs/setup/img/assembly/final0.png b/setup/img/assembly/final0.png similarity index 100% rename from docs/setup/img/assembly/final0.png rename to setup/img/assembly/final0.png diff --git a/docs/setup/img/assembly/heatsink1.png b/setup/img/assembly/heatsink1.png similarity index 100% rename from docs/setup/img/assembly/heatsink1.png rename to setup/img/assembly/heatsink1.png diff --git a/docs/setup/img/assembly/heatsink2.png b/setup/img/assembly/heatsink2.png similarity index 100% rename from docs/setup/img/assembly/heatsink2.png rename to setup/img/assembly/heatsink2.png diff --git a/docs/setup/img/assembly/lcd.png b/setup/img/assembly/lcd.png similarity index 100% rename from docs/setup/img/assembly/lcd.png rename to setup/img/assembly/lcd.png diff --git a/docs/setup/img/assembly/lcd2.png b/setup/img/assembly/lcd2.png similarity index 100% rename from docs/setup/img/assembly/lcd2.png rename to setup/img/assembly/lcd2.png diff --git a/docs/setup/img/assembly/m2.png b/setup/img/assembly/m2.png similarity index 100% rename from docs/setup/img/assembly/m2.png rename to setup/img/assembly/m2.png diff --git a/docs/setup/img/assembly/microsd.png b/setup/img/assembly/microsd.png similarity index 100% rename from docs/setup/img/assembly/microsd.png rename to setup/img/assembly/microsd.png diff --git a/docs/setup/img/assembly/nvme.png b/setup/img/assembly/nvme.png similarity index 100% rename from docs/setup/img/assembly/nvme.png rename to setup/img/assembly/nvme.png diff --git a/docs/setup/img/assembly/nvme2.png b/setup/img/assembly/nvme2.png similarity index 100% rename from docs/setup/img/assembly/nvme2.png rename to setup/img/assembly/nvme2.png diff --git a/docs/setup/img/assembly/nvme3.png b/setup/img/assembly/nvme3.png similarity index 100% rename from docs/setup/img/assembly/nvme3.png rename to setup/img/assembly/nvme3.png diff --git a/docs/setup/img/assembly/parts.png b/setup/img/assembly/parts.png similarity index 100% rename from docs/setup/img/assembly/parts.png rename to setup/img/assembly/parts.png diff --git a/docs/setup/img/assembly/pcie.png b/setup/img/assembly/pcie.png similarity index 100% rename from docs/setup/img/assembly/pcie.png rename to setup/img/assembly/pcie.png diff --git a/docs/setup/img/assembly/plug.png b/setup/img/assembly/plug.png similarity index 100% rename from docs/setup/img/assembly/plug.png rename to setup/img/assembly/plug.png diff --git a/docs/setup/img/assembly/thermal.png b/setup/img/assembly/thermal.png similarity index 100% rename from docs/setup/img/assembly/thermal.png rename to setup/img/assembly/thermal.png diff --git a/docs/setup/img/assembly/thermal2.png b/setup/img/assembly/thermal2.png similarity index 100% rename from docs/setup/img/assembly/thermal2.png rename to setup/img/assembly/thermal2.png diff --git a/docs/setup/img/dual/w3p-finish.png b/setup/img/dual/w3p-finish.png similarity index 100% rename from docs/setup/img/dual/w3p-finish.png rename to setup/img/dual/w3p-finish.png diff --git a/docs/setup/img/dual/w3p-imager.png b/setup/img/dual/w3p-imager.png similarity index 100% rename from docs/setup/img/dual/w3p-imager.png rename to setup/img/dual/w3p-imager.png diff --git a/docs/setup/img/dual/w3p-inserting.png b/setup/img/dual/w3p-inserting.png similarity index 100% rename from docs/setup/img/dual/w3p-inserting.png rename to setup/img/dual/w3p-inserting.png diff --git a/docs/setup/img/dual/w3p-replacing.png b/setup/img/dual/w3p-replacing.png similarity index 100% rename from docs/setup/img/dual/w3p-replacing.png rename to setup/img/dual/w3p-replacing.png diff --git a/docs/setup/img/dual/w3p-settings.png b/setup/img/dual/w3p-settings.png similarity index 100% rename from docs/setup/img/dual/w3p-settings.png rename to setup/img/dual/w3p-settings.png diff --git a/docs/setup/img/dual/w3p-writing1.png b/setup/img/dual/w3p-writing1.png similarity index 100% rename from docs/setup/img/dual/w3p-writing1.png rename to setup/img/dual/w3p-writing1.png diff --git a/docs/setup/img/dual/w3p-writing2.png b/setup/img/dual/w3p-writing2.png similarity index 100% rename from docs/setup/img/dual/w3p-writing2.png rename to setup/img/dual/w3p-writing2.png diff --git a/docs/setup/img/dual_advanced.jpg b/setup/img/dual_advanced.jpg similarity index 100% rename from docs/setup/img/dual_advanced.jpg rename to setup/img/dual_advanced.jpg diff --git a/docs/setup/img/dual_consensus_complete.jpg b/setup/img/dual_consensus_complete.jpg similarity index 100% rename from docs/setup/img/dual_consensus_complete.jpg rename to setup/img/dual_consensus_complete.jpg diff --git a/docs/setup/img/dual_execution_complete.jpg b/setup/img/dual_execution_complete.jpg similarity index 100% rename from docs/setup/img/dual_execution_complete.jpg rename to setup/img/dual_execution_complete.jpg diff --git a/docs/setup/img/dual_network.jpg b/setup/img/dual_network.jpg similarity index 100% rename from docs/setup/img/dual_network.jpg rename to setup/img/dual_network.jpg diff --git a/docs/setup/img/dual_network2.jpg b/setup/img/dual_network2.jpg similarity index 100% rename from docs/setup/img/dual_network2.jpg rename to setup/img/dual_network2.jpg diff --git a/docs/setup/img/dual_web3pi_settings.jpg b/setup/img/dual_web3pi_settings.jpg similarity index 100% rename from docs/setup/img/dual_web3pi_settings.jpg rename to setup/img/dual_web3pi_settings.jpg diff --git a/docs/setup/img/dual_write_complete.jpg b/setup/img/dual_write_complete.jpg similarity index 100% rename from docs/setup/img/dual_write_complete.jpg rename to setup/img/dual_write_complete.jpg diff --git a/docs/setup/img/dual_write_consensus.jpg b/setup/img/dual_write_consensus.jpg similarity index 100% rename from docs/setup/img/dual_write_consensus.jpg rename to setup/img/dual_write_consensus.jpg diff --git a/docs/setup/img/dual_write_execution.jpg b/setup/img/dual_write_execution.jpg similarity index 100% rename from docs/setup/img/dual_write_execution.jpg rename to setup/img/dual_write_execution.jpg diff --git a/docs/setup/img/hw/ArgonNeo5.jpg b/setup/img/hw/ArgonNeo5.jpg similarity index 100% rename from docs/setup/img/hw/ArgonNeo5.jpg rename to setup/img/hw/ArgonNeo5.jpg diff --git a/docs/setup/img/hw/ArgonNeo5NVMe.jpg b/setup/img/hw/ArgonNeo5NVMe.jpg similarity index 100% rename from docs/setup/img/hw/ArgonNeo5NVMe.jpg rename to setup/img/hw/ArgonNeo5NVMe.jpg diff --git a/docs/setup/img/hw/ArgonOneV2.jpg b/setup/img/hw/ArgonOneV2.jpg similarity index 100% rename from docs/setup/img/hw/ArgonOneV2.jpg rename to setup/img/hw/ArgonOneV2.jpg diff --git a/docs/setup/img/hw/ArgonOneV2m2.webp b/setup/img/hw/ArgonOneV2m2.webp similarity index 100% rename from docs/setup/img/hw/ArgonOneV2m2.webp rename to setup/img/hw/ArgonOneV2m2.webp diff --git a/docs/setup/img/hw/Goodram_PX700.jpg b/setup/img/hw/Goodram_PX700.jpg similarity index 100% rename from docs/setup/img/hw/Goodram_PX700.jpg rename to setup/img/hw/Goodram_PX700.jpg diff --git a/docs/setup/img/hw/Lexar_NM790.jpg b/setup/img/hw/Lexar_NM790.jpg similarity index 100% rename from docs/setup/img/hw/Lexar_NM790.jpg rename to setup/img/hw/Lexar_NM790.jpg diff --git a/docs/setup/img/hw/Micron2230.jpg b/setup/img/hw/Micron2230.jpg similarity index 100% rename from docs/setup/img/hw/Micron2230.jpg rename to setup/img/hw/Micron2230.jpg diff --git a/docs/setup/img/hw/Samsung980Pro.jpg b/setup/img/hw/Samsung980Pro.jpg similarity index 100% rename from docs/setup/img/hw/Samsung980Pro.jpg rename to setup/img/hw/Samsung980Pro.jpg diff --git a/docs/setup/img/hw/SamsungT7_1.jpg b/setup/img/hw/SamsungT7_1.jpg similarity index 100% rename from docs/setup/img/hw/SamsungT7_1.jpg rename to setup/img/hw/SamsungT7_1.jpg diff --git a/docs/setup/img/hw/argon-one-v3-m2.jpg b/setup/img/hw/argon-one-v3-m2.jpg similarity index 100% rename from docs/setup/img/hw/argon-one-v3-m2.jpg rename to setup/img/hw/argon-one-v3-m2.jpg diff --git a/docs/setup/img/hw/argon-one-v3.jpg b/setup/img/hw/argon-one-v3.jpg similarity index 100% rename from docs/setup/img/hw/argon-one-v3.jpg rename to setup/img/hw/argon-one-v3.jpg diff --git a/docs/setup/img/hw/caseJustpiRpi4.jpg b/setup/img/hw/caseJustpiRpi4.jpg similarity index 100% rename from docs/setup/img/hw/caseJustpiRpi4.jpg rename to setup/img/hw/caseJustpiRpi4.jpg diff --git a/docs/setup/img/hw/caseJustpiRpi4_2.jpg b/setup/img/hw/caseJustpiRpi4_2.jpg similarity index 100% rename from docs/setup/img/hw/caseJustpiRpi4_2.jpg rename to setup/img/hw/caseJustpiRpi4_2.jpg diff --git a/docs/setup/img/img-example-setup-1.jpg b/setup/img/img-example-setup-1.jpg similarity index 100% rename from docs/setup/img/img-example-setup-1.jpg rename to setup/img/img-example-setup-1.jpg diff --git a/docs/setup/img/img-example-setup-NVMe.jpg b/setup/img/img-example-setup-NVMe.jpg similarity index 100% rename from docs/setup/img/img-example-setup-NVMe.jpg rename to setup/img/img-example-setup-NVMe.jpg diff --git a/docs/setup/img/img-example-setup-USB.jpg b/setup/img/img-example-setup-USB.jpg similarity index 100% rename from docs/setup/img/img-example-setup-USB.jpg rename to setup/img/img-example-setup-USB.jpg diff --git a/docs/setup/img/img-rpi4-connection-diagram-1.png b/setup/img/img-rpi4-connection-diagram-1.png similarity index 100% rename from docs/setup/img/img-rpi4-connection-diagram-1.png rename to setup/img/img-rpi4-connection-diagram-1.png diff --git a/docs/setup/img/img-rpi5-connection-diagram-1.png b/setup/img/img-rpi5-connection-diagram-1.png similarity index 100% rename from docs/setup/img/img-rpi5-connection-diagram-1.png rename to setup/img/img-rpi5-connection-diagram-1.png diff --git a/docs/setup/img/install1.jpg b/setup/img/install1.jpg similarity index 100% rename from docs/setup/img/install1.jpg rename to setup/img/install1.jpg diff --git a/docs/setup/img/install2.jpg b/setup/img/install2.jpg similarity index 100% rename from docs/setup/img/install2.jpg rename to setup/img/install2.jpg diff --git a/docs/setup/img/install3.jpg b/setup/img/install3.jpg similarity index 100% rename from docs/setup/img/install3.jpg rename to setup/img/install3.jpg diff --git a/docs/setup/img/install4.jpg b/setup/img/install4.jpg similarity index 100% rename from docs/setup/img/install4.jpg rename to setup/img/install4.jpg diff --git a/docs/setup/img/install5.jpg b/setup/img/install5.jpg similarity index 100% rename from docs/setup/img/install5.jpg rename to setup/img/install5.jpg diff --git a/docs/setup/img/install6.jpg b/setup/img/install6.jpg similarity index 100% rename from docs/setup/img/install6.jpg rename to setup/img/install6.jpg diff --git a/docs/setup/img/install7.jpg b/setup/img/install7.jpg similarity index 100% rename from docs/setup/img/install7.jpg rename to setup/img/install7.jpg diff --git a/docs/setup/img/lcd-dashboard.jpg b/setup/img/lcd-dashboard.jpg similarity index 100% rename from docs/setup/img/lcd-dashboard.jpg rename to setup/img/lcd-dashboard.jpg diff --git a/docs/setup/img/sd_reader_and_card.jpg b/setup/img/sd_reader_and_card.jpg similarity index 100% rename from docs/setup/img/sd_reader_and_card.jpg rename to setup/img/sd_reader_and_card.jpg diff --git a/docs/setup/img/single/grafana0.png b/setup/img/single/grafana0.png similarity index 100% rename from docs/setup/img/single/grafana0.png rename to setup/img/single/grafana0.png diff --git a/docs/setup/img/single/grafana1.png b/setup/img/single/grafana1.png similarity index 100% rename from docs/setup/img/single/grafana1.png rename to setup/img/single/grafana1.png diff --git a/docs/setup/img/single/grafana2.png b/setup/img/single/grafana2.png similarity index 100% rename from docs/setup/img/single/grafana2.png rename to setup/img/single/grafana2.png diff --git a/docs/setup/img/single/grafana3.png b/setup/img/single/grafana3.png similarity index 100% rename from docs/setup/img/single/grafana3.png rename to setup/img/single/grafana3.png diff --git a/docs/setup/img/single/install.png b/setup/img/single/install.png similarity index 100% rename from docs/setup/img/single/install.png rename to setup/img/single/install.png diff --git a/docs/setup/img/single/ssh.png b/setup/img/single/ssh.png similarity index 100% rename from docs/setup/img/single/ssh.png rename to setup/img/single/ssh.png diff --git a/docs/setup/img/single/w3p-accept.png b/setup/img/single/w3p-accept.png similarity index 100% rename from docs/setup/img/single/w3p-accept.png rename to setup/img/single/w3p-accept.png diff --git a/docs/setup/img/single/w3p-advanced.png b/setup/img/single/w3p-advanced.png similarity index 100% rename from docs/setup/img/single/w3p-advanced.png rename to setup/img/single/w3p-advanced.png diff --git a/docs/setup/img/single/w3p-drive.png b/setup/img/single/w3p-drive.png similarity index 100% rename from docs/setup/img/single/w3p-drive.png rename to setup/img/single/w3p-drive.png diff --git a/docs/setup/img/single/w3p-imager.png b/setup/img/single/w3p-imager.png similarity index 100% rename from docs/setup/img/single/w3p-imager.png rename to setup/img/single/w3p-imager.png diff --git a/docs/setup/img/single/w3p-inserting.png b/setup/img/single/w3p-inserting.png similarity index 100% rename from docs/setup/img/single/w3p-inserting.png rename to setup/img/single/w3p-inserting.png diff --git a/docs/setup/img/single/w3p-searching.png b/setup/img/single/w3p-searching.png similarity index 100% rename from docs/setup/img/single/w3p-searching.png rename to setup/img/single/w3p-searching.png diff --git a/docs/setup/img/single/w3p-settings.png b/setup/img/single/w3p-settings.png similarity index 100% rename from docs/setup/img/single/w3p-settings.png rename to setup/img/single/w3p-settings.png diff --git a/docs/setup/img/single/w3p-track.png b/setup/img/single/w3p-track.png similarity index 100% rename from docs/setup/img/single/w3p-track.png rename to setup/img/single/w3p-track.png diff --git a/docs/setup/img/single/w3p-writing.png b/setup/img/single/w3p-writing.png similarity index 100% rename from docs/setup/img/single/w3p-writing.png rename to setup/img/single/w3p-writing.png diff --git a/docs/setup/img/storage_consensus.jpg b/setup/img/storage_consensus.jpg similarity index 100% rename from docs/setup/img/storage_consensus.jpg rename to setup/img/storage_consensus.jpg diff --git a/docs/setup/img/storage_execution.jpg b/setup/img/storage_execution.jpg similarity index 100% rename from docs/setup/img/storage_execution.jpg rename to setup/img/storage_execution.jpg diff --git a/setup/next-steps/index.html b/setup/next-steps/index.html new file mode 100644 index 0000000..1e672ca --- /dev/null +++ b/setup/next-steps/index.html @@ -0,0 +1,2460 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Next Steps - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Post-Installation: Next Steps

+

Congratulations! You've successfully assembled your hardware, flashed the Web3 Pi image, and completed the initial automated setup process. Your Ethereum node(s) should now be running and synchronizing with the network.

+

Here’s what to do next to manage, monitor, and utilize your new node:

+

1. Verify Synchronization Status

+

The most crucial step after installation is blockchain synchronization. This process can take many hours, sometimes even days, depending on your hardware, network speed, and the chosen Ethereum network (Mainnet takes the longest).

+ +

2. Access Your Node(s)

+

You have several ways to interact with the underlying system(s):

+
    +
  • +

    SSH (Command Line): For direct terminal access, advanced configuration, and troubleshooting. +

    +
      +
    • Guide: Connecting via SSH
    • +
    • Default Credentials: Username ethereum, Password ethereum
    • +
    • Action Required: You must change this password on your first SSH login.
    • +
    +
  • +
  • +

    Cockpit (Web Interface): For a graphical overview of system resources, logs, services, and basic management tasks. +

    +
      +
    • Guide: Cockpit Dashboard
    • +
    • Access: http://<your-pi-hostname-or-ip>:9090
    • +
    • Login: Use the ethereum username and the password you set via SSH (or the default ethereum if you haven't logged in via SSH yet).
    • +
    +
  • +
+
+

Dual-Mode Access

+

If you set up a dual-device node, remember that you have two separate systems. +You need to use the specific hostname for each device when connecting via SSH or Cockpit (e.g., ssh ethereum@eop-1-exec.local and ssh ethereum@eop-1-cons.local).

+
+

3. Explore Monitoring Tools

+

Beyond checking sync status, familiarize yourself with the monitoring tools:

+
    +
  • Grafana Dashboards: Detailed performance graphs for EL/CL clients, system resources.
  • +
  • Cockpit Dashboard: System-level monitoring (CPU, RAM, Disk, Network).
  • +
  • LCD Display: (Optional Hardware) At-a-glance status directly on the device.
  • +
  • HTTP API: Programmatic access to system metrics.
  • +
+

4. Learn About Management Tools

+

Web3 Pi includes tools within Cockpit to help manage your node:

+
    +
  • Web3 Pi Updater: Keep your Ethereum clients and Web3 Pi components up-to-date. Check this periodically.
  • +
  • Web3 Pi Link: Securely expose services (like your node's RPC endpoint) to the internet without complex firewall rules.
  • +
  • Script Runner: Execute useful pre-installed utility and diagnostic scripts.
  • +
+

5. Utilize Your Node

+

Now that your node is running, you can start using it:

+
    +
  • Connect Your Wallet: Point wallets like MetaMask to your own node's RPC endpoint (http://<your-pi-hostname-or-ip>:8545) for enhanced privacy and reliability. Use Web3 Pi Link for access outside your home network.
  • +
  • Transaction Firewall: Add an extra layer of security by manually approving transactions initiated from your wallet.
  • +
  • Development: Use the local RPC endpoint for developing and testing decentralized applications (dApps).
  • +
  • Staking (Advanced/Unsupported): While Web3 Pi provides the foundation, configuring for staking is complex and high-risk. Read the Staking Considerations carefully before proceeding independently.
  • +
+

6. Bookmark Support Resources

+

Keep these pages handy for future reference:

+
    +
  • Cheatsheet: Quick reference for commands, ports, and file locations.
  • +
  • Troubleshooting: Guidance for common issues (needs expansion!).
  • +
  • Contact: How to reach the Web3 Pi community (Discord, GitHub).
  • +
+

7. Consider Advanced Setup (Optional)

+

Explore options to enhance reliability and performance:

+ +
+

You've taken a significant step towards supporting the Ethereum network and gaining more control over your web3 experience. Keep exploring the documentation, join the community if you have questions, and enjoy running your own node!

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/prerequisites/index.html b/setup/prerequisites/index.html new file mode 100644 index 0000000..ac86459 --- /dev/null +++ b/setup/prerequisites/index.html @@ -0,0 +1,2377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Prerequisites - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + +

Web3 Pi: Prerequisites

+

Internet Requirements

+
    +
  • Speed: A download speed of at least 160 Mb/s (20 MB/s) is required, with 240+ Mb/s (30+ MB/s) recommended for optimal synchronization performance (allowing sync completion in under 24 hours on a Raspberry Pi 5). Slower connections will still work but will result in significantly longer synchronization times. Upload speed requirements are much lower.
  • +
  • Stability: A stable, low-latency (ping) connection is important for reliable node operation.
  • +
  • Data: Initial synchronization requires downloading approximately 1.4 TB (May 2025) of data from the Ethereum network. An unmetered internet connection is essential to avoid unexpected charges. Do not use connections with data caps.
  • +
+
+

Lan Requirements

+

You'll need a gigabit LAN, including a gigabit network switch and Ethernet cables rated Cat5e or higher. The network must support automatic DHCP configuration and have internet access.

+

Wifi Connection

+

The default and recommended method for connecting the Raspberry Pi in the Web3 Pi project is via a wired Ethernet connection with automatic DHCP configuration.

+

However, you can also connect Raspberry Pi 4/5 to the internet using the built-in WiFi module. You'd need to provide the SSID and password for your WiFi network during setup. This is documented in the setup guide.

+

Although using WiFi is possible, we strongly recommend using a wired connection. Over time, WiFi may lead to issues with connection stability and bandwidth performance.

+

Note: If you're using WiFi, do not connect the Ethernet cable.

+
+

Uninterruptible Power Supply (UPS)

+

We strongly recommend connecting your Web3 Pi device and essential networking equipment (such as a router/modem/switch) to an uninterruptible power supply (UPS). +A UPS protects against voltage fluctuations and short power outages, which are common causes of node downtime, potential disk data corruption, and may lead to penalties for validator inactivity (in Solo Staking setups).

+

See UPS Recommendations

+
+

Hardware Requirements

+

The Easiest Start: The Web3 Pi WelcomeBox
+For those who prefer a guaranteed-compatible, all-in-one solution, the Web3 Pi WelcomeBox is the recommended starting point. This kit contains all the hardware you need to run a single node.

+

If you would prefer to use your own existing Raspberry Pi, please read the Hardware Checklist to make sure you have everything you need. If you need to purchase anything further, the Hardware Recommendations document will help you choose suitable additions to your existing setup.

+

Optionally, you can also purchase an LCD screen for the Pi. This gives you a quick way of checking that your node is functional. The LCD screen is included in the WelcomeBox.

+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/single-mode/hardware-assembly/index.html b/setup/single-mode/hardware-assembly/index.html new file mode 100644 index 0000000..d89bcb7 --- /dev/null +++ b/setup/single-mode/hardware-assembly/index.html @@ -0,0 +1,2784 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Hardware Assembly - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi: Hardware Assembly

+

This assembly guide is primarily aimed at assembling the components included in the Welcome Box, but will also be useful for anyone assembling their own hardware.

+
+

Info

+

To avoid errors during the first setup, please follow the instructions precisely.

+
+

Please also see the video instructions for more information.

+

If you have a Welcome Box, unpack the contents and check them against the components listed here.

+

Components Overview

+
    +
  1. Aluminium Top Cover with Screw Points
  2. +
  3. Aluminium Case
  4. +
  5. Cooling Fins and Exhaust Vent
  6. +
  7. Fan Port Access
  8. +
  9. POE HAT Connection
  10. +
  11. GPIO Access
  12. +
  13. MIPI Ports Access
  14. +
  15. 30mm PWM Blower-type Fan
  16. +
  17. UART Connector
  18. +
  19. RTC Battery Connector Access
  20. +
  21. PCIe Port Access
  22. +
  23. PCIE Film Strip
  24. +
  25. Power Button and LED Light
  26. +
  27. THRML M.2 Heatsink
  28. +
  29. M.2 NVMe Drive Socket
  30. +
+

Component diagram of the case parts

+

Some parts are in two zipper bags. Open them and carefully pour out the contents. You will find:

+
    +
  • Screws (two types)
  • +
  • Rubber feet
  • +
  • Two ribbon cables. You need one, the other one is a spare
  • +
+

Assembly Instructions

+

1. Prepare the Raspberry Pi 5

+

Place the thermal pads on the CPU, RP1, RAM and PMIC Chip of the RPi 5.

+

There are different versions of this case on the market: +* If you have four thermal pads, place them in the areas marked in blue. +* If you have two thermal pads in the set, place them on the CPU and PMIC (left corner, near the USB-C connector).

+

Thermal pad placement diagram

+

2. Connect the Fan

+

Connect the NEO 5 fan to the RPi 5 fan connector as shown in the image. Please pay attention to how the cable is routed.

+

Fan connection diagram

+
+

Note

+

There may be a small plug inserted in the fan connector. Remove it.

+
+

Small plug

+

3. Connect PCIe Ribbon Cable

+

Connect the PCIe flat ribbon cable to the Raspberry Pi 5 PCIe port. Be careful when handling brown PCIe flip/cover. Pull up the brown flip to release the lock.

+

PCIe cable connection

+

4. Place Raspberry Pi in the Case

+

Drop in the RPi 5 inside the Argon NEO 5 M.2 NVMe Case

+
+

Warning

+

After inserting and pressing the RPi 5 into the central part of the Argon Neo 5 case, they will adhere due to the stickiness of the thermal pads. To ensure good thermal conductivity, do this once and avoid removing the RPi 5 from this part of the case again.

+
+

RPi 5 placement in case

+

5. Route the PCIe Cable

+

The PCIe flat ribbon cable should be threaded through the hole in the case, as shown in the picture.

+

PCIe cable routing +PCIe cable routing

+

6. Connect PCIe Cable to M.2 Board

+

Carefully connect the Raspberry Pi 5 with the PCIe flat ribbon cable with copper facing up to the Argon NEO 5 M.2 NVMe Carrier Board Case. Flip up the cover on the M.2 NVMe Expansion Board.

+

PCIe connection to M.2 board

+

7. Insert MicroSD Card

+

Here we want you to insert the PREVIOUSLY flashed microSD card with Web3 Pi image.

+

MicroSD card insertion

+

8. Prepare M.2 NVMe Drive Installation

+

Connect your M.2 NVMe Drive to the Argon NEO 5 M.2 NVMe Carrier Board. Detailed instructions for this process are described in the following steps.

+

M.2 NVMe preparation

+

9. Check NVMe Compatibility

+

Connect your M.2 NVMe Drive to the Argon NEO 5 M.2 NVMe Carrier Board. Detailed instructions for this process are described in the following steps.

+

This Board will accept M.2 Key M and M.2 Key B+M NVMe Storage Drive.

+
+

Warning

+

This Board will accept M.2 Key M and M.2 Key B+M NVMe Storage Drive.

+
+

NVMe compatibility illustration

+

10. Remove Heatsink Cover

+

Remove the "THRMK M.2 Heatsink" cover by unscrewing the four screws at its corners.

+

Removing heatsink cover

+

11. Adjust Screw Mount Position

+

Move the screw point on the Board to the appropriate size of your Storage Drive.

+

Adjusting screw mount

+

12. Insert NVMe Drive

+

Insert the NVMe drive into the M.2 slot as shown in the picture.

+

NVMe drive insertion

+

13. Secure the NVMe Drive

+

Screw in the NVMe drive as shown in the picture.

+

Securing NVMe drive

+

14. Apply Thermal Pad

+

Mount the thermal pad on the NVMe drive. There is no need to shorten it. Remember to remove the protective film from both sides.

+

Thermal pad application

+

15. Mount Metal Cover

+

Mount the metal cover and screw it in using four screws with conical heads.

+

Mounting metal cover

+

16. Secure Top Cover

+

Secure the Aluminium Top Cover with 2 screws.

+

Securing top cover

+

LCD Display Option

+

If you have a plastic cover with an LCD display, connect it according to the diagram instead of the original metal one.

+

LCD display connection

+
+

Warning

+

Pay attention to the positioning of the cables when mounting the cover to ensure they don't mechanically obstruct the fan blades.

+
+

LCD display connection

+

Final Assembly

+

The final result should look like this:

+
+

Note

+

Pay attention to the positioning of the cables when mounting the cover to ensure they don't mechanically obstruct the fan blades.

+
+

Image: Final assembled device

+

Hardware Connections

+

Once you have assembled your device, connect the Raspberry Pi as shown below:

+

Hardware connection diagram

+

Refer to the following images to verify your setup. These show two typical configurations:

+
    +
  • With a USB drive:
  • +
+

Assembled device with USB drive

+
    +
  • With an NVMe drive:
  • +
+

Assembled device with NVMe drive

+

Ensure all cables and storage devices are securely connected before proceeding.

+
+

Note

+

Before you connect power, make sure that the ethernet cable is connected with DHCP. Internet connection is required during the installation process.

+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/single-mode/hardware-checklist/index.html b/setup/single-mode/hardware-checklist/index.html new file mode 100644 index 0000000..8ab4611 --- /dev/null +++ b/setup/single-mode/hardware-checklist/index.html @@ -0,0 +1,2396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Required Hardware - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Web3 Pi: Hardware Checklist

+

Active cooling is required to avoid throttling and keep sufficient performance/stability of the system.

+

As a power supply, we recommend using the official Raspberry Pi power supply for your model.

+

Raspberry Pi + 2TB drive can use a significant amount of power so a sufficient power supply is important for stability.

+

Single Device Node Requirements

+

You can run a single device node on any of the following.

+ +

Optionally, you can add an LCD display for monitoring purposes.

+

Raspberry Pi 5

+ +

Raspberry Pi 4

+ +

Raspberry Pi Compute Module

+

The CM4/5 module needs a carrier board. There are many on the market.

+

Minimum requiments are:

+
    +
  • 1 x Raspberry Pi CM4/CM5 8GB
  • +
  • 1 x 2TB fast storage
  • +
  • 1 x Power supply
  • +
  • 1 x Raspberry Pi CM4 Active Cooler
  • +
  • 1 x 32GB+ storage for operating system (microSD or eMMC)
  • +
  • 1 x Motherboard
  • +
+

Installing Web3 Pi on the CM4/5 requires more knowledge. CM4/5 modules come with built-in eMMC memory, and in this case, you need to use the rpiboot application before using the Raspberry Pi Imager. If your module does not have built-in memory and uses an SD card, the installation process is similar to a standard Raspberry Pi.

+

More information can be found on the manufacturer's website: Raspberry Pi Documentation.

+

In some cases, a bootloader update may be necessary, which is described here: How to Update the Raspberry Pi Compute Module 4 Bootloader EEPROM.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/single-mode/hardware-recommendations/index.html b/setup/single-mode/hardware-recommendations/index.html new file mode 100644 index 0000000..576ff01 --- /dev/null +++ b/setup/single-mode/hardware-recommendations/index.html @@ -0,0 +1,2733 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Recommended Hardware - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Web3 Pi: Recommended Hardware

+

SSD Drive

+

2 TB fast drive is required.

+

For the Raspberry Pi 5, you have three options for storage:

+
    +
  • external USB SSD drive (wide availability)
  • +
  • m.2 NVMe drive with NVMe HAT for Raspberry Pi 5 (max performance)
  • +
  • m.2 NVMe drive with USB m.2 adapter
  • +
+

Raspberry Pi 5 has a PCIe x1 connector on board so with a special adapter m.2 NVMe drive can be used. +This option gives the maximum possible performance.

+

For the Raspberry Pi 4, you have two options for storage:

+
    +
  • external USB SSD drive (wide availability)
  • +
  • SSD drive with USB adapter
  • +
+
+

Note

+

If you use USB always choose USB 3.0 ports (blue)

+
+
+

Warning

+

Do NOT use HDD drives!

+
+ +

USB Drive

+ + + + + + + + + + + + + + + + + +
Brand/ModelCommentLink
Samsung T7Samsung T7 2TB USB 3.2Recommended for RPi4 users. Compatible with Raspberry Pi 4 and 5More Info
+
+

Tip

+

Some external disks consume more power than Raspberry Pi can deliver via USB. For Raspberry Pi 5, the max power output of the USB ports is 600mA if you're using a 3A supply, and 1600mA if you're using a 5A supply. You can edit /boot/firmware/config.txt and add usb_max_current_enable=1 to disable the current limit. Please read the documentation: Link

+
+

NVMe Drive

+

These drives need adapters.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brand/ModelControllerCommentLink
Lexar NM790 2TBLexar NM790
2TB m.2 2280
Maxiotek
MAP1602A
single side design
4TB available
Product page

More Info
Goodram PX700 2TBGoodram PX700
2TB m.2 2280
Maxiotek
MAP1602A
single side design
4TB available
Product page
Micron 2400 2TBMicron 2400
2TB m.2 2230
Silicon Motion
SM2269XT
single side design
~4 W (Max)
small 2230 form factor
low power consumption
low heat
Product page

More Info
Samsung 980 ProSamsung 980 Pro
2TB m.2 2280
Samsung
Elpis (S4LV003)
single side design
7.2 W (Max)
Product page

More Info
+
+

Note

+

Double-sided NVMe m.2 memory modules (with memory chips on both sides of the PCB) may not be fully compatible with every enclosure due to physical dimensions, specifically the height of the m.2 slot in the adapter/enclosure.

+
+

NVMe Hat

+
NVMe HAT for Raspberry Pi 5
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BrandLink
PimoroniProduct page
Raspberry Pi m.2 HatProduct page
Pineboards HatDrive: BottomProduct page
Pineboards HatDrive: TopProduct page
Pineboards HatDrive: NanoProduct page
Waveshare 26583Product page
+

We do not recommend the following:

+
    +
  • +

    KAmodRPi5 PCIe-M.2

    +
  • +
  • +

    Geekworm X1001

    +
  • +
+

SD Card Reader and Writer

+

You will use this on your PC for flashing the boot card. Since this operation takes time, we recommend a high-speed device.

+

MicroSD Card

+

Flashing a microSD card takes time, but it can be reduced by using a fast device. Additionally, using a fast micro SD card results in a shorter booting time.

+

You will require at least 32GB capacity.

+

A few examples:

+ +

More Information

+

Enclosures

+

Enclosures for Raspberry Pi 5

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brand/ModelCommentLink
Argon NEO 5 M.2 NVMeArgon NEO 5 M.2 NVMe+good cooling
+metal
-inconvenient access to the microSD card
-m.2 slot not compatible with double side NVMe
(easy modification possible with utility knife)
Product page

Shop
Argon NEO 5Argon NEO 5+easy acces to microSD card
+good cooling
+metal
Product page

Shop
Argon ONE V3 M.2 NVMeArgon ONE V3 M.2 NVMe+good cooling
+metal
-m.2 slot not compatible with double side NVMe
(easy modification possible with utility knife)
Product page

Shop
Argon ONE V3Argon ONE V3+good cooling
+metal
Product page

Shop
+

Enclosures for Raspberry Pi 4B

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brand/ModelCommentLink
Case justPiCase justPiassembly instructionsProduct page

Shop
Argon One V2Argon One V2Fan control needs additional configuration
as described by the manufacturer manual
Product page

Shop
Argon One V2 m.2Argon One V2 m.2Fan control needs additional configuration
as described by the manufacturer manual
Product page

Shop
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/single-mode/installation-monitoring/index.html b/setup/single-mode/installation-monitoring/index.html new file mode 100644 index 0000000..3ecbd70 --- /dev/null +++ b/setup/single-mode/installation-monitoring/index.html @@ -0,0 +1,2513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Installation Monitoring - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Web3 Pi: Installation Monitoring Guide - Single Device Node

+

Now that the device is assembled and the card is inside, go back to the Web3 Pi Imager and follow the instructions:

+
    +
  1. Connect the necessary cables.
  2. +
  3. Ensure Internet connection (via DHCP) is available.
  4. +
  5. Turn on the device and then press the NEXT button.
  6. +
+

Device Setup

+

SD Card Installation Complete

+
    +
  • Web3 Pi installer has been successfully written to the SD card.
  • +
  • At this point, the card is ready, and the installation process on the device begins.
  • +
  • Now the device is being searched on the network - this may take about 2 minutes, after which the user will be able to monitor the further installation process.
  • +
+

Installation Complete

+

Track Installation Progress

+

Now you can click the "Track" button - a page with the software installation process on the device will open.

+

Track Button

+
+

Note

+

From this point, the user can log in via SSH using the credentials ethereum:ethereum

+
+

Monitor Installation

+

You can monitor the installation process through a dedicated website: +http://eop-1.local

+

The monitoring should start working approximately three minutes after the device is first switched on.

+

Replace eop-1 with your hostname that you entered during the microSD card burning process in Web3 Pi Imager, if you used a name other than eop-1.

+

After approximately 3 minutes from powering on the device for the first time, you should see a similar page.

+

Installation Monitoring

+
+

Note

+

Leave the device for about 8-15 minutes to complete the installation process. +Do not disconnect power during this time. +The time may vary depending on the bandwidth of the internet connection.

+
+

Installation Web Interface

+

The Raspberry Pi with the Web3 Pi image on port 80 hosts an HTTP server that continuously displays the following in the web browser:

+
    +
  • The installation stage
  • +
  • The hostname and IP address of the device
  • +
  • The full installation log and uptime
  • +
  • A link to the Grafana dashboard and a JSON status file
  • +
+

The installation is divided into stages. The installation is complete when you see: "STAGE 100: Installation completed." This status is shown in the following screenshot.

+

Grafana Dashboard Access

+

Grafana Dashboard

+

Next, click the link to the Grafana dashboard. If everything has gone smoothly, you should see the login panel. The default username is 'admin', and the password is 'admin'. You will be required to change the password upon first login.

+

In the Grafana Panel, click on the dashboard named 'Ethereum Nodes Monitor'.

+

Grafana Dashboard

+
+

Note

+

Pay attention to the status of the consensus and execution clients. Initially, both will be 'inactive'

+
+

Grafana Dashboard

+

In the next step, the execution client will change to 'waiting'.

+

Then both will transition to the 'syncing' state.

+

Grafana URL: http://eop-1.local:3000

+

Blockchain Synchronization

+

At this point, the blockchain synchronization process will begin and will take approximately 19 hours.

+

The CPU load will increase until it reaches its maximum possible value.

+

Disk usage will grow to around 1.2TB.

+

Synchronization Complete

+

Synced Status

+

Full synchronization will be achieved when the status of both the execution and consensus clients turns green ("synced").

+

SSH Access

+

SSH Access

+

After the installation completes successfully, you should have SSH access to the Web3 Pi node.

+

Username: ethereum
+Password: ethereum

+

You can use [your-hostname].local as the SSH address or the IP address if you know it.

+

If the 'ethereum' user does not exist, it means the installation failed unexpectedly (in such case, please contact support).

+

By default, the ethereum user is required to change the password during the first login.

+

Network Configuration Verification

+

To check that the network is working correctly, SSH into the Rasberry Pi and run a ping command:

+
ping -c 4 google.com
+
+

You should see similar response:

+
PING google.com (142.250.186.206) 56(84) bytes of data.
+64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=1 ttl=59 time=2.83 ms
+64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=2 ttl=59 time=3.62 ms
+64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=3 ttl=59 time=2.23 ms
+64 bytes from waw07s05-in-f14.1e100.net (142.250.186.206): icmp_seq=4 ttl=59 time=3.73 ms
+
+--- google.com ping statistics ---
+4 packets transmitted, 4 received, 0% packet loss, time 3005ms
+rtt min/avg/max/mdev = 2.229/3.102/3.734/0.614 ms
+
+

You now have a fully operational Ethereum node running Geth and Nimbus.

+

For more information on managing, configuring and troubleshooting your node, please refer to the Managing Your Node menu.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/single-mode/software-setup/index.html b/setup/single-mode/software-setup/index.html new file mode 100644 index 0000000..d589e89 --- /dev/null +++ b/setup/single-mode/software-setup/index.html @@ -0,0 +1,2475 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Software Setup - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + +

Web3 Pi Image Installation Guide

+

This guide will walk you through the process of writing the Web3 Pi image to a microSD card using the Web3 Pi Imager tool.

+

Getting Started

+

Follow the instructions below to write images on the microSD card:

+
    +
  1. Download and install Web3 Pi Imager
  2. +
  3. Insert the microSD card into the card reader and connect the reader to your PC
  4. +
  5. Open the Web3 Pi Imager on your PC
  6. +
  7. Choose the Single Mode Device
  8. +
+

Web3 Pi Imager selection screen

+

Configuration Options

+

For Single Mode device, the following settings can be configured:

+
    +
  • Image version: Default is the latest version of Web3 Pi Image
  • +
  • Default Ethereum Network: Choice between Mainnet, Sepolia, or Holesky
  • +
  • Hostname for Raspberry Pi: Use a unique hostname. Default is eop-1.local
  • +
  • Execution client: Choose between Geth or Disabled
  • +
  • Execution Port for Geth: 30303
  • +
  • Consensus Client: Choose between Nimbus or Lighthouse
  • +
  • Consensus Client Port: 9000
  • +
  • Enable Grafana Monitoring: Turn on the advanced monitoring system by Grafana
  • +
  • Format storage: Option to format external storage during installation
  • +
+

Configuration options screen

+

Advanced Options

+

If you click the ADVANCED button, you can configure these additional options:

+
    +
  • Execution endpoint address: Optional custom endpoint for execution client
  • +
  • Locale settings: Including:
      +
    • Time zone selection
    • +
    • Keyboard layout
    • +
    +
  • +
  • Wireless LAN configuration: Including:
      +
    • SSID
    • +
    • Password
    • +
    • Wireless LAN country selection
    • +
    +
  • +
+
+

Note

+

Wired Ethernet connection is recommended over Wi-Fi to ensure better synchronization.

+
+

Advanced configuration screen

+

Drive Selection

+

After setting up the configuration and clicking NEXT, a dialog box will appear allowing you to select the drive where the image with settings will be saved.

+
    +
  • Drive selection: The list shows available storage devices
  • +
  • Display options:
      +
    • By default, only drives smaller than 300GB are displayed
    • +
    • Checking the "Show large external storage device" option will display devices larger than 300GB
    • +
    +
  • +
+
+

Note

+

Make sure you select the correct drive to avoid data loss on other devices

+
+

Drive selection screen

+

Accepting Terms of Use

+

After selecting your target drive, a warning dialog will appear informing you that all data on the selected device will be erased.

+
    +
  • Confirms that all existing data on the selected drive will be permanently deleted
  • +
  • You must accept the terms to proceed (the "Yes" button remains disabled until accepted). The full Terms of Use can be found at www.web3pi.io/terms.
  • +
+
+

Note

+

This is your final confirmation before the write process begins - ensure you have selected the correct device

+
+

Data loss warning screen

+

Writing Process

+

After confirmation, the writing process to the selected storage device begins.

+
    +
  • Writing process: The progress bar displays the current writing status
  • +
  • Verification phase: After the writing completes, the verification process automatically starts. This step ensures data integrity and proper image installation.
  • +
+
+

Note

+

Do not disconnect or remove the storage device until both the writing and verification processes are complete

+
+

Writing progress screen

+

Final Steps

+

Now after the card has finished writing you will see a screen informing you about installing the prepared card to the device.

+

Final screen

+

Keep Web3 Pi Imager open at this step and now we will start assembling the device. We will come back to this step after we finish assembling.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/supported-configurations/index.html b/setup/supported-configurations/index.html new file mode 100644 index 0000000..af75187 --- /dev/null +++ b/setup/supported-configurations/index.html @@ -0,0 +1,2343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Supported Configurations - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Supported Configurations

+ +

Web3 Pi provides a custom image file that contains all the software needed to run a full Ethereum node on Raspberry Pi single-board computers.

+
    +
  • Execution client
  • +
  • Consensus client
  • +
  • Monitoring tools
  • +
+

Available Configurations

+

The Web3 Pi node can be deployed in either a single-device or on dual-devices.

+

Single-Device Mode

+

Single device mode is the simplest configuration. For optimal performance, it is recommended to use a Raspberry Pi 5.

+

Single Mode - Hardware Checklist

+

Dual-Devices Mode

+

Dual-devices mode requires additional configuration, but a single device may start running low on resources for a Raspberry Pi 4. In dual-devices mode, one Raspberry Pi acts as the consensus client and the other as the execution client. The solution maintains performance and stability by splitting tasks across two devices.

+

The following combinations are supported:

+
    +
  • Two Raspberry Pi 5
  • +
  • Two Raspberry Pi 4
  • +
  • One Raspberry Pi 5 and one Raspberry Pi 4
  • +
  • Any combination including Compute Module 4 (CM4)
  • +
+

Dual-Devices - Hardware Checklist

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..10d3e1a --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,179 @@ + + + + https://docs.web3pi.io/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/2fa/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/OC/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/config/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/poe/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/ufw/ + 2025-06-11 + + + https://docs.web3pi.io/advanced-setup/ups/ + 2025-06-11 + + + https://docs.web3pi.io/downloads/ + 2025-06-11 + + + https://docs.web3pi.io/introduction/ethereum-node/ + 2025-06-11 + + + https://docs.web3pi.io/introduction/next-steps/ + 2025-06-11 + + + https://docs.web3pi.io/introduction/staking/ + 2025-06-11 + + + https://docs.web3pi.io/introduction/web3-pi-project/ + 2025-06-11 + + + https://docs.web3pi.io/management/ + 2025-06-11 + + + https://docs.web3pi.io/management/ssh/ + 2025-06-11 + + + https://docs.web3pi.io/management/cockpit/dashboard/ + 2025-06-11 + + + https://docs.web3pi.io/management/cockpit/web3-pi-link/ + 2025-06-11 + + + https://docs.web3pi.io/management/cockpit/web3-pi-script-runner/ + 2025-06-11 + + + https://docs.web3pi.io/management/cockpit/web3-pi-updater/ + 2025-06-11 + + + https://docs.web3pi.io/monitoring/ + 2025-06-11 + + + https://docs.web3pi.io/monitoring/grafana/ + 2025-06-11 + + + https://docs.web3pi.io/monitoring/installation-monitor/ + 2025-06-11 + + + https://docs.web3pi.io/monitoring/lcd/ + 2025-06-11 + + + https://docs.web3pi.io/monitoring/system-monitor/ + 2025-06-11 + + + https://docs.web3pi.io/setup/next-steps/ + 2025-06-11 + + + https://docs.web3pi.io/setup/prerequisites/ + 2025-06-11 + + + https://docs.web3pi.io/setup/supported-configurations/ + 2025-06-11 + + + https://docs.web3pi.io/setup/dual-mode/hardware-assembly/ + 2025-06-11 + + + https://docs.web3pi.io/setup/dual-mode/hardware-checklist/ + 2025-06-11 + + + https://docs.web3pi.io/setup/dual-mode/hardware-recommendations/ + 2025-06-11 + + + https://docs.web3pi.io/setup/dual-mode/installation-monitoring/ + 2025-06-11 + + + https://docs.web3pi.io/setup/dual-mode/software-setup/ + 2025-06-11 + + + https://docs.web3pi.io/setup/single-mode/hardware-assembly/ + 2025-06-11 + + + https://docs.web3pi.io/setup/single-mode/hardware-checklist/ + 2025-06-11 + + + https://docs.web3pi.io/setup/single-mode/hardware-recommendations/ + 2025-06-11 + + + https://docs.web3pi.io/setup/single-mode/installation-monitoring/ + 2025-06-11 + + + https://docs.web3pi.io/setup/single-mode/software-setup/ + 2025-06-11 + + + https://docs.web3pi.io/support/cheatsheet/ + 2025-06-11 + + + https://docs.web3pi.io/support/contact/ + 2025-06-11 + + + https://docs.web3pi.io/support/troubleshooting/ + 2025-06-11 + + + https://docs.web3pi.io/use-cases/solo-staking/ + 2025-06-11 + + + https://docs.web3pi.io/use-cases/transaction-firewall/ + 2025-06-11 + + + https://docs.web3pi.io/use-cases/wallet/ + 2025-06-11 + + + https://docs.web3pi.io/welcome-box/ + 2025-06-11 + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000..1f657b3 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/docs/stylesheets/extra.css b/stylesheets/extra.css similarity index 100% rename from docs/stylesheets/extra.css rename to stylesheets/extra.css diff --git a/docs/stylesheets/extra.js b/stylesheets/extra.js similarity index 100% rename from docs/stylesheets/extra.js rename to stylesheets/extra.js diff --git a/support/cheatsheet/index.html b/support/cheatsheet/index.html new file mode 100644 index 0000000..f2ebc7a --- /dev/null +++ b/support/cheatsheet/index.html @@ -0,0 +1,3139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Cheatsheet - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Cheatsheet

+

This cheatsheet provides a quick reference for commonly used commands, ports, credentials, and configurations for your Web3 Pi node.

+

Default Credentials

+

SSH Access:

+ + + + + + + + + + + + + +
UsernamePassword
ethereumethereum
+

Note: You are required to change this password upon first SSH login.

+

Grafana Dashboard:

+ + + + + + + + + + + + + +
UsernamePassword
adminadmin
+

Note: You will be prompted to change this password upon first Grafana login. It is highly recommended to do so.

+

Common Network Ports

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PortService / DescriptionAccess Method
22SSHSSH
80Installation Monitor / Status PageHTTP
3000Grafana DashboardHTTP
5052Lighthouse HTTP REST APIHTTP
5353mDNS (Avahi Daemon)mDNS
7197Basic System Monitor JSON APIHTTP
8008InfluxDB HTTP APIHTTP
8545Execution Client JSON-RPC (Geth)HTTP
8546Execution Client WebSocket RPC (Geth)HTTP
8551Execution Client Engine API (Geth)HTTP
9000Consensus Client P2P (Lighthouse)TCP/UDP
9000Consensus Client P2P (Nimbus)TCP/UDP
9090Cockpit System DashboardHTTPS
30303Execution Client P2P (Geth)TCP/UDP
+

How to find your nodes ip address or hostname

+
    +
  • Hostname: Use the hostname you chose during image creation (e.g., web3pi.local, eop-1.local). Check via SSH with hostname.
  • +
  • IP Address: Check your router's admin panel or use network scanning tools to find the IP address assigned to your Raspberry Pi.
  • +
  • LCD Display: If installed, the LCD Dashboard shows the current IP address and hostname.
  • +
+

Log files

+
    +
  • Web3 Pi Logs: /var/log/web3pi.log
  • +
  • rc.local logs: /root/first-run.flag
  • +
+

Internet connectivity

+

Bandwidth

+

To achieve optimal synchronization performance, your internet connection should have a download bandwidth of at least 160 Mb/s (20 MB/s). The upload requirement, however, is significantly lower. +The synchronization process with the Ethereum mainnet requires downloading approximately 1.2 TB of data. +[1.1 TB download, 25 GB upload - October 2024] So please be cautious if your internet connection is metered. +A slower internet connection will still function, though the synchronization process will take longer. While upload and download speeds are important, they are only one factor in determining the quality of your connection. Ideally, a stable connection with low latency (ping) is recommended. +For optimal performance, having a static public IP address is beneficial, but it is not strictly necessary.

+

WIFI

+

The default and recommended method for connecting the Raspberry Pi in the Web3 Pi project is via a wired Ethernet connection with automatic DHCP configuration. +However, you can also connect Raspberry Pi 4/5 to the internet using the built-in WiFi module. +To do so, in Raspberry Pi Imager, you must provide the SSID and password for your WiFi network.

+

Although using WiFi is possible, we strongly recommend using a wired connection. Over time, WiFi may lead to issues with connection stability and bandwidth performance.

+
+

Note

+

If you are using WIFI, do not connect the Ethernet cable to the Raspberry Pi.

+
+

Common Monitoring & Management Commands (via SSH)

+

Many system management and logging commands require elevated privileges. Use sudo before the command as shown below. You will be prompted for the ethereum user's password the first time you use sudo in a session.

+

Replace <service_name> with the actual service: w3p_geth, w3p_lighthouse-beacon, or w3p_nimbus-beacon.

+

Service Management (systemd)

+
    +
  • Check Status: See if a service is running and view recent log snippets. +
    sudo systemctl status <service_name>
    +# Example: Check Geth status
    +sudo systemctl status w3p_geth
    +
  • +
  • Start Service: +
    sudo systemctl start <service_name>
    +
  • +
  • Stop Service: +
    sudo systemctl stop <service_name>
    +
  • +
  • Restart Service: (Stop and then start) +
    sudo systemctl restart <service_name>
    +
  • +
  • Enable Service: (Start automatically on boot - usually pre-configured) +
    sudo systemctl enable <service_name>
    +
  • +
  • Disable Service: (Prevent starting automatically on boot) +
    sudo systemctl disable <service_name>
    +
  • +
+

Viewing Logs (journald)

+
    +
  • View Full Logs: Show all logs for a service (press q to exit). +
    sudo journalctl -u <service_name>
    +# Example: View all Lighthouse logs
    +sudo journalctl -u w3p_lighthouse-beacon
    +
  • +
  • Follow Logs (Live): Watch logs as they are generated (press Ctrl+C to exit). +
    sudo journalctl -f -u <service_name>
    +# Example: Follow Geth logs
    +sudo journalctl -f -u w3p_geth
    +
  • +
  • View Last N Lines: Show only the most recent log entries. +
    sudo journalctl -n 50 -u <service_name> # Shows last 50 lines
    +
  • +
  • View Logs Since Time: Show logs since a specific time (e.g., "1 hour ago", "2025-04-24 10:00:00"). +
    sudo journalctl --since "1 hour ago" -u <service_name>
    +
  • +
+

System Resource Monitoring

+
    +
  • Interactive Process Viewer: Shows CPU, Memory usage, tasks. +
    htop
    +
  • +
  • Disk Usage (Overall): Show usage for all mounted filesystems. (No sudo needed) +
    df -h
    +
  • +
  • Memory Usage: Show free and used RAM and Swap. (No sudo needed) +
    free -h
    +
  • +
  • System Uptime & Load: (No sudo needed) +
    uptime
    +
  • +
+

Node Specific Checks

+
    +
  • Geth Console (Sync Status): Attach to the Geth console. (No sudo needed) +
    geth attach http://localhost:8545
    +# Inside the console, type:
    +eth.syncing
    +# (Returns 'false' when synced, or sync progress object if syncing)
    +eth.blockNumber
    +# (Shows the latest block number Geth knows)
    +exit
    +# (To leave the console)
    +
  • +
+

System Updates

+
    +
  • Check for Available Updates: Refreshes the package list. +
    sudo apt update
    +
  • +
  • List Upgradable Packages: See what packages have updates available. (No sudo needed) +
    apt list --upgradable
    +
  • +
  • (Use with Caution) Apply Updates: Upgrades installed packages. Refer to specific Web3 Pi update guides if available. +
    sudo apt upgrade
    +
  • +
+

InfluxDB

+

InfluxDB stores device status measurements and serves as the data source for Grafana.

+

The Influx database is fed by the basic-eth2-node-monitor application, which collects data from Ethereum clients. It also receives input from the basic-system-monitor application, which gathers operating system statistics and serves them as JSON over HTTP.

+

Clearing the Database

+

To clear the database, run the following commands:

+
influx
+USE ethonrpi
+DROP SERIES FROM /.*/
+exit
+
+

Nimbus

+
    +
  • Service name: w3p_nimbus-beacon
  • +
  • Default directory: /mnt/storage/.nimbus/data/shared_mainnet_0
  • +
  • Startup script: /home/ethereum/clients/nimbus/nimbus.sh
  • +
+

Clear saved data

+
sudo rm -r /mnt/storage/.nimbus/data/shared_mainnet_0
+
+

Geth

+
    +
  • Service name: w3p_geth
  • +
  • Default directory: /mnt/storage/.ethereum
  • +
  • Startup script: /home/ethereum/clients/geth/geth.sh
  • +
+

Clear saved data

+
sudo rm -r /mnt/storage/.ethereum
+
+

Lighthouse

+
    +
  • Service name: w3p_lighthouse-beacon
  • +
  • Default directory: /mnt/storage/.lighthouse
  • +
  • Startup script: /home/ethereum/clients/lighthouse/lighthouse.sh
  • +
+

Clear saved data

+
sudo rm -r /mnt/storage/.lighthouse
+
+

JWT Secret

+

The jwt.hex file contains the JWT secret used to enable authenticated communication between the execution client and the consensus client. This file is generated by the installation script.

+
    +
  • Location: /home/ethereum/clients/secrets/jwt.hex
  • +
+

Storage

+

PCIe Generation

+

By default, Web3 Pi uses PCIe Gen 2, which is the officially supported version. However, if you have a PCIe Gen 3 capable device, you can enable it by editing the config.txt file.

+

Benefits of PCIe Gen 3:

+
    +
  • Higher Bandwidth: PCIe Gen 3 offers double the bandwidth compared to PCIe Gen 2. This means faster data transfer rates, which can be especially beneficial for high-speed storage devices like NVMe SSDs or network cards.
  • +
  • Improved Performance: For applications that are bottlenecked by PCIe bandwidth, enabling Gen 3 can significantly improve performance.
  • +
+

While Raspberry Pi 5 is designed for PCIe Gen 2, upgrading to Gen 3 can unlock more potential in compatible devices.

+
+

Warning

+

Enabling PCIe Gen 3 on a Raspberry Pi 5 can cause instability. Only proceed if you know what you're doing and have a compatible device.

+
+

Scripts

+

The Web3 Pi image comes with several useful scripts pre-installed. These can be found in the home directory of the ethereum user at /home/ethereum/scripts.

+

Below is a description of each script and how to execute them:

+
    +
  • +

    sudo ./scripts/shutdown.sh + This script gracefully shuts down the device. It first stops the services, allowing them time to finish their tasks, and then powers off the system.

    +
  • +
  • +

    sudo ./scripts/reboot.sh + This script gracefully reboots the device. It first stops the services, giving them time to complete their tasks, and then restarts the system.

    +
  • +
  • +

    sudo ./scripts/formatMe.sh + This script marks the mapped storage as "to be formatted" during the next installation. It’s useful when reinstalling the Web3 Pi image.

    +
  • +
  • +

    sudo ./scripts/versions.sh + This script checks the versions of the currently installed applications and compares them to the latest available online. It covers applications such as Geth, Nimbus, and Lighthouse.

    +
  • +
  • +

    sudo ./scripts/update_geth.sh + This script updates the Geth application to the latest available version. It stops the service, installs the new version, and then restarts the service.

    +
  • +
  • +

    sudo ./scripts/update_nimbus.sh + This script updates the Nimbus application to the latest available version. It stops the service, installs the new version, and then restarts the service.

    +
  • +
  • +

    sudo ./scripts/check_install.sh + This script checks the installation and configuration of Web3 Pi. It verifies installed packages, active services, disk and swap usage, network connectivity, and other important aspects. The output is formatted and color-coded for better readability.

    +
  • +
+

You can also view and execute scripts from the Cockpit Web3 Pi Script Runner plugin.

+

CCZE

+

Newer version of the Web3 Pi image include CCZE, which enables automatic colorization of logs from applications such as Geth and Nimbus, significantly improving readability.

+

An example how to use CCZE:

+
sudo journalctl -xfu w3p_geth.service | ccze -A
+
+sudo journalctl -xfu w3p_nimbus-beacon.service | ccze -A
+
+cat /var/log/web3pi.log | ccze
+
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/support/contact/index.html b/support/contact/index.html new file mode 100644 index 0000000..33f7e3c --- /dev/null +++ b/support/contact/index.html @@ -0,0 +1,2341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Contact - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Contact

+

Discord

+

You can reach us on our Discord server: Join Discord

+

Contact Form

+

Fill out our contact form to ask a question: Contact Form

+

GitHub

+

For issues, projects, or contributions, visit our GitHub: GitHub Repository

+

X (Twitter)

+

Follow us or send us a message on X: Follow us on X

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/support/troubleshooting/index.html b/support/troubleshooting/index.html new file mode 100644 index 0000000..db36e20 --- /dev/null +++ b/support/troubleshooting/index.html @@ -0,0 +1,2287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Troubleshooting - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Troubleshooting

+

My node cannot synchronize

+

Synchronization issues can stem from several causes. First, check your internet connection quality - unstable or slow connections significantly delay the synchronization process. Also ensure your NVMe SSD has sufficient capacity (minimum 2TB) and is functioning properly. Overheating can also slow down or interrupt synchronization, so verify that your CPU cooling is working correctly.

+

If your Raspberry Pi 4 is synchronizing too slowly, remember this is normal - the process is much faster on Raspberry Pi 5. You can check synchronization progress in the Web3 Pi panel and verify if the node is actually downloading new blocks.

+

If the problem persists, check: +- Whether your firewall is blocking required ports +- If the SSD is properly mounted and recognized by the system +- If you have the latest version of Web3 Pi software

+

For further assistance, join our Discord channel where you can get help from our community and team.

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/use-cases/solo-staking/index.html b/use-cases/solo-staking/index.html new file mode 100644 index 0000000..0b6248c --- /dev/null +++ b/use-cases/solo-staking/index.html @@ -0,0 +1,2234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Solo Staking (coming soon) - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Solo Staking with Web3 Pi

+
+

Under Development & High Risk

+

Web3 Pi does not currently offer official support for staking configurations. Proceed at your own risk.

+
+

While Web3 Pi provides the necessary Execution and Consensus client foundation, configuring it securely and reliably for staking requires significant technical expertise beyond the standard setup.

+

You are welcome to explore configuring your node for staking independently, but please be aware:

+
    +
  • This is an advanced procedure.
  • +
  • Web3 Pi does not currently offer official support for staking configurations.
  • +
  • You proceed entirely at your own risk. Mistakes can lead to financial penalties (slashing).
  • +
+

We strongly recommend thoroughly understanding the responsibilities and substantial risks involved before attempting solo staking.

+

➡️ Review Staking Risks and Considerations

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/use-cases/transaction-firewall/index.html b/use-cases/transaction-firewall/index.html new file mode 100644 index 0000000..ea59a75 --- /dev/null +++ b/use-cases/transaction-firewall/index.html @@ -0,0 +1,2842 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Transaction Firewall - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Web3 Pi Transactions Firewall

+

TxFirewall is a tool that enhances the security of interactions with the Ethereum network through local RPC endpoints. It is designed to work with the Ethereum On Raspberry Pi suite.

+

At its core, TxFirewall functions as a local transaction-intercepting proxy server. It acts as an intermediary between the user's wallet (e.g., MetaMask) and the RPC endpoint of their own W3P Ethereum node.

+

Technically, TxFirewall listens on a specific local port configured in the wallet. When the wallet sends an RPC request, TxFirewall intercepts it. Simple data read calls (like eth_getBalance) can be passed directly to the W3P node. However, critical calls like eth_sendTransaction are halted and subjected to a verification process. Crucially, this entire process happens locally on the W3P device or the user's machine, before the transaction is broadcast to the Ethereum network.

+

Firewall diagram

+

Installation

+

The firewall can be installed manually on any computer by following the instructions available at Github repository: Web3 Pi Transaction Firewall.

+

However, Web3 Pi Transaction Firewall is part of the Web3 Pi ecosystem, and the recommended installation method is through the Cockpit management system, accessible via the Web3 Pi Updater plugin. This installation approach automatically sets up the firewall along with the cockpit plugin, providing a complete graphical interface for firewall management.

+

You can easily install Transaction Firewall on your Raspberry Pi by clicking Install in the Web3 Pi Updater plugin.

+

Firewall installation

+

In this version, the entire package is installed with initial configuration. The firewall is installed as a systemd service with appropriate permissions and configuration paths to ensure it uses the local Ethereum RPC client on your Web3 Pi by default. Additionally, the package includes a Cockpit plugin for complete management and control of the firewall.

+

Configuration

+

After installation, a new item labeled Web3 Pi Tx Firewall will appear in the Cockpit Menu. +The firewall configuration window looks as follows:

+

Firewall Cockpit plugin

+
+

Note

+

You must have administrator privileges enabled to use the Web3 Pi Tx Firewall. Otherwise, you will see a warning message: +Admin warn

+
+

Main settings

+

The top menu of the panel provides options to stop and start the firewall service, as well as configure basic settings:

+

Firewall settings

+
    +
  • +

    Server Port: Port number where the main application serves the web interface for users to verify transactions.
    +Default: 8454

    +
  • +
  • +

    Proxy Port: Port number used for the proxy service. Used by RPC clients such as Metamask.
    +Default: 18500

    +
  • +
  • +

    WSS Port: Port number dedicated for WebSocket connections used between the web application and the firewall.
    +Default: 18501

    +
  • +
  • +

    RPC Endpoint: The RPC endpoint used to communicate with your Ethereum or blockchain node.
    +Default: http://localhost:8545 - local Ethereum node in Web3 Pi

    +
  • +
  • +

    Interactive Mode Timeout: Timeout duration for user decision in interactive mode (in seconds).
    +Default: 60

    +
  • +
+
+

Note

+

Changing these settings requires a restart of the firewall service.

+
+

Opening ports in OS firewall

+

The default firewall application ports are blocked by the operating system firewall (USW). This is indicated by an icon next to the listed ports.

+

Firewall ports

+

To use the Tx firewall, you need to unblock these ports. You can do this manually via the USW CLI or by clicking the lock icon next to each port.

+
+

Warning

+

If the ports are not open, attempting to access the firewall's frontend application will result in the following message: +Admin warn

+
+

Authorized address

+

In this table, you can add, edit or delete addresses that you recognize and set an appropriate name for them. This name will be displayed in the transaction verification window. It can be either an Ethereum account address or a contract address.

+

Firewall Authorized addresses

+

Known contracts

+

In this table, you can define contracts that you recognize, including both their names and ABIs. This configuration will be used to decode transaction parameters. In the verification window, the name of the smart contract function being called and its argument names will be displayed. Additionally, if the argument type is address, it will be displayed with the appropriate name if it is found as an authorized address.

+

Firewall Known contracts

+

Client configuration (Metamask)

+

You can configure any Ethereum RPC client - any wallet (including hardware wallets), or it can be an application using blockchain. This example demonstrates configuration instructions for the popular Metamask wallet.

+

For this purpose, you need to:

+

To navigate to the network configuration window in Metamask, follow the official Metamask instructions and then fill in the required fields.

+

Firewall settings

+
+

Note

+

You should use as Default RPC URL a URL consisting of your Pi's hostname or IP address and the port defined in the configuration as Proxy Port

+
+

Usage

+

To use the Tx Firewall, open the frontend application by clicking the Open Firewall App button in the top menu.

+
+

Warning

+

The Firewall in its current version works exclusively in interactive mode, which means that transactions will only be verified and rejected when the frontend application window is active. If the window is not running,
all (!) transactions will be forwarded to the specified RPC node.

+
+

Frontend App

+

After opening the application, you can monitor transactions in the window:

+

Frontend App

+

If for any reason you lose the connection, you should see a notification message.

+

Frontend App

+
+

Warning

+

Remember! In such a situation, the Firewall operates in mode: Accepts everything!

+
+

Validating transaction

+
Simple transfer
+

During a simple funds transfer from one account to another, the transfer transaction will be displayed in the application window with properly tagged addresses if they have been defined as Authorized Addresses.

+

Transfer

+

You can accept or reject the displayed transaction within the time shown on the screen.

+
+

Note

+

If you don't make a decision within the specified time, the transaction will be automatically rejected! +The decision time is set in the Interactive Mode Timeout parameter.

+
+
Contract call
+

If you execute a transaction on a smart contract - for example, an ERC-20 token transfer like Golem Network Token - by calling the transfer function on a specific smart contract, it will be decoded and displayed in the application window.

+

Contract

+
+

Note

+

Pay attention that the address fields have been tagged with names defined in the Authorized Addresses and Known Contract tables. If these definitions weren't present, the labels would display as Unknown.

+
+

If you make a transaction on a contract that hasn't been defined in the Known Contracts table, a data field with undecoded transaction data in hexadecimal format will appear in the verification window.

+

Contract

+
+

Note

+

That's why it's important to define contract ABIs for proper transaction verification!

+
+
Predefined Standard Interfaces
+

If a contract address is not matched with any entries in the Known Contracts table, the firewall attempts to recognize and match the contract data against a set of predefined standard interfaces:

+
    +
  • +

    Main standards:

    +
      +
    • ERC20 - Standard interface for fungible tokens
    • +
    • ERC721 - Standard interface for non-fungible tokens (NFTs)
    • +
    • ERC1155 - Multi-token standard
    • +
    • ERC4626 - Tokenized vault standard
    • +
    +
  • +
  • +

    Popular extensions:

    +
      +
    • ERC20Burnable - ERC20 extension allowing token burning
    • +
    +
  • +
  • +

    Popular utility contracts:

    +
      +
    • Ownable - Contracts with ownership functionality
    • +
    • AccessControl - Contracts with role-based access control
    • +
    +
  • +
+

All these interfaces are imported from the OpenZeppelin library, which provides secure and community-vetted implementations of common smart contract standards. This automatic detection allows the firewall to correctly parse and display transaction data. Contract will be tagged as Possible Interface: ...

+

Contract

+

Logs

+

While operating and using the firewall, you can view service logs in the Cockpit panel, where firewall activities are recorded:

+

logs

+

By clicking on a specific log entry, you can see its details. Logs are stored in JSON format:

+
{
+  "level": 30,
+  "time": "2025-04-25T16:54:57.819Z",
+  "pid": 33525,
+  "hostname": "eop-1",
+  "transaction": {
+    "id": "0xd1490ab481cb7c2d7713a98fa52878300d04ae750b73ed42e28724387aa840a1",
+    "from": "0x19ee20338a4c4bf8f6aebc79d9d3af2a01434119",
+    "to": "0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429",
+    "value": "0",
+    "data": "0xa9059cbb000000000000000000000000de07073781cadad26053b6d36d8768f0bd283751000000000000000000000000000000000000000000000000001c6bf526340000",
+    "labelFrom": "Bob",
+    "labelTo": "GLM Token Contract",
+    "txType": "contract-call",
+    "contractInfo": {
+      "address": "0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429",
+      "labelAddress": "Golem Contract",
+      "functionName": "transfer",
+      "args": [
+        {
+          "name": "recipient",
+          "type": "address",
+          "value": "0xdE07073781CADaD26053b6d36D8768f0bD283751",
+          "label": "Alice"
+        },
+        {
+          "name": "amount",
+          "type": "uint256",
+          "value": "8000000000000000"
+        }
+      ]
+    }
+  },
+  "msg": "Transaction accepted"
+}
+
+

Security

+

Limitations

+

Currently, only the Interactive Mode of the Firewall is available. This means that transaction verification can only be performed when the frontend application is open.

+
    +
  • If the web page is not open, the service automatically forwards all requests to the configured RPC endpoint
  • +
  • Only one web page instance may be opened at a time
  • +
  • Opening an additional webpage instance drops the old connection and redirects all queries to the current page
  • +
  • This is an asynchronous service, but it serves only one request at a time
  • +
  • Requests are not queued
  • +
  • New requests sent during the processing of a previous one are automatically forwarded to the configured RPC endpoint
  • +
+

More about threats...

+

You can read more about the need to use the Firewall together with Web3 Pi on our blog Fortify Your Ethereum Journey Web3 Pi Transaction Firewall

+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/use-cases/wallet/index.html b/use-cases/wallet/index.html new file mode 100644 index 0000000..497b341 --- /dev/null +++ b/use-cases/wallet/index.html @@ -0,0 +1,2428 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Install in your wallet - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Connect Your Wallet to Your Node

+

One of the significant advantages of running your own Ethereum node with Web3 Pi is the ability to use it as a private and trusted backend for your crypto wallets, such as MetaMask. Instead of relying on default public RPC endpoints provided by wallet developers or third parties, you can point your wallet directly to your own node running on your local network.

+

Why Connect Your Wallet to Your Own Node?

+
    +
  • Enhanced Privacy: When you use public RPC endpoints, the provider could potentially log your IP address and the wallet addresses you query. By using your own node, your transaction lookups and balance checks stay within your local network, significantly improving your privacy.
  • +
  • Increased Reliability: Public endpoints can sometimes become congested or experience downtime. Your own node provides a dedicated resource that you control, potentially offering more consistent availability (assuming your node and internet connection are stable).
  • +
  • Trust Minimization: You are directly querying the Ethereum blockchain state as validated by your node, rather than trusting a third-party provider's node. This aligns with the core principles of decentralization.
  • +
  • No Rate Limiting: Public RPCs often have rate limits to prevent abuse. Your own node doesn't impose such external limits (though it's still bound by its own processing capabilities).
  • +
+

Prerequisites

+
    +
  1. Web3 Pi Node Running: Your Raspberry Pi must be powered on, connected to your network, and the Ethereum Execution Client must be running and fully synced with the network you intend to use (e.g., Mainnet, Sepolia).
  2. +
  3. Wallet Installed: You need a wallet that supports custom RPC endpoints (MetaMask is a common example).
  4. +
  5. Node's Local IP Address or Hostname: You need to know the IP address (e.g., 192.168.1.123) or hostname (e.g., web3pi.local) of your Raspberry Pi on your local network. Consult the Cheatsheet if you're not sure how to find it.
  6. +
+

Finding Your Node's RPC Address

+

Your Execution Client exposes an RPC endpoint that wallets can connect to. You can access it in a few ways:

+
    +
  1. +

    Local IP Address: Use your node's IP address on your local network.

    +
      +
    • Format: http://<your-pi-ip-address>:<rpc-port>
    • +
    • Default Port: 8545
    • +
    • Example: http://192.168.1.123:8545
    • +
    • Use Case: Connecting from devices on the same local network (Wi-Fi).
    • +
    +
  2. +
  3. +

    Local Hostname: Use the hostname assigned to your node.

    +
      +
    • Format: http://<your-pi-hostname>:<rpc-port>
    • +
    • Default Port: 8545
    • +
    • Example: http://web3pi.local:8545
    • +
    • Use Case: Connecting from devices on the same local network (Wi-Fi). Requires local DNS resolution (mDNS/Bonjour) to work.
    • +
    +
  4. +
  5. +

    Web3 Pi Link Address (Remote Access): If you have set up Web3 Pi Link, you can use the secure public HTTPS URL provided by the service.

    +
      +
    • Format: https://<your-chosen-name>.web3pi.link
    • +
    • (Note: Web3 Pi Link handles the port mapping and provides a standard HTTPS port 443 endpoint).
    • +
    • Example: https://my-awesome-node.web3pi.link
    • +
    • Use Case: Connecting from anywhere, including outside your local network (e.g., when you're not home).
    • +
    +
  6. +
+

Choose the appropriate address based on how and where you want to connect to your node.

+

Configuration Steps (MetaMask Example)

+

MetaMask doesn't allow adding a new network with the same Chain ID as an existing one (like Mainnet 1, Sepolia 11155111, etc.). Therefore, to use your node for these standard networks, you need to edit the existing network settings in MetaMask.

+

Editing an Existing Network (e.g., Mainnet, Sepolia):

+
    +
  1. Open MetaMask: Unlock your MetaMask extension.
  2. +
  3. Network Dropdown: Click on the network selection dropdown menu (top-left).
  4. +
  5. Open the Edit Network Wizard: Click the "Edit" button in the network dropdown. + MetaMask Select a network dialog with "Edit" highlighted
  6. +
  7. +

    Update RPC URL: Locate the "Default RPC URL" field and add a new RPC URL.

    +

    MetaMask edit network wizard

    +
      +
    • For local access: http://web3pi.local:8545 or http://192.168.1.123:8545 (Use http)
    • +
    • For remote access via Web3 Pi Link: https://my-awesome-node.web3pi.link (Use https)
    • +
    +
  8. +
  9. +

    Select Your RPC Now that you've added your RPC endpoint, select it from the dropdown menu.

    +

    Default RPC URL dropdown with "Web3 Pi RPC" selected

    +
  10. +
  11. +

    (Optional) Rename the network: You can rename the network to something more descriptive (e.g., Web3 Pi Mainnet or Ethereum Mainnet (Web3 Pi)).

    +
  12. +
  13. Save: Click the "Save" button. + Your MetaMask wallet is now configured to communicate directly with your own Web3 Pi Ethereum node using the specified RPC URL.
  14. +
+

Important Considerations

+
    +
  • Local vs. Remote: Remember to use the local http://...:8545 address only when your wallet device is on the same network as the Pi. Use the https://....web3pi.link address for access from anywhere else.
  • +
  • Security (Manual Exposure): The default Web3 Pi setup exposes the RPC port (8545) only locally. Do not manually configure your router/firewall to expose port 8545 directly to the public internet unless you implement robust security. Web3 Pi Link is the recommended way to achieve secure remote access.
  • +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/welcome-box/index.html b/welcome-box/index.html new file mode 100644 index 0000000..d071462 --- /dev/null +++ b/welcome-box/index.html @@ -0,0 +1,2429 @@ + + + + + + + + + + + + + + + + + + + + + + + + + WelcomeBox - Web3 Pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + + + + + + + + + +
+
+ +
+
+ + + +
+
+ + + + + + + + + + +

Web3 Pi WelcomeBox

+

The Complete Starter Kit for Ethereum Nodes on Raspberry Pi

+

The Web3 Pi WelcomeBox is designed to be an all-in-one package, providing you with everything you need to launch your own Ethereum node on a Raspberry Pi 5 with a seamless setup experience.

+

It bundles compatible, tested hardware components to save you the time and effort of sourcing them individually.

+
+ +
+ +

What’s Inside?

+

The WelcomeBox includes the following components:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PhotoProduct nameStore Link
Raspberry Pi 5 8GB RAMRaspberry Pi 5 - 8GB RAM

The heart of Web3 Pi. Powered by an efficient ARM processor, it delivers high performance while maintaining energy efficiency.
TME / Kamami / Botland
 Official Raspberry Pi 5 power supply

Official Raspberry Pi 5 power adapter with a USB-C connector. Delivers up to 27W for stable and reliable operation.
TME / Kamami / Botland
 2TB m.2 storage: Lexar NM790 or Goodram PX700

A high-speed 2TB NVMe storage for fast blockchain synchronization. Designed for low power consumption, high performance, and long life.
Lexar NM790:
Product page / Morele.net

Goodram PX700:
Product page / Morele.net
 Argon Neo 5 NVMe enclosure

A compact enclosure with active cooling. It looks sleek, provides excellent thermal management, and fits in any corner.
Kamami / Botland
 microSD Card

High-speed microSD card (preferably 32GB or larger) for storing the operating system and node data files, with plenty of space for future expansions.
Kamami / Botland
 Color LCD Dashboard

A high-quality LCD screen designed to provide real-time insights into your node and operating system status.
Kamami / Botland
 3D Printed Cover

A custom-designed plastic cover, 3D printed specifically for Web3 Pi. It allows for mounting the LCD display while providing a sleek and functional finish.
3D models
 Ethernet Cable cat. 5e

A 2-meter high-quality Ethernet cable, included to complete the set.
TME / Kamami / Botland
 microSD Card Reader: Ugreen CR127

A handy tool for flashing your microSD card with the operating system and node data.
Ugreen / Morele.net
 microHDMI-HDMI cable

Not required for running Web3 Pi, but invaluable for troubleshooting and initial setup.
TME / Kamami / Botland
 Pliers

Handy pliers that make hardware assembly easier. Ideal for gripping small components and included for your convenience.
TME / Kamami / Botland
 Screwdrivers: PH0 and flat 3mm

A set of screwdrivers to tighten screws and secure components. Included to ensure you have everything needed to set up Web3 Pi.
PH0: TME
flat: TME
+ +

(Note: Exact components like storage brand or specific tool types may vary slightly based on availability, but will meet the required specifications.)

+
+

Assembly and Setup

+

Follow the instructions in the Full Setup Guide to assemble and configure your Web3 Pi.

+
+

Availability

+

Coming soon

+ + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file