diff --git a/.dockerignore b/.dockerignore index 8245cd53..f0ed014b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ .git -.gitignore .github -.gitattributes -READMETEMPLATE.md -README.md -api \ No newline at end of file +api +node_modules +**/node_modules +**/dist diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9483fa22..96839ca8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,123 +1,14 @@ -# Contributing to wireguard +# Contributing -## Gotchas +Keep the Dappmanager API, terminal commands, Compose service names and persisted +WireGuard identities compatible. Runtime TypeScript uses Node built-ins and the +native QR tool; new production dependencies need a concrete justification. -* While contributing make sure to make all your changes before creating a Pull Request, as our pipeline builds each commit after the PR is open. -* Read, and fill the Pull Request template - * If this is a fix for a typo (in code, documentation, or the README) please file an issue and let us sort it out. We do not need a PR - * If the PR is addressing an existing issue include, closes #\, in the body of the PR commit message -* If you want to discuss changes, you can also bring it up in [#dev-talk](https://discordapp.com/channels/354974912613449730/757585807061155840) in our [Discord server](https://linuxserver.io/discord) +Follow the [README validation procedure](../README.md#validation) before opening +a PR. Add a regression test for networking, state migration or credential changes. +Use the PR template and report the tested baseline and any untested deployment +paths. The integration test supports a disposable native host through DOCKER_HOST; +it must never replace installed services or use their live config volume. -## Common files - -| File | Use case | -| :----: | --- | -| `Dockerfile` | Dockerfile used to build amd64 images | -| `Dockerfile.aarch64` | Dockerfile used to build 64bit ARM architectures | -| `Dockerfile.armhf` | Dockerfile used to build 32bit ARM architectures | -| `Jenkinsfile` | This file is a product of our builder and should not be edited directly. This is used to build the image | -| `jenkins-vars.yml` | This file is used to generate the `Jenkinsfile` mentioned above, it only affects the build-process | -| `package_versions.txt` | This file is generated as a part of the build-process and should not be edited directly. It lists all the installed packages and their versions | -| `README.md` | This file is a product of our builder and should not be edited directly. This displays the readme for the repository and image registries | -| `readme-vars.yml` | This file is used to generate the `README.md` | - -## Readme - -If you would like to change our readme, please __**do not**__ directly edit the readme, as it is auto-generated on each commit. -Instead edit the [readme-vars.yml](https://github.com/linuxserver/docker-wireguard/edit/master/readme-vars.yml). - -These variables are used in a template for our [Jenkins Builder](https://github.com/linuxserver/docker-jenkins-builder) as part of an ansible play. -Most of these variables are also carried over to [docs.linuxserver.io](https://docs.linuxserver.io/images/docker-wireguard) - -### Fixing typos or clarify the text in the readme - -There are variables for multiple parts of the readme, the most common ones are: - -| Variable | Description | -| :----: | --- | -| `project_blurb` | This is the short excerpt shown above the project logo. | -| `app_setup_block` | This is the text that shows up under "Application Setup" if enabled | - -### Parameters - -The compose and run examples are also generated from these variables. - -We have a [reference file](https://github.com/linuxserver/docker-jenkins-builder/blob/master/vars/_container-vars-blank) in our Jenkins Builder. - -These are prefixed with `param_` for required parameters, or `opt_param` for optional parameters, except for `cap_add`. -Remember to enable param, if currently disabled. This differs between parameters, and can be seen in the reference file. - -Devices, environment variables, ports and volumes expects its variables in a certain way. - -### Devices - -```yml -param_devices: - - { device_path: "/dev/dri", device_host_path: "/dev/dri", desc: "For hardware transcoding" } -opt_param_devices: - - { device_path: "/dev/dri", device_host_path: "/dev/dri", desc: "For hardware transcoding" } -``` - -### Environment variables - -```yml -param_env_vars: - - { env_var: "TZ", env_value: "Europe/London", desc: "Specify a timezone to use EG Europe/London." } -opt_param_env_vars: - - { env_var: "VERSION", env_value: "latest", desc: "Supported values are LATEST, PLEXPASS or a specific version number." } -``` - -### Ports - -```yml -param_ports: - - { external_port: "80", internal_port: "80", port_desc: "Application WebUI" } -opt_param_ports: - - { external_port: "80", internal_port: "80", port_desc: "Application WebUI" } -``` - -### Volumes - -```yml -param_volumes: - - { vol_path: "/config", vol_host_path: "", desc: "Configuration files." } -opt_param_volumes: - - { vol_path: "/config", vol_host_path: "", desc: "Configuration files." } -``` - -### Testing template changes - -After you make any changes to the templates, you can use our [Jenkins Builder](https://github.com/linuxserver/docker-jenkins-builder) to have the files updated from the modified templates. Please use the command found under `Running Locally` [on this page](https://github.com/linuxserver/docker-jenkins-builder/blob/master/README.md) to generate them prior to submitting a PR. - -## Dockerfiles - -We use multiple Dockerfiles in our repos, this is because sometimes some CPU architectures needs different packages to work. -If you are proposing additional packages to be added, ensure that you added the packages to all the Dockerfiles in alphabetical order. - -### Testing your changes - -```bash -git clone https://github.com/linuxserver/docker-wireguard.git -cd docker-wireguard -docker build \ - --no-cache \ - --pull \ - -t linuxserver/wireguard:latest . -``` - -The ARM variants can be built on x86_64 hardware and vice versa using `lscr.io/linuxserver/qemu-static` - -```bash -docker run --rm --privileged lscr.io/linuxserver/qemu-static --reset -``` - -Once registered you can define the dockerfile to use with `-f Dockerfile.aarch64`. - -## Update the changelog - -If you are modifying the Dockerfiles or any of the startup scripts in [root](https://github.com/linuxserver/docker-wireguard/tree/master/root), add an entry to the changelog - -```yml -changelogs: - - { date: "DD.MM.YY:", desc: "Added some love to templates" } -``` +README.md is maintained directly. There is no LinuxServer/Jenkins documentation +generation, architecture-specific VPN Dockerfile or generated package inventory. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index afaa9f68..0653b12b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,29 +1,52 @@ -name: "Main" +name: Main on: pull_request: push: branches: - - "main" - - "master" + - main + - master - "v[0-9]+.[0-9]+.[0-9]+" paths-ignore: - - "README.md" + - README.md + +permissions: + contents: read jobs: + integration: + name: TypeScript, images and VPN regression + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: "24.22.1" + - run: npm install --global yarn@1.22.22 + - run: yarn --cwd api install --frozen-lockfile --non-interactive + - run: yarn --cwd api format:check + - run: docker build -t wireguard-refactor:test . + - run: docker build -t wireguard-api-refactor:test api + - run: sudo modprobe wireguard + - run: node tests/integration.cjs + timeout-minutes: 15 + build-test: + name: SDK build runs-on: ubuntu-latest - name: Build test if: github.event_name != 'push' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: npx @dappnode/dappnodesdk build --skip_save release: name: Release runs-on: ubuntu-latest + needs: integration if: github.event_name == 'push' + permissions: + contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Publish run: npx @dappnode/dappnodesdk publish patch --dappnode_team_preset env: diff --git a/Dockerfile b/Dockerfile index 1ef3b882..a07bba6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,47 +1,16 @@ # syntax=docker/dockerfile:1 - -FROM ghcr.io/linuxserver/baseimage-alpine:3.23 - -# set version label -ARG BUILD_DATE -ARG VERSION -ARG WIREGUARD_RELEASE -ARG TARGETPLATFORM -LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}" -LABEL maintainer="thespad" - -RUN \ - if [ -z "${WIREGUARD_RELEASE+x}" ]; then \ - WIREGUARD_RELEASE=$(curl -sL "http://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/APKINDEX.tar.gz" | tar -xz -C /tmp \ - && awk '/^P:wireguard-tools$/,/V:/' /tmp/APKINDEX | sed -n 2p | sed 's/^V://'); \ - fi && \ - echo "**** install dependencies ****" && \ - apk add --no-cache \ - bc \ - coredns \ - grep \ - iproute2 \ - iptables \ - ip6tables \ - iputils \ - kmod \ - libcap-utils \ - libqrencode-tools \ - net-tools \ - nftables \ - openresolv \ - wireguard-tools==${WIREGUARD_RELEASE} && \ - echo "wireguard" >> /etc/modules && \ - sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' /usr/bin/wg-quick && \ - rm -rf /etc/wireguard && \ - ln -s /config/wg_confs /etc/wireguard && \ - printf "Linuxserver.io version: ${VERSION}\nBuild-date: ${BUILD_DATE}" > /build_version && \ - echo "**** clean up ****" && \ - rm -rf \ - /tmp/* - -# add local files -COPY /root / - -# ports and volumes +FROM alpine:3.23@sha256:ff7f0c727dd796aa36787bfcd9a64bf35a6ce72a73451b3359fde849b6703d63 +RUN apk add --no-cache \ + bash curl iproute2 iptables ip6tables libqrencode-tools tini \ + 'ca-certificates>=20261020-r0' 'ca-certificates-bundle>=20261020-r0' \ + wireguard-tools && \ + update-ca-certificates && \ + mkdir -p /config /app && \ + rm -rf /etc/wireguard && ln -s /config/wg_confs /etc/wireguard +COPY wireguard /usr/local/lib/wireguard +RUN chmod +x /usr/local/lib/wireguard/*.sh /usr/local/lib/wireguard/show-peer && \ + ln -s /usr/local/lib/wireguard/show-peer /app/show-peer +RUN --mount=type=bind,source=tests/configuration.sh,target=/tmp/configuration-test.sh bash /tmp/configuration-test.sh EXPOSE 51820/udp +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s CMD ["/usr/local/lib/wireguard/healthcheck.sh"] +ENTRYPOINT ["/sbin/tini", "-g", "--", "/usr/local/lib/wireguard/start.sh"] diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 deleted file mode 100644 index 668f98d1..00000000 --- a/Dockerfile.aarch64 +++ /dev/null @@ -1,46 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.23 - -# set version label -ARG BUILD_DATE -ARG VERSION -ARG WIREGUARD_RELEASE -LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}" -LABEL maintainer="thespad" - -RUN \ - if [ -z "${WIREGUARD_RELEASE+x}" ]; then \ - WIREGUARD_RELEASE=$(curl -sL "http://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/APKINDEX.tar.gz" | tar -xz -C /tmp \ - && awk '/^P:wireguard-tools$/,/V:/' /tmp/APKINDEX | sed -n 2p | sed 's/^V://'); \ - fi && \ - echo "**** install dependencies ****" && \ - apk add --no-cache \ - bc \ - coredns \ - grep \ - iproute2 \ - iptables \ - ip6tables \ - iputils \ - kmod \ - libcap-utils \ - libqrencode-tools \ - net-tools \ - nftables \ - openresolv \ - wireguard-tools==${WIREGUARD_RELEASE} && \ - echo "wireguard" >> /etc/modules && \ - sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' /usr/bin/wg-quick && \ - rm -rf /etc/wireguard && \ - ln -s /config/wg_confs /etc/wireguard && \ - printf "Linuxserver.io version: ${VERSION}\nBuild-date: ${BUILD_DATE}" > /build_version && \ - echo "**** clean up ****" && \ - rm -rf \ - /tmp/* - -# add local files -COPY /root / - -# ports and volumes -EXPOSE 51820/udp diff --git a/README.md b/README.md index 522f5555..d659e38f 100644 --- a/README.md +++ b/README.md @@ -1,425 +1,133 @@ - - - -[![linuxserver.io](https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/linuxserver_medium.png)](https://linuxserver.io) - -[![Blog](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=Blog)](https://blog.linuxserver.io "all the things you can do with our containers including How-To guides, opinions and much more!") -[![Discord](https://img.shields.io/discord/354974912613449730.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=Discord&logo=discord)](https://linuxserver.io/discord "realtime support / chat with the community and the team.") -[![Discourse](https://img.shields.io/discourse/https/discourse.linuxserver.io/topics.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=discourse)](https://discourse.linuxserver.io "post on our community forum.") -[![GitHub](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=GitHub&logo=github)](https://github.com/linuxserver "view the source for all of our repositories.") -[![Open Collective](https://img.shields.io/opencollective/all/linuxserver.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=Supporters&logo=open%20collective)](https://opencollective.com/linuxserver "please consider helping us by either donating or contributing to our budget") - -The [LinuxServer.io](https://linuxserver.io) team brings you another container release featuring: - -* regular and timely application updates -* easy user mappings (PGID, PUID) -* custom base image with s6 overlay -* weekly base OS updates with common layers across the entire LinuxServer.io ecosystem to minimise space usage, down time and bandwidth -* regular security updates - -Find us at: - -* [Blog](https://blog.linuxserver.io) - all the things you can do with our containers including How-To guides, opinions and much more! -* [Discord](https://linuxserver.io/discord) - realtime support / chat with the community and the team. -* [Discourse](https://discourse.linuxserver.io) - post on our community forum. -* [GitHub](https://github.com/linuxserver) - view the source for all of our repositories. -* [Open Collective](https://opencollective.com/linuxserver) - please consider helping us by either donating or contributing to our budget - -# [linuxserver/wireguard](https://github.com/linuxserver/docker-wireguard) - -[![Scarf.io pulls](https://scarf.sh/installs-badge/linuxserver-ci/linuxserver%2Fwireguard?color=94398d&label-color=555555&logo-color=ffffff&style=for-the-badge&package-type=docker)](https://scarf.sh) -[![GitHub Stars](https://img.shields.io/github/stars/linuxserver/docker-wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/linuxserver/docker-wireguard) -[![GitHub Release](https://img.shields.io/github/release/linuxserver/docker-wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/linuxserver/docker-wireguard/releases) -[![GitHub Package Repository](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=GitHub%20Package&logo=github)](https://github.com/linuxserver/docker-wireguard/packages) -[![GitLab Container Registry](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=GitLab%20Registry&logo=gitlab)](https://gitlab.com/linuxserver.io/docker-wireguard/container_registry) -[![Quay.io](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=Quay.io)](https://quay.io/repository/linuxserver.io/wireguard) -[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=pulls&logo=docker)](https://hub.docker.com/r/linuxserver/wireguard) -[![Docker Stars](https://img.shields.io/docker/stars/linuxserver/wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=stars&logo=docker)](https://hub.docker.com/r/linuxserver/wireguard) -[![Jenkins Build](https://img.shields.io/jenkins/build?labelColor=555555&logoColor=ffffff&style=for-the-badge&jobUrl=https%3A%2F%2Fci.linuxserver.io%2Fjob%2FDocker-Pipeline-Builders%2Fjob%2Fdocker-wireguard%2Fjob%2Fmaster%2F&logo=jenkins)](https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-wireguard/job/master/) - -[WireGuard®](https://www.wireguard.com/) is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform (Windows, macOS, BSD, iOS, Android) and widely deployable. It is currently under heavy development, but already it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry. - -[![wireguard](https://www.wireguard.com/img/wireguard.svg)](https://www.wireguard.com/) - -## Supported Architectures - -Our images support multiple architectures such as `x86-64`, `arm64` and `armhf`. We utilise the docker manifest for multi-platform awareness. More information is available from docker [here](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list) and our announcement [here](https://blog.linuxserver.io/2019/02/21/the-lsio-pipeline-project/). - -Simply pulling `lscr.io/linuxserver/wireguard:latest` should retrieve the correct image for your arch, but you can also pull specific arch images via tags. - -The architectures supported by this image are: - -| Architecture | Available | Tag | -| :----: | :----: | ---- | -| x86-64 | ✅ | amd64-\ | -| arm64 | ✅ | arm64v8-\ | - -## Application Setup - -During container start, it will first check if the wireguard module is already installed and loaded. All currently supported kernels should have the wireguard module built-in (along with some older custom kernels). However, the module may not be enabled. Make sure it is enabled prior to starting the container. - -This can be run as a server or a client, based on the parameters used. - -## Note on iptables - -Some hosts may not load the iptables kernel modules by default. In order for the container to be able to load them, you need to assign the `SYS_MODULE` capability and add the optional `/lib/modules` volume mount. Alternatively you can `modprobe` them from the host before starting the container. - -## Server Mode - -If the environment variable `PEERS` is set to a number or a list of strings separated by comma, the container will run in server mode and the necessary server and peer/client confs will be generated. The peer/client config qr codes will be output in the docker log if `LOG_CONFS` is set to `true`. They will also be saved in text and png format under `/config/peerX` in case `PEERS` is a variable and an integer or `/config/peer_X` in case a list of names was provided instead of an integer. - -Variables `SERVERURL`, `SERVERPORT`, `INTERNAL_SUBNET`, `PEERDNS`, `INTERFACE`, `ALLOWEDIPS` and `PERSISTENTKEEPALIVE_PEERS` are optional variables used for server mode. Any changes to these environment variables will trigger regeneration of server and peer confs. Peer/client confs will be recreated with existing private/public keys. Delete the peer folders for the keys to be recreated along with the confs. - -To add more peers/clients later on, you increment the `PEERS` environment variable or add more elements to the list and recreate the container. - -To display the QR codes of active peers again, you can use the following command and list the peer numbers as arguments: `docker exec -it wireguard /app/show-peer 1 4 5` or `docker exec -it wireguard /app/show-peer myPC myPhone myTablet` (Keep in mind that the QR codes are also stored as PNGs in the config folder). - -The templates used for server and peer confs are saved under `/config/templates`. Advanced users can modify these templates and force conf generation by deleting `/config/wg_confs/wg0.conf` and restarting the container. - -The container managed server conf is hardcoded to `wg0.conf`. However, the users can add additional tunnel config files with `.conf` extensions into `/config/wg_confs/` and the container will attempt to start them all in alphabetical order. If any one of the tunnels fail, they will all be stopped and the default route will be deleted, requiring user intervention to fix the invalid conf and a container restart. - -## Client Mode - -Do not set the `PEERS` environment variable. Drop your client conf(s) into the config folder as `/config/wg_confs/.conf` and start the container. If there are multiple tunnel configs, the container will attempt to start them all in alphabetical order. If any one of the tunnels fail, they will all be stopped and the default route will be deleted, requiring user intervention to fix the invalid conf and a container restart. - -If you get IPv6 related errors in the log and connection cannot be established, edit the `AllowedIPs` line in your peer/client wg0.conf to include only `0.0.0.0/0` and not `::/0`; and restart the container. - -## Road warriors, roaming and returning home - -If you plan to use Wireguard both remotely and locally, say on your mobile phone, you will need to consider routing. Most firewalls will not route ports forwarded on your WAN interface correctly to the LAN out of the box. This means that when you return home, even though you can see the Wireguard server, the return packets will probably get lost. - -This is not a Wireguard specific issue and the two generally accepted solutions are NAT reflection (setting your edge router/firewall up in such a way as it translates internal packets correctly) or split horizon DNS (setting your internal DNS to return the private rather than public IP when connecting locally). - -Both of these approaches have positives and negatives however their setup is out of scope for this document as everyone's network layout and equipment will be different. - -## Maintaining local access to attached services - -** Note: This is not a supported configuration by Linuxserver.io - use at your own risk. - -When routing via Wireguard from another container using the `service` option in docker, you might lose access to the containers webUI locally. To avoid this, exclude the docker subnet from being routed via Wireguard by modifying your `wg0.conf` like so (modifying the subnets as you require): - - ```ini - [Interface] - PrivateKey = - Address = 9.8.7.6/32 - DNS = 8.8.8.8 - PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route add $HOMENET3 via $DROUTE;ip route add $HOMENET2 via $DROUTE; ip route add $HOMENET via $DROUTE;iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT; iptables -A OUTPUT -d $HOMENET3 -j ACCEPT; iptables -A OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT - PreDown = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route del $HOMENET3 via $DROUTE;ip route del $HOMENET2 via $DROUTE; ip route del $HOMENET via $DROUTE; iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; iptables -D OUTPUT -d $HOMENET -j ACCEPT; iptables -D OUTPUT -d $HOMENET2 -j ACCEPT; iptables -D OUTPUT -d $HOMENET3 -j ACCEPT - ``` - -## Site-to-site VPN - -** Note: This is not a supported configuration by Linuxserver.io - use at your own risk. - -Site-to-site VPN in server mode requires customizing the `AllowedIPs` statement for a specific peer in `wg0.conf`. Since `wg0.conf` is autogenerated when server vars are changed, it is not recommended to edit it manually. - -In order to customize the `AllowedIPs` statement for a specific peer in `wg0.conf`, you can set an env var `SERVER_ALLOWEDIPS_PEER_` to the additional subnets you'd like to add, comma separated and excluding the peer IP (ie. `"192.168.1.0/24,192.168.2.0/24"`). Replace `` with either the name or number of a peer (whichever is used in the `PEERS` var). - -For instance `SERVER_ALLOWEDIPS_PEER_laptop="192.168.1.0/24,192.168.2.0/24"` will result in the wg0.conf entry `AllowedIPs = 10.13.13.2,192.168.1.0/24,192.168.2.0/24` for the peer named `laptop`. - -Keep in mind that this var will only be considered when the confs are regenerated. Adding this var for an existing peer won't force a regeneration. You can delete wg0.conf and restart the container to force regeneration if necessary. - -Don't forget to set the necessary POSTUP and POSTDOWN rules in your client's peer conf for lan access. - -## Read-Only Operation - -This image can be run with a read-only container filesystem. For details please [read the docs](https://docs.linuxserver.io/misc/read-only/). - -### Caveats - -* Not supported in client mode. - -## Usage - -To help you get started creating a container from this image you can either use docker-compose or the docker cli. - ->[!NOTE] ->Unless a parameter is flagged as 'optional', it is *mandatory* and a value must be provided. - -### docker-compose (recommended, [click here for more info](https://docs.linuxserver.io/general/docker-compose)) - -```yaml ---- -services: - wireguard: - image: lscr.io/linuxserver/wireguard:latest - container_name: wireguard - cap_add: - - NET_ADMIN - - SYS_MODULE #optional - environment: - - PUID=1000 - - PGID=1000 - - TZ=Etc/UTC - - SERVERURL=wireguard.domain.com #optional - - SERVERPORT=51820 #optional - - PEERS=1 #optional - - PEERDNS=auto #optional - - INTERNAL_SUBNET=10.13.13.0 #optional - - ALLOWEDIPS=0.0.0.0/0 #optional - - PERSISTENTKEEPALIVE_PEERS= #optional - - LOG_CONFS=true #optional - volumes: - - /path/to/wireguard/config:/config - - /lib/modules:/lib/modules #optional - ports: - - 51820:51820/udp - sysctls: - - net.ipv4.conf.all.src_valid_mark=1 - restart: unless-stopped -``` - -### docker cli ([click here for more info](https://docs.docker.com/engine/reference/commandline/cli/)) - -```bash -docker run -d \ - --name=wireguard \ - --cap-add=NET_ADMIN \ - --cap-add=SYS_MODULE `#optional` \ - -e PUID=1000 \ - -e PGID=1000 \ - -e TZ=Etc/UTC \ - -e SERVERURL=wireguard.domain.com `#optional` \ - -e SERVERPORT=51820 `#optional` \ - -e PEERS=1 `#optional` \ - -e PEERDNS=auto `#optional` \ - -e INTERNAL_SUBNET=10.13.13.0 `#optional` \ - -e ALLOWEDIPS=0.0.0.0/0 `#optional` \ - -e PERSISTENTKEEPALIVE_PEERS= `#optional` \ - -e LOG_CONFS=true `#optional` \ - -p 51820:51820/udp \ - -v /path/to/wireguard/config:/config \ - -v /lib/modules:/lib/modules `#optional` \ - --sysctl="net.ipv4.conf.all.src_valid_mark=1" \ - --restart unless-stopped \ - ghcr.io/linuxserver/wireguard -``` - -## Parameters - -Containers are configured using parameters passed at runtime (such as those above). These parameters are separated by a colon and indicate `:` respectively. For example, `-p 8080:80` would expose port `80` from inside the container to be accessible from the host's IP on port `8080` outside the container. - -| Parameter | Function | -| :----: | --- | -| `-p 51820:51820/udp` | wireguard port | -| `-e PUID=1000` | for UserID - see below for explanation | -| `-e PGID=1000` | for GroupID - see below for explanation | -| `-e TZ=Etc/UTC` | specify a timezone to use, see this [list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List). | -| `-e SERVERURL=wireguard.domain.com` | External IP or domain name for docker host. Used in server mode. If set to `auto`, the container will try to determine and set the external IP automatically | -| `-e SERVERPORT=51820` | External port for docker host. Used in server mode. | -| `-e PEERS=1` | Number of peers to create confs for. Required for server mode. Can also be a list of names: `myPC,myPhone,myTablet` (alphanumeric only) | -| `-e PEERDNS=auto` | DNS server set in peer/client configs (can be set as `8.8.8.8`). Used in server mode. Defaults to `auto`, which uses wireguard docker host's DNS via included CoreDNS forward. | -| `-e INTERNAL_SUBNET=10.13.13.0` | Internal subnet for the wireguard and server and peers (only change if it clashes). Used in server mode. | -| `-e ALLOWEDIPS=0.0.0.0/0` | The IPs/Ranges that the peers will be able to reach using the VPN connection. If not specified the default value is: '0.0.0.0/0, ::0/0' This will cause ALL traffic to route through the VPN, if you want split tunneling, set this to only the IPs you would like to use the tunnel AND the ip of the server's WG ip, such as 10.13.13.1. | -| `-e PERSISTENTKEEPALIVE_PEERS=` | Set to `all` or a list of comma separated peers (ie. `1,4,laptop`) for the wireguard server to send keepalive packets to listed peers every 25 seconds. Useful if server is accessed via domain name and has dynamic IP. Used only in server mode. | -| `-e LOG_CONFS=true` | Generated QR codes will be displayed in the docker log. Set to `false` to skip log output. | -| `-v /config` | Contains all relevant configuration files. | -| `-v /lib/modules` | Path to host kernel module for situations where it's not already loaded. | -| `--sysctl=` | Required for client mode. | -| `--read-only=true` | Run container with a read-only filesystem. Please [read the docs](https://docs.linuxserver.io/misc/read-only/). | -| `--cap-add=NET_ADMIN` | Neccessary for Wireguard to create its VPN interface. | -| `--cap-add=SYS_MODULE` | Neccessary for loading Wireguard kernel module if it's not already loaded. | - -### Portainer notice - -This image utilises `cap_add` or `sysctl` to work properly. This is not implemented properly in some versions of Portainer, thus this image may not work if deployed through Portainer. - -## Environment variables from files (Docker secrets) - -You can set any environment variable from a file by using a special prepend `FILE__`. - -As an example: - -``` --e FILE__PASSWORD=/run/secrets/mysecretpassword -``` - -Will set the environment variable `MYVAR` based on the contents of the `/run/secrets/mysecretvariable` file. - -## Umask for running applications - -For all of our images we provide the ability to override the default umask settings for services started within the containers using the optional `-e UMASK=022` setting. -Keep in mind umask is not chmod it subtracts from permissions based on it's value it does not add. Please read up [here](https://en.wikipedia.org/wiki/Umask) before asking for support. - -## User / Group Identifiers - -When using volumes (`-v` flags), permissions issues can arise between the host OS and the container, we avoid this issue by allowing you to specify the user `PUID` and group `PGID`. - -Ensure any volume directories on the host are owned by the same user you specify and any permissions issues will vanish like magic. - -In this instance `PUID=1000` and `PGID=1000`, to find yours use `id user` as below: - +# Dappnode WireGuard + +A kernel-backed WireGuard server for Dappnode. The `wireguard` service configures +one `wg0` interface and forwarding/NAT. The `api` service supplies client +credentials to Dappmanager, the installer and terminal commands. Both services +support AMD64 and ARM64 with the same architecture-neutral Dockerfiles. + +## Requirements + +The host must provide WireGuard and the netfilter support used by iptables. +Enable missing modules through the host installer/administrator. The VPN +container requires only `NET_ADMIN`; it does not load host modules, mount +`/lib/modules`, use host networking or access the Docker socket. Missing kernel +support fails startup with an explicit error instead of hanging indefinitely. + +The API is internal to `dncore_network`; never publish its TCP port. It serves +private client credentials and retains the existing Dappnode network trust model. +Its config volume is read-only. The VPN service is the only config writer. + +## Dappnode contract + +- Services, container names, DNS aliases, `/config` volume and UDP port 51820 are + unchanged. Dappmanager adds/removes devices through the `PEERS` environment + variable and recreates the server container. +- `PEERS=dappnode_admin` creates the default admin. A comma-separated name list + or a numeric count is supported. An explicitly empty list keeps the server + running with no authorized peers. Removed peers lose tunnel and API access; + their files remain available for identity-preserving re-add and rollback. +- `GET /:device` returns the remote config; `?local` substitutes the host LAN IP; + `?qr` returns a PNG. The unsupported `?local&qr` combination returns 400. + Readiness is available at `/-/health`, outside the peer-name namespace. + Credentials have `Cache-Control: no-store`. Invalid device names return 400, + inactive/missing peers return 404 and local-IP discovery failures return 503. +- `getWireguardCredentials` remains installed in the `api` container. The host + `dappnode_wireguard` alias still supports `--local`, `--localhost`, `--qr` and + `--help`. Local variants retain the configured server port. +- `/app/show-peer` remains available in the VPN container. QR rendering uses + upstream `qrencode` for both services. The API remains strict TypeScript, + using Node built-ins with **zero production npm dependencies**. + +## Configuration + +| Variable | Default / behavior | +| --- | --- | +| `PEERS` | Required; Dappnode sets `dappnode_admin`. Empty means no authorized peers. | +| `SERVERURL` | Explicit host; otherwise `_DAPPNODE_GLOBAL_HOSTNAME`, then Dappmanager's `HOSTNAME` endpoint. | +| `SERVERPORT` | `51820`. Update the Compose port mapping too when changing it. | +| `PEERDNS` | `172.33.1.2,10.20.0.2` (Dappnode BIND); explicit servers only. | +| `INTERNAL_SUBNET` | `10.24.0.0`; IPv4 /24 addressing, preserving existing host IDs. | +| `ALLOWEDIPS` | `172.33.0.0/16,10.20.0.0/24`; client-side routed ranges. | +| `LOG_CONFS` | Credential contents are never logged. Use the credential CLI explicitly. | + +Generated files belong to Dappnode's UID/GID `1000:1000`, mode `0600`. +APK selects signed WireGuard tools for the target architecture. +Both images require CA bundles at least `20261020-r0` for RHSB-2026-012; Node is +pinned to the patched 24.22.1 LTS image. + +## Upgrade and rollback + +Server/peer private keys, public keys, PSKs, client addresses, peer folder names +and generated WireGuard config format are retained. Files are staged, validated +by `wg setconf` against a temporary interface, and individually replaced by +atomic rename; the server config is published last. Identical files are not +rewritten. Existing PSK-less clients stay PSK-less. Missing identities and +inconsistent key/config pairs fail instead of generating replacement identities. +Persisted `.donoteditthisfile` content is never read or executed. A sanitized +copy is written only so the currently installed Dappnode image can roll back. + +The refactor is Dappnode server-only. Preflight rejects the following before +publishing configs, rather than silently dropping unsupported behavior: + +- Client mode (no `PEERS` variable) and additional tunnel files. +- `PEERDNS=auto` and clients pointing at the former VPN/CoreDNS address. + Move DNS to explicit BIND servers using the previous + release and redistribute changed client configs before migrating. +- Custom server/client options, firewall hooks and symlinks in the volume. +- `SERVER_ALLOWEDIPS_PEER_` and `PERSISTENTKEEPALIVE_PEERS` overrides. + +Older numeric peer folders, existing client listen ports and the old +`/config/wg0.conf` location are retained for upgrade compatibility. Saved template +and CoreDNS files no longer control generation; they are left untouched for +rollback. The runtime ships no template fixtures or template matching logic. +Keep a backup of the volume and previous images before upgrading. An unsupported +installation should remain on its previous image until its customization has +been reviewed. The previous image can reuse the same config volume for rollback; +upgrade/rollback/re-add behavior is exercised by the integration suite. + +For a read-only compatibility audit, run +`/usr/local/lib/wireguard/configure.sh --preflight` in a disposable new-image +container with the old volume mounted read-only and the same server environment. +The diagnostic checks persisted configuration without publishing files or keys; +normal startup additionally validates the draft through the kernel. + +The package does not include CoreDNS, resolver rewriting, LinuxServer/s6, Docker +Mods, cron, module tools, arbitrary shell templates or a user-space VPN daemon. +Upstream `wg-quick` still handles interface/routing lifecycle and the established +iptables forwarding rules are retained. + +## Validation + +```sh +cd api +# Development tools only; the final API image contains no node_modules. +yarn install --frozen-lockfile +yarn format:check +yarn build && yarn test +cd .. +docker build -t wireguard-refactor:test . +docker build -t wireguard-api-refactor:test api +node tests/integration.cjs ``` - $ id username - uid=1000(dockeruser) gid=1000(dockergroup) groups=1000(dockergroup) -``` - - -  -## Application Setup - -This image is designed for Ubuntu and Debian based systems mainly (it works on some others, but ymmv). During container start, it will first check if the wireguard module is already installed and loaded. If not, it will then check if the kernel headers are already installed (in `/usr/src`) and if not, attempt to download the necessary kernel headers from the ubuntu/debian/raspbian repos; then will compile and install the kernel module. - -If you're on a debian/ubuntu based host with a custom or downstream distro provided kernel (ie. Pop!_OS), the container won't be able to install the kernel headers from the regular ubuntu and debian repos. In those cases, you can try installing the headers on the host via `sudo apt install linux-headers-$(uname -r)` (if distro version) and then add a volume mapping for `/usr/src:/usr/src`, or if custom built, map the location of the existing headers to allow the container to use host installed headers to build the kernel module (tested successful on Pop!_OS, ymmv). - -With regards to arm32/64 devices, Raspberry Pi 2-4 running the [official ubuntu images prior to focal](https://ubuntu.com/download/raspberry-pi) or Raspbian Buster are supported out of the box. For all other devices and OSes, you can try installing the kernel headers on the host, and mapping `/usr/src:/usr/src` and it may just work (no guarantees). - -This can be run as a server or a client, based on the parameters used. - -## Server Mode -If the environment variable `PEERS` is set to a number or a list of strings separated by comma, the container will run in server mode and the necessary server and peer/client confs will be generated. The peer/client config qr codes will be output in the docker log. They will also be saved in text and png format under `/config/peerX` in case `PEERS` is a variable and an integer or `/config/peer_X` in case a list of names was provided instead of an integer. - -Variables `SERVERURL`, `SERVERPORT`, `INTERNAL_SUBNET` and `PEERDNS` are optional variables used for server mode. Any changes to these environment variables will trigger regeneration of server and peer confs. Peer/client confs will be recreated with existing private/public keys. Delete the peer folders for the keys to be recreated along with the confs. - -To add more peers/clients later on, you increment the `PEERS` environment variable or add more elements to the list and recreate the container. - -To display the QR codes of active peers again, you can use the following command and list the peer numbers as arguments: `docker exec -it wireguard /app/show-peer 1 4 5` or `docker exec -it wireguard /app/show-peer myPC myPhone myTablet` (Keep in mind that the QR codes are also stored as PNGs in the config folder). - -The templates used for server and peer confs are saved under `/config/templates`. Advanced users can modify these templates and force conf generation by deleting `/config/wg0.conf` and restarting the container. - -## Client Mode -Do not set the `PEERS` environment variable. Drop your client conf into the config folder as `/config/wg0.conf` and start the container. - -If you get IPv6 related errors in the log and connection cannot be established, edit the `AllowedIPs` line in your peer/client wg0.conf to include only `0.0.0.0/0` and not `::/0`; and restart the container. - -## Road warriors, roaming and returning home -If you plan to use Wireguard both remotely and locally, say on your mobile phone, you will need to consider routing. Most firewalls will not route ports forwarded on your WAN interface correctly to the LAN out of the box. This means that when you return home, even though you can see the Wireguard server, the return packets will probably get lost. - -This is not a Wireguard specific issue and the two generally accepted solutions are NAT reflection (setting your edge router/firewall up in such a way as it translates internal packets correctly) or split horizon DNS (setting your internal DNS to return the private rather than public IP when connecting locally). - -Both of these approaches have positives and negatives however their setup is out of scope for this document as everyone's network layout and equipment will be different. - -## Maintaining local access to attached services - -** Note: This is not a supported configuration by Linuxserver.io - use at your own risk. - -When routing via Wireguard from another container using the `service` option in docker, you might lose access to the containers webUI locally. To avoid this, exclude the docker subnet from being routed via Wireguard by modifying your `wg0.conf` like so (modifying the subnets as you require): - - ``` - [Interface] - PrivateKey = - Address = 9.8.7.6/32 - DNS = 8.8.8.8 - PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route add $HOMENET3 via $DROUTE;ip route add $HOMENET2 via $DROUTE; ip route add $HOMENET via $DROUTE;iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT; iptables -A OUTPUT -d $HOMENET3 -j ACCEPT; iptables -A OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT - PreDown = HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route del $HOMENET3 via $DROUTE;ip route del $HOMENET2 via $DROUTE; ip route del $HOMENET via $DROUTE; iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; iptables -D OUTPUT -d $HOMENET -j ACCEPT; iptables -D OUTPUT -d $HOMENET2 -j ACCEPT; iptables -D OUTPUT -d $HOMENET3 -j ACCEPT - ``` - - -## Docker Mods - -[![Docker Mods](https://img.shields.io/badge/dynamic/yaml?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=wireguard&query=%24.mods%5B%27wireguard%27%5D.mod_count&url=https%3A%2F%2Fraw.githubusercontent.com%2Flinuxserver%2Fdocker-mods%2Fmaster%2Fmod-list.yml)](https://mods.linuxserver.io/?mod=wireguard "view available mods for this container.") [![Docker Universal Mods](https://img.shields.io/badge/dynamic/yaml?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=universal&query=%24.mods%5B%27universal%27%5D.mod_count&url=https%3A%2F%2Fraw.githubusercontent.com%2Flinuxserver%2Fdocker-mods%2Fmaster%2Fmod-list.yml)](https://mods.linuxserver.io/?mod=universal "view available universal mods.") - -We publish various [Docker Mods](https://github.com/linuxserver/docker-mods) to enable additional functionality within the containers. The list of Mods available for this image (if any) as well as universal mods that can be applied to any one of our images can be accessed via the dynamic badges above. - -## Support Info - -* Shell access whilst the container is running: `docker exec -it wireguard /bin/bash` -* To monitor the logs of the container in realtime: `docker logs -f wireguard` -* container version number - * `docker inspect -f '{{ index .Config.Labels "build_version" }}' wireguard` -* image version number - * `docker inspect -f '{{ index .Config.Labels "build_version" }}' ghcr.io/linuxserver/wireguard` - -## Updating Info - -Most of our images are static, versioned, and require an image update and container recreation to update the app inside. With some exceptions (noted in the relevant readme.md), we do not recommend or support updating apps inside the container. Please consult the [Application Setup](#application-setup) section above to see if it is recommended for the image. - -Below are the instructions for updating containers: - -### Via Docker Compose -* Update all images: `docker-compose pull` - * or update a single image: `docker-compose pull wireguard` -* Let compose update all containers as necessary: `docker-compose up -d` - * or update a single container: `docker-compose up -d wireguard` -* You can also remove the old dangling images: `docker image prune` - -### Via Docker Run -* Update the image: `docker pull ghcr.io/linuxserver/wireguard` -* Stop the running container: `docker stop wireguard` -* Delete the container: `docker rm wireguard` -* Recreate a new container with the same docker run parameters as instructed above (if mapped correctly to a host folder, your `/config` folder and settings will be preserved) -* You can also remove the old dangling images: `docker image prune` - -### Via Watchtower auto-updater (only use if you don't remember the original parameters) -* Pull the latest image at its tag and replace it with the same env variables in one run: - ``` - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - containrrr/watchtower \ - --run-once wireguard - ``` -* You can also remove the old dangling images: `docker image prune` - -**Note:** We do not endorse the use of Watchtower as a solution to automated updates of existing Docker containers. In fact we generally discourage automated updates. However, this is a useful tool for one-time manual updates of containers where you have forgotten the original parameters. In the long term, we highly recommend using [Docker Compose](https://docs.linuxserver.io/general/docker-compose). - -### Image Update Notifications - Diun (Docker Image Update Notifier) - ->[!TIP] ->We recommend [Diun](https://crazymax.dev/diun/) for update notifications. Other tools that automatically update containers unattended are not recommended or supported. - -## Building locally - -If you want to make local modifications to these images for development purposes or just to customize the logic: - -```bash -git clone https://github.com/linuxserver/docker-wireguard.git -cd docker-wireguard -docker build \ - --no-cache \ - --pull \ - -t lscr.io/linuxserver/wireguard:latest . -``` - -The ARM variants can be built on x86_64 hardware and vice versa using `lscr.io/linuxserver/qemu-static` - -```bash -docker run --rm --privileged lscr.io/linuxserver/qemu-static --reset -``` - -Once registered you can define the dockerfile to use with `-f Dockerfile.aarch64`. - -## Versions -* **24.01.26:** - Rebase to Alpine 3.23 again as openresolv alpine 3.23 package has now been updated. -* **22.01.26:** - Revert to Alpine 3.22 due to resolvconf bug. -* **04.01.26:** - Rebase to Alpine 3.23. -* **15.07.25:** - Rebase to Alpine 3.22. Remove iptables-legacy shim. -* **01.01.25:** - Deprecate legacy branch. -* **20.12.24:** - Rebase to Alpine 3.21. -* **13.08.24:** - Add `errors` plugin to default Corefile. -* **23.07.24:** - Install kmod from alpine repository. -* **24.05.24:** - Rebase to Alpine 3.20, install wireguard-tools from Alpine repo. -* **10.03.24:** - Use iptables-legacy on Alpine 3.19. -* **05.03.24:** - Rebase master to Alpine 3.19. -* **03.10.23:** - **Potentially Breaking Change:** Support for multiple interfaces added. Wireguard confs moved to `/config/wg_confs/`. Any file with a `.conf` extension in that folder will be treated as a live tunnel config and will be attempted to start. If any of the tunnels fail, all tunnels will be stopped. Tunnels are started in alphabetical order. Managed server conf will continue to be hardcoded to `wg0.conf`. -* **28.06.23:** - Rebase master to Alpine 3.18 again. -* **26.06.23:** - Revert master to Alpine 3.17, due to issue with openresolv. -* **24.06.23:** - Rebase master to Alpine 3.18, deprecate armhf as per [https://www.linuxserver.io/armhf](https://www.linuxserver.io/armhf). -* **26.04.23:** - Rework branches. Swap alpine and ubuntu builds. -* **29.01.23:** - Rebase to alpine 3.17. -* **10.01.23:** - Add new var to add `PersistentKeepalive` to server config for select peers to survive server IP changes when domain name is used. -* **26.10.22:** - Better handle unsupported peer names. Improve logging. -* **12.10.22:** - Add Alpine branch. Optimize wg and coredns services. -* **04.10.22:** - Rebase to Jammy. Upgrade to s6v3. -* **16.05.22:** - Improve NAT handling in server mode when multiple ethernet devices are present. -* **23.04.22:** - Add pre-shared key support. Automatically added to all new peer confs generated, existing ones are left without to ensure no breaking changes. -* **10.04.22:** - Rebase to Ubuntu Focal. Add `LOG_CONFS` env var. Remove deprecated `add-peer` command. -* **28.10.21:** - Add site-to-site vpn support. -* **11.02.21:** - Fix bug related to changing internal subnet and named peer confs not updating. -* **06.10.20:** - Disable CoreDNS in client mode, or if port 53 is already in use in server mode. -* **04.10.20:** - Allow to specify a list of names as PEERS and add ALLOWEDIPS environment variable. Also, add peer name/id to each one of the peer sections in wg0.conf. Important: Existing users need to delete `/config/templates/peer.conf` and restart -* **27.09.20:** - Cleaning service binding example to have accurate PreDown script. -* **06.08.20:** - Replace resolvconf with openresolv due to dns issues when a client based on this image is connected to a server also based on this image. Add IPv6 info to readme. Display kernel version in logs. -* **29.07.20:** - Update Coredns config to detect dns loops (existing users need to delete `/config/coredns/Corefile` and restart). -* **27.07.20:** - Update Coredns config to prevent issues with non-user-defined bridge networks (existing users need to delete `/config/coredns/Corefile` and restart). -* **05.07.20:** - Add Debian updates and security repos for headers. -* **25.06.20:** - Simplify module tests, prevent iptables issues from resulting in false negatives. -* **19.06.20:** - Add support for Ubuntu Focal (20.04) kernels. Compile wireguard tools and kernel module instead of using the ubuntu packages. Make module install optional. Improve verbosity in logs. -* **29.05.20:** - Add support for 64bit raspbian. -* **28.04.20:** - Add Buster/Stretch backports repos for Debian. Tested with OMV 5 and OMV 4 (on kernel 4.19.0-0.bpo.8-amd64). -* **20.04.20:** - Fix typo in client mode conf existence check. -* **13.04.20:** - Fix bug that forced conf recreation on every start. -* **08.04.20:** - Add arm32/64 builds and enable multi-arch (rpi4 with ubuntu and raspbian buster tested). Add CoreDNS for `PEERDNS=auto` setting. Update the `add-peer`/`show-peer` scripts to utilize the templates and the `INTERNAL_SUBNET` var (previously missed, oops). -* **05.04.20:** - Add `INTERNAL_SUBNET` variable to prevent subnet clashes. Add templates for server and peer confs. -* **01.04.20:** - Add `show-peer` script and include info on host installed headers. -* **31.03.20:** - Initial Release. +Both image builds run their own tests. The VPN configuration tests need no +kernel privileges. Integration tests require a native Linux Docker host with +WireGuard support and create only disposable containers, volumes and private +networks. They publish no ports and never mount the host's live config volume. +`DOCKER_HOST=ssh://` selects a remote daemon; the Node test runner stays +local. Set `WIREGUARD_TEST_IMAGE` and `WIREGUARD_API_TEST_IMAGE` for custom tags. + +For an actual release upgrade/rollback test, also set `WIREGUARD_BASELINE_IMAGE` +and `WIREGUARD_BASELINE_API_IMAGE` to the currently installed image IDs. The suite +uses those images with **fresh synthetic credentials**, never the installed +service's secrets. It checks handshake/traffic, direct BIND DNS, forwarding/NAT, +all credential modes, read-only API storage, peer add/remove/revocation/re-add, +restart recovery, rollback/roll-forward and CA distrust. The Linux test client +uses `wg-quick` with explicit DNS queries; real-device/public-endpoint testing is +separate. + +Set `WIREGUARD_LEGACY_LISTEN_PORT=1` with the old baseline image to also exercise +the historical stock peer template and CoreDNS file retained in older installations. + +The previous package was derived from [LinuxServer's WireGuard image](https://github.com/linuxserver/docker-wireguard). +See [LICENSE](LICENSE) for repository licensing. diff --git a/api/Dockerfile b/api/Dockerfile index 93aaceee..c63111ee 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,23 +1,17 @@ -FROM node:12-alpine AS builder +FROM node:24.22.1-alpine3.23@sha256:0468540aa58203ff9e40c2aab3907c52644ab818af6176b1c70a026137dc92a2 AS base WORKDIR /app -COPY . . -RUN yarn -RUN yarn build - -RUN rm -rf node_modules && yarn --production - - -# Our Second stage, that creates an image for production -FROM alpine -WORKDIR /app - -RUN apk add --update --no-cache nodejs - -COPY --from=builder /app/dist/ ./ -COPY --from=builder /app/node_modules/ ./node_modules - -# Prepare external executables +RUN apk add --no-cache libqrencode-tools \ + 'ca-certificates>=20261020-r0' 'ca-certificates-bundle>=20261020-r0' && \ + update-ca-certificates +FROM base AS test +COPY package.json yarn.lock tsconfig.json ./ +RUN yarn install --frozen-lockfile --non-interactive +COPY src ./src +COPY test ./test +RUN yarn build && yarn test +FROM base +COPY --from=test /app/dist/src ./src RUN chmod +x /app/src/executables/getWireguardCredentials.js && \ ln -s /app/src/executables/getWireguardCredentials.js /usr/local/bin/getWireguardCredentials - -ENTRYPOINT [ "node", "src/index" ] +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s CMD ["node", "-e", "fetch('http://127.0.0.1:'+(process.env.SERVER_PORT||80)+'/-/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] +ENTRYPOINT ["node", "src/index.js"] diff --git a/api/package.json b/api/package.json index bd122f7a..fff7ca46 100644 --- a/api/package.json +++ b/api/package.json @@ -1,37 +1,22 @@ { "name": "wireguard-http", "version": "0.1.0", - "description": "", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "private": true, + "license": "MIT", + "engines": { + "node": ">=24.22.1" + }, "scripts": { - "test": "mocha ./dist/test", "build": "tsc", - "start": "node ./dist/server.js", - "lint": "eslint . --ext .ts" - }, - "license": "MIT", - "dependencies": { - "cors": "^2.8.5", - "express": "^4.17.1", - "got": "^11.8.2", - "ip-regex": "^4.3.0", - "qrcode-terminal": "^0.12.0" + "test": "node --test test/*.test.js", + "start": "node dist/src/index.js", + "format": "prettier --write src test ../tests/*.cjs", + "format:check": "prettier --check src test ../tests/*.cjs" }, "devDependencies": { - "@types/chai": "^4.2.18", - "@types/cors": "2.8.9", - "@types/express": "^4.17.11", - "@types/got": "^9.6.11", - "@types/ip-regex": "^4.1.1", - "@types/mocha": "^8.2.2", - "@types/node": "^14.14.21", - "@types/qrcode-terminal": "^0.12.0", - "@typescript-eslint/eslint-plugin": "^4.6.1", - "@typescript-eslint/parser": "^4.6.1", - "chai": "^4.3.4", - "eslint": "^7.13.0", - "mocha": "^8.4.0", - "typescript": "4.1.3" - } + "@types/node": "24.19.2", + "typescript": "6.0.3", + "prettier": "3.9.10" + }, + "packageManager": "yarn@1.22.22" } diff --git a/api/src/config.ts b/api/src/config.ts new file mode 100644 index 00000000..95117c89 --- /dev/null +++ b/api/src/config.ts @@ -0,0 +1,134 @@ +import fs from "node:fs"; +import path from "node:path"; +import { isIP } from "node:net"; +import { setTimeout as delay } from "node:timers/promises"; +import { spawnSync } from "node:child_process"; + +export class ConfigError extends Error { + constructor( + public readonly status: number, + message: string, + ) { + super(message); + } +} + +export interface ConfigOptions { + dataDir?: string; + hostnames?: readonly string[]; + attempts?: number; + retryDelay?: number; + requestTimeout?: number; +} + +export interface CredentialOptions extends ConfigOptions { + local?: boolean; + localhost?: boolean; +} + +export const defaults = { + dataDir: process.env.DATA_DIR || "/config", + hostnames: ["my.dappnode", "dappmanager.dappnode", "172.33.1.7", "10.20.0.7"], + attempts: 30, + retryDelay: 1000, + requestTimeout: 3000, +}; + +export function readConfig( + device: string, + options: ConfigOptions = {}, +): string { + if (!/^[a-zA-Z0-9_]+$/.test(device)) { + throw new ConfigError(400, "Invalid device"); + } + + const dataDir = options.dataDir || defaults.dataDir; + const server = fs.readFileSync( + path.join(dataDir, "wg_confs/wg0.conf"), + "utf8", + ); + const activePeers = new Set( + server + .split(/\r?\n/) + .filter((line) => /^# peer_?\w+$/.test(line)) + .map((line) => line.slice(2)), + ); + // Numeric PEERS counts use peer1; explicitly named peers use peer_name. + const candidates = [`peer_${device}`]; + if (/^\d+$/.test(device)) candidates.push(`peer${device}`); + const peer = candidates.find((name) => activePeers.has(name)); + if (!peer) throw new ConfigError(404, "Not found"); + + const file = fs.realpathSync(path.join(dataDir, peer, `${peer}.conf`)); + const root = fs.realpathSync(dataDir); + if (!file.startsWith(`${root}${path.sep}${peer}${path.sep}`)) { + throw new ConfigError(400, "Invalid config path"); + } + if (fs.statSync(file).size > 65536) { + throw new ConfigError(500, "Invalid config size"); + } + return fs.readFileSync(file, "utf8"); +} + +export function setEndpoint(config: string, endpoint: string): string { + if (!/^Endpoint\s*=/m.test(config)) { + throw new ConfigError(500, "Config has no endpoint"); + } + return config.replace(/^Endpoint\s*=.*$/m, `Endpoint = ${endpoint}`); +} + +export async function getLocalIp(options: ConfigOptions = {}): Promise { + const settings = { ...defaults, ...options }; + for (let attempt = 0; attempt < settings.attempts; attempt++) { + for (const hostname of settings.hostnames) { + try { + const response = await fetch( + `http://${hostname}/global-envs/INTERNAL_IP`, + { + redirect: "error", + signal: AbortSignal.timeout(settings.requestTimeout), + }, + ); + if (!response.ok) { + await response.body?.cancel(); + continue; + } + const ip = await response.text(); + if (isIP(ip)) return ip; + } catch { + // Try the next Dappmanager address without logging credential contents. + } + } + if (attempt + 1 < settings.attempts) await delay(settings.retryDelay); + } + throw new ConfigError(503, "Dappmanager local IP is unavailable"); +} + +export async function getConfig( + device: string, + { local = false, localhost = false, ...options }: CredentialOptions = {}, +): Promise { + let config = readConfig(device, options); + // Local variants change only the host, including for non-default server ports. + const port = config.match(/^Endpoint\s*=\s*.*:(\d+)\s*$/m)?.[1]; + if (!port) throw new ConfigError(500, "Invalid endpoint"); + + if (local) { + const ip = await getLocalIp(options); + config = setEndpoint(config, `${isIP(ip) === 6 ? `[${ip}]` : ip}:${port}`); + } + if (localhost) config = setEndpoint(config, `localhost:${port}`); + return config; +} + +export function renderQr(config: string, terminal = false): Buffer { + const result = spawnSync( + "qrencode", + ["-l", "L", "-t", terminal ? "ANSIUTF8" : "PNG", "-o", "-"], + { input: config, maxBuffer: 1024 * 1024, timeout: 5000 }, + ); + if (result.error || result.status !== 0) { + throw new ConfigError(500, "Unable to render QR code"); + } + return result.stdout; +} diff --git a/api/src/createLocalConfigFile.ts b/api/src/createLocalConfigFile.ts deleted file mode 100644 index 9fcf92e5..00000000 --- a/api/src/createLocalConfigFile.ts +++ /dev/null @@ -1,81 +0,0 @@ -import fs from "fs"; -import got from "got"; -import ipRegex from "ip-regex"; -import path from "path"; -import { params } from "./params"; - -export async function createLocalConfigFile(device: string): Promise { - try { - const remoteFilePath = getRemoteConfigFilePath(device, "conf"); - const localIp = await getLocalIpWithRetries(30); - const localEndpoint = `${localIp}:${params.SERVER_PORT}`; - - const remoteConfigFile = fs.readFileSync(remoteFilePath, "utf8"); - const localConfigFile = setLocalEndpoint(remoteConfigFile, localEndpoint); - - if (localConfigFile === remoteConfigFile) - throw Error("Error generating localConfigFile"); - return localConfigFile; - } catch (e) { - e.message = `Error creating localConfigFile: ${e.message}`; - throw e; - } -} - -// Utils - -async function getLocalIpWithRetries(retries: number): Promise { - // An integer n >= 1 - retries = Math.max(Math.floor(retries), 1); - - for (let i = 0; i < retries - 1; i++) { - try { - return await getLocalIp(); - } catch (e) { - console.log(`Error getting local IP: ${e.message}`); - console.log(`Retrying... (${i + 1}/${retries})`); - } - } - - return await getLocalIp(); -} - -async function getLocalIp(): Promise { - const dappmanagerHostnames = params.DAPPMANAGER_HOSTNAMES; - const getLocalIpUrls = dappmanagerHostnames.map( - (hostname) => `http://${hostname}${params.GET_INTERNAL_IP_ENDPOINT}`, - ); - - let errorMessages: string[] = []; - - for (const url of getLocalIpUrls) { - try { - const localIp = await got(url).text(); - if (!localIp) throw Error("Local IP is empty"); - if (!ipRegex({ exact: true }).test(localIp)) - throw Error("Invalid local IP"); - return localIp; - } catch (e) { - errorMessages.push( - `Local IP could not be fetched from ${url}: ${e.message}`, - ); - } - } - throw Error(errorMessages.join("\n")); -} - -export function setLocalEndpoint( - configFile: string, - localEndpoint: string, -): string { - return configFile - .split("\n") - .map((row) => - row.startsWith("Endpoint =") ? `Endpoint = ${localEndpoint}` : row, - ) - .join("\n"); -} - -export function getRemoteConfigFilePath(device: string, mode: string): string { - return path.join(params.DATA_DIR, `peer_${device}`, `peer_${device}.${mode}`); -} diff --git a/api/src/executables/getWireguardCredentials.ts b/api/src/executables/getWireguardCredentials.ts index 31b8229c..20b09cc6 100644 --- a/api/src/executables/getWireguardCredentials.ts +++ b/api/src/executables/getWireguardCredentials.ts @@ -1,60 +1,53 @@ #!/usr/bin/env node +import { ConfigError, getConfig, renderQr } from "../config"; -// This script must return the MASTER_ADMIN credentials formated -// as a nice message in the terminal + a QR code -// -// It will be run as a separate process, maybe in a different container +const help = `Get your wireguard credentials. Command: dappnode_wireguard -import { renderQrCode } from "../renderQrCode"; -import { params } from "../params"; -import { - createLocalConfigFile, - getRemoteConfigFilePath, -} from "../createLocalConfigFile"; -import { setLocalhostConfig } from "../setLocalhostConfig"; -import fs from "fs"; +--qr Show a terminal QR code +--local Use the LAN endpoint +--localhost Use the localhost endpoint +--help Print this message`; -(async function (): Promise { - try { - // Help message - if (process.argv.includes("--help")) { - console.log(params.HELP_MESSAGE); - return; +async function main(): Promise { + const flags = new Set(process.argv.slice(2)); + if (flags.has("--help")) { + console.log(help); + return; + } + for (const flag of flags) { + if (!["--qr", "--local", "--localhost"].includes(flag)) { + throw new ConfigError(400, `Unknown option: ${flag}`); } + } - const isQr = process.argv.includes("--qr"); - const isLocal = process.argv.includes("--local"); - const isLocalhost = process.argv.includes("--localhost"); - - const configName = isLocal ? "local" : "remote"; - const configFormat = isQr ? "qr" : "text"; - console.log( - `Preparing ${configName} ${configFormat} Wireguard credentials; use CTRL + C to stop`, + const config = await getConfig("dappnode_admin", { + local: flags.has("--local"), + localhost: flags.has("--localhost"), + }); + const mode = flags.has("--local") ? "local" : "remote"; + const format = flags.has("--qr") ? "qr" : "text"; + console.log( + `Preparing ${mode} ${format} Wireguard credentials; use CTRL + C to stop`, + ); + if (!flags.has("--qr")) { + console.log(`\n\n${config}\n\n`); + return; + } + try { + process.stdout.write(renderQr(config, true)); + } catch { + console.error( + "Unable to render QR code; showing text credentials instead.", ); - - const baseConfig = isLocal - ? await createLocalConfigFile(params.MASTER_ADMIN) - : fs.readFileSync( - getRemoteConfigFilePath(params.MASTER_ADMIN, "conf"), - "utf-8", - ); - - // Mostly required to connect from within the same machine, - // especially when DAppNode is installed on macOS. - const config = isLocalhost ? setLocalhostConfig(baseConfig) : baseConfig; - - const str = isQr - ? // If rendering the QR fails, show error and the raw remoteTextCreds - await renderQrCode(config).catch((e) => { - console.error("Error rendering remote QR code", e); - return config; - }) - : config; - - console.log(`\n\n${str}\n\n`); - } catch (e) { - // Exit process cleanly to prevent showing 'Unhandled rejection' - console.error(e); - process.exit(1); + console.log(config); } -})(); +} + +main().catch((error: unknown) => { + console.error( + error instanceof ConfigError + ? error.message + : "Unable to read WireGuard credentials", + ); + process.exitCode = 1; +}); diff --git a/api/src/index.ts b/api/src/index.ts index c8e0b895..d87c1798 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,55 +1,100 @@ -import express, { ErrorRequestHandler } from "express"; -import cors from "cors"; +import http, { type Server } from "node:http"; +import fs from "node:fs"; +import path from "node:path"; import { - createLocalConfigFile, - getRemoteConfigFilePath, -} from "./createLocalConfigFile"; + ConfigError, + type ConfigOptions, + defaults, + getConfig, + renderQr, +} from "./config"; -const port = process.env.SERVER_PORT || 80; +export function createServer(options: ConfigOptions = {}): Server { + return http.createServer(async (req, res) => { + res.setHeader("Access-Control-Allow-Origin", "http://my.dappnode"); + res.setHeader("Cache-Control", "no-store"); -const app = express(); -const corsOptions = { - origin: "http://my.dappnode" -} -app.use(cors(corsOptions)); - -// - remote: '/dappnode_admin' -// - remote qr: '/dappnode_admin?qr' -// - local: '/dappnode_admin?local' -// - local qr: '/dappnode_admin?local&qr' -app.get("/:device", async (req: express.Request, res: express.Response) => { - try { - const { device } = req.params; - // Allow to send `?local` without setting to `?local=1` - const local = Boolean(req.query.local) || req.query.local === ""; - const qr = Boolean(req.query.qr) || req.query.qr === ""; - - if (local) { - if (qr) throw Error("qr mode not supported in local"); - const localConfigFile = await createLocalConfigFile(device); - res.status(200).send(localConfigFile); - } else { - const fileExt = qr ? "png" : "conf"; - const remoteFilePath = getRemoteConfigFilePath(device, fileExt); - res.status(200).sendFile(remoteFilePath); + if (req.method === "OPTIONS") { + res + .writeHead(204, { + "Access-Control-Allow-Methods": "GET,HEAD", + "Access-Control-Allow-Headers": + req.headers["access-control-request-headers"] || "", + }) + .end(); + return; } - } catch (e) { - if (e.code === "ENOENT") res.status(404).send("Not found"); - else res.status(500).send(e.stack); - } -}); -// Return JSON instead of HTML -const errorJsonHandler: ErrorRequestHandler = async (err, req, res, next) => { - res.status(500).send(err); -}; -app.use(errorJsonHandler); + try { + const url = new URL(req.url || "/", "http://api.wireguard.dappnode"); + if (req.method !== "GET" && req.method !== "HEAD") { + throw new ConfigError(405, "Method not allowed"); + } + if (url.pathname === "/-/health") { + const ready = fs.existsSync( + path.join(options.dataDir || defaults.dataDir, ".ready"), + ); + res + .writeHead(ready ? 200 : 503) + .end(ready ? "OK" : "WireGuard is not ready"); + return; + } -const server = app.listen(port, () => console.log(`Listening on port ${port}`)); + const encodedDevice = url.pathname.match(/^\/([^/]+)\/?$/)?.[1]; + if (!encodedDevice) throw new ConfigError(404, "Not found"); + let device: string; + try { + device = decodeURIComponent(encodedDevice); + } catch { + throw new ConfigError(400, "Invalid device"); + } -process.on("SIGTERM", shutdown); -process.on("SIGINT", shutdown); -function shutdown() { - console.log("Closing server..."); - server.close((err) => console.log(err || "Bye")); + const local = url.searchParams.has("local"); + const qr = url.searchParams.has("qr"); + if (local && qr) { + throw new ConfigError( + 400, + "QR mode is not supported for local HTTP credentials", + ); + } + const config = await getConfig(device, { ...options, local }); + const body = + req.method === "HEAD" ? undefined : qr ? renderQr(config) : config; + res.writeHead(200, { + "Content-Type": qr ? "image/png" : "text/plain; charset=utf-8", + }); + res.end(body); + } catch (error: unknown) { + const missing = + error instanceof Error && "code" in error && error.code === "ENOENT"; + const status = + error instanceof ConfigError ? error.status : missing ? 404 : 500; + const message = + error instanceof ConfigError + ? error.message + : status === 404 + ? "Not found" + : "Unable to read peer configuration"; + if (!res.headersSent) { + res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" }); + } + res.end(message); + } + }); +} + +if (require.main === module) { + const port = Number(process.env.SERVER_PORT || 80); + if (!Number.isInteger(port) || port < 1 || port > 65535) { + throw new Error("SERVER_PORT must be a valid TCP port"); + } + const server = createServer().listen(port, "0.0.0.0", () => { + console.log(`Listening on port ${port}`); + }); + for (const signal of ["SIGTERM", "SIGINT"]) { + process.on(signal, () => { + server.close(); + server.closeAllConnections(); + }); + } } diff --git a/api/src/params.ts b/api/src/params.ts deleted file mode 100644 index d28278eb..00000000 --- a/api/src/params.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const params = { - MASTER_ADMIN: "dappnode_admin", - DAPPMANAGER_HOSTNAMES: ["my.dappnode", "dappmanager.dappnode", "172.33.1.7"], // TODO: Remove dappmanager IP when IP migration is performed - GET_INTERNAL_IP_ENDPOINT: "/global-envs/INTERNAL_IP", - SERVER_PORT: 51820, - DATA_DIR: process.env.DATA_DIR || "/config", - HELP_MESSAGE: - "Get your wireguard credentials. Command: dappnode_wireguard\n\n--qr => will return credentials in QR format. Default are plain text\n--local => return local credentials to solve NAT loopback issues. Default are remote credentials\n--help => print out this message", -}; diff --git a/api/src/renderQrCode.ts b/api/src/renderQrCode.ts deleted file mode 100644 index a99e8d5b..00000000 --- a/api/src/renderQrCode.ts +++ /dev/null @@ -1,8 +0,0 @@ -import qrcode from "qrcode-terminal"; - -export function renderQrCode(data: string): Promise { - return new Promise((resolve) => { - qrcode.setErrorLevel("L"); - qrcode.generate(data, { small: false }, resolve); - }); -} diff --git a/api/src/setLocalhostConfig.ts b/api/src/setLocalhostConfig.ts deleted file mode 100644 index b703cca8..00000000 --- a/api/src/setLocalhostConfig.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function setLocalhostConfig(configFile: string): string { - return configFile - .split("\n") - .map((row) => - row.match(/^Endpoint\s*=/) ? "Endpoint = localhost:51820" : row, - ) - .join("\n"); -} diff --git a/api/test/api.test.js b/api/test/api.test.js new file mode 100644 index 00000000..40ef691d --- /dev/null +++ b/api/test/api.test.js @@ -0,0 +1,242 @@ +const { test } = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const http = require("node:http"); +const { spawnSync } = require("node:child_process"); +const { createServer } = require("../dist/src/index"); +const { + readConfig, + getConfig, + getLocalIp, + setEndpoint, + renderQr, +} = require("../dist/src/config"); + +const config = + "[Interface]\nAddress = 10.24.0.2\nPrivateKey = fixture-key\nDNS = 172.33.1.2\n\n[Peer]\nPublicKey = fixture-public-key\nEndpoint = example.org:51821\nAllowedIPs = 172.33.0.0/16\n"; +function fixture(t) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "wg-api-")); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + fs.mkdirSync(path.join(dir, "wg_confs")); + fs.mkdirSync(path.join(dir, "peer_dappnode_admin")); + fs.writeFileSync( + path.join(dir, "wg_confs/wg0.conf"), + "[Interface]\n# peer_dappnode_admin\n# peer1\n", + ); + fs.writeFileSync( + path.join(dir, "peer_dappnode_admin/peer_dappnode_admin.conf"), + config, + ); + return dir; +} +async function listen(t, server) { + await new Promise((resolve, reject) => + server.listen(0, "127.0.0.1", (err) => (err ? reject(err) : resolve())), + ); + t.after( + () => + new Promise((resolve) => { + server.closeAllConnections(); + server.close(resolve); + }), + ); + return `127.0.0.1:${server.address().port}`; +} + +test("remote credentials retain their exact contents; local/localhost variants preserve the port", async (t) => { + const dataDir = fixture(t); + assert.equal(await getConfig("dappnode_admin", { dataDir }), config); + const hostname = await listen( + t, + http.createServer((req, res) => res.end("192.168.1.45")), + ); + assert.equal( + await getConfig("dappnode_admin", { + dataDir, + local: true, + hostnames: [hostname], + }), + config.replace("example.org", "192.168.1.45"), + ); + assert.equal( + await getConfig("dappnode_admin", { dataDir, localhost: true }), + config.replace("example.org", "localhost"), + ); + assert.equal( + setEndpoint("Endpoint = example.org:42", "localhost:42"), + "Endpoint = localhost:42", + ); +}); + +test("local IP discovery rejects redirects, errors and invalid addresses and uses the fallback", async (t) => { + let requests = 0; + let redirected = false; + const hostname = await listen( + t, + http.createServer((req, res) => { + redirected ||= req.url === "/redirected"; + requests++; + if (requests === 1) res.writeHead(302, { location: "/redirected" }); + else if (requests === 2) res.writeHead(503); + res.end(requests === 3 ? "not-an-ip" : "192.168.1.45"); + }), + ); + assert.equal( + await getLocalIp({ hostnames: Array(4).fill(hostname), attempts: 1 }), + "192.168.1.45", + ); + assert.equal(redirected, false); + assert.equal(requests, 4); +}); + +test("IPv6 LAN credentials use a bracketed endpoint", async (t) => { + const dataDir = fixture(t); + const hostname = await listen( + t, + http.createServer((req, res) => res.end("fd00::1234")), + ); + assert.match( + await getConfig("dappnode_admin", { + dataDir, + local: true, + hostnames: [hostname], + }), + /Endpoint = \[fd00::1234\]:51821/, + ); +}); + +test("local discovery fails with a bounded, actionable error", async (t) => { + const hostname = await listen( + t, + http.createServer((req, res) => res.end("invalid")), + ); + await assert.rejects( + getLocalIp({ hostnames: [hostname], attempts: 2, retryDelay: 1 }), + { status: 503 }, + ); +}); + +test("removed peers, traversal and symlinks cannot expose credentials", (t) => { + const dataDir = fixture(t); + for (const name of ["../server", "a/../../server", "a\\b", "", "x\0y"]) + assert.throws(() => readConfig(name, { dataDir }), { status: 400 }); + fs.writeFileSync(path.join(dataDir, "wg_confs/wg0.conf"), "[Interface]\n"); + assert.throws(() => readConfig("dappnode_admin", { dataDir }), { + status: 404, + }); + fs.writeFileSync( + path.join(dataDir, "wg_confs/wg0.conf"), + "# peer_dappnode_admin\n", + ); + const conf = path.join( + dataDir, + "peer_dappnode_admin/peer_dappnode_admin.conf", + ); + fs.unlinkSync(conf); + fs.symlinkSync(path.join(dataDir, "wg_confs/wg0.conf"), conf); + assert.throws(() => readConfig("dappnode_admin", { dataDir }), { + status: 400, + }); +}); + +test("numeric legacy and named peer folders remain readable", (t) => { + const dataDir = fixture(t); + fs.mkdirSync(path.join(dataDir, "peer1")); + fs.writeFileSync(path.join(dataDir, "peer1/peer1.conf"), config); + assert.equal(readConfig("1", { dataDir }), config); + fs.appendFileSync(path.join(dataDir, "wg_confs/wg0.conf"), "# peer_1\n"); + fs.mkdirSync(path.join(dataDir, "peer_1")); + fs.writeFileSync(path.join(dataDir, "peer_1/peer_1.conf"), config); + assert.equal(readConfig("1", { dataDir }), config); +}); + +test("HTTP contract: raw text, bare local query, HEAD, CORS, errors and readiness", async (t) => { + const dataDir = fixture(t); + const manager = await listen( + t, + http.createServer((req, res) => res.end("192.168.1.45")), + ); + const host = await listen(t, createServer({ dataDir, hostnames: [manager] })); + const url = `http://${host}`; + const remote = await fetch(`${url}/dappnode_admin`); + assert.equal(remote.status, 200); + assert.equal(remote.headers.get("cache-control"), "no-store"); + assert.equal( + remote.headers.get("access-control-allow-origin"), + "http://my.dappnode", + ); + assert.equal(await remote.text(), config); + assert.equal( + await (await fetch(`${url}/dappnode_admin?local`)).text(), + config.replace("example.org", "192.168.1.45"), + ); + assert.equal( + await (await fetch(`${url}/dappnode_admin`, { method: "HEAD" })).text(), + "", + ); + assert.equal( + (await fetch(`${url}/dappnode_admin`, { method: "OPTIONS" })).status, + 204, + ); + assert.equal( + (await fetch(`${url}/dappnode_admin`, { method: "POST" })).status, + 405, + ); + assert.equal((await fetch(`${url}/dappnode_admin?local&qr`)).status, 400); + assert.equal((await fetch(`${url}/missing`)).status, 404); + assert.equal((await fetch(`${url}/a%2F..%2Fserver`)).status, 400); + assert.equal((await fetch(`${url}/-/health`)).status, 503); + fs.writeFileSync(path.join(dataDir, ".ready"), ""); + assert.equal((await fetch(`${url}/-/health`)).status, 200); +}); + +test( + "QR output is PNG over HTTP and ANSI in the CLI", + { skip: spawnSync("qrencode", ["--version"]).status !== 0 }, + async (t) => { + const dataDir = fixture(t); + const host = await listen(t, createServer({ dataDir })); + const response = await fetch(`http://${host}/dappnode_admin?qr`); + assert.equal(response.headers.get("content-type"), "image/png"); + assert.deepEqual( + Buffer.from(await response.arrayBuffer()).subarray(0, 8), + Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), + ); + assert.ok(renderQr(config, true).includes(Buffer.from("\x1b["))); + }, +); + +test("the installed CLI supports help, remote/localhost credentials and rejects removed peers", (t) => { + const dataDir = fixture(t); + const run = (...flags) => + spawnSync( + process.execPath, + [ + path.join( + __dirname, + "../dist/src/executables/getWireguardCredentials.js", + ), + ...flags, + ], + { env: { ...process.env, DATA_DIR: dataDir }, encoding: "utf8" }, + ); + assert.match(run("--help").stdout, /--localhost/); + assert.ok(run().stdout.includes(config)); + assert.match(run("--localhost").stdout, /Endpoint = localhost:51821/); + assert.equal(run("--unknown").status, 1); + fs.writeFileSync(path.join(dataDir, "wg_confs/wg0.conf"), "[Interface]\n"); + assert.equal(run().status, 1); +}); + +test("health remains a valid peer name; readiness has a separate namespace", async (t) => { + const dataDir = fixture(t); + fs.appendFileSync(path.join(dataDir, "wg_confs/wg0.conf"), "# peer_health\n"); + fs.mkdirSync(path.join(dataDir, "peer_health")); + fs.writeFileSync(path.join(dataDir, "peer_health/peer_health.conf"), config); + const host = await listen(t, createServer({ dataDir })); + const response = await fetch(`http://${host}/health`); + assert.equal(response.status, 200); + assert.equal(await response.text(), config); +}); diff --git a/api/test/createLocalConfigFile.test.ts b/api/test/createLocalConfigFile.test.ts deleted file mode 100644 index fc7da7ce..00000000 --- a/api/test/createLocalConfigFile.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import "mocha"; -import { expect } from "chai"; -import { setLocalEndpoint } from "../src/createLocalConfigFile"; - -describe("Config files", function () { - it("Create localConfig file from remoteConfigFile with local endpoint", function () { - const remoteConfig = `[Interface] -Address = AX032NVGI2RIB4 -PrivateKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= -ListenPort = 51820 -DNS = 172.33.1.2 - -[Peer] -PublicKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= -Endpoint = ff0239facf453517.dyndns.dappnode.io:51820 -AllowedIPs = 0.0.0.0/0`; - - const expextedLocalConfig = `[Interface] -Address = AX032NVGI2RIB4 -PrivateKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= -ListenPort = 51820 -DNS = 172.33.1.2 - -[Peer] -PublicKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= -Endpoint = 192.168.1.45:51820 -AllowedIPs = 0.0.0.0/0`; - - const localEndpoint = `192.168.1.45:51820`; - - expect(setLocalEndpoint(remoteConfig, localEndpoint)).to.deep.equal( - expextedLocalConfig, - ); - }); -}); diff --git a/api/test/setLocalhostConfig.test.ts b/api/test/setLocalhostConfig.test.ts deleted file mode 100644 index 8087ff17..00000000 --- a/api/test/setLocalhostConfig.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import "mocha"; -import { expect } from "chai"; -import { setLocalhostConfig } from "../src/setLocalhostConfig"; - -describe("setLocalhostConfig", function () { - it("sets localhost endpoint", function () { - const remoteConfig = `[Interface] -Address = AX032NVGI2RIB4 -PrivateKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= -DNS = 172.33.1.2 - -[Peer] -PublicKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= -Endpoint = ff0239facf453517.dyndns.dappnode.io:51820 -AllowedIPs = 0.0.0.0/0`; - - const expectedLocalhostConfig = `[Interface] -Address = AX032NVGI2RIB4 -PrivateKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= -DNS = 172.33.1.2 - -[Peer] -PublicKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= -Endpoint = localhost:51820 -AllowedIPs = 0.0.0.0/0`; - - expect(setLocalhostConfig(remoteConfig)).to.deep.equal( - expectedLocalhostConfig, - ); - }); - - it("replaces endpoint when spacing around equal sign varies", function () { - const remoteConfig = `[Peer] -Endpoint = ff0239facf453517.dyndns.dappnode.io:51820`; - - const expectedLocalhostConfig = `[Peer] -Endpoint = localhost:51820`; - - expect(setLocalhostConfig(remoteConfig)).to.deep.equal( - expectedLocalhostConfig, - ); - }); -}); diff --git a/api/tsconfig.json b/api/tsconfig.json index 6f6294cb..b053d238 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -1,11 +1,13 @@ { "compilerOptions": { - "allowJs": true, - "target": "es5", - "sourceMap": true, - "esModuleInterop": true, + "target": "ES2022", + "module": "Node16", + "rootDir": ".", + "outDir": "dist", "strict": true, - "outDir": "dist" + "noUncheckedIndexedAccess": true, + "esModuleInterop": true, + "types": ["node"] }, - "include": ["src/**/*", "test/*"] + "include": ["src/**/*.ts"] } diff --git a/api/tslint.json b/api/tslint.json deleted file mode 100644 index a29704ff..00000000 --- a/api/tslint.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rules": { - "no-debugger": true, - "mocha-avoid-only": true, - "prefer-const": true, - "no-var-keyword": true, - "no-commented-code": { - "severity": "warning" - }, - "no-use-before-declare": false - } -} diff --git a/api/yarn.lock b/api/yarn.lock index 8d87f620..b063f65e 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -2,2066 +2,24 @@ # yarn lockfile v1 -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/helper-validator-identifier@^7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" - integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== - -"@babel/highlight@^7.10.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" - integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww== - dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@eslint/eslintrc@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" - integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== - dependencies: - ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^3.13.1" - lodash "^4.17.20" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" - -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== - dependencies: - "@nodelib/fs.stat" "2.0.4" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== - dependencies: - "@nodelib/fs.scandir" "2.1.4" - fastq "^1.6.0" - -"@sindresorhus/is@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" - integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== - -"@szmarczak/http-timer@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" - integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== - dependencies: - defer-to-connect "^2.0.0" - -"@types/body-parser@*": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" - integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/cacheable-request@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" - integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "*" - "@types/node" "*" - "@types/responselike" "*" - -"@types/chai@^4.2.18": - version "4.2.18" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4" - integrity sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ== - -"@types/connect@*": - version "3.4.34" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" - integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== - dependencies: - "@types/node" "*" - -"@types/cors@2.8.9": - version "2.8.9" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.9.tgz#4bd1fcac72eca8d5bec93e76c7fdcbdc1bc2cd4a" - integrity sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg== - -"@types/express-serve-static-core@^4.17.18": - version "4.17.18" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" - integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@^4.17.11": - version "4.17.11" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" - integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/got@^9.6.11": - version "9.6.11" - resolved "https://registry.yarnpkg.com/@types/got/-/got-9.6.11.tgz#482b402cc5ee459481aeeadb08142ebb1a9afb26" - integrity sha512-dr3IiDNg5TDesGyuwTrN77E1Cd7DCdmCFtEfSGqr83jMMtcwhf/SGPbN2goY4JUWQfvxwY56+e5tjfi+oXeSdA== - dependencies: - "@types/node" "*" - "@types/tough-cookie" "*" - form-data "^2.5.0" - -"@types/http-cache-semantics@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" - integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== - -"@types/ip-regex@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/ip-regex/-/ip-regex-4.1.1.tgz#1afdee4731cb07bc7984a1669af19e02e536106e" - integrity sha512-RUEV39p+XoK1rO4nDQYXb1TATpyVKzfIr75HgR0074G/uA9RzjPsIK4TMlxh7KZ4WxcM512j03esP06tHMZNLA== - dependencies: - ip-regex "*" - -"@types/json-schema@^7.0.3": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" - integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== - -"@types/keyv@*": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" - integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== - dependencies: - "@types/node" "*" - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - -"@types/mocha@^8.2.2": - version "8.2.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" - integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== - -"@types/node@*", "@types/node@^14.14.21": - version "14.14.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" - integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== - -"@types/qrcode-terminal@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#cee13a96db9346e3240e2128290465db56799961" - integrity sha512-g0fQKl3IVnUePV8tLkiBTduxAVSZsXts444T2aMfjOEGxd7XoeSIs16/AmRe2NlcJLqbvsOBSXov7dKSo4wigA== - -"@types/qs@*": - version "6.9.5" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" - integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== - -"@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== - -"@types/responselike@*", "@types/responselike@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" - integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== - dependencies: - "@types/node" "*" - -"@types/serve-static@*": - version "1.13.9" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" - integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/tough-cookie@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" - integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== - -"@typescript-eslint/eslint-plugin@^4.6.1": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.2.tgz#981b26b4076c62a5a55873fbef3fe98f83360c61" - integrity sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q== - dependencies: - "@typescript-eslint/experimental-utils" "4.15.2" - "@typescript-eslint/scope-manager" "4.15.2" - debug "^4.1.1" - functional-red-black-tree "^1.0.1" - lodash "^4.17.15" - regexpp "^3.0.0" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@4.15.2": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.2.tgz#5efd12355bd5b535e1831282e6cf465b9a71cf36" - integrity sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.15.2" - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/typescript-estree" "4.15.2" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^4.6.1": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.15.2.tgz#c804474321ef76a3955aec03664808f0d6e7872e" - integrity sha512-SHeF8xbsC6z2FKXsaTb1tBCf0QZsjJ94H6Bo51Y1aVEZ4XAefaw5ZAilMoDPlGghe+qtq7XdTiDlGfVTOmvA+Q== - dependencies: - "@typescript-eslint/scope-manager" "4.15.2" - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/typescript-estree" "4.15.2" - debug "^4.1.1" - -"@typescript-eslint/scope-manager@4.15.2": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz#5725bda656995960ae1d004bfd1cd70320f37f4f" - integrity sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ== - dependencies: - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/visitor-keys" "4.15.2" - -"@typescript-eslint/types@4.15.2": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.15.2.tgz#04acf3a2dc8001a88985291744241e732ef22c60" - integrity sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ== - -"@typescript-eslint/typescript-estree@4.15.2": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz#c2f7a1e94f3428d229d5ecff3ead6581ee9b62fa" - integrity sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw== - dependencies: - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/visitor-keys" "4.15.2" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/visitor-keys@4.15.2": - version "4.15.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz#3d1c7979ce75bf6acf9691109bd0d6b5706192b9" - integrity sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg== - dependencies: - "@typescript-eslint/types" "4.15.2" - eslint-visitor-keys "^2.0.0" - -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== - -accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== - -acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^7.0.2: - version "7.1.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.1.1.tgz#1e6b37a454021fa9941713f38b952fc1c8d32a84" - integrity sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-colors@4.1.1, ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^2.0.0" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== - -chai@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" - integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - pathval "^1.1.1" - type-detect "^4.0.5" - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= - -chokidar@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.5.0" - optionalDependencies: - fsevents "~2.3.1" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -combined-stream@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4.3.1, debug@^4.0.1, debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== - dependencies: - type-detect "^4.0.0" - -deep-is@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-scope@^5.0.0, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-utils@^2.0.0, eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== - -eslint@^7.13.0: - version "7.20.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.20.0.tgz#db07c4ca4eda2e2316e7aa57ac7fc91ec550bdc7" - integrity sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.3.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - file-entry-cache "^6.0.0" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash "^4.17.20" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.4" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== - dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.1.1: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== - dependencies: - reusify "^1.0.4" - -file-entry-cache@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" - integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== - -form-data@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -glob-parent@^5.0.0, glob-parent@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob-parent@~5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@7.1.6, glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globby@^11.0.1: - version "11.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" - integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -got@^11.8.2: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ip-regex@*, ip-regex@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" - integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" - integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== - dependencies: - argparse "^2.0.1" - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -keyv@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" - integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== - dependencies: - json-buffer "3.0.1" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash@^4.17.15, lodash@^4.17.20: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime-db@1.46.0: - version "1.46.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" - integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== - -mime-db@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" - integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== - -mime-types@^2.1.12: - version "2.1.30" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" - integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== - dependencies: - mime-db "1.47.0" - -mime-types@~2.1.24: - version "2.1.29" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" - integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== - dependencies: - mime-db "1.46.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -minimatch@3.0.4, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -mocha@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" - integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.1" - debug "4.3.1" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.1.6" - growl "1.10.5" - he "1.2.0" - js-yaml "4.0.0" - log-symbols "4.0.0" - minimatch "3.0.4" - ms "2.1.3" - nanoid "3.1.20" - serialize-javascript "5.0.1" - strip-json-comments "3.1.1" - supports-color "8.1.1" - which "2.0.2" - wide-align "1.1.3" - workerpool "6.1.0" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== - -picomatch@^2.0.4: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== - -picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qrcode-terminal@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" - integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -queue-microtask@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" - integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== - dependencies: - picomatch "^2.2.1" - -regexpp@^3.0.0, regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -resolve-alpn@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" - integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -responselike@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" - integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== - dependencies: - lowercase-keys "^2.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^7.2.1, semver@^7.3.2: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== - dependencies: - lru-cache "^6.0.0" - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^4.1.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -table@^6.0.4: - version "6.0.7" - resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" - integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== - dependencies: - ajv "^7.0.2" - lodash "^4.17.20" - slice-ansi "^4.0.0" - string-width "^4.2.0" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tsutils@^3.17.1: - version "3.20.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.20.0.tgz#ea03ea45462e146b53d70ce0893de453ff24f698" - integrity sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg== - dependencies: - tslib "^1.8.1" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -v8-compile-cache@^2.0.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" - integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -which@2.0.2, which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -workerpool@6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" - integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: - version "20.2.7" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" - integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== - -yargs-unparser@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +"@types/node@24.19.2": + version "24.19.2" + resolved "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz#8718eedbc0f89648de6525be9f4a9c61cdbaa502" + integrity sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ== + dependencies: + undici-types ">=7.24.0 <7.24.7" + +prettier@3.9.10: + version "3.9.10" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz#ac378e432f1a7381518f2e080816bbaefa38d585" + integrity sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA== + +typescript@6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + +"undici-types@>=7.24.0 <7.24.7": + version "7.24.6" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz#61275b485d7fd4e9d269c7cf04ec2873c9cc0f91" + integrity sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg== diff --git a/docker-compose.yml b/docker-compose.yml index 965e815e..9feb1910 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,4 @@ +# Required by the Dappnode SDK validator. version: "3.5" services: wireguard: @@ -7,20 +8,16 @@ services: restart: unless-stopped cap_add: - NET_ADMIN - - SYS_MODULE environment: - PEERS=dappnode_admin - SERVERURL= - - WIREGUARD_RELEASE= - - PUID=1000 - - PGID=1000 - SERVERPORT=51820 + - LOG_CONFS=false - "PEERDNS=172.33.1.2,10.20.0.2" - INTERNAL_SUBNET=10.24.0.0 - "ALLOWEDIPS=172.33.0.0/16,10.20.0.0/24" volumes: - "wg-config:/config" - - "/lib/modules:/lib/modules" ports: - "51820:51820/udp" networks: @@ -33,7 +30,7 @@ services: container_name: DAppNodeCore-api.wireguard.dnp.dappnode.eth restart: unless-stopped volumes: - - "wg-config:/config" + - "wg-config:/config:ro" networks: dncore_network: aliases: diff --git a/package_versions.txt b/package_versions.txt deleted file mode 100755 index 4d643b2c..00000000 --- a/package_versions.txt +++ /dev/null @@ -1,265 +0,0 @@ -NAME VERSION TYPE -acl-libs 2.3.2-r1 apk -alpine-baselayout 3.7.1-r8 apk -alpine-baselayout-data 3.7.1-r8 apk -alpine-keys 2.6-r0 apk -alpine-release 3.23.3-r0 apk -apk-tools 3.0.4-r0 apk -bash 5.3.3-r1 apk -bc 1.08.2-r0 apk -brotli-libs 1.2.0-r0 apk -busybox 1.37.0-r30 apk -busybox-binsh 1.37.0-r30 apk -c-ares 1.34.6-r0 apk -ca-certificates 20251003-r0 apk -ca-certificates-bundle 20251003-r0 apk -catatonit 0.2.1-r0 apk -cloud.google.com/go/auth v0.16.3 go-module -cloud.google.com/go/auth/oauth2adapt v0.2.8 go-module -cloud.google.com/go/compute/metadata v0.7.0 go-module -coredns 1.12.3-r7 apk -coreutils 9.8-r1 apk -coreutils-env 9.8-r1 apk -coreutils-fmt 9.8-r1 apk -coreutils-sha512sum 9.8-r1 apk -curl 8.17.0-r1 apk -findutils 4.10.0-r0 apk -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible go-module -github.com/Azure/go-autorest/autorest v0.11.30 go-module -github.com/Azure/go-autorest/autorest/adal v0.9.22 go-module -github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 go-module -github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 go-module -github.com/Azure/go-autorest/autorest/date v0.3.0 go-module -github.com/Azure/go-autorest/autorest/to v0.2.0 go-module -github.com/Azure/go-autorest/logger v0.2.1 go-module -github.com/Azure/go-autorest/tracing v0.6.0 go-module -github.com/DataDog/appsec-internal-go v1.13.0 go-module -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/obfuscate v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/proto v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/trace v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/util/log v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/util/scrubber v0.66.1 go-module -github.com/DataDog/datadog-agent/pkg/version v0.66.1 go-module -github.com/DataDog/datadog-go/v5 v5.6.0 go-module -github.com/DataDog/dd-trace-go/v2 v2.1.0 go-module -github.com/DataDog/go-libddwaf/v4 v4.3.0 go-module -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250603194815-7edb7c2ad56a go-module -github.com/DataDog/go-sqllexer v0.1.6 go-module -github.com/DataDog/go-tuf v1.1.0-0.5.2 go-module -github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.26.0 go-module -github.com/DataDog/sketches-go v1.4.7 go-module -github.com/Masterminds/semver/v3 v3.3.1 go-module -github.com/apparentlymart/go-cidr v1.1.0 go-module -github.com/aws/aws-sdk-go-v2 v1.36.6 go-module -github.com/aws/aws-sdk-go-v2/config v1.29.18 go-module -github.com/aws/aws-sdk-go-v2/credentials v1.17.71 go-module -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 go-module -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 go-module -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 go-module -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 go-module -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 go-module -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 go-module -github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 go-module -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.8 go-module -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 go-module -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 go-module -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 go-module -github.com/aws/smithy-go v1.22.4 go-module -github.com/beorn7/perks v1.0.1 go-module -github.com/caddyserver/certmagic v0.13.1 go-module -github.com/cespare/xxhash/v2 v2.3.0 go-module -github.com/chinzhiweiblank/coredns-acme v1.0.2 go-module -github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 go-module -github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98 go-module -github.com/coredns/coredns UNKNOWN go-module -github.com/coredns/unbound v0.0.7 go-module -github.com/coreos/go-semver v0.3.1 go-module -github.com/coreos/go-systemd/v22 v22.5.0 go-module -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc go-module -github.com/dimchansky/utfbom v1.1.1 go-module -github.com/dnstap/golang-dnstap v0.4.0 go-module -github.com/dustin/go-humanize v1.0.1 go-module -github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 go-module -github.com/ebitengine/purego v0.8.3 go-module -github.com/emicklei/go-restful/v3 v3.11.0 go-module -github.com/expr-lang/expr v1.17.5 go-module -github.com/farsightsec/golang-framestream v0.3.0 go-module -github.com/felixge/httpsnoop v1.0.4 go-module -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 go-module -github.com/fxamacker/cbor/v2 v2.7.0 go-module -github.com/go-logr/logr v1.4.3 go-module -github.com/go-logr/stdr v1.2.2 go-module -github.com/go-openapi/jsonpointer v0.21.0 go-module -github.com/go-openapi/jsonreference v0.20.2 go-module -github.com/go-openapi/swag v0.23.0 go-module -github.com/go-viper/mapstructure/v2 v2.3.0 go-module -github.com/gogo/protobuf v1.3.2 go-module -github.com/golang-jwt/jwt/v4 v4.5.2 go-module -github.com/golang/protobuf v1.5.4 go-module -github.com/google/gnostic-models v0.6.8 go-module -github.com/google/go-cmp v0.7.0 go-module -github.com/google/gofuzz v1.2.0 go-module -github.com/google/s2a-go v0.1.9 go-module -github.com/google/uuid v1.6.0 go-module -github.com/googleapis/enterprise-certificate-proxy v0.3.6 go-module -github.com/googleapis/gax-go/v2 v2.15.0 go-module -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 go-module -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 go-module -github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9 go-module -github.com/josharian/intern v1.0.0 go-module -github.com/json-iterator/go v1.1.12 go-module -github.com/klauspost/cpuid/v2 v2.2.5 go-module -github.com/libdns/libdns v0.2.1 go-module -github.com/mailru/easyjson v0.7.7 go-module -github.com/matttproud/golang_protobuf_extensions v1.0.4 go-module -github.com/mholt/acmez v0.1.3 go-module -github.com/miekg/dns v1.1.67 go-module -github.com/miekg/unbound v0.0.0-20240613151107-1f0f3b231f04 go-module -github.com/mitchellh/go-homedir v1.1.0 go-module -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd go-module -github.com/modern-go/reflect2 v1.0.2 go-module -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 go-module -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 go-module -github.com/opentracing/opentracing-go v1.2.0 go-module -github.com/openzipkin-contrib/zipkin-go-opentracing v0.5.0 go-module -github.com/openzipkin/zipkin-go v0.4.3 go-module -github.com/oschwald/geoip2-golang v1.13.0 go-module -github.com/oschwald/maxminddb-golang v1.13.0 go-module -github.com/outcaste-io/ristretto v0.2.3 go-module -github.com/oz123/coredns-netbox-plugin v0.4.0 go-module -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c go-module -github.com/pkg/errors v0.9.1 go-module -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 go-module -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 go-module -github.com/prometheus/client_golang v1.22.0 go-module -github.com/prometheus/client_model v0.6.2 go-module -github.com/prometheus/common v0.65.0 go-module -github.com/prometheus/procfs v0.15.1 go-module -github.com/puzpuzpuz/xsync/v3 v3.5.1 go-module -github.com/quic-go/quic-go v0.54.0 go-module -github.com/secure-systems-lab/go-securesystemslib v0.9.0 go-module -github.com/shirou/gopsutil/v4 v4.25.3 go-module -github.com/spf13/pflag v1.0.5 go-module -github.com/stretchr/testify v1.10.0 go-module -github.com/tinylib/msgp v1.2.5 go-module -github.com/tklauser/go-sysconf v0.3.15 go-module -github.com/tklauser/numcpus v0.10.0 go-module -github.com/x448/float16 v0.8.4 go-module -gmp 6.3.0-r4 apk -go.etcd.io/etcd/api/v3 v3.6.2 go-module -go.etcd.io/etcd/client/pkg/v3 v3.6.2 go-module -go.etcd.io/etcd/client/v3 v3.6.2 go-module -go.opentelemetry.io/auto/sdk v1.1.0 go-module -go.opentelemetry.io/collector/component v1.28.1 go-module -go.opentelemetry.io/collector/pdata v1.28.1 go-module -go.opentelemetry.io/collector/semconv v0.123.0 go-module -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 go-module -go.opentelemetry.io/otel v1.36.0 go-module -go.opentelemetry.io/otel/metric v1.36.0 go-module -go.opentelemetry.io/otel/trace v1.36.0 go-module -go.uber.org/atomic v1.11.0 go-module -go.uber.org/automaxprocs v1.6.0 go-module -go.uber.org/multierr v1.11.0 go-module -go.uber.org/zap v1.27.0 go-module -golang.org/x/crypto v0.40.0 go-module -golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac go-module -golang.org/x/mod v0.25.0 go-module -golang.org/x/net v0.42.0 go-module -golang.org/x/oauth2 v0.30.0 go-module -golang.org/x/sys v0.34.0 go-module -golang.org/x/term v0.33.0 go-module -golang.org/x/text v0.27.0 go-module -golang.org/x/time v0.12.0 go-module -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 go-module -google.golang.org/api v0.243.0 go-module -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 go-module -google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 go-module -google.golang.org/grpc v1.73.0 go-module -google.golang.org/protobuf v1.36.6 go-module -gopkg.in/DataDog/dd-trace-go.v1 v1.74.3 go-module -gopkg.in/evanphx/json-patch.v4 v4.12.0 go-module -gopkg.in/inf.v0 v0.9.1 go-module -gopkg.in/ini.v1 v1.67.0 go-module -gopkg.in/yaml.v3 v3.0.1 go-module -grep 3.12-r0 apk -hiredis 1.3.0-r0 apk -iproute2 6.17.0-r0 apk -iproute2-minimal 6.17.0-r0 apk -iproute2-ss 6.17.0-r0 apk -iproute2-tc 6.17.0-r0 apk -iptables 1.8.11-r1 apk -iputils 20250605-r0 apk -iputils-arping 20250605-r0 apk -iputils-clockdiff 20250605-r0 apk -iputils-ping 20250605-r0 apk -iputils-tracepath 20250605-r0 apk -jansson 2.14.1-r0 apk -jq 1.8.1-r0 apk -k8s.io/api v0.32.5 go-module -k8s.io/apimachinery v0.32.5 go-module -k8s.io/client-go v0.32.5 go-module -k8s.io/klog/v2 v2.130.1 go-module -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f go-module -k8s.io/utils v0.0.0-20241210054802-24370beab758 go-module -kmod 34.2-r1 apk -libapk 3.0.4-r0 apk -libattr 2.5.2-r2 apk -libbsd 0.12.2-r0 apk -libcap-getcap 2.77-r0 apk -libcap-setcap 2.77-r0 apk -libcap-utils 2.77-r0 apk -libcap2 2.77-r0 apk -libcrypto3 3.5.5-r0 apk -libcurl 8.17.0-r1 apk -libelf 0.194-r0 apk -libevent 2.1.12-r8 apk -libidn2 2.3.8-r0 apk -libintl 0.24.1-r1 apk -libmd 1.1.0-r0 apk -libmnl 1.0.5-r2 apk -libncursesw 6.5_p20251123-r0 apk -libnftnl 1.3.0-r0 apk -libpng 1.6.54-r0 apk -libproc2 4.0.5-r0 apk -libpsl 0.21.5-r3 apk -libqrencode 4.1.1-r3 apk -libqrencode-tools 4.1.1-r3 apk -libssl3 3.5.5-r0 apk -libunistring 1.4.1-r0 apk -libxtables 1.8.11-r1 apk -linux-pam 1.7.1-r2 apk -mii-tool 2.10-r3 apk -musl 1.2.5-r21 apk -musl-utils 1.2.5-r21 apk -ncurses-terminfo-base 6.5_p20251123-r0 apk -net-tools 2.10-r3 apk -netcat-openbsd 1.234.1-r0 apk -nftables 1.1.5-r2 apk -nghttp2-libs 1.68.0-r0 apk -nghttp3 1.13.1-r0 apk -oniguruma 6.9.10-r0 apk -openresolv 3.17.4-r0 apk -pcre2 10.47-r0 apk -procps-ng 4.0.5-r0 apk -protobuf-c 1.5.2-r2 apk -readline 8.3.1-r0 apk -scanelf 1.3.8-r2 apk -shadow 4.18.0-r0 apk -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 go-module -sigs.k8s.io/mcs-api v0.2.0 go-module -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 go-module -sigs.k8s.io/yaml v1.4.0 go-module -skalibs-libs 2.14.4.0-r0 apk -ssl_client 1.37.0-r30 apk -stdlib go1.24.5 go-module -tzdata 2025c-r0 apk -unbound-libs 1.24.2-r0 apk -utmps-libs 0.1.3.1-r0 apk -wireguard-tools 1.0.20250521-r1 apk -wireguard-tools-wg 1.0.20250521-r1 apk -wireguard-tools-wg-quick 1.0.20250521-r1 apk -xz-libs 5.8.2-r0 apk -zlib 1.3.1-r2 apk -zstd-libs 1.5.7-r2 apk diff --git a/readme-vars.yml b/readme-vars.yml deleted file mode 100644 index 643bce8e..00000000 --- a/readme-vars.yml +++ /dev/null @@ -1,203 +0,0 @@ ---- - -# project information -project_name: wireguard -project_url: "https://www.wireguard.com/" -project_logo: "https://www.wireguard.com/img/wireguard.svg" -project_blurb: "[WireGuard®]({{ project_url }}) is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform (Windows, macOS, BSD, iOS, Android) and widely deployable. It is currently under heavy development, but already it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry." -project_lsio_github_repo_url: "https://github.com/linuxserver/docker-{{ project_name }}" -project_categories: "Network,VPN" -# supported architectures -available_architectures: - - {arch: "{{ arch_x86_64 }}", tag: "amd64-latest"} - - {arch: "{{ arch_arm64 }}", tag: "arm64v8-latest"} -# container parameters -common_param_env_vars_enabled: true -param_container_name: "{{ project_name }}" -param_usage_include_vols: true -param_volumes: - - {vol_path: "/config", vol_host_path: "/path/to/{{ project_name }}/config", desc: "Contains all relevant configuration files."} -opt_param_usage_include_vols: true -opt_param_volumes: - - {vol_path: "/lib/modules", vol_host_path: "/lib/modules", desc: "Path to host kernel module for situations where it's not already loaded."} -param_usage_include_ports: true -param_ports: - - {external_port: "51820", internal_port: "51820/udp", port_desc: "wireguard port"} -cap_add_param: true -cap_add_param_vars: - - {cap_add_var: "NET_ADMIN", desc: "Neccessary for Wireguard to create its VPN interface."} -opt_cap_add_param: true -opt_cap_add_param_vars: - - {cap_add_var: "SYS_MODULE", desc: "Neccessary for loading Wireguard kernel module if it's not already loaded."} -custom_params: - - {name: "sysctl", name_compose: "sysctls", value: ["net.ipv4.conf.all.src_valid_mark=1"], desc: "Required for client mode.", array: "true"} -# optional container parameters -opt_param_usage_include_env: true -opt_param_env_vars: - - {env_var: "SERVERURL", env_value: "wireguard.domain.com", desc: "External IP or domain name for docker host. Used in server mode. If set to `auto`, the container will try to determine and set the external IP automatically"} - - {env_var: "SERVERPORT", env_value: "51820", desc: "External port for docker host. Used in server mode."} - - {env_var: "PEERS", env_value: "1", desc: "Number of peers to create confs for. Required for server mode. Can also be a list of names: `myPC,myPhone,myTablet` (alphanumeric only)"} - - {env_var: "PEERDNS", env_value: "auto", desc: "DNS server set in peer/client configs (can be set as `8.8.8.8`). Used in server mode. Defaults to `auto`, which uses wireguard docker host's DNS via included CoreDNS forward."} - - {env_var: "INTERNAL_SUBNET", env_value: "10.13.13.0", desc: "Internal subnet for the wireguard and server and peers (only change if it clashes). Used in server mode."} - - {env_var: "ALLOWEDIPS", env_value: "0.0.0.0/0", desc: "The IPs/Ranges that the peers will be able to reach using the VPN connection. If not specified the default value is: '0.0.0.0/0, ::0/0' This will cause ALL traffic to route through the VPN, if you want split tunneling, set this to only the IPs you would like to use the tunnel AND the ip of the server's WG ip, such as 10.13.13.1."} - - {env_var: "PERSISTENTKEEPALIVE_PEERS", env_value: "", desc: "Set to `all` or a list of comma separated peers (ie. `1,4,laptop`) for the wireguard server to send keepalive packets to listed peers every 25 seconds. Useful if server is accessed via domain name and has dynamic IP. Used only in server mode."} - - {env_var: "LOG_CONFS", env_value: "true", desc: "Generated QR codes will be displayed in the docker log. Set to `false` to skip log output."} -readonly_supported: true -readonly_message: | - * Not supported in client mode. -# application setup block -app_setup_block_enabled: true -app_setup_block: | - During container start, it will first check if the wireguard module is already installed and loaded. All currently supported kernels should have the wireguard module built-in (along with some older custom kernels). However, the module may not be enabled. Make sure it is enabled prior to starting the container. - - This can be run as a server or a client, based on the parameters used. - - ## Note on iptables - - Some hosts may not load the iptables kernel modules by default. In order for the container to be able to load them, you need to assign the `SYS_MODULE` capability and add the optional `/lib/modules` volume mount. Alternatively you can `modprobe` them from the host before starting the container. - - ## Server Mode - - If the environment variable `PEERS` is set to a number or a list of strings separated by comma, the container will run in server mode and the necessary server and peer/client confs will be generated. The peer/client config qr codes will be output in the docker log if `LOG_CONFS` is set to `true`. They will also be saved in text and png format under `/config/peerX` in case `PEERS` is a variable and an integer or `/config/peer_X` in case a list of names was provided instead of an integer. - - Variables `SERVERURL`, `SERVERPORT`, `INTERNAL_SUBNET`, `PEERDNS`, `INTERFACE`, `ALLOWEDIPS` and `PERSISTENTKEEPALIVE_PEERS` are optional variables used for server mode. Any changes to these environment variables will trigger regeneration of server and peer confs. Peer/client confs will be recreated with existing private/public keys. Delete the peer folders for the keys to be recreated along with the confs. - - To add more peers/clients later on, you increment the `PEERS` environment variable or add more elements to the list and recreate the container. - - To display the QR codes of active peers again, you can use the following command and list the peer numbers as arguments: `docker exec -it wireguard /app/show-peer 1 4 5` or `docker exec -it wireguard /app/show-peer myPC myPhone myTablet` (Keep in mind that the QR codes are also stored as PNGs in the config folder). - - The templates used for server and peer confs are saved under `/config/templates`. Advanced users can modify these templates and force conf generation by deleting `/config/wg_confs/wg0.conf` and restarting the container. - - The container managed server conf is hardcoded to `wg0.conf`. However, the users can add additional tunnel config files with `.conf` extensions into `/config/wg_confs/` and the container will attempt to start them all in alphabetical order. If any one of the tunnels fail, they will all be stopped and the default route will be deleted, requiring user intervention to fix the invalid conf and a container restart. - - ## Client Mode - - Do not set the `PEERS` environment variable. Drop your client conf(s) into the config folder as `/config/wg_confs/.conf` and start the container. If there are multiple tunnel configs, the container will attempt to start them all in alphabetical order. If any one of the tunnels fail, they will all be stopped and the default route will be deleted, requiring user intervention to fix the invalid conf and a container restart. - - If you get IPv6 related errors in the log and connection cannot be established, edit the `AllowedIPs` line in your peer/client wg0.conf to include only `0.0.0.0/0` and not `::/0`; and restart the container. - - ## Road warriors, roaming and returning home - - If you plan to use Wireguard both remotely and locally, say on your mobile phone, you will need to consider routing. Most firewalls will not route ports forwarded on your WAN interface correctly to the LAN out of the box. This means that when you return home, even though you can see the Wireguard server, the return packets will probably get lost. - - This is not a Wireguard specific issue and the two generally accepted solutions are NAT reflection (setting your edge router/firewall up in such a way as it translates internal packets correctly) or split horizon DNS (setting your internal DNS to return the private rather than public IP when connecting locally). - - Both of these approaches have positives and negatives however their setup is out of scope for this document as everyone's network layout and equipment will be different. - - ## Maintaining local access to attached services - - ** Note: This is not a supported configuration by Linuxserver.io - use at your own risk. - - When routing via Wireguard from another container using the `service` option in docker, you might lose access to the containers webUI locally. To avoid this, exclude the docker subnet from being routed via Wireguard by modifying your `wg0.conf` like so (modifying the subnets as you require): - - ```ini - [Interface] - PrivateKey = - Address = 9.8.7.6/32 - DNS = 8.8.8.8 - PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route add $HOMENET3 via $DROUTE;ip route add $HOMENET2 via $DROUTE; ip route add $HOMENET via $DROUTE;iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT; iptables -A OUTPUT -d $HOMENET3 -j ACCEPT; iptables -A OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT - PreDown = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route del $HOMENET3 via $DROUTE;ip route del $HOMENET2 via $DROUTE; ip route del $HOMENET via $DROUTE; iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; iptables -D OUTPUT -d $HOMENET -j ACCEPT; iptables -D OUTPUT -d $HOMENET2 -j ACCEPT; iptables -D OUTPUT -d $HOMENET3 -j ACCEPT - ``` - - ## Site-to-site VPN - - ** Note: This is not a supported configuration by Linuxserver.io - use at your own risk. - - Site-to-site VPN in server mode requires customizing the `AllowedIPs` statement for a specific peer in `wg0.conf`. Since `wg0.conf` is autogenerated when server vars are changed, it is not recommended to edit it manually. - - In order to customize the `AllowedIPs` statement for a specific peer in `wg0.conf`, you can set an env var `SERVER_ALLOWEDIPS_PEER_` to the additional subnets you'd like to add, comma separated and excluding the peer IP (ie. `"192.168.1.0/24,192.168.2.0/24"`). Replace `` with either the name or number of a peer (whichever is used in the `PEERS` var). - - For instance `SERVER_ALLOWEDIPS_PEER_laptop="192.168.1.0/24,192.168.2.0/24"` will result in the wg0.conf entry `AllowedIPs = 10.13.13.2,192.168.1.0/24,192.168.2.0/24` for the peer named `laptop`. - - Keep in mind that this var will only be considered when the confs are regenerated. Adding this var for an existing peer won't force a regeneration. You can delete wg0.conf and restart the container to force regeneration if necessary. - - Don't forget to set the necessary POSTUP and POSTDOWN rules in your client's peer conf for lan access. -# init diagram -init_diagram: | - "wireguard:latest": { - docker-mods - base { - fix-attr +\nlegacy cont-init - } - docker-mods -> base - legacy-services - custom services - init-services -> legacy-services - init-services -> custom services - custom services -> legacy-services - legacy-services -> ci-service-check - init-migrations -> init-adduser - init-os-end -> init-config - init-config -> init-config-end - init-crontab-config -> init-config-end - init-wireguard-confs -> init-config-end - init-config -> init-crontab-config - init-mods-end -> init-custom-files - init-adduser -> init-device-perms - base -> init-envfile - base -> init-migrations - init-config-end -> init-mods - init-mods-package-install -> init-mods-end - init-mods -> init-mods-package-install - init-adduser -> init-os-end - init-device-perms -> init-os-end - init-envfile -> init-os-end - init-custom-files -> init-services - init-wireguard-module -> init-wireguard-confs - init-config -> init-wireguard-module - init-services -> svc-coredns - svc-coredns -> legacy-services - init-services -> svc-cron - svc-cron -> legacy-services - svc-coredns -> svc-wireguard - svc-wireguard -> legacy-services - } - Base Images: { - "baseimage-alpine:3.23" - } - "wireguard:latest" <- Base Images -# changelog -changelogs: - - {date: "24.01.26:", desc: "Rebase to Alpine 3.23 again as openresolv alpine 3.23 package has now been updated."} - - {date: "22.01.26:", desc: "Revert to Alpine 3.22 due to resolvconf bug."} - - {date: "04.01.26:", desc: "Rebase to Alpine 3.23."} - - {date: "15.07.25:", desc: "Rebase to Alpine 3.22. Remove iptables-legacy shim."} - - {date: "01.01.25:", desc: "Deprecate legacy branch."} - - {date: "20.12.24:", desc: "Rebase to Alpine 3.21."} - - {date: "13.08.24:", desc: "Add `errors` plugin to default Corefile."} - - {date: "23.07.24:", desc: "Install kmod from alpine repository."} - - {date: "24.05.24:", desc: "Rebase to Alpine 3.20, install wireguard-tools from Alpine repo."} - - {date: "10.03.24:", desc: "Use iptables-legacy on Alpine 3.19."} - - {date: "05.03.24:", desc: "Rebase master to Alpine 3.19."} - - {date: "03.10.23:", desc: "**Potentially Breaking Change:** Support for multiple interfaces added. Wireguard confs moved to `/config/wg_confs/`. Any file with a `.conf` extension in that folder will be treated as a live tunnel config and will be attempted to start. If any of the tunnels fail, all tunnels will be stopped. Tunnels are started in alphabetical order. Managed server conf will continue to be hardcoded to `wg0.conf`."} - - {date: "28.06.23:", desc: "Rebase master to Alpine 3.18 again."} - - {date: "26.06.23:", desc: "Revert master to Alpine 3.17, due to issue with openresolv."} - - {date: "24.06.23:", desc: "Rebase master to Alpine 3.18, deprecate armhf as per [https://www.linuxserver.io/armhf](https://www.linuxserver.io/armhf)."} - - {date: "26.04.23:", desc: "Rework branches. Swap alpine and ubuntu builds."} - - {date: "29.01.23:", desc: "Rebase to alpine 3.17."} - - {date: "10.01.23:", desc: "Add new var to add `PersistentKeepalive` to server config for select peers to survive server IP changes when domain name is used."} - - {date: "26.10.22:", desc: "Better handle unsupported peer names. Improve logging."} - - {date: "12.10.22:", desc: "Add Alpine branch. Optimize wg and coredns services."} - - {date: "04.10.22:", desc: "Rebase to Jammy. Upgrade to s6v3."} - - {date: "16.05.22:", desc: "Improve NAT handling in server mode when multiple ethernet devices are present."} - - {date: "23.04.22:", desc: "Add pre-shared key support. Automatically added to all new peer confs generated, existing ones are left without to ensure no breaking changes."} - - {date: "10.04.22:", desc: "Rebase to Ubuntu Focal. Add `LOG_CONFS` env var. Remove deprecated `add-peer` command."} - - {date: "28.10.21:", desc: "Add site-to-site vpn support."} - - {date: "11.02.21:", desc: "Fix bug related to changing internal subnet and named peer confs not updating."} - - {date: "06.10.20:", desc: "Disable CoreDNS in client mode, or if port 53 is already in use in server mode."} - - {date: "04.10.20:", desc: "Allow to specify a list of names as PEERS and add ALLOWEDIPS environment variable. Also, add peer name/id to each one of the peer sections in wg0.conf. Important: Existing users need to delete `/config/templates/peer.conf` and restart"} - - {date: "27.09.20:", desc: "Cleaning service binding example to have accurate PreDown script."} - - {date: "06.08.20:", desc: "Replace resolvconf with openresolv due to dns issues when a client based on this image is connected to a server also based on this image. Add IPv6 info to readme. Display kernel version in logs."} - - {date: "29.07.20:", desc: "Update Coredns config to detect dns loops (existing users need to delete `/config/coredns/Corefile` and restart)."} - - {date: "27.07.20:", desc: "Update Coredns config to prevent issues with non-user-defined bridge networks (existing users need to delete `/config/coredns/Corefile` and restart)."} - - {date: "05.07.20:", desc: "Add Debian updates and security repos for headers."} - - {date: "25.06.20:", desc: "Simplify module tests, prevent iptables issues from resulting in false negatives."} - - {date: "19.06.20:", desc: "Add support for Ubuntu Focal (20.04) kernels. Compile wireguard tools and kernel module instead of using the ubuntu packages. Make module install optional. Improve verbosity in logs."} - - {date: "29.05.20:", desc: "Add support for 64bit raspbian."} - - {date: "28.04.20:", desc: "Add Buster/Stretch backports repos for Debian. Tested with OMV 5 and OMV 4 (on kernel 4.19.0-0.bpo.8-amd64)."} - - {date: "20.04.20:", desc: "Fix typo in client mode conf existence check."} - - {date: "13.04.20:", desc: "Fix bug that forced conf recreation on every start."} - - {date: "08.04.20:", desc: "Add arm32/64 builds and enable multi-arch (rpi4 with ubuntu and raspbian buster tested). Add CoreDNS for `PEERDNS=auto` setting. Update the `add-peer`/`show-peer` scripts to utilize the templates and the `INTERNAL_SUBNET` var (previously missed, oops)."} - - {date: "05.04.20:", desc: "Add `INTERNAL_SUBNET` variable to prevent subnet clashes. Add templates for server and peer confs."} - - {date: "01.04.20:", desc: "Add `show-peer` script and include info on host installed headers."} - - {date: "31.03.20:", desc: "Initial Release."} diff --git a/root/app/show-peer b/root/app/show-peer deleted file mode 100755 index fc5e12d2..00000000 --- a/root/app/show-peer +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -if [[ ! $# -gt 0 ]]; then - echo "You need to specify which peers to show" - exit 0 -fi - -for i in "$@"; do - if [[ "${i}" =~ ^[0-9]+$ ]]; then - PEER_ID="peer${i}" - else - PEER_ID="peer_${i//[^[:alnum:]_-]/}" - fi - - if grep -q "# ${PEER_ID}" /config/wg_confs/wg0.conf; then - echo "PEER ${i} QR code:" - qrencode -t ansiutf8 < /config/${PEER_ID}/${PEER_ID}.conf - else - echo "PEER ${i} is not active" - fi -done diff --git a/root/defaults/Corefile b/root/defaults/Corefile deleted file mode 100644 index e26fbe6f..00000000 --- a/root/defaults/Corefile +++ /dev/null @@ -1,6 +0,0 @@ -. { - loop - errors - health - forward . /etc/resolv.conf -} diff --git a/root/defaults/peer.conf b/root/defaults/peer.conf deleted file mode 100644 index 525b2bd8..00000000 --- a/root/defaults/peer.conf +++ /dev/null @@ -1,10 +0,0 @@ -[Interface] -Address = ${CLIENT_IP} -PrivateKey = $(cat /config/${PEER_ID}/privatekey-${PEER_ID}) -DNS = ${PEERDNS} - -[Peer] -PublicKey = $(cat /config/server/publickey-server) -PresharedKey = $(cat /config/${PEER_ID}/presharedkey-${PEER_ID}) -Endpoint = ${SERVERURL}:${SERVERPORT} -AllowedIPs = ${ALLOWEDIPS} diff --git a/root/defaults/server.conf b/root/defaults/server.conf deleted file mode 100644 index 757682d6..00000000 --- a/root/defaults/server.conf +++ /dev/null @@ -1,6 +0,0 @@ -[Interface] -Address = ${INTERFACE}.1 -ListenPort = 51820 -PrivateKey = $(cat /config/server/privatekey-server) -PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE -PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE diff --git a/root/donate.txt b/root/donate.txt deleted file mode 100644 index a3160324..00000000 --- a/root/donate.txt +++ /dev/null @@ -1 +0,0 @@ -WireGuard: https://www.wireguard.com/donations/ diff --git a/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-wireguard-confs b/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-wireguard-confs deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/dependencies.d/init-wireguard-module b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/dependencies.d/init-wireguard-module deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run deleted file mode 100755 index 080ac006..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash -# shellcheck disable=SC2016,SC1091,SC2183 - -mkdir -p /config/wg_confs - -# migration to subfolder for wg confs -if [[ -z "$(ls -A /config/wg_confs)" ]] && [[ -f /config/wg0.conf ]]; then - echo "**** Performing migration to new folder structure for confs. Please see the image changelog 2023-10-03 entry for more details. ****" - cp /config/wg0.conf /config/wg_confs/wg0.conf - rm -rf /config/wg0.conf || : -fi - -# prepare templates -if [[ ! -f /config/templates/server.conf ]]; then - cp /defaults/server.conf /config/templates/server.conf -fi -if [[ ! -f /config/templates/peer.conf ]]; then - cp /defaults/peer.conf /config/templates/peer.conf -fi -# add preshared key to user templates (backwards compatibility) -if ! grep -q 'PresharedKey' /config/templates/peer.conf; then - sed -i 's|^Endpoint|PresharedKey = \$\(cat /config/\${PEER_ID}/presharedkey-\${PEER_ID}\)\nEndpoint|' /config/templates/peer.conf -fi - -generate_confs () { - mkdir -p /config/server - if [[ ! -f /config/server/privatekey-server ]]; then - umask 077 - wg genkey | tee /config/server/privatekey-server | wg pubkey > /config/server/publickey-server - fi - eval "$(printf %s) - cat < /config/wg_confs/wg0.conf -$(cat /config/templates/server.conf) - -DUDE" - for i in "${PEERS_ARRAY[@]}"; do - if [[ ! "${i}" =~ ^[[:alnum:]_]+$ ]]; then - echo "**** Peer ${i} contains non-alphanumeric characters and thus will be skipped. No config for peer ${i} will be generated. ****" - else - if [[ "${i}" =~ ^[0-9]+$ ]]; then - PEER_ID="peer${i}" - else - PEER_ID="peer_${i}" - fi - mkdir -p "/config/${PEER_ID}" - if [[ ! -f "/config/${PEER_ID}/privatekey-${PEER_ID}" ]]; then - umask 077 - wg genkey | tee "/config/${PEER_ID}/privatekey-${PEER_ID}" | wg pubkey > "/config/${PEER_ID}/publickey-${PEER_ID}" - wg genpsk > "/config/${PEER_ID}/presharedkey-${PEER_ID}" - fi - if [[ -f "/config/${PEER_ID}/${PEER_ID}.conf" ]]; then - CLIENT_IP=$(grep "Address" "/config/${PEER_ID}/${PEER_ID}.conf" | awk '{print $NF}') - if [[ -n "${ORIG_INTERFACE}" ]] && [[ "${INTERFACE}" != "${ORIG_INTERFACE}" ]]; then - CLIENT_IP="${CLIENT_IP//${ORIG_INTERFACE}/${INTERFACE}}" - fi - else - for idx in {2..254}; do - PROPOSED_IP="${INTERFACE}.${idx}" - if ! grep -q -R "${PROPOSED_IP}" /config/peer*/*.conf 2>/dev/null && ([[ -z "${ORIG_INTERFACE}" ]] || ! grep -q -R "${ORIG_INTERFACE}.${idx}" /config/peer*/*.conf 2>/dev/null); then - CLIENT_IP="${PROPOSED_IP}" - break - fi - done - fi - if [[ -f "/config/${PEER_ID}/presharedkey-${PEER_ID}" ]]; then - # create peer conf with presharedkey - eval "$(printf %s) - cat < /config/${PEER_ID}/${PEER_ID}.conf -$(cat /config/templates/peer.conf) -DUDE" - # add peer info to server conf with presharedkey - cat <> /config/wg_confs/wg0.conf -[Peer] -# ${PEER_ID} -PublicKey = $(cat "/config/${PEER_ID}/publickey-${PEER_ID}") -PresharedKey = $(cat "/config/${PEER_ID}/presharedkey-${PEER_ID}") -DUDE - else - echo "**** Existing keys with no preshared key found for ${PEER_ID}, creating confs without preshared key for backwards compatibility ****" - # create peer conf without presharedkey - eval "$(printf %s) - cat < /config/${PEER_ID}/${PEER_ID}.conf -$(sed '/PresharedKey/d' "/config/templates/peer.conf") -DUDE" - # add peer info to server conf without presharedkey - cat <> /config/wg_confs/wg0.conf -[Peer] -# ${PEER_ID} -PublicKey = $(cat "/config/${PEER_ID}/publickey-${PEER_ID}") -DUDE - fi - SERVER_ALLOWEDIPS=SERVER_ALLOWEDIPS_PEER_${i} - # add peer's allowedips to server conf - if [[ -n "${!SERVER_ALLOWEDIPS}" ]]; then - echo "Adding ${!SERVER_ALLOWEDIPS} to wg0.conf's AllowedIPs for peer ${i}" - cat <> /config/wg_confs/wg0.conf -AllowedIPs = ${CLIENT_IP}/32,${!SERVER_ALLOWEDIPS} -DUDE - else - cat <> /config/wg_confs/wg0.conf -AllowedIPs = ${CLIENT_IP}/32 -DUDE - fi - # add PersistentKeepalive if the peer is specified - if [[ -n "${PERSISTENTKEEPALIVE_PEERS_ARRAY}" ]] && ([[ "${PERSISTENTKEEPALIVE_PEERS_ARRAY[0]}" = "all" ]] || printf '%s\0' "${PERSISTENTKEEPALIVE_PEERS_ARRAY[@]}" | grep -Fxqz -- "${i}"); then - cat <> /config/wg_confs/wg0.conf -PersistentKeepalive = 25 - -DUDE - else - cat <> /config/wg_confs/wg0.conf - -DUDE - fi - if [[ -z "${LOG_CONFS}" ]] || [[ "${LOG_CONFS}" = "true" ]]; then - echo "PEER ${i} QR code (conf file is saved under /config/${PEER_ID}):" - qrencode -t ansiutf8 < "/config/${PEER_ID}/${PEER_ID}.conf" - else - echo "PEER ${i} conf and QR code png saved in /config/${PEER_ID}" - fi - qrencode -o "/config/${PEER_ID}/${PEER_ID}.png" < "/config/${PEER_ID}/${PEER_ID}.conf" - fi - done -} - -save_vars () { - cat < /config/.donoteditthisfile -ORIG_SERVERURL="$SERVERURL" -ORIG_SERVERPORT="$SERVERPORT" -ORIG_PEERDNS="$PEERDNS" -ORIG_PEERS="$PEERS" -ORIG_INTERFACE="$INTERFACE" -ORIG_ALLOWEDIPS="$ALLOWEDIPS" -ORIG_PERSISTENTKEEPALIVE_PEERS="$PERSISTENTKEEPALIVE_PEERS" -DUDE -} - -if [[ -n "$PEERS" ]]; then - echo "**** Server mode is selected ****" - if [[ "$PEERS" =~ ^[0-9]+$ ]] && ! [[ "$PEERS" = *,* ]]; then - mapfile -t PEERS_ARRAY < <(seq 1 "${PEERS}") - else - mapfile -t PEERS_ARRAY < <(echo "${PEERS}" | tr ',' '\n') - fi - if [[ -n "${PERSISTENTKEEPALIVE_PEERS}" ]]; then - echo "**** PersistentKeepalive will be set for: ${PERSISTENTKEEPALIVE_PEERS/,/ } ****" - mapfile -t PERSISTENTKEEPALIVE_PEERS_ARRAY < <(echo "${PERSISTENTKEEPALIVE_PEERS}" | tr ',' '\n') - fi - if [ -z "$SERVERURL" ] || [ "$SERVERURL" = "auto" ]; then - - # If _DAPPNODE_GLOBAL_HOSTNAME is set, use it as SERVERURL - if [ ! -z "$_DAPPNODE_GLOBAL_HOSTNAME" ]; then - echo "**** _DAPPNODE_GLOBAL_HOSTNAME is set, using it as SERVERURL ****" - SERVERURL="$_DAPPNODE_GLOBAL_HOSTNAME" - else - function fetchDappnodeDomain { - for i in {1..10}; do - for domain in "$@"; do - echo "**** Fetching DAppNode domain from $domain..." - SERVERURL=$(curl -s "$domain/global-envs/HOSTNAME") - if [ ! -z "$SERVERURL" ]; then - break - fi - sleep 2 - done - done - } - - domains=("my.dappnode" "dappmanager.dappnode" "172.33.1.7" "10.20.0.7") - - fetchDappnodeDomain "${domains[@]}" - fi - else - echo "**** External server address is set to $SERVERURL ****" - fi - SERVERPORT=${SERVERPORT:-51820} - echo "**** External server port is set to ${SERVERPORT}. Make sure that port is properly forwarded to port 51820 inside this container ****" - INTERNAL_SUBNET=${INTERNAL_SUBNET:-10.13.13.0} - echo "**** Internal subnet is set to $INTERNAL_SUBNET ****" - INTERFACE=$(echo "$INTERNAL_SUBNET" | awk 'BEGIN{FS=OFS="."} NF--') - ALLOWEDIPS=${ALLOWEDIPS:-0.0.0.0/0, ::/0} - echo "**** AllowedIPs for peers $ALLOWEDIPS ****" - if [[ -z "$PEERDNS" ]] || [[ "$PEERDNS" = "auto" ]]; then - PEERDNS="${INTERFACE}.1" - echo "**** PEERDNS var is either not set or is set to \"auto\", setting peer DNS to ${INTERFACE}.1 to use wireguard docker host's DNS. ****" - else - echo "**** Peer DNS servers will be set to $PEERDNS ****" - fi - if [[ ! -f /config/wg_confs/wg0.conf ]]; then - echo "**** No wg0.conf found (maybe an initial install), generating 1 server and ${PEERS} peer/client confs ****" - generate_confs - save_vars - else - echo "**** Server mode is selected ****" - if [[ -f /config/.donoteditthisfile ]]; then - . /config/.donoteditthisfile - fi - if [[ "$SERVERURL" != "$ORIG_SERVERURL" ]] || [[ "$SERVERPORT" != "$ORIG_SERVERPORT" ]] || [[ "$PEERDNS" != "$ORIG_PEERDNS" ]] || [[ "$PEERS" != "$ORIG_PEERS" ]] || [[ "$INTERFACE" != "$ORIG_INTERFACE" ]] || [[ "$ALLOWEDIPS" != "$ORIG_ALLOWEDIPS" ]] || [[ "$PERSISTENTKEEPALIVE_PEERS" != "$ORIG_PERSISTENTKEEPALIVE_PEERS" ]]; then - echo "**** Server related environment variables changed, regenerating 1 server and ${PEERS} peer/client confs ****" - generate_confs - save_vars - else - echo "**** No changes to parameters. Existing configs are used. ****" - fi - fi -else - echo "**** Client mode selected. ****" - USE_COREDNS="${USE_COREDNS,,}" - printf %s "${USE_COREDNS:-false}" > /run/s6/container_environment/USE_COREDNS -fi - -# set up CoreDNS -if [[ ! -f /config/coredns/Corefile ]]; then - cp /defaults/Corefile /config/coredns/Corefile -fi - -# let openresolv take control of resolv.conf -resolvconf -a control 2>/dev/null < /etc/resolv.conf -resolvconf -u - -# permissions -lsiown -R abc:abc \ - /config diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/type b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/up b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/up deleted file mode 100644 index 892cce9e..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/dependencies.d/init-config b/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/dependencies.d/init-config deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/run b/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/run deleted file mode 100755 index 5b298dc2..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/run +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/with-contenv bash - -mkdir -p /config/{templates,coredns} - -echo "Uname info: $(uname -a)" -# check for wireguard module -ip link del dev test 2>/dev/null -if ip link add dev test type wireguard; then - ip link del dev test - if capsh --current | grep "Current:" | grep -q "cap_sys_module"; then - echo "**** As the wireguard module is already active you can remove the SYS_MODULE capability from your container run/compose. ****" - echo "**** If your host does not automatically load the iptables module, you may still need the SYS_MODULE capability. ****" - fi -else - echo "**** The wireguard module is not active. If you believe that your kernel should have wireguard support already, make sure that it is activated via modprobe! ****" - sleep infinity -fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/type b/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/up b/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/up deleted file mode 100644 index b197ae8e..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-module/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-wireguard-module/run diff --git a/root/etc/s6-overlay/s6-rc.d/svc-coredns/dependencies.d/init-services b/root/etc/s6-overlay/s6-rc.d/svc-coredns/dependencies.d/init-services deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/svc-coredns/notification-fd b/root/etc/s6-overlay/s6-rc.d/svc-coredns/notification-fd deleted file mode 100644 index 00750edc..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-coredns/notification-fd +++ /dev/null @@ -1 +0,0 @@ -3 diff --git a/root/etc/s6-overlay/s6-rc.d/svc-coredns/run b/root/etc/s6-overlay/s6-rc.d/svc-coredns/run deleted file mode 100755 index f9a478c3..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-coredns/run +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -if netstat -apn | grep -q ":53 " && [[ -z "${USE_COREDNS}" ]]; then - USE_COREDNS="false" -fi - -if [[ ${USE_COREDNS} == "false" ]]; then - s6-notifyoncheck -d -n 300 -w 1000 -c "echo **** Disabling CoreDNS ****" \ - sleep infinity -else - if grep -q "health" /config/coredns/Corefile; then - exec \ - s6-notifyoncheck -d -n 300 -w 1000 -c "redirfd -w 1 /dev/null curl -s http://localhost:8080/health" \ - cd /config/coredns \ - /usr/bin/coredns -dns.port=53 - else - exec \ - s6-notifyoncheck -d -n 300 -w 1000 -c "nc -zu localhost 53" \ - cd /config/coredns \ - /usr/bin/coredns -dns.port=53 - fi -fi diff --git a/root/etc/s6-overlay/s6-rc.d/svc-coredns/type b/root/etc/s6-overlay/s6-rc.d/svc-coredns/type deleted file mode 100644 index 5883cff0..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-coredns/type +++ /dev/null @@ -1 +0,0 @@ -longrun diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/dependencies.d/svc-coredns b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/dependencies.d/svc-coredns deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/down b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/down deleted file mode 100644 index 5ef932fc..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/down +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/svc-wireguard/finish diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/finish b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/finish deleted file mode 100755 index f36d374d..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/finish +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -if [[ -f "/run/activeconfs" ]]; then - . /run/activeconfs - for tunnel in $(printf '%s\n' "${WG_CONFS[@]}" | tac | tr '\n' ' '; echo); do - echo "**** Disabling tunnel ${tunnel} ****" - wg-quick down "${tunnel}" || : - done - echo "**** All tunnels are down ****" - rm -rf /run/activeconfs -fi diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/run b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/run deleted file mode 100755 index fa0b092f..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/run +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -unset WG_CONFS -rm -rf /run/activeconfs -# Enumerate interfaces -for wgconf in /config/wg_confs/*.conf; do - if grep -q "\[Interface\]" "${wgconf}"; then - echo "**** Found WG conf ${wgconf}, adding to list ****" - WG_CONFS+=("${wgconf}") - else - echo "**** Found WG conf ${wgconf}, but it doesn't seem to be valid, skipping. ****" - fi -done - -if [[ -z "${WG_CONFS[*]}" ]]; then - echo "**** No valid tunnel config found. Please create a valid config and restart the container ****" - ip route del default - exit 0 -fi - -unset FAILED -for tunnel in "${WG_CONFS[@]}"; do - echo "**** Activating tunnel ${tunnel} ****" - if ! wg-quick up "${tunnel}"; then - FAILED="${tunnel}" - break - fi -done - -if [[ -z "${FAILED}" ]]; then - declare -p WG_CONFS > /run/activeconfs - echo "**** All tunnels are now active ****" -else - echo "**** Tunnel ${FAILED} failed, will stop all others! ****" - for tunnel in "${WG_CONFS[@]}"; do - if [[ "${tunnel}" = "${FAILED}" ]]; then - break - else - echo "**** Disabling tunnel ${tunnel} ****" - wg-quick down "${tunnel}" || : - fi - done - ip route del default - echo "**** All tunnels are now down. Please fix the tunnel config ${FAILED} and restart the container ****" -fi diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/type b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/up b/root/etc/s6-overlay/s6-rc.d/svc-wireguard/up deleted file mode 100644 index 47b67d6b..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-wireguard/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/svc-wireguard/run diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-wireguard-confs b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-wireguard-confs deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-wireguard-module b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-wireguard-module deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-coredns b/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-coredns deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-wireguard b/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-wireguard deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/configuration.sh b/tests/configuration.sh new file mode 100644 index 00000000..253d5a6a --- /dev/null +++ b/tests/configuration.sh @@ -0,0 +1,116 @@ +#!/bin/bash +set -euo pipefail +umask 077 +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +export DATA_DIR="$work/config" PEERS=dappnode_admin SERVERURL=example.org SERVERPORT=51820 +export PEERDNS=172.33.1.2,10.20.0.2 INTERNAL_SUBNET=10.24.0.0 +export ALLOWEDIPS=172.33.0.0/16,10.20.0.0/24 +configure=/usr/local/lib/wireguard/configure.sh +fail() { echo "Configuration test failed: $*" >&2; exit 1; } +hashes() { find "$DATA_DIR" -type f | sort | xargs sha256sum; } +"$configure" +before=$(hashes) +"$configure" --preflight +[[ $(hashes) == "$before" ]] || fail 'preflight changed files' +"$configure" +[[ $(hashes) == "$before" ]] || fail 'non-idempotent render' +key=$(cat "$DATA_DIR/peer_dappnode_admin/privatekey-peer_dappnode_admin") +psk=$(cat "$DATA_DIR/peer_dappnode_admin/presharedkey-peer_dappnode_admin") +PEERS=dappnode_admin,laptop "$configure" +grep -Fq 'Address = 10.24.0.3' "$DATA_DIR/peer_laptop/peer_laptop.conf" || fail 'address allocation' +PEERS=laptop "$configure" +! grep -Fq '# peer_dappnode_admin' "$DATA_DIR/wg_confs/wg0.conf" || fail 'peer not revoked' +"$configure" +[[ $(cat "$DATA_DIR/peer_dappnode_admin/privatekey-peer_dappnode_admin") == "$key" ]] || fail 'identity rotated' +[[ $(cat "$DATA_DIR/peer_dappnode_admin/presharedkey-peer_dappnode_admin") == "$psk" ]] || fail 'PSK rotated' +grep -Fq 'Address = 10.24.0.2' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" || fail 'address changed' +PEERS='' "$configure" +! grep -q '^\[Peer\]' "$DATA_DIR/wg_confs/wg0.conf" || fail 'empty peer list is not a server' +"$configure" +SERVERURL=new.example.org SERVERPORT=51821 "$configure" +grep -Fq 'Endpoint = new.example.org:51821' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" || fail 'endpoint update' +[[ $(cat "$DATA_DIR/server/privatekey-server") == $(sed -n 's/^PrivateKey = //p' "$DATA_DIR/wg_confs/wg0.conf") ]] || fail 'server identity differs' +INTERNAL_SUBNET=10.25.0.0 "$configure" +grep -Fq 'Address = 10.25.0.2' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" || fail 'subnet change did not preserve host ID' +"$configure" +before=$(hashes) +for args in 'PEERDNS=auto' 'SERVERPORT=99999' 'INTERNAL_SUBNET=999.1.1.0' 'PEERS=bad/name' 'PEERS=dappnode_admin,dappnode_admin' 'PERSISTENTKEEPALIVE_PEERS=all' 'SERVER_ALLOWEDIPS_PEER_dappnode_admin=192.168.1.0/24' "SERVERURL=\$(touch /tmp/wg-injection)"; do + if env "$args" "$configure" >/dev/null 2>&1; then fail 'invalid input accepted'; fi + [[ $(hashes) == "$before" ]] || fail 'invalid input changed files' +done +[[ ! -f /tmp/wg-injection ]] || fail 'shell injection executed' +cp "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" "$work/original-profile" +sed -i 's/^DNS = .*/DNS = 10.24.0.1, 1.1.1.1/' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +before=$(hashes) +if "$configure" >/dev/null 2>&1; then fail 'removed CoreDNS address accepted in DNS list'; fi +[[ $(hashes) == "$before" ]] || fail 'removed CoreDNS rejection changed files' +mv "$work/original-profile" "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +printf '%s\n' "\$(touch \"$work/executed\")" > "$DATA_DIR/.donoteditthisfile" +"$configure" +[[ ! -f $work/executed ]] || fail 'persisted state was executed' +mkdir -p "$DATA_DIR/templates" +mkdir -p "$DATA_DIR/coredns" +printf '%s\n' "\$(touch \"$work/template-executed\")" > "$DATA_DIR/templates/peer.conf" +printf 'unused CoreDNS file\n' > "$DATA_DIR/coredns/Corefile" +# Installed Dappnode client profiles retain their listen ports. Old template +# and CoreDNS files are inert and do not control the renderer. +sed -i '/^PrivateKey =/a ListenPort = 51820' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +before=$(hashes) +"$configure" --preflight +[[ $(hashes) == "$before" ]] || fail 'historical stock preflight changed files' +profile=$(cat "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf") +"$configure" +[[ $(cat "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf") == "$profile" ]] || fail 'historical stock migration changed a profile' +[[ ! -f $work/template-executed ]] || fail 'a discarded template was executed' +sed -i 's/^ListenPort = .*/ListenPort = 99999/' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +before=$(hashes) +if "$configure" >/dev/null 2>&1; then fail 'invalid saved client port accepted'; fi +[[ $(hashes) == "$before" ]] || fail 'invalid saved client port changed files' +sed -i 's/^ListenPort = .*/ListenPort = 51820/' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +cp "$DATA_DIR/wg_confs/wg0.conf" "$work/server-profile" +sed -i '/^PostUp =/a PreUp = false' "$DATA_DIR/wg_confs/wg0.conf" +before=$(hashes) +if "$configure" >/dev/null 2>&1; then fail 'custom server option discarded'; fi +[[ $(hashes) == "$before" ]] || fail 'custom server rejection changed files' +mv "$work/server-profile" "$DATA_DIR/wg_confs/wg0.conf" +cp "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" "$work/client-profile" +sed -i '/^Address =/a MTU = 1280' "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +before=$(hashes) +if "$configure" >/dev/null 2>&1; then fail 'custom client option discarded'; fi +[[ $(hashes) == "$before" ]] || fail 'custom client rejection changed files' +mv "$work/client-profile" "$DATA_DIR/peer_dappnode_admin/peer_dappnode_admin.conf" +cp "$DATA_DIR/wg_confs/wg0.conf" "$DATA_DIR/wg_confs/extra.conf" +before=$(hashes) +if "$configure" >/dev/null 2>&1; then fail 'additional tunnel ignored'; fi +[[ $(hashes) == "$before" ]] || fail 'extra tunnel failure changed files' +rm "$DATA_DIR/wg_confs/extra.conf" +if env -u PEERS "$configure" >/dev/null 2>&1; then fail 'client mode accepted'; fi +PEERS=3 "$configure" +test -f "$DATA_DIR/peer1/peer1.conf" +numeric_key=$(cat "$DATA_DIR/peer1/privatekey-peer1") +numeric_address=$(sed -n 's/^Address = //p' "$DATA_DIR/peer1/peer1.conf") +mv "$DATA_DIR/wg_confs/wg0.conf" "$DATA_DIR/wg0.conf" +PEERS=3 "$configure" +test -f "$DATA_DIR/wg_confs/wg0.conf" +test "$(cat "$DATA_DIR/peer1/privatekey-peer1")" = "$numeric_key" +PEERS=4 "$configure" +test "$(cat "$DATA_DIR/peer1/privatekey-peer1")" = "$numeric_key" +test "$(sed -n 's/^Address = //p' "$DATA_DIR/peer1/peer1.conf")" = "$numeric_address" +PEERS=2 "$configure" +! grep -Fq '# peer3' "$DATA_DIR/wg_confs/wg0.conf" || fail 'numeric peer not revoked' +PEERS=3 "$configure" +# Missing PSKs are valid for previously generated clients and must stay missing. +rm "$DATA_DIR/peer1/presharedkey-peer1" +sed -i '/^PresharedKey =/d' "$DATA_DIR/peer1/peer1.conf" +PEERS=3 "$configure" +test ! -e "$DATA_DIR/peer1/presharedkey-peer1" +# A missing PSK file can be recovered from the existing client config without rotation. +psk=$(cat "$DATA_DIR/peer2/presharedkey-peer2") +rm "$DATA_DIR/peer2/presharedkey-peer2" +PEERS=3 "$configure" +test "$(cat "$DATA_DIR/peer2/presharedkey-peer2")" = "$psk" +test "$(cat "$DATA_DIR/peer1/privatekey-peer1")" = "$numeric_key" +test "$(sed -n 's/^Address = //p' "$DATA_DIR/peer1/peer1.conf")" = "$numeric_address" +test "$(stat -c '%u:%g:%a' "$DATA_DIR/server/privatekey-server")" = 1000:1000:600 +echo 'Configuration lifecycle, migration guards and identity preservation passed.' diff --git a/tests/integration.cjs b/tests/integration.cjs new file mode 100644 index 00000000..579987f3 --- /dev/null +++ b/tests/integration.cjs @@ -0,0 +1,502 @@ +// Run with DOCKER_HOST=ssh:// to use a remote native Docker daemon. +// Only fresh named volumes/private networks are used. No host ports are published. +const { execFileSync } = require("node:child_process"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { X509Certificate } = require("node:crypto"); + +const image = process.env.WIREGUARD_TEST_IMAGE || "wireguard-refactor:test"; +const apiImage = + process.env.WIREGUARD_API_TEST_IMAGE || "wireguard-api-refactor:test"; +const baseline = process.env.WIREGUARD_BASELINE_IMAGE || image; +const baselineApi = process.env.WIREGUARD_BASELINE_API_IMAGE; +const legacyListenPort = process.env.WIREGUARD_LEGACY_LISTEN_PORT === "1"; +const prefix = `wg-refactor-${process.pid}-${Date.now()}`; +const network = `${prefix}-network`; +let volume = `${prefix}-config`; +const volumes = [volume]; +const server = `${prefix}-server`, + client = `${prefix}-client`, + api = `${prefix}-api`, + backend = `${prefix}-backend`; +const fixture = fs.mkdtempSync(path.join(os.tmpdir(), "wg-refactor-")); +const checks = []; +let backendIp, serverIp; +function docker(args, input) { + if (["run", "create"].includes(args[0])) + args = [args[0], "--cpus", "1", "--memory", "512m", ...args.slice(1)]; + return execFileSync("docker", args, { + input, + encoding: "utf8", + timeout: 90000, + stdio: ["pipe", "pipe", "pipe"], + }).trim(); +} +function inside(name, args, input) { + return docker( + ["exec", ...(input === undefined ? [] : ["-i"]), name, ...args], + input, + ); +} +async function until(check, description, timeout = 180000) { + const deadline = Date.now() + timeout; + while (Date.now() < deadline) { + try { + if (check()) return; + } catch { + /* A starting/rekeying service is expected to be temporarily unavailable. */ + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + throw new Error(`Timed out: ${description}`); +} +function startServer(tag, peers = "dappnode_admin") { + docker([ + "run", + "-d", + "--name", + server, + "--network", + network, + "--network-alias", + "wg-test-server", + ...(serverIp ? ["--ip", serverIp] : []), + "--cap-add", + "NET_ADMIN", + "-v", + `${volume}:/config`, + "-e", + `PEERS=${peers}`, + "-e", + "SERVERURL=wg-test-server", + "-e", + "SERVERPORT=51820", + "-e", + "INTERNAL_SUBNET=10.24.0.0", + "-e", + `PEERDNS=${backendIp}`, + "-e", + `ALLOWEDIPS=10.24.0.0/24,${backendIp}/32`, + "-e", + "LOG_CONFS=false", + tag, + ]); +} +async function ready() { + await until( + () => + inside(server, [ + "/bin/sh", + "-ec", + 'test "$(wg show wg0 listen-port)" = 51820; test -f /run/activeconfs -o -f /run/wireguard-ready', + ]) === "", + "server readiness", + ); +} +function identity(peer = "peer_dappnode_admin") { + return inside(server, [ + "/bin/sh", + "-ec", + `sha256sum /config/server/* /config/${peer}/*key*; grep '^Address =' /config/${peer}/${peer}.conf`, + ]); +} +function traffic() { + inside(client, ["ping", "-c", "2", "-W", "2", "10.24.0.1"]); + const handshakes = inside(server, ["wg", "show", "wg0", "latest-handshakes"]); + assert.ok( + handshakes.split("\n").some((line) => Number(line.split(/\s+/)[1]) > 0), + "no handshake", + ); +} +function reconnect() { + inside(client, [ + "/bin/bash", + "-ec", + "wg-quick down /tmp/wg0.conf; wg-quick up /tmp/wg0.conf", + ]); +} +function startClient(config) { + docker([ + "run", + "-d", + "--name", + client, + "--network", + network, + "--cap-add", + "NET_ADMIN", + "--entrypoint", + "/bin/sh", + image, + "-c", + "sleep 1800", + ]); + inside( + client, + ["/bin/sh", "-ec", "cat > /tmp/wg0.conf; chmod 600 /tmp/wg0.conf"], + config.replace(/^DNS =.*\n/m, "") + "\n", + ); + inside(client, ["wg-quick", "up", "/tmp/wg0.conf"]); +} +async function replaceServer(tag, peers, forceReconnect = true) { + docker(["stop", server]); + docker(["rm", server]); + startServer(tag, peers); + await ready(); + if (forceReconnect) reconnect(); +} +function startApi(tag) { + docker([ + "run", + "-d", + "--name", + api, + "--network", + network, + "-v", + `${volume}:/config:ro`, + tag, + ]); +} +async function apiReady(device = "dappnode_admin") { + await until( + () => + inside(api, [ + "node", + "-e", + `fetch('http://127.0.0.1/${device}').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`, + ]) === "", + "API readiness", + ); +} +const apiCheck = `const assert=require('node:assert/strict'),fs=require('node:fs'),{execFileSync}=require('node:child_process');(async()=>{ +const expected=fs.readFileSync('/config/peer_dappnode_admin/peer_dappnode_admin.conf','utf8'); +for(const suffix of ['', '?local']){const r=await fetch('http://127.0.0.1/dappnode_admin'+suffix);assert.ok(r.ok,'HTTP config status');assert.ok(r.headers.get('access-control-allow-origin')==='http://my.dappnode','CORS');assert.ok(await r.text()===(suffix?expected.replace(/^Endpoint =.*$/m,'Endpoint = 192.168.50.2:51820'):expected),'credential response changed');} +const qr=await fetch('http://127.0.0.1/dappnode_admin?qr');assert.ok(qr.ok,'HTTP QR status');assert.ok(Buffer.from(await qr.arrayBuffer()).subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])),'invalid PNG'); +for(const flags of [[],['--local'],['--localhost'],['--qr'],['--local','--qr'],['--help']]){const out=execFileSync('getWireguardCredentials',flags,{encoding:'utf8'});assert.ok(out.length>0,'empty CLI result');if(!flags.length)assert.ok(out.includes(expected),'CLI config changed');if(flags.includes('--localhost'))assert.ok(out.includes('Endpoint = localhost:51820'),'localhost endpoint');} +console.log('API/CLI modes passed');})().catch(e=>{console.error(e.message);process.exit(1)});`; + +(async () => { + try { + docker(["network", "create", "--internal", network]); + // Let Docker choose a free subnet, then make it explicit. Older daemons + // reject static container IPs when the network used automatic IPAM. + const subnet = JSON.parse(docker(["network", "inspect", network]))[0].IPAM + .Config[0].Subnet; + docker(["network", "rm", network]); + docker(["network", "create", "--internal", "--subnet", subnet, network]); + docker(["volume", "create", volume]); + docker([ + "create", + "--name", + backend, + "--network", + network, + "--network-alias", + "my.dappnode", + "--entrypoint", + "node", + apiImage, + "/tmp/mock-services.cjs", + ]); + docker([ + "cp", + path.join(__dirname, "mock-services.cjs"), + `${backend}:/tmp/mock-services.cjs`, + ]); + docker(["start", backend]); + backendIp = JSON.parse(docker(["inspect", backend]))[0].NetworkSettings + .Networks[network].IPAddress; + startServer(baseline); + await ready(); + if (legacyListenPort) { + assert.notEqual( + baseline, + image, + "historical-template test needs the old image", + ); + // Reproduce the historical stock template using synthetic credentials only. + inside(server, [ + "/bin/sh", + "-ec", + "sed -i '/^PrivateKey =/a ListenPort = 51820' /config/templates/peer.conf; sed -i '/^[[:space:]]*errors$/d' /config/coredns/Corefile; rm /config/wg_confs/wg0.conf", + ]); + docker(["restart", server]); + await ready(); + checks.push("historical stock peer template and CoreDNS file"); + } + serverIp = JSON.parse(docker(["inspect", server]))[0].NetworkSettings + .Networks[network].IPAddress; + const originalIdentity = identity(); + const originalConfig = inside(server, [ + "cat", + "/config/peer_dappnode_admin/peer_dappnode_admin.conf", + ]); + // The peer is an independent Linux client, not the removed package client mode. + startClient(originalConfig); + traffic(); + checks.push("baseline handshake and encrypted traffic"); + if (baselineApi) { + startApi(baselineApi); + await apiReady(); + inside(api, ["node", "-e", apiCheck]); + docker(["stop", api]); + docker(["rm", api]); + checks.push("deployed API/CLI baseline"); + } + await replaceServer(image, "dappnode_admin", false); + await until(() => { + traffic(); + return true; + }, "upgrade reconnect"); + assert.equal( + identity(), + originalIdentity, + "upgrade rotated identity or address", + ); + assert.ok( + inside(server, [ + "cat", + "/config/peer_dappnode_admin/peer_dappnode_admin.conf", + ]) === originalConfig, + "upgrade changed client credentials", + ); + checks.push("upgrade preserves credentials, keys, PSKs and peer IPs"); + assert.ok( + inside(client, ["nslookup", "my.dappnode", backendIp]).includes( + backendIp, + ), + "BIND DNS route failed", + ); + assert.ok( + inside(client, ["ip", "route", "get", backendIp]).includes("dev wg0"), + "backend bypasses tunnel", + ); + assert.equal( + inside(client, [ + "curl", + "-fsS", + "--max-time", + "5", + `http://${backendIp}/source`, + ]), + serverIp, + "NAT did not use server IP", + ); + checks.push( + "DNS and HTTP forwarding/NAT through the tunnel without CoreDNS", + ); + startApi(apiImage); + await apiReady(); + inside(api, ["node", "-e", apiCheck]); + assert.ok( + JSON.parse(docker(["inspect", api]))[0].Mounts.some( + (mount) => mount.Destination === "/config" && !mount.RW, + ), + "API volume is writable", + ); + inside(api, [ + "node", + "-e", + "fetch('http://127.0.0.1/dappnode_admin%2f..%2fserver').then(r=>process.exit(r.status===400?0:1))", + ]); + checks.push("API/CLI credentials, CORS, PNG and read-only storage"); + await replaceServer(image, "dappnode_admin,laptop"); + traffic(); + assert.equal(identity(), originalIdentity, "adding peer rotated identity"); + await replaceServer(image, "laptop", false); + const result = inside(api, [ + "node", + "-e", + "fetch('http://127.0.0.1/dappnode_admin').then(r=>console.log(r.status))", + ]); + assert.equal(result, "404", "removed peer credentials remain accessible"); + let blocked = false; + try { + traffic(); + } catch { + blocked = true; + } + assert.ok(blocked, "removed peer can still use tunnel"); + await replaceServer(image, "dappnode_admin,laptop"); + traffic(); + assert.equal( + identity(), + originalIdentity, + "re-adding peer rotated identity/address", + ); + checks.push( + "PEERS add/remove revokes traffic and credentials; re-add preserves identity", + ); + docker(["restart", server]); + await ready(); + await until(() => { + traffic(); + return true; + }, "restart reconnect"); + docker(["restart", api]); + await apiReady(); + inside(api, ["node", "-e", apiCheck]); + assert.equal(identity(), originalIdentity, "restart changed identity"); + assert.equal( + inside(client, [ + "curl", + "-fsS", + "--max-time", + "5", + `http://${backendIp}/source`, + ]), + serverIp, + ); + checks.push("restart reconnect, credentials and NAT"); + if (baseline !== image) { + await replaceServer(baseline, "dappnode_admin,laptop"); + traffic(); + assert.equal(identity(), originalIdentity, "rollback changed identity"); + await replaceServer(image, "dappnode_admin,laptop"); + traffic(); + assert.equal( + identity(), + originalIdentity, + "roll-forward changed identity", + ); + checks.push( + "rollback to deployed image and roll-forward preserve access", + ); + + // Exercise a separate old numeric installation, never converting its + // peer1 directories into named peers or generating replacement keys. + docker(["rm", "-f", client]); + docker(["stop", server]); + docker(["rm", server]); + volume = `${prefix}-numeric-config`; + volumes.push(volume); + docker(["volume", "create", volume]); + startServer(baseline, "2"); + await ready(); + const numericIdentity = identity("peer1"); + const numericConfig = inside(server, ["cat", "/config/peer1/peer1.conf"]); + startClient(numericConfig); + traffic(); + await replaceServer(image, "2", false); + await until(() => { + traffic(); + return true; + }, "numeric upgrade reconnect"); + assert.equal( + identity("peer1"), + numericIdentity, + "numeric identity changed", + ); + assert.ok( + inside(server, ["cat", "/config/peer1/peer1.conf"]) === numericConfig, + "numeric client profile changed", + ); + docker(["rm", "-f", api]); + startApi(apiImage); + await apiReady("1"); + await replaceServer(image, "1"); + inside(api, [ + "node", + "-e", + "fetch('http://127.0.0.1/2').then(r=>process.exit(r.status===404?0:1))", + ]); + await replaceServer(image, "2"); + assert.equal( + identity("peer1"), + numericIdentity, + "numeric resize changed identity", + ); + assert.ok( + inside(server, ["/app/show-peer", "1"]).includes("\x1b["), + "numeric CLI QR failed", + ); + await replaceServer(baseline, "2"); + traffic(); + await replaceServer(image, "2"); + traffic(); + assert.equal( + identity("peer1"), + numericIdentity, + "numeric rollback changed identity", + ); + checks.push( + "old numeric PEERS upgrade, resize, API/CLI, rollback and roll-forward", + ); + } + for (const tag of [image, apiImage]) { + const bundle = docker([ + "run", + "--rm", + "--entrypoint", + "cat", + tag, + "/etc/ssl/certs/ca-certificates.crt", + ]); + const certs = bundle.match( + /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g, + ); + assert.ok( + certs.length > 100 && + !certs.some((cert) => + /CN=(?:AAA Certificate Services|Comodo AAA Services|emSign ECC Root CA - G3)(?:\n|$)/i.test( + new X509Certificate(cert).subject, + ), + ), + "compromised CA remains trusted", + ); + } + inside(api, [ + "node", + "-e", + "const {X509Certificate}=require('node:crypto');if(require('node:tls').rootCertificates.some(c=>/CN=(AAA Certificate Services|Comodo AAA Services|emSign ECC Root CA - G3)(\\n|$)/i.test(new X509Certificate(c).subject)))process.exit(1)", + ]); + checks.push("system and bundled Node CA distrust"); + const report = { + dockerHost: process.env.DOCKER_HOST || "local", + image, + apiImage, + baseline, + checks, + }; + if (process.env.WIREGUARD_TEST_REPORT) + fs.writeFileSync( + process.env.WIREGUARD_TEST_REPORT, + JSON.stringify(report, null, 2), + ); + console.log(JSON.stringify(report, null, 2)); + } catch (error) { + console.error(error.message); + for (const name of [server, client, api]) { + try { + console.error(`${name}: ${docker(["logs", "--tail", "30", name])}`); + } catch { + /* May not have started. */ + } + } + process.exitCode = 1; + } finally { + for (const name of [api, client, server, backend]) { + try { + docker(["rm", "-f", name]); + } catch { + /* Absent after failed startup. */ + } + } + for (const testVolume of volumes) { + try { + docker(["volume", "rm", testVolume]); + } catch { + /* Reported by the final cleanup check when applicable. */ + } + } + try { + docker(["network", "rm", network]); + } catch { + /* Absent after failed creation. */ + } + fs.rmSync(fixture, { recursive: true, force: true }); + } +})(); diff --git a/tests/mock-services.cjs b/tests/mock-services.cjs new file mode 100644 index 00000000..e048ab04 --- /dev/null +++ b/tests/mock-services.cjs @@ -0,0 +1,55 @@ +// Private-network stand-in for Dappmanager and BIND. No external DNS is used. +const http = require("node:http"); +const dgram = require("node:dgram"); +const os = require("node:os"); +const ip = Object.values(os.networkInterfaces()) + .flat() + .find((entry) => entry.family === "IPv4" && !entry.internal).address; +http + .createServer((req, res) => { + res.end( + req.url === "/source" + ? req.socket.remoteAddress + : req.url.endsWith("INTERNAL_IP") + ? "192.168.50.2" + : "wg-test-server", + ); + }) + .listen(80, "0.0.0.0"); +const dns = dgram.createSocket("udp4"); +dns.on("message", (query, client) => { + let end = 12; + while (end < query.length && query[end]) end += query[end] + 1; + if (end + 5 > query.length) return; + const isA = query.readUInt16BE(end + 1) === 1; + const header = Buffer.from(query.subarray(0, 12)); + header.writeUInt16BE(0x8180, 2); + header.writeUInt16BE(1, 4); + header.writeUInt16BE(isA ? 1 : 0, 6); + header.writeUInt32BE(0, 8); + const answer = Buffer.from([ + 0xc0, + 0x0c, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 60, + 0, + 4, + ...ip.split(".").map(Number), + ]); + dns.send( + Buffer.concat([ + header, + query.subarray(12, end + 5), + ...(isA ? [answer] : []), + ]), + client.port, + client.address, + ); +}); +dns.bind(53, "0.0.0.0"); diff --git a/wireguard/configure.sh b/wireguard/configure.sh new file mode 100644 index 00000000..c65c1fb3 --- /dev/null +++ b/wireguard/configure.sh @@ -0,0 +1,232 @@ +#!/bin/bash +# Render the Dappnode server configuration. Never evaluate persisted shell files. +set -euo pipefail +umask 077 +shopt -s nullglob +data=${DATA_DIR:-/config} +fail() { echo "WireGuard configuration: $*" >&2; exit 1; } +[[ $# == 0 || ( $# == 1 && $1 == --preflight ) ]] || fail 'Supported option: --preflight' +ipv4() { + local part + [[ $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1 + local -a parts + IFS=. read -ra parts <<< "$1" + for part in "${parts[@]}"; do + [[ ${#part} -le 3 ]] && ((10#$part <= 255)) || return 1 + done +} + +[[ ${PEERS+x} ]] || fail 'PEERS must be set to a comma-separated device list (or an empty list).' +[[ ${USE_COREDNS:-false} == false ]] || fail 'CoreDNS is no longer included. Configure PEERDNS to use Dappnode BIND.' +[[ ${PEERDNS:-} != auto ]] || fail 'PEERDNS=auto requires the previous image. Set explicit DNS servers before migrating.' +[[ -z ${PERSISTENTKEEPALIVE_PEERS:-} ]] || fail 'PERSISTENTKEEPALIVE_PEERS is unsupported.' +for file in "$data"/wg_confs/*.conf; do + [[ ${file##*/} == wg0.conf ]] || fail 'Additional tunnels require the previous image or manual migration.' +done +[[ ! -d $data ]] || [[ -z $(find "$data" -type l -print -quit) ]] || fail 'Symlinks inside the config volume require manual migration.' + +subnet=${INTERNAL_SUBNET:-10.24.0.0} +ipv4 "$subnet" || fail 'INTERNAL_SUBNET must be an IPv4 /24 network address.' +prefix=${subnet%.*} +port=${SERVERPORT:-51820} +if ! [[ $port =~ ^[0-9]+$ && ${#port} -le 5 ]] || ! ((10#$port >= 1 && 10#$port <= 65535)); then + fail 'Invalid SERVERPORT.' +fi +dns=${PEERDNS:-172.33.1.2,10.20.0.2} +allowed=${ALLOWEDIPS:-172.33.0.0/16,10.20.0.0/24} +[[ $dns =~ ^[a-zA-Z0-9.:,\ _-]+$ ]] || fail 'Invalid PEERDNS.' +[[ $allowed =~ ^[a-fA-F0-9.:/,\ ]+$ ]] || fail 'Invalid ALLOWEDIPS.' +host=${SERVERURL:-${_DAPPNODE_GLOBAL_HOSTNAME:-}} +if [[ -z $host || $host == auto ]]; then + host=${_DAPPNODE_GLOBAL_HOSTNAME:-} + for ((attempt=0; attempt<10 && ${#host}==0; attempt++)); do + for manager in my.dappnode dappmanager.dappnode 172.33.1.7 10.20.0.7; do + host=$(curl --fail --silent --show-error --max-time 3 --max-redirs 0 "http://$manager/global-envs/HOSTNAME" 2>/dev/null) || host= + [[ -z $host ]] || break + done + [[ -n $host ]] || sleep 1 + done +fi +if [[ $host == \[*\] ]]; then host=${host:1:${#host}-2}; fi +if [[ $host == *:* ]]; then + [[ $host =~ ^[a-fA-F0-9:.]+$ ]] || fail 'Invalid IPv6 SERVERURL.' +else + [[ $host =~ ^[a-zA-Z0-9._-]+$ ]] || fail 'SERVERURL is unavailable or invalid.' +fi +endpoint=$host +if [[ $host == *:* && $host != \[*\] ]]; then endpoint="[$host]"; fi + +declare -a peers=() +declare -A seen=() reserved=() assigned=() +if [[ $PEERS =~ ^[0-9]+$ ]]; then + if [[ ${#PEERS} -gt 3 ]] || ((10#$PEERS > 253)); then + fail 'At most 253 peers are supported.' + fi + for ((i=1;i<=10#$PEERS;i++)); do peers+=("$i"); done +else + IFS=, read -ra peers <<< "$PEERS" +fi +for i in "${peers[@]}"; do + [[ $i =~ ^[a-zA-Z0-9_]+$ ]] || fail 'Peer names must contain only letters, numbers and underscores.' + [[ ! ${seen[$i]+yes} ]] || fail 'Duplicate peer name.' + seen[$i]=1 + extra_name="SERVER_ALLOWEDIPS_PEER_$i" + extra=${!extra_name:-} + [[ -z $extra ]] || fail "$extra_name is unsupported." +done +((${#peers[@]} <= 253)) || fail 'At most 253 peers are supported.' + +server_conf="$data/wg_confs/wg0.conf" +if [[ ! -e $server_conf && -f $data/wg0.conf ]]; then server_conf="$data/wg0.conf"; fi +post_up='iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE' +post_down='iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE' +if [[ -f $server_conf ]]; then + [[ -s $data/server/privatekey-server ]] || fail 'Existing server key is missing; refusing to replace its identity.' + [[ $(sed -n 's/^PrivateKey = //p' "$server_conf") == "$(cat "$data/server/privatekey-server")" ]] || fail 'Saved server configuration and private key disagree.' + # Custom lifecycle commands and interface extensions must not be discarded. + while IFS= read -r line; do + case $line in + ''|'#'*|'[Interface]'|'[Peer]'|Address\ =*|ListenPort\ =*|PrivateKey\ =*|PublicKey\ =*|PresharedKey\ =*|AllowedIPs\ =*) ;; + "PostUp = $post_up"|"PostDown = $post_down") ;; + *) fail 'Custom server configuration requires manual migration.' ;; + esac + done < "$server_conf" +fi +for file in "$data"/peer*/peer*.conf; do + while IFS= read -r line; do + case $line in + ''|'#'*|'[Interface]'|'[Peer]'|Address\ =*|ListenPort\ =*|PrivateKey\ =*|DNS\ =*|PublicKey\ =*|PresharedKey\ =*|Endpoint\ =*|AllowedIPs\ =*) ;; + *) fail 'Custom client configuration requires manual migration.' ;; + esac + done < "$file" + address=$(sed -n 's/^Address[[:space:]]*=[[:space:]]*//p' "$file") + address=${address%/32} + ipv4 "$address" || fail "Invalid saved peer address in $file." + last=${address##*.} + ((10#$last >= 2 && 10#$last <= 254)) || fail 'Saved peer address is outside the supported pool.' + reserved[$((10#$last))]=1 + saved_dns=$(sed -n 's/^DNS[[:space:]]*=[[:space:]]*//p' "$file") + [[ ,${saved_dns// /}, != *,"${address%.*}.1",* ]] || fail 'A saved client uses the removed CoreDNS server. Migrate its DNS using the previous image first.' +done + +if [[ ${1:-} == --preflight ]]; then + echo 'WireGuard compatibility preflight passed; no files were changed.' + exit 0 +fi + +mkdir -p "$data" +stage=$(mktemp -d "$data/.wireguard-stage.XXXXXX") +trap 'rm -rf -- "$stage"' EXIT +key() { + local relative=$1 existing_config=$2 private public + mkdir -p "$stage/$(dirname "$relative")" + if [[ -s $data/$relative ]]; then cp "$data/$relative" "$stage/$relative" + elif [[ -f $existing_config ]]; then fail 'A saved peer key is missing; refusing to replace it.' + else wg genkey > "$stage/$relative"; fi + private=$(cat "$stage/$relative") + if [[ -f $existing_config ]]; then + [[ $(sed -n 's/^PrivateKey = //p' "$existing_config") == "$private" ]] || fail 'Saved configuration and private key disagree.' + fi + public=$(printf '%s\n' "$private" | wg pubkey 2>/dev/null) || fail 'Invalid saved private key.' + KEY_PRIVATE=$private + KEY_PUBLIC=$public +} +key server/privatekey-server "$server_conf" +server_public=$KEY_PUBLIC +printf '%s\n' "$server_public" > "$stage/server/publickey-server" +mkdir -p "$stage/wg_confs" +cat > "$stage/wg_confs/wg0.conf" < "$stage/$peer/publickey-$peer" + if [[ -f $conf ]]; then + address=$(sed -n 's/^Address[[:space:]]*=[[:space:]]*//p' "$conf") + address=${address%/32} + last=${address##*.} + last=$((10#$last)) + else + last=2 + while [[ ${reserved[$last]+yes} && $last -le 254 ]]; do last=$((last+1)); done + ((last <= 254)) || fail 'No free peer addresses remain; archive unused peer folders explicitly to reclaim them.' + fi + client_port= + if [[ -f $conf ]]; then + client_port=$(sed -n 's/^ListenPort = //p' "$conf") + if [[ -n $client_port ]]; then + if ! [[ $client_port =~ ^[0-9]+$ && ${#client_port} -le 5 ]] || ! ((10#$client_port >= 1 && 10#$client_port <= 65535)); then + fail 'Invalid saved client ListenPort.' + fi + fi + fi + [[ ! ${assigned[$last]+yes} ]] || fail 'Saved peers have conflicting addresses; refusing to renumber them.' + assigned[$last]=1 reserved[$last]=1 + psk= + if [[ -f $conf ]]; then + psk=$(sed -n 's/^PresharedKey = //p' "$conf") + if [[ -s $data/$peer/presharedkey-$peer ]]; then + [[ $psk == "$(cat "$data/$peer/presharedkey-$peer")" ]] || fail 'Saved peer configuration and preshared key disagree.' + fi + [[ -z $psk ]] || printf '%s\n' "$psk" > "$stage/$peer/presharedkey-$peer" + else + wg genpsk > "$stage/$peer/presharedkey-$peer" + psk=$(cat "$stage/$peer/presharedkey-$peer") + fi + cat > "$stage/$peer/$peer.conf" <> "$stage/$peer/$peer.conf" + printf 'DNS = %s\n\n[Peer]\nPublicKey = %s\n' "$dns" "$server_public" >> "$stage/$peer/$peer.conf" + [[ -z $psk ]] || printf 'PresharedKey = %s\n' "$psk" >> "$stage/$peer/$peer.conf" + printf 'Endpoint = %s:%s\nAllowedIPs = %s\n' "$endpoint" "$port" "$allowed" >> "$stage/$peer/$peer.conf" + { + printf '[Peer]\n# %s\nPublicKey = %s\n' "$peer" "$KEY_PUBLIC" + [[ -z $psk ]] || printf 'PresharedKey = %s\n' "$psk" + printf 'AllowedIPs = %s.%s/32\n' "$prefix" "$last" + printf '\n' + } >> "$stage/wg_confs/wg0.conf" + qrencode -l L -o "$stage/$peer/$peer.png" < "$stage/$peer/$peer.conf" +done +# Normal startup validates the draft through the kernel before publishing any file. +if [[ -n ${WG_VALIDATE_INTERFACE:-} ]]; then + if ! wg setconf "$WG_VALIDATE_INTERFACE" <(wg-quick strip "$stage/wg_confs/wg0.conf") 2> "$stage/kernel-error"; then + fail 'WireGuard rejected the draft. Verify the saved keys and allowed routes; existing configuration was not replaced.' + fi + rm "$stage/kernel-error" +fi +# Keep sanitized metadata solely so the installed Dappnode image can roll back. +# This renderer never reads or evaluates it. +cat > "$stage/.donoteditthisfile" <]*UP' diff --git a/wireguard/show-peer b/wireguard/show-peer new file mode 100644 index 00000000..84573a2c --- /dev/null +++ b/wireguard/show-peer @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail +data=${DATA_DIR:-/config} +for i in "$@"; do + [[ $i =~ ^[a-zA-Z0-9_]+$ ]] || { echo 'Invalid peer name' >&2; exit 1; } + if [[ $i =~ ^[0-9]+$ ]]; then peer="peer$i"; else peer="peer_$i"; fi + if grep -Fxq "# $peer" "$data/wg_confs/wg0.conf"; then + echo "PEER $i QR code:" + qrencode -t ANSIUTF8 < "$data/$peer/$peer.conf" + else echo "PEER $i is not active"; fi +done diff --git a/wireguard/start.sh b/wireguard/start.sh new file mode 100644 index 00000000..0b9890b5 --- /dev/null +++ b/wireguard/start.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -euo pipefail +data=${DATA_DIR:-/config} +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +probe="wgcheck$$" +active=false +cleanup() { + local status=$1 + trap - EXIT + rm -f /run/wireguard-ready "$data/.ready" + ip link del "$probe" 2>/dev/null || true + if $active; then wg-quick down "$data/wg_confs/wg0.conf" || true; fi + exit "$status" +} +trap 'cleanup "$?"' EXIT +trap 'exit 0' TERM INT +rm -f /run/wireguard-ready "$data/.ready" +if ! ip link add "$probe" type wireguard; then + echo 'Host WireGuard support and NET_ADMIN are required. Enable the host kernel module before starting this package.' >&2 + exit 1 +fi +WG_VALIDATE_INTERFACE=$probe "$here/configure.sh" +ip link del "$probe" +wg-quick up "$data/wg_confs/wg0.conf" +active=true +touch /run/wireguard-ready "$data/.ready" +echo 'WireGuard is ready.' +while "$here/healthcheck.sh"; do + sleep 30 & + wait "$!" +done +echo 'WireGuard interface is no longer healthy.' >&2 +cleanup 1