[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: acmesh\nko_fi: neilpang\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--\r\n我很忙, 每天可能只有 几秒钟 时间看你的 issue, 如果不按照我的要求写 issue, 你可能不会得到任何回复, 石沉大海.\r\n\r\n请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试信息. 我做不了什么.\r\n如何调试 https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh\r\n\r\nIf it is a bug report:\r\n- make sure you are able to repro it on the latest released version.\r\nYou can install the latest version by: `acme.sh --upgrade`\r\n\r\n- Search the existing issues.\r\n- Refer to the [WIKI](https://wiki.acme.sh).\r\n- Debug info [Debug](https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh).\r\n\r\n-->\r\n\r\nSteps to reproduce\r\n------------------\r\n\r\nDebug log\r\n-----------------\r\n\r\n```\r\nacme.sh  --issue .....   --debug 2\r\n```\r\n\r\n\r\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\r\n1. Do NOT send pull request to `master` branch.\r\nPlease send to `dev` branch instead.\r\nAny PR to `master` branch will NOT be merged.\r\n\r\n2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide\r\nYou will NOT get any review without passing this guide.  You also need to fix the CI errors.\r\n\r\n-->"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# GitHub Copilot Shell Scripting (sh) Review Instructions\r\n\r\n## 🎯 Overall Goal\r\n\r\nYour role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files). Ensure the code exhibits the highest levels of robustness, security, and portability.\r\nThe review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands.\r\n\r\n## 📝 Required Output Format\r\n\r\nPlease adhere to the previous format: organize the feedback into a single, structured report, using the three-level marking system:\r\n\r\n1.  **🔴 Critical Issues (Must Fix Before Merge)**\r\n2.  **🟡 Suggestions (Improvements to Consider)**\r\n3.  **✅ Good Practices (Points to Commend)**\r\n\r\n---\r\n\r\n## 🔍 Focus Areas and Rules for Shell\r\n\r\n### 1. Robustness and Error Handling\r\n\r\n* **Shebang:** Check that the script starts with the correct Shebang, must be \"#!/usr/bin/env sh\".\r\n* **Startup Options:** **(🔴 Critical)** Enforce the use of the following combination at the start of the script for safety and robustness:\r\n    * `set -e`: Exit immediately if a command exits with a non-zero status.\r\n    * `set -u`: Treat unset variables as an error and exit.\r\n    * `set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails.\r\n* **Exit Codes:** Ensure functions and the main script use `exit 0` for success and a non-zero exit code upon failure.\r\n* **Temporary Files:** Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks.\r\n\r\n### 2. Security and Quoting\r\n\r\n* **Variable Quoting:** **(🔴 Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `\"$VAR\"` and `\"$(COMMAND)\"`) to prevent **Word Splitting** and **Globbing**.\r\n* **Hardcoded Secrets:** **(🔴 Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details.\r\n* **Untrusted Input:** Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use.\r\n* **Avoid `eval`:** Warn against and suggest alternatives to using `eval`, as it can lead to arbitrary code execution.\r\n\r\n### 3. Readability and Maintainability\r\n\r\n* **Function Usage:** Recommend wrapping complex or reusable logic within clearly named functions.\r\n* **Local Variables:** Check that variables inside functions are declared using the `local` keyword to avoid unintentionally modifying global state.\r\n* **Naming Convention:** Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions.\r\n* **Test Conditions:** Encourage the use of Bash's **double brackets `[[ ... ]]`** for conditional tests, as it is generally safer and more powerful (e.g., supports pattern matching and avoids Word Splitting) than single brackets `[ ... ]`.\r\n* **Command Substitution:** Encourage using `$(command)` over backticks `` `command` `` for command substitution, as it is easier to nest and improves readability.\r\n\r\n### 4. External Commands and Environment\r\n\r\n* **`for` Loops:** Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces.\r\n* **Use existing acme.sh functions whenever possible.** For example: do not use `tr '[:upper:]' '[:lower:]'`, use `_lower_case` instead.\r\n* **Do not use `head -n`.** Use the `_head_n()` function instead.\r\n* **Do not use `curl` or `wget`.** Use the `_post()` and `_get()` functions instead.\r\n\r\n---\r\n\r\n### 5. Review Rules for Files Under `dnsapi/`:\r\n\r\n* **Each file must contain a `{filename}_add` function** for adding DNS TXT records. It should use `_readaccountconf_mutable` to read the API key and `_saveaccountconf_mutable` to save it. Do not use `_saveaccountconf` or `_readaccountconf`.\r\n\r\n\r\n## ❌ Things to Avoid\r\n\r\n* Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or Prettier.\r\n* Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable.\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": ".github/workflows/DNS.yml",
    "content": "name: DNS\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - 'dnsapi/*.sh'\n      - '.github/workflows/DNS.yml'\n  pull_request:\n    branches:\n      - 'dev'\n    paths:\n      - 'dnsapi/*.sh'\n      - '.github/workflows/DNS.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  CheckToken:\n    runs-on: ubuntu-latest\n    outputs:\n      hasToken: ${{ steps.step_one.outputs.hasToken }}\n    steps:\n      - name: Set the value\n        id: step_one\n        run: |\n          if [ \"${{secrets.TokenName1}}\" ] ; then\n            echo \"::set-output name=hasToken::true\"\n          else\n            echo \"::set-output name=hasToken::false\"\n          fi\n      - name: Check the value\n        run: echo ${{ steps.step_one.outputs.hasToken }}\n\n  Fail:\n    runs-on: ubuntu-latest\n    needs: CheckToken\n    if: \"contains(needs.CheckToken.outputs.hasToken, 'false')\"\n    steps:\n    - name: \"Read this:   https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test\"\n      run: |\n        echo \"Read this:   https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test\"\n        if [ \"${{github.repository_owner}}\" != \"acmesh-official\" ]; then\n          false\n        fi\n\n  Docker:\n    runs-on: ubuntu-latest\n    needs: CheckToken\n    if: \"contains(needs.CheckToken.outputs.hasToken, 'true')\"\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - name: Set env file\n      run: |\n        cd ../acmetest\n        if [ \"${{ secrets.TokenName1}}\" ] ; then\n          echo \"${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}\" >> docker.env\n        fi\n        if [ \"${{ secrets.TokenName2}}\" ] ; then\n          echo \"${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}\" >> docker.env\n        fi\n        if [ \"${{ secrets.TokenName3}}\" ] ; then\n          echo \"${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}\" >> docker.env\n        fi\n        if [ \"${{ secrets.TokenName4}}\" ] ; then\n          echo \"${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}\" >> docker.env\n        fi\n        if [ \"${{ secrets.TokenName5}}\" ] ; then\n          echo \"${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}\" >> docker.env\n        fi\n\n    - name: Run acmetest\n      run: cd ../acmetest && ./rundocker.sh  testall\n\n\n\n\n  MacOS:\n    runs-on: macos-latest\n    needs: Docker\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install tools\n      run:  brew install socat\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - name: Run acmetest\n      run: |\n        if [ \"${{ secrets.TokenName1}}\" ] ; then\n          export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n        fi\n        if [ \"${{ secrets.TokenName2}}\" ] ; then\n          export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n        fi\n        if [ \"${{ secrets.TokenName3}}\" ] ; then\n          export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n        fi\n        if [ \"${{ secrets.TokenName4}}\" ] ; then\n          export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n        fi\n        if [ \"${{ secrets.TokenName5}}\" ] ; then\n          export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n        fi\n        cd ../acmetest\n        ./letest.sh\n\n\n\n\n  Windows:\n    runs-on: windows-latest\n    needs: MacOS\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - name: Set git to use LF\n      run: |\n          git config --global core.autocrlf false\n    - uses: actions/checkout@v6\n    - name: Install cygwin base packages with chocolatey\n      run: |\n          choco config get cacheLocation\n          choco install --no-progress cygwin\n      shell: cmd\n    - name: Install cygwin additional packages\n      run: |\n          C:\\tools\\cygwin\\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git\n      shell: cmd\n    - name: Set ENV\n      shell: cmd\n      run: |\n          echo PATH=C:\\tools\\cygwin\\bin;C:\\tools\\cygwin\\usr\\bin >> %GITHUB_ENV%\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - name: Run acmetest\n      shell: bash\n      run: |\n        if [ \"${{ secrets.TokenName1}}\" ] ; then\n          export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n        fi\n        if [ \"${{ secrets.TokenName2}}\" ] ; then\n          export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n        fi\n        if [ \"${{ secrets.TokenName3}}\" ] ; then\n          export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n        fi\n        if [ \"${{ secrets.TokenName4}}\" ] ; then\n          export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n        fi\n        if [ \"${{ secrets.TokenName5}}\" ] ; then\n          export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n        fi\n        cd ../acmetest\n        ./letest.sh\n\n\n\n  FreeBSD:\n    runs-on: ubuntu-latest\n    needs: Windows\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/freebsd-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        prepare: pkg install -y socat curl\n        usesh: true\n        sync: nfs\n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n\n  OpenBSD:\n    runs-on: ubuntu-latest\n    needs: FreeBSD\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/openbsd-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        prepare: pkg_add socat curl libiconv\n        usesh: true\n        sync: nfs\n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n\n  NetBSD:\n    runs-on: ubuntu-latest\n    needs: OpenBSD\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/netbsd-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        prepare: |\n          /usr/sbin/pkg_add curl socat\n        usesh: true\n        sync: nfs\n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n\n  DragonFlyBSD:\n    runs-on: ubuntu-latest\n    needs: NetBSD\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/dragonflybsd-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        prepare: |\n          pkg install -y libnghttp2\n          pkg install -y curl socat\n        usesh: true\n        sync: nfs\n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n\n\n\n\n  Solaris:\n    runs-on: ubuntu-latest\n    needs: DragonFlyBSD\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Solaris doesn't accept the expired ISRG X1 root\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/solaris-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        sync: nfs\n        prepare: |\n          pkgutil -U\n          pkgutil -y -i socat\n        run: |\n          pkg set-mediator -v -I default@1.1 openssl\n          export PATH=/usr/gnu/bin:$PATH\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n  Omnios:\n    runs-on: ubuntu-latest\n    needs: Solaris\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Omnios doesn't accept the expired ISRG X1 root\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/omnios-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        sync: nfs\n        prepare: pkg install socat\n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n\n  OpenIndiana:\n    runs-on: ubuntu-latest\n    needs: Omnios\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/openindiana-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        sync: nfs\n        prepare: pkg install socat\n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n        \n  Haiku:\n    runs-on: ubuntu-latest\n    needs: OpenIndiana\n    env:\n      TEST_DNS : ${{ secrets.TEST_DNS }}\n      TestingDomain: ${{ secrets.TestingDomain }}\n      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}\n      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}\n      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}\n      CASE: le_test_dnsapi\n      TEST_LOCAL: 1\n      DEBUG: ${{ secrets.DEBUG }}\n      http_proxy: ${{ secrets.http_proxy }}\n      https_proxy: ${{ secrets.https_proxy }}\n      HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root\n      TokenName1: ${{ secrets.TokenName1}}\n      TokenName2: ${{ secrets.TokenName2}}\n      TokenName3: ${{ secrets.TokenName3}}\n      TokenName4: ${{ secrets.TokenName4}}\n      TokenName5: ${{ secrets.TokenName5}}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/haiku-vm@v1\n      with:\n        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\n        sync: rsync\n        copyback: false\n        prepare: |\n          mkdir -p /boot/home/.cache\n          pkgman install -y cronie\n          \n        run: |\n          if [ \"${{ secrets.TokenName1}}\" ] ; then\n            export ${{ secrets.TokenName1}}=\"${{ secrets.TokenValue1}}\"\n          fi\n          if [ \"${{ secrets.TokenName2}}\" ] ; then\n            export ${{ secrets.TokenName2}}=\"${{ secrets.TokenValue2}}\"\n          fi\n          if [ \"${{ secrets.TokenName3}}\" ] ; then\n            export ${{ secrets.TokenName3}}=\"${{ secrets.TokenValue3}}\"\n          fi\n          if [ \"${{ secrets.TokenName4}}\" ] ; then\n            export ${{ secrets.TokenName4}}=\"${{ secrets.TokenValue4}}\"\n          fi\n          if [ \"${{ secrets.TokenName5}}\" ] ; then\n            export ${{ secrets.TokenName5}}=\"${{ secrets.TokenValue5}}\"\n          fi\n          cd ../acmetest\n          ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n\n\n"
  },
  {
    "path": ".github/workflows/DragonFlyBSD.yml",
    "content": "name: DragonFlyBSD\non:\n  push:\n    branches:\n      - '*'\n    paths:\n      - '*.sh'\n      - '.github/workflows/DragonFlyBSD.yml'\n\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '*.sh'\n      - '.github/workflows/DragonFlyBSD.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n\n\njobs:\n  DragonFlyBSD:\n    strategy:\n      matrix:\n        include:\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\n         #  CA_EMAIL: \"githubtest@acme.sh\"\n         #  TEST_PREFERRED_CHAIN: \"\"\n    runs-on: ubuntu-latest\n    env:\n      TEST_LOCAL: 1\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\n      CA: ${{ matrix.CA }}\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\n    steps:\n    - uses: actions/checkout@v6\n    - uses: anyvm-org/cf-tunnel@v0\n      id: tunnel\n      with:\n        protocol: http\n        port: 8080\n    - name: Set envs\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/dragonflybsd-vm@v1\n      with:\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\n        nat: |\n          \"8080\": \"80\"\n        prepare: |\n          pkg install -y libnghttp2\n          pkg install -y curl socat\n        usesh: true\n        sync: nfs\n        run: |\n          cd ../acmetest \\\n          && ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n"
  },
  {
    "path": ".github/workflows/FreeBSD.yml",
    "content": "name: FreeBSD\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/FreeBSD.yml'\r\n\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/FreeBSD.yml'\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\n\r\njobs:\r\n  FreeBSD:\r\n    strategy:\r\n      matrix:\r\n        include:\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n           ACME_USE_WGET: 1\r\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\r\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\r\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\r\n         #  CA_EMAIL: \"githubtest@acme.sh\"\r\n         #  TEST_PREFERRED_CHAIN: \"\"\r\n    runs-on: ubuntu-latest\r\n    env:\r\n      TEST_LOCAL: 1\r\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r\n      CA: ${{ matrix.CA }}\r\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - uses: anyvm-org/cf-tunnel@v0\r\n      id: tunnel\r\n      with:\r\n        protocol: http\r\n        port: 8080\r\n    - name: Set envs\r\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\r\n    - name: Clone acmetest\r\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r\n    - uses: vmactions/freebsd-vm@v1\r\n      with:\r\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\r\n        nat: |\r\n          \"8080\": \"80\"\r\n        prepare: pkg install -y socat curl wget\r\n        usesh: true\r\n        sync: nfs\r\n        run: |\r\n          cd ../acmetest \\\r\n          && ./letest.sh\r\n    - name: DebugOnError\r\n      if: ${{ failure() }}\r\n      run: |\r\n        echo \"See how to debug in VM:\"\r\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\r\n\r\n"
  },
  {
    "path": ".github/workflows/Haiku.yml",
    "content": "name: Haiku\non:\n  push:\n    branches:\n      - '*'\n    paths:\n      - '*.sh'\n      - '.github/workflows/Haiku.yml'\n\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '*.sh'\n      - '.github/workflows/Haiku.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n\n\njobs:\n  Haiku:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n           ACME_USE_WGET: 1\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\n         #  CA_EMAIL: \"githubtest@acme.sh\"\n         #  TEST_PREFERRED_CHAIN: \"\"\n    runs-on: ubuntu-latest\n    env:\n      TEST_LOCAL: 1\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\n      CA: ${{ matrix.CA }}\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\n    steps:\n    - uses: actions/checkout@v6\n    - uses: anyvm-org/cf-tunnel@v0\n      id: tunnel\n      with:\n        protocol: http\n        port: 8080\n    - name: Set envs\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/haiku-vm@v1\n      with:\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\n        nat: |\n          \"8080\": \"80\"\n        prepare: |\n          mkdir -p /boot/home/.cache\n          pkgman install -y cronie\n        sync: rsync\n        copyback: false\n        run: |\n          cd ../acmetest \\\n          && ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n"
  },
  {
    "path": ".github/workflows/Linux.yml",
    "content": "name: Linux\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/Linux.yml'\r\n\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/Linux.yml'\r\n\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\n\r\n\r\njobs:\r\n  Linux:\r\n    strategy:\r\n      matrix:\r\n        os: [\"ubuntu:latest\", \"debian:latest\", \"almalinux:latest\", \"fedora:latest\", \"opensuse/leap:latest\", \"alpine:latest\", \"oraclelinux:8\", \"kalilinux/kali\", \"archlinux:latest\", \"gentoo/stage3\"]\r\n    runs-on: ubuntu-latest\r\n    env:\r\n      TEST_LOCAL: 1\r\n      TEST_PREFERRED_CHAIN: (STAGING)\r\n      TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - uses: anyvm-org/cf-tunnel@v0\r\n      id: tunnel\r\n      with:\r\n        protocol: http\r\n        port: 80\r\n    - name: Set envs\r\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\r\n    - name: Clone acmetest\r\n      run: |\r\n          cd .. \\\r\n          && git clone --depth=1 https://github.com/acmesh-official/acmetest.git \\\r\n          && cp -r acme.sh acmetest/\r\n    - name: Run acmetest\r\n      run: |\r\n          cd ../acmetest \\\r\n          && ./rundocker.sh  testplat ${{ matrix.os }}\r\n\r\n\r\n\r\n"
  },
  {
    "path": ".github/workflows/MacOS.yml",
    "content": "name: MacOS\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/MacOS.yml'\r\n\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/MacOS.yml'\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\n\r\njobs:\r\n  MacOS:\r\n    strategy:\r\n      matrix:\r\n        include:\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\r\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\r\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\r\n         #  CA_EMAIL: \"githubtest@acme.sh\"\r\n         #  TEST_PREFERRED_CHAIN: \"\"\r\n    runs-on: macos-latest\r\n    env:\r\n      TEST_LOCAL: 1\r\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r\n      CA: ${{ matrix.CA }}\r\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - name: Install tools\r\n      run:  brew install socat\r\n    - uses: anyvm-org/cf-tunnel@v0\r\n      id: tunnel\r\n      with:\r\n        protocol: http\r\n        port: 80\r\n    - name: Set envs\r\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\r\n    - name: Clone acmetest\r\n      run: |\r\n          cd .. \\\r\n          && git clone --depth=1 https://github.com/acmesh-official/acmetest.git \\\r\n          && cp -r acme.sh acmetest/\r\n    - name: Run acmetest\r\n      run: |\r\n          cd ../acmetest \\\r\n          && sudo --preserve-env ./letest.sh\r\n\r\n\r\n"
  },
  {
    "path": ".github/workflows/NetBSD.yml",
    "content": "name: NetBSD\non:\n  push:\n    branches:\n      - '*'\n    paths:\n      - '*.sh'\n      - '.github/workflows/NetBSD.yml'\n\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '*.sh'\n      - '.github/workflows/NetBSD.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n\n\njobs:\n  NetBSD:\n    strategy:\n      matrix:\n        include:\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\n         #  CA_EMAIL: \"githubtest@acme.sh\"\n         #  TEST_PREFERRED_CHAIN: \"\"\n    runs-on: ubuntu-latest\n    env:\n      TEST_LOCAL: 1\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\n      CA: ${{ matrix.CA }}\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\n    steps:\n    - uses: actions/checkout@v6\n    - uses: anyvm-org/cf-tunnel@v0\n      id: tunnel\n      with:\n        protocol: http\n        port: 8080\n    - name: Set envs\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/netbsd-vm@v1\n      with:\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\n        nat: |\n          \"8080\": \"80\"\n        prepare: |\n          /usr/sbin/pkg_add curl socat\n        usesh: true\n        sync: nfs\n        run: |\n          cd ../acmetest \\\n          && ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n        \n"
  },
  {
    "path": ".github/workflows/Omnios.yml",
    "content": "name: Omnios\non:\n  push:\n    branches:\n      - '*'\n    paths:\n      - '*.sh'\n      - '.github/workflows/Omnios.yml'\n\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '*.sh'\n      - '.github/workflows/Omnios.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n\n\njobs:\n  Omnios:\n    strategy:\n      matrix:\n        include:\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n           ACME_USE_WGET: 1\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\n         #  CA_EMAIL: \"githubtest@acme.sh\"\n         #  TEST_PREFERRED_CHAIN: \"\"\n    runs-on: ubuntu-latest\n    env:\n      TEST_LOCAL: 1\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\n      CA: ${{ matrix.CA }}\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\n    steps:\n    - uses: actions/checkout@v6\n    - uses: anyvm-org/cf-tunnel@v0\n      id: tunnel\n      with:\n        protocol: http\n        port: 8080\n    - name: Set envs\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/omnios-vm@v1\n      with:\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\n        nat: |\n          \"8080\": \"80\"\n        prepare: pkg install socat wget\n        sync: nfs\n        run: |\n          cd ../acmetest \\\n          && ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n"
  },
  {
    "path": ".github/workflows/OpenBSD.yml",
    "content": "name: OpenBSD\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/OpenBSD.yml'\r\n\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/OpenBSD.yml'\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\n\r\njobs:\r\n  OpenBSD:\r\n    strategy:\r\n      matrix:\r\n        include:\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n           ACME_USE_WGET: 1\r\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\r\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\r\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\r\n         #  CA_EMAIL: \"githubtest@acme.sh\"\r\n         #  TEST_PREFERRED_CHAIN: \"\"\r\n    runs-on: ubuntu-latest\r\n    env:\r\n      TEST_LOCAL: 1\r\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r\n      CA: ${{ matrix.CA }}\r\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - uses: anyvm-org/cf-tunnel@v0\r\n      id: tunnel\r\n      with:\r\n        protocol: http\r\n        port: 8080\r\n    - name: Set envs\r\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\r\n    - name: Clone acmetest\r\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r\n    - uses: vmactions/openbsd-vm@v1\r\n      with:\r\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\r\n        nat: |\r\n          \"8080\": \"80\"\r\n        prepare: pkg_add socat curl wget libnghttp2\r\n        usesh: true\r\n        sync: nfs\r\n        run: |\r\n          cd ../acmetest \\\r\n          && ./letest.sh\r\n    - name: DebugOnError\r\n      if: ${{ failure() }}\r\n      run: |\r\n        echo \"See how to debug in VM:\"\r\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\r\n\r\n"
  },
  {
    "path": ".github/workflows/OpenIndiana.yml",
    "content": "name: OpenIndiana\non:\n  push:\n    branches:\n      - '*'\n    paths:\n      - '*.sh'\n      - '.github/workflows/OpenIndiana.yml'\n\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '*.sh'\n      - '.github/workflows/OpenIndiana.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n\n\njobs:\n  OpenIndiana:\n    strategy:\n      matrix:\n        include:\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n           ACME_USE_WGET: 1\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\n         #  CA_EMAIL: \"githubtest@acme.sh\"\n         #  TEST_PREFERRED_CHAIN: \"\"\n    runs-on: ubuntu-latest\n    env:\n      TEST_LOCAL: 1\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\n      CA: ${{ matrix.CA }}\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\n    steps:\n    - uses: actions/checkout@v6\n    - uses: anyvm-org/cf-tunnel@v0\n      id: tunnel\n      with:\n        protocol: http\n        port: 8080\n    - name: Set envs\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/openindiana-vm@v1\n      with:\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\n        nat: |\n          \"8080\": \"80\"\n        prepare: pkg install socat curl\n        sync: nfs\n        run: |\n          cd ../acmetest \\\n          && ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n"
  },
  {
    "path": ".github/workflows/PebbleStrict.yml",
    "content": "name: PebbleStrict\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/PebbleStrict.yml'\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/PebbleStrict.yml'\r\n\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\n\r\njobs:\r\n  PebbleStrict:\r\n    runs-on: ubuntu-latest\r\n    env:\r\n      TestingDomain: example.com\r\n      TestingAltDomains: www.example.com\r\n      TEST_ACME_Server: https://localhost:14000/dir\r\n      HTTPS_INSECURE: 1\r\n      Le_HTTPPort: 5002\r\n      TEST_LOCAL: 1\r\n      TEST_CA: \"Pebble Intermediate CA\"\r\n\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - name: Install tools\r\n      run: sudo apt-get install -y socat\r\n    - name: Run Pebble\r\n      run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d\r\n    - name: Set up Pebble\r\n      run: curl --request POST --data '{\"ip\":\"10.30.50.1\"}' http://localhost:8055/set-default-ipv4\r\n    - name: Clone acmetest\r\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r\n    - name: Run acmetest\r\n      run: cd ../acmetest && ./letest.sh\r\n\r\n  PebbleStrict_IPCert:\r\n    runs-on: ubuntu-latest\r\n    env:\r\n      TestingDomain: 1.23.45.67\r\n      TEST_ACME_Server: https://localhost:14000/dir\r\n      HTTPS_INSECURE: 1\r\n      Le_HTTPPort: 5002\r\n      Le_TLSPort: 5001\r\n      TEST_LOCAL: 1\r\n      TEST_CA: \"Pebble Intermediate CA\"\r\n      TEST_IPCERT: 1\r\n\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - name: Install tools\r\n      run: sudo apt-get install -y socat\r\n    - name: Run Pebble\r\n      run: |\r\n        docker run --rm -itd --name=pebble \\\r\n        -e PEBBLE_VA_ALWAYS_VALID=1 \\\r\n        -p 14000:14000 -p 15000:15000   ghcr.io/letsencrypt/pebble:latest -config /test/config/pebble-config.json -strict\r\n    - name: Clone acmetest\r\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r\n    - name: Run acmetest\r\n      run: cd ../acmetest && ./letest.sh"
  },
  {
    "path": ".github/workflows/Solaris.yml",
    "content": "name: Solaris\non:\n  push:\n    branches:\n      - '*'\n    paths:\n      - '*.sh'\n      - '.github/workflows/Solaris.yml'\n\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '*.sh'\n      - '.github/workflows/Solaris.yml'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n\n\njobs:\n  Solaris:\n    strategy:\n      matrix:\n        include:\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\n           CA_ECDSA: \"\"\n           CA: \"\"\n           CA_EMAIL: \"\"\n           TEST_PREFERRED_CHAIN: (STAGING)\n           ACME_USE_WGET: 1\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\n         #  CA_EMAIL: \"githubtest@acme.sh\"\n         #  TEST_PREFERRED_CHAIN: \"\"\n    runs-on: ubuntu-latest\n    env:\n      TEST_LOCAL: 1\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\n      CA: ${{ matrix.CA }}\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\n    steps:\n    - uses: actions/checkout@v6\n    - uses: anyvm-org/cf-tunnel@v0\n      id: tunnel\n      with:\n        protocol: http\n        port: 8080\n    - name: Set envs\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\n    - name: Clone acmetest\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\n    - uses: vmactions/solaris-vm@v1\n      with:\n        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'\n        nat: |\n          \"8080\": \"80\"\n        prepare: |\n          pkgutil -U\n          pkgutil -y -i socat curl wget\n        sync: nfs\n        run: |\n          cd ../acmetest \\\n          && ./letest.sh\n    - name: DebugOnError\n      if: ${{ failure() }}\n      run: |\n        echo \"See how to debug in VM:\"\n        echo \"https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM\"\n\n"
  },
  {
    "path": ".github/workflows/Ubuntu.yml",
    "content": "name: Ubuntu\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/Ubuntu.yml'\r\n\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/Ubuntu.yml'\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\n\r\njobs:\r\n  Ubuntu:\r\n    strategy:\r\n      matrix:\r\n        include:\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n           ACME_USE_WGET: 1\r\n         - TEST_ACME_Server: \"ZeroSSL.com\"\r\n           CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\r\n           CA: \"ZeroSSL RSA Domain Secure Site CA\"\r\n           CA_EMAIL: \"githubtest@acme.sh\"\r\n           TEST_PREFERRED_CHAIN: \"\"\r\n         - TEST_ACME_Server: \"https://localhost:9000/acme/acme/directory\"\r\n           CA_ECDSA: \"Smallstep Intermediate CA\"\r\n           CA: \"Smallstep Intermediate CA\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: \"\"\r\n           NO_REVOKE: 1\r\n         - TEST_ACME_Server: \"https://localhost:9000/acme/acme/directory\"\r\n           CA_ECDSA: \"Smallstep Intermediate CA\"\r\n           CA: \"Smallstep Intermediate CA\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: \"\"\r\n           NO_REVOKE: 1\r\n           TEST_IPCERT: 1\r\n           TestingDomain: \"172.17.0.1\"\r\n\r\n    runs-on: ubuntu-latest\r\n    env:\r\n      TEST_LOCAL: 1\r\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r\n      CA: ${{ matrix.CA }}\r\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r\n      NO_ECC_384: ${{ matrix.NO_ECC_384 }}\r\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r\n      NO_REVOKE: ${{ matrix.NO_REVOKE }}\r\n      TEST_IPCERT: ${{ matrix.TEST_IPCERT }}\r\n      TestingDomain: ${{ matrix.TestingDomain }}\r\n      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - name: Install tools\r\n      run: sudo apt-get install -y socat wget\r\n    - name: Start StepCA\r\n      if: ${{ matrix.TEST_ACME_Server=='https://localhost:9000/acme/acme/directory' }}\r\n      run: |\r\n           docker run --rm -d \\\r\n            -p 9000:9000 \\\r\n            -e \"DOCKER_STEPCA_INIT_NAME=Smallstep\" \\\r\n            -e \"DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)\" \\\r\n            -e \"DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true\" \\\r\n            -e \"DOCKER_STEPCA_INIT_PASSWORD=test\" \\\r\n            --name stepca \\\r\n            smallstep/step-ca:0.23.1\r\n\r\n            sleep 5\r\n            docker exec  stepca bash -c \"echo test >test\" \\\r\n            && docker exec stepca step ca provisioner add acme --type ACME --admin-subject  step --admin-password-file=/home/step/test \\\r\n            && docker exec  stepca kill -1 1 \\\r\n            && docker exec  stepca cat /home/step/certs/root_ca.crt | sudo bash -c \"cat - >>/etc/ssl/certs/ca-certificates.crt\"\r\n    - name: Clone acmetest\r\n      run: |\r\n          cd .. \\\r\n          && git clone --depth=1 https://github.com/acmesh-official/acmetest.git \\\r\n          && cp -r acme.sh acmetest/\r\n    - name: Run acmetest\r\n      run: |\r\n          cd ../acmetest \\\r\n          && sudo --preserve-env ./letest.sh\r\n\r\n\r\n"
  },
  {
    "path": ".github/workflows/Windows.yml",
    "content": "name: Windows\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/Windows.yml'\r\n\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '*.sh'\r\n      - '.github/workflows/Windows.yml'\r\n\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\njobs:\r\n  Windows:\r\n    strategy:\r\n      matrix:\r\n        include:\r\n         - TEST_ACME_Server: \"LetsEncrypt.org_test\"\r\n           CA_ECDSA: \"\"\r\n           CA: \"\"\r\n           CA_EMAIL: \"\"\r\n           TEST_PREFERRED_CHAIN: (STAGING)\r\n         #- TEST_ACME_Server: \"ZeroSSL.com\"\r\n         #  CA_ECDSA: \"ZeroSSL ECC Domain Secure Site CA\"\r\n         #  CA: \"ZeroSSL RSA Domain Secure Site CA\"\r\n         #  CA_EMAIL: \"githubtest@acme.sh\"\r\n         #  TEST_PREFERRED_CHAIN: \"\"\r\n    runs-on: windows-latest\r\n    env:\r\n      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r\n      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r\n      CA: ${{ matrix.CA }}\r\n      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r\n      TEST_LOCAL: 1\r\n      #The 80 port is used by Windows server, we have to use a custom port, tunnel will also use this port.\r\n      Le_HTTPPort: 8888\r\n      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r\n    steps:\r\n    - name: Set git to use LF\r\n      run: |\r\n          git config --global core.autocrlf false\r\n    - uses: actions/checkout@v6\r\n    - name: Install cygwin base packages with chocolatey\r\n      run: |\r\n          choco config get cacheLocation\r\n          choco install --no-progress cygwin\r\n      shell: cmd\r\n    - name: Install cygwin additional packages\r\n      run: |\r\n          C:\\tools\\cygwin\\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,xxd\r\n      shell: cmd\r\n    - name: Set ENV\r\n      shell: cmd\r\n      run: |\r\n          echo PATH=C:\\tools\\cygwin\\bin;C:\\tools\\cygwin\\usr\\bin;%PATH% >> %GITHUB_ENV%\r\n    - name: Check ENV\r\n      shell: cmd\r\n      run: |\r\n          echo \"PATH=%PATH%\"\r\n    - uses: anyvm-org/cf-tunnel@v0\r\n      id: tunnel\r\n      with:\r\n        protocol: http\r\n        port: 80\r\n    - name: Set envs\r\n      run: echo \"TestingDomain=${{steps.tunnel.outputs.server}}\" >> $GITHUB_ENV\r\n    - name: Clone acmetest\r\n      shell: cmd\r\n      run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r\n    - name: Run acmetest\r\n      shell: cmd\r\n      run: cd ../acmetest && bash.exe -c ./letest.sh\r\n\r\n\r\n\r\n"
  },
  {
    "path": ".github/workflows/dockerhub.yml",
    "content": "\r\nname: Build DockerHub\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    tags:\r\n      - '*'\r\n    paths:\r\n      - '**.sh'\r\n      - \"Dockerfile\"\r\n      - '.github/workflows/dockerhub.yml'\r\n\r\nconcurrency:\r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\nenv:\r\n  DOCKER_IMAGE: neilpang/acme.sh\r\n\r\njobs:\r\n  CheckToken:\r\n    runs-on: ubuntu-latest\r\n    outputs:\r\n      hasToken: ${{ steps.step_one.outputs.hasToken }}\r\n    env: \r\n      DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }}\r\n    steps:\r\n      - name: Set the value\r\n        id: step_one\r\n        run: |\r\n          if [ \"$DOCKER_PASSWORD\" ] ; then\r\n            echo \"hasToken=true\" >>$GITHUB_OUTPUT\r\n          else\r\n            echo \"hasToken=false\" >>$GITHUB_OUTPUT\r\n          fi\r\n      - name: Check the value\r\n        run: echo ${{ steps.step_one.outputs.hasToken }}\r\n        \r\n  build:\r\n    runs-on: ubuntu-latest\r\n    needs: CheckToken\r\n    if: \"contains(needs.CheckToken.outputs.hasToken, 'true')\"\r\n    steps:\r\n      - name: checkout code\r\n        uses: actions/checkout@v6\r\n        with:\r\n          persist-credentials: false\r\n      - name: Set up QEMU\r\n        uses: docker/setup-qemu-action@v2\r\n      - name: Extract Docker metadata\r\n        id: meta\r\n        uses: docker/metadata-action@v6\r\n        with:\r\n          images: ${DOCKER_IMAGE}\r\n      - name: Set up Docker Buildx\r\n        uses: docker/setup-buildx-action@v2\r\n      - name: login to docker hub\r\n        run: |\r\n          echo \"${{ secrets.DOCKER_PASSWORD }}\" | docker login -u \"${{ secrets.DOCKER_USERNAME }}\" --password-stdin\r\n      - name: build and push the image\r\n        run: |\r\n          if [[ $GITHUB_REF == refs/tags/* ]]; then\r\n            DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/}\r\n          fi\r\n\r\n          if [[ $GITHUB_REF == refs/heads/* ]]; then\r\n            DOCKER_IMAGE_TAG=${GITHUB_REF#refs/heads/}\r\n\r\n            if [[ $DOCKER_IMAGE_TAG == master ]]; then\r\n              DOCKER_IMAGE_TAG=latest\r\n              AUTO_UPGRADE=1\r\n            fi\r\n          fi\r\n\r\n          DOCKER_LABELS=()\r\n          while read -r label; do\r\n            DOCKER_LABELS+=(--label \"${label}\")\r\n          done <<<\"${DOCKER_METADATA_OUTPUT_LABELS}\"\r\n\r\n          docker buildx build \\\r\n            --tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \\\r\n            \"${DOCKER_LABELS[@]}\" \\\r\n            --output \"type=image,push=true\" \\\r\n            --build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \\\r\n            --platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .\r\n"
  },
  {
    "path": ".github/workflows/issue.yml",
    "content": "name: \"Update issues\"\non:\n  issues:\n    types: [opened]\n\njobs:\n  comment:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/github-script@v6\n        with:\n          script: |\n            github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: \"Please upgrade to the latest code and try again first. Maybe it's already fixed.              ```acme.sh --upgrade```              If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you.\"\n              \n            })"
  },
  {
    "path": ".github/workflows/pr_dns.yml",
    "content": "name: Check dns api\n\non:\n  pull_request_target:\n    types:\n      - opened\n    paths:\n      - 'dnsapi/*.sh'\n\n\njobs:\n  welcome:\n    runs-on: ubuntu-latest\n    if: github.actor != 'neilpang'\n    steps:\n      - uses: actions/github-script@v6\n        with:\n          script: |\n            await github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: `**Welcome**\n                    READ ME !!!!!\n                    Read me !!!!!!\n                    First thing: don't send PR to the master branch, please send to the dev branch instead.\n                    Please read the [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide).\n                    You MUST pass the [DNS-API-Test](../wiki/DNS-API-Test).\n                    Then reply on this message, otherwise, your code will not be reviewed or merged.\n                    Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2\n                    注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.\n                `\n            })\n\n"
  },
  {
    "path": ".github/workflows/pr_notify.yml",
    "content": "name: Check notify api\n\non:\n  pull_request_target:\n    types:\n      - opened\n    branches:\n      - 'dev'\n    paths:\n      - 'notify/*.sh'\n\n\njobs:\n  welcome:\n    runs-on: ubuntu-latest\n    if: github.actor != 'neilpang'\n    steps:\n      - uses: actions/github-script@v6\n        with:\n          script: |\n            await github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: `**Welcome**\n                Please make sure you've read our [Code-of-conduct](../wiki/Code-of-conduct) and  add the usage here: [notify](../wiki/notify).\n                Then reply on this message, otherwise, your code will not be reviewed or merged.\n                We look forward to reviewing your Pull request shortly ✨\n                `\n            })\n\n"
  },
  {
    "path": ".github/workflows/shellcheck.yml",
    "content": "name: Shellcheck\r\non:\r\n  push:\r\n    branches:\r\n      - '*'\r\n    paths:\r\n      - '**.sh'\r\n      - '.github/workflows/shellcheck.yml'\r\n  pull_request:\r\n    branches:\r\n      - dev\r\n    paths:\r\n      - '**.sh'\r\n      - '.github/workflows/shellcheck.yml'\r\n\r\nconcurrency: \r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\n\r\njobs:\r\n  ShellCheck:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - name: Install Shellcheck\r\n      run: sudo apt-get install -y shellcheck\r\n    - name: DoShellcheck\r\n      run: shellcheck -V  && shellcheck -e SC2181 -e SC2089 **/*.sh && echo \"shellcheck OK\"\r\n\r\n  shfmt:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n    - uses: actions/checkout@v6\r\n    - name: Install shfmt\r\n      run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt\r\n    - name: shfmt\r\n      run: ~/shfmt -l -w -i 2 . ; git diff --exit-code && echo \"shfmt OK\"\r\n"
  },
  {
    "path": ".github/workflows/wiki-monitor.yml",
    "content": "name: Notify via Issue on Wiki Edit\r\n\r\non:\r\n  gollum:\r\n\r\njobs:\r\n  notify:\r\n    runs-on: ubuntu-latest\r\n    if: github.actor != 'neilpang'\r\n    steps:\r\n      - name: Checkout wiki repository\r\n        uses: actions/checkout@v6\r\n        with:\r\n          repository: ${{ github.repository }}.wiki\r\n          path: wiki\r\n          fetch-depth: 0\r\n\r\n      - name: Generate wiki change message\r\n        run: |\r\n            actor=\"${{ github.actor }}\"\r\n            sender_url=$(jq -r '.sender.html_url' \"$GITHUB_EVENT_PATH\")\r\n            page_name=$(jq -r '.pages[0].page_name' \"$GITHUB_EVENT_PATH\")\r\n            page_sha=$(jq -r '.pages[0].sha' \"$GITHUB_EVENT_PATH\")\r\n            page_url=$(jq -r '.pages[0].html_url' \"$GITHUB_EVENT_PATH\")\r\n            page_action=$(jq -r '.pages[0].action' \"$GITHUB_EVENT_PATH\")\r\n            page_summary=$(jq -r '.pages[0].summary' \"$GITHUB_EVENT_PATH\")\r\n            now=\"$(date '+%Y-%m-%d %H:%M:%S')\"\r\n\r\n            cd wiki\r\n            prev_sha=$(git rev-list $page_sha^ -- \"$page_name.md\" | head -n 1)\r\n            if [ -n \"$prev_sha\" ]; then\r\n                git diff $prev_sha $page_sha -- \"$page_name.md\" > ../wiki.diff || echo \"(No diff found)\" > ../wiki.diff\r\n            else\r\n                echo \"(no diff)\" > ../wiki.diff\r\n            fi\r\n            cd ..\r\n            {\r\n            echo \"Wiki edited\"\r\n            echo -n \"User: \"\r\n            echo \"@$actor [$actor]($sender_url)\"\r\n            echo \"Time: $now\"\r\n            echo \"Page: [$page_name]($page_url) (Action: $page_action)\"\r\n            echo \"Comment: $page_summary\"\r\n            echo \"[Click here to Revert](${page_url}/_history)\"\r\n            echo \"\"\r\n            echo \"----\"\r\n            echo \"###  diff：\"\r\n            echo '```diff'\r\n            cat wiki.diff\r\n            echo '```'\r\n            } > wiki-change-msg.txt\r\n\r\n      - name: Create issue to notify Neilpang\r\n        uses: peter-evans/create-issue-from-file@v5\r\n        with:\r\n          title: \"Wiki edited\"\r\n          content-filepath: ./wiki-change-msg.txt\r\n          assignees: Neilpang\r\n        env:\r\n          TZ: Asia/Shanghai\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:3.22\n\nRUN apk --no-cache add -f \\\n  openssl \\\n  openssh-client \\\n  coreutils \\\n  bind-tools \\\n  curl \\\n  sed \\\n  socat \\\n  tzdata \\\n  oath-toolkit-oathtool \\\n  tar \\\n  libidn \\\n  jq \\\n  yq-go \\\n  supercronic\n\nENV LE_WORKING_DIR=/acmebin\n\nENV LE_CONFIG_HOME=/acme.sh\n\nENV HOME=/acme.sh\n\nARG AUTO_UPGRADE=1\n\nENV AUTO_UPGRADE=$AUTO_UPGRADE\n\n#Install\nCOPY ./acme.sh /install_acme.sh/acme.sh\nCOPY ./deploy /install_acme.sh/deploy\nCOPY ./dnsapi /install_acme.sh/dnsapi\nCOPY ./notify /install_acme.sh/notify\n\nRUN addgroup -g 1000 acme && adduser -h $LE_CONFIG_HOME -s /bin/sh -G acme -D -H -u 1000 acme\n\nRUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/\n\nRUN ln -s $LE_WORKING_DIR/acme.sh /usr/local/bin/acme.sh\n\nRUN chown -R acme:acme $LE_CONFIG_HOME\n\nRUN for verb in help \\\n  version \\\n  install \\\n  uninstall \\\n  upgrade \\\n  issue \\\n  signcsr \\\n  deploy \\\n  install-cert \\\n  renew \\\n  renew-all \\\n  revoke \\\n  remove \\\n  list \\\n  info \\\n  showcsr \\\n  install-cronjob \\\n  uninstall-cronjob \\\n  cron \\\n  toPkcs \\\n  toPkcs8 \\\n  update-account \\\n  register-account \\\n  create-account-key \\\n  create-domain-key \\\n  createCSR \\\n  deactivate \\\n  deactivate-account \\\n  set-notify \\\n  set-default-ca \\\n  set-default-chain \\\n  ; do \\\n    printf -- \"%b\" \"#!/usr/bin/env sh\\n$LE_WORKING_DIR/acme.sh --${verb} --config-home $LE_CONFIG_HOME \\\"\\$@\\\"\" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \\\n  ; done\n\nRUN printf \"%b\" '#!'\"/usr/bin/env sh\\n \\\nif [ \\\"\\$1\\\" = \\\"daemon\\\" ];  then \\n \\\n  if [ ! -f \\\"\\$LE_CONFIG_HOME/crontab\\\" ]; then \\n \\\n     echo \\\"\\$LE_CONFIG_HOME/crontab not found, generating one\\\" \\n \\\n     time=\\$(date -u \\\"+%s\\\") \\n \\\n     random_minute=\\$((\\$time % 60)) \\n \\\n     random_hour=\\$((\\$time / 60 % 24)) \\n \\\n     echo \\\"\\$random_minute \\$random_hour * * * \\\\\\\"\\$LE_WORKING_DIR\\\\\\\"/acme.sh --cron --home \\\\\\\"\\$LE_WORKING_DIR\\\\\\\" --config-home \\\\\\\"\\$LE_CONFIG_HOME\\\\\\\"\\\" > \\\"\\$LE_CONFIG_HOME\\\"/crontab \\n \\\n  fi \\n \\\n  echo \\\"Running Supercronic using crontab at \\$LE_CONFIG_HOME/crontab\\\" \\n \\\n  exec -- /usr/bin/supercronic \\\"\\$LE_CONFIG_HOME/crontab\\\" \\n \\\nelse \\n \\\n exec -- \\\"\\$@\\\"\\n \\\nfi\\n\" >/entry.sh && chmod +x /entry.sh && chmod -R o+rwx $LE_WORKING_DIR && chmod -R o+rwx $LE_CONFIG_HOME\n\nVOLUME /acme.sh\n\nENTRYPOINT [\"/entry.sh\"]\nCMD [\"--help\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://zerossl.com/?fromacme.sh\">\n    <img src=\"https://github.com/user-attachments/assets/7531085e-399b-4ac2-82a2-90d14a0b7f05\" alt=\"zerossl.com\">\n  </a>\n</p>\n\n<h1 align=\"center\">🔐 acme.sh</h1>\n<h3 align=\"center\">An ACME Protocol Client Written Purely in Shell</h3>\n\n<p align=\"center\">\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg\" alt=\"FreeBSD\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg\" alt=\"OpenBSD\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg\" alt=\"NetBSD\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg\" alt=\"MacOS\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg\" alt=\"Ubuntu\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg\" alt=\"Windows\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg\" alt=\"Solaris\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg\" alt=\"DragonFlyBSD\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg\" alt=\"Omnios\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg\" alt=\"OpenIndiana\"></a>\n  <a href=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml\"><img src=\"https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg\" alt=\"Haiku\"></a>\n</p>\n\n<p align=\"center\">\n  <img src=\"https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg\" alt=\"Shellcheck\">\n  <img src=\"https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg\" alt=\"PebbleStrict\">\n  <img src=\"https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg\" alt=\"DockerHub\">\n</p>\n\n<p align=\"center\">\n  <a href=\"https://opencollective.com/acmesh\"><img src=\"https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors\" alt=\"Financial Contributors on Open Collective\"></a>\n  <a href=\"https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge\"><img src=\"https://badges.gitter.im/acme-sh/Lobby.svg\" alt=\"Join the chat at Gitter\"></a>\n  <a href=\"https://hub.docker.com/r/neilpang/acme.sh\" title=\"Click to view the image on Docker Hub\"><img src=\"https://img.shields.io/docker/stars/neilpang/acme.sh.svg\" alt=\"Docker stars\"></a>\n  <a href=\"https://hub.docker.com/r/neilpang/acme.sh\" title=\"Click to view the image on Docker Hub\"><img src=\"https://img.shields.io/docker/pulls/neilpang/acme.sh.svg\" alt=\"Docker pulls\"></a>\n</p>\n\n\n---\n\n## ✨ Features\n\n- 🐚 An ACME protocol client written **purely in Shell** (Unix shell) language\n- 📜 Full ACME protocol implementation\n- 🔑 Support **ECDSA** certificates\n- 🌐 Support **SAN** and **wildcard** certificates\n- ⚡ Simple, powerful and very easy to use — only **3 minutes** to learn!\n- 🔧 Compatible with **Bash**, **dash** and **sh**\n- 🚫 No dependencies on Python\n- 🔄 One script to issue, renew and install your certificates automatically\n- 👤 **DOES NOT** require `root/sudoer` access\n- 🐳 Docker ready\n- 🌍 IPv6 ready\n- 📧 Cron job notifications for renewal or error\n\n> 💡 It's probably the **easiest & smartest** shell script to automatically issue & renew free certificates.\n\n<p align=\"center\">\n  <a href=\"https://github.com/acmesh-official/acme.sh/wiki\"><strong>📚 Wiki</strong></a> •\n  <a href=\"https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker\"><strong>🐳 Docker Guide</strong></a> •\n  <a href=\"https://twitter.com/neilpangxa\"><strong>🐦 Twitter</strong></a>\n</p>\n\n---\n\n## 🌏 [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)\n\n---\n\n## 🏆 Who Uses acme.sh?\n- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)\n- [ruby-china.org](https://ruby-china.org/topics/31983)\n- [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management)\n- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89)\n- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)\n- [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)\n- [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html)\n- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)\n- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)\n- [CentOS Web Panel](https://control-webpanel.com)\n- [lnmp.org](https://lnmp.org/)\n- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)\n\n---\n\n## 🖥️ Tested OS\n\n| NO | Status| Platform|\n|----|-------|---------|\n|1|[![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)|Mac OSX\n|2|[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)|Windows (cygwin with curl, openssl and crontab included)\n|3|[![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)|FreeBSD\n|4|[![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)|Solaris\n|5|[![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)| Ubuntu\n|6|NA|pfsense\n|7|[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)|OpenBSD\n|8|[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)|NetBSD\n|9|[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)|DragonFlyBSD\n|10|[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios\n|11|[![OpenIndiana](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml)|OpenIndiana\n|12|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian\n|13|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE\n|14|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)\n|15|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux\n|16|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora\n|17|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux\n|18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux\n|19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia\n|20|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux\n|21|-----| Cloud Linux  https://github.com/acmesh-official/acme.sh/issues/111\n|22|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)\n|23|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)\n|24|[![Haiku](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS\n\n\n> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)\n>\n> 🖥️ The testing VMs are supported by [vmactions.org](https://vmactions.org)\n\n---\n\n## 🏛️ Supported CA\n\n| CA | Status |\n|---|---|\n| [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) | ⭐ **Default** |\n| Letsencrypt.org CA | ✅ Supported |\n| [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) | ✅ Supported |\n| [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) | ✅ Supported |\n| [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) | ✅ Supported |\n| [Pebble strict Mode](https://github.com/letsencrypt/pebble) | ✅ Supported |\n| Any [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA | ✅ Supported |\n\n---\n\n## ⚙️ Supported Modes\n\n| Mode | Description |\n|------|-------------|\n| 📁 Webroot mode | Use existing webroot directory |\n| 🖥️ Standalone mode | Built-in webserver on port 80 |\n| 🔐 Standalone tls-alpn mode | Built-in webserver on port 443 |\n| 🪶 Apache mode | Use Apache for verification |\n| ⚡ Nginx mode | Use Nginx for verification |\n| 🌐 DNS mode | Use DNS TXT records |\n| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |\n| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |\n\n---\n\n## 📖 Usage Guide\n\n### 1️⃣ How to Install\n\n#### 📥 Install Online\n\n> Check this project: https://github.com/acmesh-official/get.acme.sh\n\n```bash\ncurl https://get.acme.sh | sh -s email=my@example.com\n```\n\n**Or:**\n\n```bash\nwget -O -  https://get.acme.sh | sh -s email=my@example.com\n```\n\n#### 📦 Install from Git\n\nClone this project and launch installation:\n\n```bash\ngit clone https://github.com/acmesh-official/acme.sh.git\ncd ./acme.sh\n./acme.sh --install -m my@example.com\n```\n\n> 💡 You `don't have to be root` then, although `it is recommended`.\n\n📚 **Advanced Installation:** https://github.com/acmesh-official/acme.sh/wiki/How-to-install\n\n**The installer will perform 3 actions:**\n\n1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`.\nAll certs will be placed in this folder too.\n2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`.\n3. Create daily cron job to check and renew the certs if needed.\n\nCron entry example:\n\n```bash\n0 0 * * * \"/home/user/.acme.sh\"/acme.sh --cron --home \"/home/user/.acme.sh\" > /dev/null\n```\n\n> ⚠️ After the installation, you must close the current terminal and reopen it to make the alias take effect.\n\n✅ **You are ready to issue certs now!**\n\n**Show help message:**\n\n```sh\nacme.sh -h\n```\n\n---\n\n### 2️⃣ Issue a Certificate\n\n**Example 1:** Single domain.\n\n```bash\nacme.sh --issue -d example.com -w /home/wwwroot/example.com\n```\n\nor:\n\n```bash\nacme.sh --issue -d example.com -w /home/username/public_html\n```\n\nor:\n\n```bash\nacme.sh --issue -d example.com -w /var/www/html\n```\n\n**Example 2:** Multiple domains in the same cert.\n\n```bash\nacme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com\n```\n\nThe parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder.\n\nSecond argument **\"example.com\"** is the main domain you want to issue the cert for.\nYou must have at least one domain there.\n\nYou must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`.\n\nThe certs will be placed in `~/.acme.sh/example.com/`\n\n> 🔄 The certs will be renewed automatically every **30** days.\n\n> 🔐 The certs will default to **ECC** certificates.\n\n📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert\n\n---\n\n### 3️⃣ Install the Certificate to Apache/Nginx\n\nAfter the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers.\n\n> ⚠️ **IMPORTANT:** You **MUST** use this command to copy the certs to the target files. **DO NOT** use the certs files in `~/.acme.sh/` folder — they are for internal use only, the folder structure may change in the future.\n\n#### 🪶 Apache Example:\n```bash\nacme.sh --install-cert -d example.com \\\n--cert-file      /path/to/certfile/in/apache/cert.pem  \\\n--key-file       /path/to/keyfile/in/apache/key.pem  \\\n--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \\\n--reloadcmd     \"service apache2 force-reload\"\n```\n\n#### ⚡ Nginx Example:\n```bash\nacme.sh --install-cert -d example.com \\\n--key-file       /path/to/keyfile/in/nginx/key.pem  \\\n--fullchain-file /path/to/fullchain/nginx/cert.pem \\\n--reloadcmd     \"service nginx force-reload\"\n```\n\nOnly the domain is required, all the other parameters are optional.\n\nThe ownership and permission info of existing files are preserved. You can pre-create the files to define the ownership and permission.\n\nInstall/copy the cert/key to the production Apache or Nginx path.\n\n> 🔄 The cert will be renewed every **30** days by default (configurable). Once renewed, the Apache/Nginx service will be reloaded automatically.\n\n> ⚠️ **IMPORTANT:** The `reloadcmd` is very important. The cert can be automatically renewed, but without a correct `reloadcmd`, the cert may not be flushed to your server (like nginx or apache), then your website will not be able to show the renewed cert.\n\n---\n\n### 4️⃣ Use Standalone Server to Issue Certificate\n\n> 🔐 Requires root/sudoer or permission to listen on port **80** (TCP)\n\n> ⚠️ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.\n\n```bash\nacme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com\n```\n\n📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert\n\n---\n\n### 5️⃣ Use Standalone TLS Server to Issue Certificate\n\n> 🔐 Requires root/sudoer or permission to listen on port **443** (TCP)\n\n> ⚠️ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.\n\n```bash\nacme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com\n```\n\n📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert\n\n---\n\n### 6️⃣ Use Apache Mode\n\n> 🔐 Requires root/sudoer to interact with Apache server\n\nIf you are running a web server, it is recommended to use the `Webroot mode`.\n\nParticularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder.\n\n```sh\nacme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com\n```\n\n> 💡 **Note:** This Apache mode is only to issue the cert, it will **not** change your Apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Apache server, don't worry!\n\n📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert\n\n---\n\n### 7️⃣ Use Nginx Mode\n\n> 🔐 Requires root/sudoer to interact with Nginx server\n\nIf you are running a web server, it is recommended to use the `Webroot mode`.\n\nParticularly, if you are running an Nginx server, you can use Nginx mode instead. This mode doesn't write any files to your web root folder.\n\nIt will configure Nginx server automatically to verify the domain and then restore the Nginx config to the original version. So, the config is not changed.\n\n```sh\nacme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com\n```\n\n> 💡 **Note:** This Nginx mode is only to issue the cert, it will **not** change your Nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Nginx server, don't worry!\n\n📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert\n\n---\n\n### 8️⃣ Automatic DNS API Integration\n\nIf your DNS provider supports API access, we can use that API to automatically issue the certs.\n\n> ✨ **You don't have to do anything manually!**\n\n📚 **Currently acme.sh supports most DNS providers:** https://github.com/acmesh-official/acme.sh/wiki/dnsapi\n\n---\n\n### 9️⃣ Use DNS Manual Mode\n\nSee: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first.\n\nIf your dns provider doesn't support any api access, you can add the txt record by hand.\n\n```bash\nacme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com\n```\n\nYou should get an output like below:\n\n```sh\nAdd the following txt record:\nDomain:_acme-challenge.example.com\nTxt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c\n\nAdd the following txt record:\nDomain:_acme-challenge.www.example.com\nTxt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nPlease add those txt records to the domains. Waiting for the dns to take effect.\n```\n\nThen just rerun with `renew` argument:\n\n```bash\nacme.sh --renew -d example.com\n```\n\n✅ **Done!**\n\n> ⚠️ **WARNING:** This is DNS manual mode — it **cannot** be renewed automatically. You will have to add a new TXT record to your domain manually when you renew your cert. **Please use DNS API mode instead.**\n\n---\n\n### 🔟 Issue Certificates of Different Key Types (ECC or RSA)\n\nJust set the `keylength` to a valid, supported value.\n\n**Valid values for the `keylength` parameter:**\n\n| Key Length | Description |\n|------------|-------------|\n| `ec-256` | prime256v1, \"ECDSA P-256\" ⭐ **Default** |\n| `ec-384` | secp384r1, \"ECDSA P-384\" |\n| `ec-521` | secp521r1, \"ECDSA P-521\" ⚠️ Not supported by Let's Encrypt yet |\n| `2048` | RSA 2048-bit |\n| `3072` | RSA 3072-bit |\n| `4096` | RSA 4096-bit |\n\n**Examples:**\n\n#### Single domain with ECDSA P-384 certificate\n\n```bash\nacme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384\n```\n\n#### SAN multi domain with RSA4096 certificate\n\n```bash\nacme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096\n```\n\n---\n\n### 1️⃣1️⃣ Issue Wildcard Certificates\n\nIt's simple! Just give a wildcard domain as the `-d` parameter:\n\n```sh\nacme.sh --issue -d example.com -d '*.example.com' --dns dns_cf\n```\n\n\n\n---\n\n### 1️⃣2️⃣ How to Renew Certificates\n\n> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days.\n\nHowever, you can force a renewal:\n\n```sh\nacme.sh --renew -d example.com --force\n```\n\n**For ECC cert:**\n\n```sh\nacme.sh --renew -d example.com --force --ecc\n```\n\n---\n\n### 1️⃣3️⃣ How to Stop Certificate Renewal\n\nTo stop renewal of a cert, you can execute the following to remove the cert from the renewal list:\n\n```sh\nacme.sh --remove -d example.com [--ecc]\n```\n\nThe cert/key file is not removed from the disk.\n\n> 💡 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) manually.\n\n---\n\n### 1️⃣4️⃣ How to Upgrade acme.sh\n\n> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code.\n\n**Update to latest:**\n\n```sh\nacme.sh --upgrade\n```\n\n**Enable auto upgrade:**\n\n```sh\nacme.sh --upgrade --auto-upgrade\n```\n\n**Disable auto upgrade:**\n\n```sh\nacme.sh --upgrade --auto-upgrade 0\n```\n\n---\n\n### 1️⃣5️⃣ Issue a Certificate from an Existing CSR\n\n📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR\n\n---\n\n### 1️⃣6️⃣ Send Notifications in Cronjob\n\n📚 https://github.com/acmesh-official/acme.sh/wiki/notify\n\n---\n\n### 1️⃣7️⃣ Under the Hood\n\n> 🔧 Speak ACME language using shell, directly to \"Let's Encrypt\".\n\n---\n\n### 1️⃣8️⃣ Acknowledgments\n\n| Project | Link |\n|---------|------|\n| 🙏 Acme-tiny | https://github.com/diafygi/acme-tiny |\n| 📜 ACME protocol | https://github.com/ietf-wg-acme/acme |\n\n---\n\n## 👥 Contributors\n\n### 💻 Code Contributors\n\nThis project exists thanks to all the people who contribute.\n\n<a href=\"https://github.com/acmesh-official/acme.sh/graphs/contributors\"><img src=\"https://opencollective.com/acmesh/contributors.svg?width=890&button=false\" /></a>\n\n### 💰 Financial Contributors\n\nBecome a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)]\n\n#### 👤 Individuals\n\n<a href=\"https://opencollective.com/acmesh\"><img src=\"https://opencollective.com/acmesh/individuals.svg?width=890\"></a>\n\n#### 🏢 Organizations\n\nSupport this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)]\n\n<a href=\"https://opencollective.com/acmesh/organization/0/website\"><img src=\"https://opencollective.com/acmesh/organization/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/1/website\"><img src=\"https://opencollective.com/acmesh/organization/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/2/website\"><img src=\"https://opencollective.com/acmesh/organization/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/3/website\"><img src=\"https://opencollective.com/acmesh/organization/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/4/website\"><img src=\"https://opencollective.com/acmesh/organization/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/5/website\"><img src=\"https://opencollective.com/acmesh/organization/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/6/website\"><img src=\"https://opencollective.com/acmesh/organization/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/7/website\"><img src=\"https://opencollective.com/acmesh/organization/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/8/website\"><img src=\"https://opencollective.com/acmesh/organization/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/acmesh/organization/9/website\"><img src=\"https://opencollective.com/acmesh/organization/9/avatar.svg\"></a>\n\n---\n\n### 1️⃣9️⃣ License & Others\n\n📄 **License:** GPLv3\n\n⭐ Please **Star** and **Fork** this project!\n\n🐛 [Issues](https://github.com/acmesh-official/acme.sh/issues) and 🔀 [Pull Requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.\n\n---\n\n### 2️⃣0️⃣ Donate\n\n> 💝 Your donation makes **acme.sh** better!\n\n| Method | Link |\n|--------|------|\n| PayPal / Alipay(支付宝) / Wechat(微信) | [https://donate.acme.sh/](https://donate.acme.sh/) |\n\n📜 [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)\n\n---\n\n### 2️⃣1️⃣ About This Repository\n\n> [!NOTE]\n> This repository is officially maintained by <strong>ZeroSSL</strong> as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community!  \n> For more information about our services, including free and paid SSL/TLS certificates, visit https://zerossl.com.\n>   \n> All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL.\n<p align=\"center\">\n\t<a href=\"https://zerossl.com\">\n\t\t<picture>\n\t\t\t<source media=\"(prefers-color-scheme: dark)\" srcset=\"https://zerossl.com/assets/images/zerossl_logo_white.svg\">\n\t\t\t<source media=\"(prefers-color-scheme: light)\" srcset=\"https://zerossl.com/assets/images/zerossl_logo.svg\">\n\t\t\t<img src=\"https://zerossl.com/assets/images/zerossl_logo.svg\" alt=\"ZeroSSL\" width=\"256\">\n\t\t</picture>\n\t</a>\n</p>\n"
  },
  {
    "path": "acme.sh",
    "content": "#!/usr/bin/env sh\n\nVER=3.1.3\n\nPROJECT_NAME=\"acme.sh\"\n\nPROJECT_ENTRY=\"acme.sh\"\n\nPROJECT=\"https://github.com/acmesh-official/$PROJECT_NAME\"\n\nDEFAULT_INSTALL_HOME=\"$HOME/.$PROJECT_NAME\"\n\n_WINDOWS_SCHEDULER_NAME=\"$PROJECT_NAME.cron\"\n\n_SCRIPT_=\"$0\"\n\n_SUB_FOLDER_NOTIFY=\"notify\"\n_SUB_FOLDER_DNSAPI=\"dnsapi\"\n_SUB_FOLDER_DEPLOY=\"deploy\"\n\n_SUB_FOLDERS=\"$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY\"\n\nCA_LETSENCRYPT_V2=\"https://acme-v02.api.letsencrypt.org/directory\"\nCA_LETSENCRYPT_V2_TEST=\"https://acme-staging-v02.api.letsencrypt.org/directory\"\n\nCA_ZEROSSL=\"https://acme.zerossl.com/v2/DV90\"\n_ZERO_EAB_ENDPOINT=\"https://api.zerossl.com/acme/eab-credentials-email\"\n\nCA_SSLCOM_RSA=\"https://acme.ssl.com/sslcom-dv-rsa\"\nCA_SSLCOM_ECC=\"https://acme.ssl.com/sslcom-dv-ecc\"\n\nCA_GOOGLE=\"https://dv.acme-v02.api.pki.goog/directory\"\nCA_GOOGLE_TEST=\"https://dv.acme-v02.test-api.pki.goog/directory\"\n\nCA_ACTALIS=\"https://acme-api.actalis.com/acme/directory\"\n\nDEFAULT_CA=$CA_ZEROSSL\nDEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST\n\nCA_NAMES=\"\nZeroSSL.com,zerossl\nLetsEncrypt.org,letsencrypt\nLetsEncrypt.org_test,letsencrypt_test,letsencrypttest\nSSL.com,sslcom\nGoogle.com,google\nGoogle.com_test,googletest,google_test\nActalis.com,actalis.com,actalis\n\"\n\nCA_SERVERS=\"$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST,$CA_ACTALIS\"\n\nDEFAULT_USER_AGENT=\"$PROJECT_NAME/$VER ($PROJECT)\"\n\nDEFAULT_ACCOUNT_KEY_LENGTH=ec-256\nDEFAULT_DOMAIN_KEY_LENGTH=ec-256\n\nDEFAULT_OPENSSL_BIN=\"openssl\"\n\nVTYPE_HTTP=\"http-01\"\nVTYPE_DNS=\"dns-01\"\nVTYPE_ALPN=\"tls-alpn-01\"\n\nID_TYPE_DNS=\"dns\"\nID_TYPE_IP=\"ip\"\n\nLOCAL_ANY_ADDRESS=\"0.0.0.0\"\n\nDEFAULT_RENEW=30\n\nNO_VALUE=\"no\"\n\nW_DNS=\"dns\"\nW_ALPN=\"alpn\"\nDNS_ALIAS_PREFIX=\"=\"\n\nMODE_STATELESS=\"stateless\"\n\nSTATE_VERIFIED=\"verified_ok\"\n\nNGINX=\"nginx:\"\nNGINX_START=\"#ACME_NGINX_START\"\nNGINX_END=\"#ACME_NGINX_END\"\n\nBEGIN_CSR=\"-----BEGIN [NEW ]\\{0,4\\}CERTIFICATE REQUEST-----\"\nEND_CSR=\"-----END [NEW ]\\{0,4\\}CERTIFICATE REQUEST-----\"\n\nBEGIN_CERT=\"-----BEGIN CERTIFICATE-----\"\nEND_CERT=\"-----END CERTIFICATE-----\"\n\nCONTENT_TYPE_JSON=\"application/jose+json\"\nRENEW_SKIP=2\nCODE_DNS_MANUAL=3\n\nB64CONF_START=\"__ACME_BASE64__START_\"\nB64CONF_END=\"__ACME_BASE64__END_\"\n\nECC_SEP=\"_\"\nECC_SUFFIX=\"${ECC_SEP}ecc\"\n\nLOG_LEVEL_1=1\nLOG_LEVEL_2=2\nLOG_LEVEL_3=3\nDEFAULT_LOG_LEVEL=\"$LOG_LEVEL_2\"\n\nDEBUG_LEVEL_1=1\nDEBUG_LEVEL_2=2\nDEBUG_LEVEL_3=3\nDEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_2\nDEBUG_LEVEL_NONE=0\n\nDOH_CLOUDFLARE=1\nDOH_GOOGLE=2\nDOH_ALI=3\nDOH_DP=4\n\nHIDDEN_VALUE=\"[hidden](please add '--output-insecure' to see this value)\"\n\nSYSLOG_ERROR=\"user.error\"\nSYSLOG_INFO=\"user.info\"\nSYSLOG_DEBUG=\"user.debug\"\n\n#error\nSYSLOG_LEVEL_ERROR=3\n#info\nSYSLOG_LEVEL_INFO=6\n#debug\nSYSLOG_LEVEL_DEBUG=7\n#debug2\nSYSLOG_LEVEL_DEBUG_2=8\n#debug3\nSYSLOG_LEVEL_DEBUG_3=9\n\nSYSLOG_LEVEL_DEFAULT=$SYSLOG_LEVEL_ERROR\n#none\nSYSLOG_LEVEL_NONE=0\n\nNOTIFY_LEVEL_DISABLE=0\nNOTIFY_LEVEL_ERROR=1\nNOTIFY_LEVEL_RENEW=2\nNOTIFY_LEVEL_SKIP=3\n\nNOTIFY_LEVEL_DEFAULT=$NOTIFY_LEVEL_RENEW\n\nNOTIFY_MODE_BULK=0\nNOTIFY_MODE_CERT=1\n\nNOTIFY_MODE_DEFAULT=$NOTIFY_MODE_BULK\n\n_BASE64_ENCODED_CFGS=\"Le_PreHook Le_PostHook Le_RenewHook Le_Preferred_Chain Le_ReloadCmd\"\n\n_DEBUG_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh\"\n\n_PREPARE_LINK=\"https://github.com/acmesh-official/acme.sh/wiki/Install-preparations\"\n\n_STATELESS_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode\"\n\n_DNS_ALIAS_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode\"\n\n_DNS_MANUAL_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode\"\n\n_DNS_API_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/dnsapi\"\n\n_NOTIFY_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/notify\"\n\n_SUDO_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/sudo\"\n\n_REVOKE_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/revokecert\"\n\n_ZEROSSL_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA\"\n\n_SSLCOM_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA\"\n\n_SERVER_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/Server\"\n\n_PREFERRED_CHAIN_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/Preferred-Chain\"\n\n_VALIDITY_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/Validity\"\n\n_DNSCHECK_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/dnscheck\"\n\n_PROFILESELECTION_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/Profile-selection\"\n\n_DNS_MANUAL_ERR=\"The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead.\"\n\n_DNS_MANUAL_WARN=\"It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR\"\n\n_DNS_MANUAL_ERROR=\"It seems that you are using dns manual mode. Read this link first: $_DNS_MANUAL_WIKI\"\n\n__INTERACTIVE=\"\"\nif [ -t 1 ]; then\n  __INTERACTIVE=\"1\"\nfi\n\n__green() {\n  if [ \"${__INTERACTIVE}${ACME_NO_COLOR:-0}\" = \"10\" -o \"${ACME_FORCE_COLOR}\" = \"1\" ]; then\n    printf '\\33[1;32m%b\\33[0m' \"$1\"\n    return\n  fi\n  printf -- \"%b\" \"$1\"\n}\n\n__red() {\n  if [ \"${__INTERACTIVE}${ACME_NO_COLOR:-0}\" = \"10\" -o \"${ACME_FORCE_COLOR}\" = \"1\" ]; then\n    printf '\\33[1;31m%b\\33[0m' \"$1\"\n    return\n  fi\n  printf -- \"%b\" \"$1\"\n}\n\n_printargs() {\n  _exitstatus=\"$?\"\n  if [ -z \"$NO_TIMESTAMP\" ] || [ \"$NO_TIMESTAMP\" = \"0\" ]; then\n    printf -- \"%s\" \"[$(date)] \"\n  fi\n  if [ -z \"$2\" ]; then\n    printf -- \"%s\" \"$1\"\n  else\n    printf -- \"%s\" \"$1='$2'\"\n  fi\n  printf \"\\n\"\n  # return the saved exit status\n  return \"$_exitstatus\"\n}\n\n_dlg_versions() {\n  echo \"Diagnosis versions: \"\n  echo \"openssl:$ACME_OPENSSL_BIN\"\n  if _exists \"${ACME_OPENSSL_BIN:-openssl}\"; then\n    ${ACME_OPENSSL_BIN:-openssl} version 2>&1\n  else\n    echo \"$ACME_OPENSSL_BIN doesn't exist.\"\n  fi\n\n  echo \"Apache:\"\n  if [ \"$_APACHECTL\" ] && _exists \"$_APACHECTL\"; then\n    $_APACHECTL -V 2>&1\n  else\n    echo \"Apache doesn't exist.\"\n  fi\n\n  echo \"nginx:\"\n  if _exists \"nginx\"; then\n    nginx -V 2>&1\n  else\n    echo \"nginx doesn't exist.\"\n  fi\n\n  echo \"socat:\"\n  if _exists \"socat\"; then\n    socat -V 2>&1\n  else\n    _debug \"socat doesn't exist.\"\n    if _exists \"python3\"; then\n      python3 -V 2>&1\n    elif _exists \"python2\"; then\n      python2 -V 2>&1\n    elif _exists \"python\"; then\n      python -V 2>&1\n    fi\n  fi\n}\n\n#class\n_syslog() {\n  _exitstatus=\"$?\"\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" = \"$SYSLOG_LEVEL_NONE\" ]; then\n    return\n  fi\n  _logclass=\"$1\"\n  shift\n  if [ -z \"$__logger_i\" ]; then\n    if _contains \"$(logger --help 2>&1)\" \"-i\"; then\n      __logger_i=\"logger -i\"\n    else\n      __logger_i=\"logger\"\n    fi\n  fi\n  $__logger_i -t \"$PROJECT_NAME\" -p \"$_logclass\" \"$(_printargs \"$@\")\" >/dev/null 2>&1\n  return \"$_exitstatus\"\n}\n\n_log() {\n  [ -z \"$LOG_FILE\" ] && return\n  _printargs \"$@\" >>\"$LOG_FILE\"\n}\n\n_info() {\n  _log \"$@\"\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_INFO\" ]; then\n    _syslog \"$SYSLOG_INFO\" \"$@\"\n  fi\n  _printargs \"$@\"\n}\n\n_err() {\n  _syslog \"$SYSLOG_ERROR\" \"$@\"\n  _log \"$@\"\n  if [ -z \"$NO_TIMESTAMP\" ] || [ \"$NO_TIMESTAMP\" = \"0\" ]; then\n    printf -- \"%s\" \"[$(date)] \" >&2\n  fi\n  if [ -z \"$2\" ]; then\n    __red \"$1\" >&2\n  else\n    __red \"$1='$2'\" >&2\n  fi\n  printf \"\\n\" >&2\n  return 1\n}\n\n_usage() {\n  __red \"$@\" >&2\n  printf \"\\n\" >&2\n}\n\n__debug_bash_helper() {\n  # At this point only do for --debug 3\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -lt \"$DEBUG_LEVEL_3\" ]; then\n    return\n  fi\n  # Return extra debug info when running with bash, otherwise return empty\n  # string.\n  if [ -z \"${BASH_VERSION}\" ]; then\n    return\n  fi\n  # We are a bash shell at this point, return the filename, function name, and\n  # line number as a string\n  _dbh_saveIFS=$IFS\n  IFS=\" \"\n  # Must use eval or syntax error happens under dash. The eval should use\n  # single quotes as older versions of busybox had a bug with double quotes and\n  # eval.\n  # Use 'caller 1' as we want one level up the stack as we should be called\n  # by one of the _debug* functions\n  eval '_dbh_called=($(caller 1))'\n  IFS=$_dbh_saveIFS\n  eval '_dbh_file=${_dbh_called[2]}'\n  if [ -n \"${_script_home}\" ]; then\n    # Trim off the _script_home directory name\n    eval '_dbh_file=${_dbh_file#$_script_home/}'\n  fi\n  eval '_dbh_function=${_dbh_called[1]}'\n  eval '_dbh_lineno=${_dbh_called[0]}'\n  printf \"%-40s \" \"$_dbh_file:${_dbh_function}:${_dbh_lineno}\"\n}\n\n_debug() {\n  if [ \"${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}\" -ge \"$LOG_LEVEL_1\" ]; then\n    _log \"$@\"\n  fi\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_DEBUG\" ]; then\n    _syslog \"$SYSLOG_DEBUG\" \"$@\"\n  fi\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_1\" ]; then\n    _bash_debug=$(__debug_bash_helper)\n    _printargs \"${_bash_debug}$@\" >&2\n  fi\n}\n\n#output the sensitive messages\n_secure_debug() {\n  if [ \"${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}\" -ge \"$LOG_LEVEL_1\" ]; then\n    if [ \"$OUTPUT_INSECURE\" = \"1\" ]; then\n      _log \"$@\"\n    else\n      _log \"$1\" \"$HIDDEN_VALUE\"\n    fi\n  fi\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_DEBUG\" ]; then\n    _syslog \"$SYSLOG_DEBUG\" \"$1\" \"$HIDDEN_VALUE\"\n  fi\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_1\" ]; then\n    if [ \"$OUTPUT_INSECURE\" = \"1\" ]; then\n      _printargs \"$@\" >&2\n    else\n      _printargs \"$1\" \"$HIDDEN_VALUE\" >&2\n    fi\n  fi\n}\n\n_debug2() {\n  if [ \"${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}\" -ge \"$LOG_LEVEL_2\" ]; then\n    _log \"$@\"\n  fi\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_DEBUG_2\" ]; then\n    _syslog \"$SYSLOG_DEBUG\" \"$@\"\n  fi\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_2\" ]; then\n    _bash_debug=$(__debug_bash_helper)\n    _printargs \"${_bash_debug}$@\" >&2\n  fi\n}\n\n_secure_debug2() {\n  if [ \"${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}\" -ge \"$LOG_LEVEL_2\" ]; then\n    if [ \"$OUTPUT_INSECURE\" = \"1\" ]; then\n      _log \"$@\"\n    else\n      _log \"$1\" \"$HIDDEN_VALUE\"\n    fi\n  fi\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_DEBUG_2\" ]; then\n    _syslog \"$SYSLOG_DEBUG\" \"$1\" \"$HIDDEN_VALUE\"\n  fi\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_2\" ]; then\n    if [ \"$OUTPUT_INSECURE\" = \"1\" ]; then\n      _printargs \"$@\" >&2\n    else\n      _printargs \"$1\" \"$HIDDEN_VALUE\" >&2\n    fi\n  fi\n}\n\n_debug3() {\n  if [ \"${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}\" -ge \"$LOG_LEVEL_3\" ]; then\n    _log \"$@\"\n  fi\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_DEBUG_3\" ]; then\n    _syslog \"$SYSLOG_DEBUG\" \"$@\"\n  fi\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_3\" ]; then\n    _bash_debug=$(__debug_bash_helper)\n    _printargs \"${_bash_debug}$@\" >&2\n  fi\n}\n\n_secure_debug3() {\n  if [ \"${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}\" -ge \"$LOG_LEVEL_3\" ]; then\n    if [ \"$OUTPUT_INSECURE\" = \"1\" ]; then\n      _log \"$@\"\n    else\n      _log \"$1\" \"$HIDDEN_VALUE\"\n    fi\n  fi\n  if [ \"${SYS_LOG:-$SYSLOG_LEVEL_NONE}\" -ge \"$SYSLOG_LEVEL_DEBUG_3\" ]; then\n    _syslog \"$SYSLOG_DEBUG\" \"$1\" \"$HIDDEN_VALUE\"\n  fi\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_3\" ]; then\n    if [ \"$OUTPUT_INSECURE\" = \"1\" ]; then\n      _printargs \"$@\" >&2\n    else\n      _printargs \"$1\" \"$HIDDEN_VALUE\" >&2\n    fi\n  fi\n}\n\n__USE_TR_TAG=\"\"\nif [ \"$(echo \"abc\" | LANG=C tr a-z A-Z 2>/dev/null)\" != \"ABC\" ]; then\n  __USE_TR_TAG=\"1\"\nfi\nexport __USE_TR_TAG\n\n_upper_case() {\n  if [ \"$__USE_TR_TAG\" ]; then\n    LANG=C tr '[:lower:]' '[:upper:]'\n  else\n    # shellcheck disable=SC2018,SC2019\n    LANG=C tr '[a-z]' '[A-Z]'\n  fi\n}\n\n_lower_case() {\n  if [ \"$__USE_TR_TAG\" ]; then\n    LANG=C tr '[:upper:]' '[:lower:]'\n  else\n    # shellcheck disable=SC2018,SC2019\n    LANG=C tr '[A-Z]' '[a-z]'\n  fi\n}\n\n_startswith() {\n  _str=\"$1\"\n  _sub=\"$2\"\n  echo \"$_str\" | grep -- \"^$_sub\" >/dev/null 2>&1\n}\n\n_endswith() {\n  _str=\"$1\"\n  _sub=\"$2\"\n  echo \"$_str\" | grep -- \"$_sub\\$\" >/dev/null 2>&1\n}\n\n_contains() {\n  _str=\"$1\"\n  _sub=\"$2\"\n  echo \"$_str\" | grep -- \"$_sub\" >/dev/null 2>&1\n}\n\n_hasfield() {\n  _str=\"$1\"\n  _field=\"$2\"\n  _sep=\"$3\"\n  if [ -z \"$_field\" ]; then\n    _usage \"Usage: str field  [sep]\"\n    return 1\n  fi\n\n  if [ -z \"$_sep\" ]; then\n    _sep=\",\"\n  fi\n\n  for f in $(echo \"$_str\" | tr \"$_sep\" ' '); do\n    if [ \"$f\" = \"$_field\" ]; then\n      _debug2 \"'$_str' contains '$_field'\"\n      return 0 #contains ok\n    fi\n  done\n  _debug2 \"'$_str' does not contain '$_field'\"\n  return 1 #not contains\n}\n\n# str index [sep]\n_getfield() {\n  _str=\"$1\"\n  _findex=\"$2\"\n  _sep=\"$3\"\n\n  if [ -z \"$_findex\" ]; then\n    _usage \"Usage: str field  [sep]\"\n    return 1\n  fi\n\n  if [ -z \"$_sep\" ]; then\n    _sep=\",\"\n  fi\n\n  _ffi=\"$_findex\"\n  while [ \"$_ffi\" -gt \"0\" ]; do\n    _fv=\"$(echo \"$_str\" | cut -d \"$_sep\" -f \"$_ffi\")\"\n    if [ \"$_fv\" ]; then\n      printf -- \"%s\" \"$_fv\"\n      return 0\n    fi\n    _ffi=\"$(_math \"$_ffi\" - 1)\"\n  done\n\n  printf -- \"%s\" \"$_str\"\n\n}\n\n_exists() {\n  cmd=\"$1\"\n  if [ -z \"$cmd\" ]; then\n    _usage \"Usage: _exists cmd\"\n    return 1\n  fi\n\n  if eval type type >/dev/null 2>&1; then\n    eval type \"$cmd\" >/dev/null 2>&1\n  elif command >/dev/null 2>&1; then\n    command -v \"$cmd\" >/dev/null 2>&1\n  else\n    which \"$cmd\" >/dev/null 2>&1\n  fi\n  ret=\"$?\"\n  _debug3 \"$cmd exists=$ret\"\n  return $ret\n}\n\n#a + b\n_math() {\n  _m_opts=\"$@\"\n  printf \"%s\" \"$(($_m_opts))\"\n}\n\n_h_char_2_dec() {\n  _ch=$1\n  case \"${_ch}\" in\n  a | A)\n    printf \"10\"\n    ;;\n  b | B)\n    printf \"11\"\n    ;;\n  c | C)\n    printf \"12\"\n    ;;\n  d | D)\n    printf \"13\"\n    ;;\n  e | E)\n    printf \"14\"\n    ;;\n  f | F)\n    printf \"15\"\n    ;;\n  *)\n    printf \"%s\" \"$_ch\"\n    ;;\n  esac\n\n}\n\n_URGLY_PRINTF=\"\"\nif [ \"$(printf '\\x41')\" != 'A' ]; then\n  _URGLY_PRINTF=1\nfi\n\n_h2b() {\n  if _exists xxd; then\n    if _contains \"$(xxd --help 2>&1)\" \"assumes -c30\"; then\n      if xxd -r -p -c 9999 2>/dev/null; then\n        return\n      fi\n    else\n      if xxd -r -p 2>/dev/null; then\n        return\n      fi\n    fi\n  fi\n\n  hex=$(cat)\n  ic=\"\"\n  jc=\"\"\n  _debug2 _URGLY_PRINTF \"$_URGLY_PRINTF\"\n  if [ -z \"$_URGLY_PRINTF\" ]; then\n    # shellcheck disable=SC2059\n    printf \"$(echo \"$hex\" | _upper_case | sed 's/\\([0-9A-F]\\{2\\}\\)/\\\\x\\1/g')\"\n  else\n    for c in $(echo \"$hex\" | _upper_case | sed 's/\\([0-9A-F]\\)/ \\1/g'); do\n      if [ -z \"$ic\" ]; then\n        ic=$c\n        continue\n      fi\n      jc=$c\n      ic=\"$(_h_char_2_dec \"$ic\")\"\n      jc=\"$(_h_char_2_dec \"$jc\")\"\n      printf '\\'\"$(printf \"%o\" \"$(_math \"$ic\" \\* 16 + $jc)\")\"\"%s\"\n      ic=\"\"\n      jc=\"\"\n    done\n  fi\n\n}\n\n_is_solaris() {\n  _contains \"${__OS__:=$(uname -a)}\" \"solaris\" || _contains \"${__OS__:=$(uname -a)}\" \"SunOS\"\n}\n\n#_ascii_hex str\n#this can only process ascii chars, should only be used when od command is missing as a backup way.\n_ascii_hex() {\n  _debug2 \"Using _ascii_hex\"\n  _str=\"$1\"\n  _str_len=${#_str}\n  _h_i=1\n  while [ \"$_h_i\" -le \"$_str_len\" ]; do\n    _str_c=\"$(printf \"%s\" \"$_str\" | cut -c \"$_h_i\")\"\n    printf \" %02x\" \"'$_str_c\"\n    _h_i=\"$(_math \"$_h_i\" + 1)\"\n  done\n}\n\n#stdin  output hexstr splited by one space\n#input:\"abc\"\n#output: \" 61 62 63\"\n_hex_dump() {\n  if _exists od; then\n    od -A n -v -t x1 | tr -s \" \" | sed 's/ $//' | tr -d \"\\r\\t\\n\"\n  elif _exists hexdump; then\n    _debug3 \"using hexdump\"\n    hexdump -v -e '/1 \"\"' -e '/1 \" %02x\" \"\"'\n  elif _exists xxd; then\n    _debug3 \"using xxd\"\n    xxd -ps -c 20 -i | sed \"s/ 0x/ /g\" | tr -d \",\\n\" | tr -s \" \"\n  else\n    _debug3 \"using _ascii_hex\"\n    str=$(cat)\n    _ascii_hex \"$str\"\n  fi\n}\n\n#url encode, no-preserved chars\n#A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z\n#41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a\n\n#a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z\n#61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a\n\n#0  1  2  3  4  5  6  7  8  9  -  _  .  ~\n#30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e\n\n#_url_encode [upper-hex]  the encoded hex will be upper-case if the argument upper-hex is followed\n#stdin stdout\n_url_encode() {\n  _upper_hex=$1\n  _hex_str=$(_hex_dump)\n  _debug3 \"_url_encode\"\n  _debug3 \"_hex_str\" \"$_hex_str\"\n  for _hex_code in $_hex_str; do\n    #upper case\n    case \"${_hex_code}\" in\n    \"41\")\n      printf \"%s\" \"A\"\n      ;;\n    \"42\")\n      printf \"%s\" \"B\"\n      ;;\n    \"43\")\n      printf \"%s\" \"C\"\n      ;;\n    \"44\")\n      printf \"%s\" \"D\"\n      ;;\n    \"45\")\n      printf \"%s\" \"E\"\n      ;;\n    \"46\")\n      printf \"%s\" \"F\"\n      ;;\n    \"47\")\n      printf \"%s\" \"G\"\n      ;;\n    \"48\")\n      printf \"%s\" \"H\"\n      ;;\n    \"49\")\n      printf \"%s\" \"I\"\n      ;;\n    \"4a\")\n      printf \"%s\" \"J\"\n      ;;\n    \"4b\")\n      printf \"%s\" \"K\"\n      ;;\n    \"4c\")\n      printf \"%s\" \"L\"\n      ;;\n    \"4d\")\n      printf \"%s\" \"M\"\n      ;;\n    \"4e\")\n      printf \"%s\" \"N\"\n      ;;\n    \"4f\")\n      printf \"%s\" \"O\"\n      ;;\n    \"50\")\n      printf \"%s\" \"P\"\n      ;;\n    \"51\")\n      printf \"%s\" \"Q\"\n      ;;\n    \"52\")\n      printf \"%s\" \"R\"\n      ;;\n    \"53\")\n      printf \"%s\" \"S\"\n      ;;\n    \"54\")\n      printf \"%s\" \"T\"\n      ;;\n    \"55\")\n      printf \"%s\" \"U\"\n      ;;\n    \"56\")\n      printf \"%s\" \"V\"\n      ;;\n    \"57\")\n      printf \"%s\" \"W\"\n      ;;\n    \"58\")\n      printf \"%s\" \"X\"\n      ;;\n    \"59\")\n      printf \"%s\" \"Y\"\n      ;;\n    \"5a\")\n      printf \"%s\" \"Z\"\n      ;;\n\n      #lower case\n    \"61\")\n      printf \"%s\" \"a\"\n      ;;\n    \"62\")\n      printf \"%s\" \"b\"\n      ;;\n    \"63\")\n      printf \"%s\" \"c\"\n      ;;\n    \"64\")\n      printf \"%s\" \"d\"\n      ;;\n    \"65\")\n      printf \"%s\" \"e\"\n      ;;\n    \"66\")\n      printf \"%s\" \"f\"\n      ;;\n    \"67\")\n      printf \"%s\" \"g\"\n      ;;\n    \"68\")\n      printf \"%s\" \"h\"\n      ;;\n    \"69\")\n      printf \"%s\" \"i\"\n      ;;\n    \"6a\")\n      printf \"%s\" \"j\"\n      ;;\n    \"6b\")\n      printf \"%s\" \"k\"\n      ;;\n    \"6c\")\n      printf \"%s\" \"l\"\n      ;;\n    \"6d\")\n      printf \"%s\" \"m\"\n      ;;\n    \"6e\")\n      printf \"%s\" \"n\"\n      ;;\n    \"6f\")\n      printf \"%s\" \"o\"\n      ;;\n    \"70\")\n      printf \"%s\" \"p\"\n      ;;\n    \"71\")\n      printf \"%s\" \"q\"\n      ;;\n    \"72\")\n      printf \"%s\" \"r\"\n      ;;\n    \"73\")\n      printf \"%s\" \"s\"\n      ;;\n    \"74\")\n      printf \"%s\" \"t\"\n      ;;\n    \"75\")\n      printf \"%s\" \"u\"\n      ;;\n    \"76\")\n      printf \"%s\" \"v\"\n      ;;\n    \"77\")\n      printf \"%s\" \"w\"\n      ;;\n    \"78\")\n      printf \"%s\" \"x\"\n      ;;\n    \"79\")\n      printf \"%s\" \"y\"\n      ;;\n    \"7a\")\n      printf \"%s\" \"z\"\n      ;;\n      #numbers\n    \"30\")\n      printf \"%s\" \"0\"\n      ;;\n    \"31\")\n      printf \"%s\" \"1\"\n      ;;\n    \"32\")\n      printf \"%s\" \"2\"\n      ;;\n    \"33\")\n      printf \"%s\" \"3\"\n      ;;\n    \"34\")\n      printf \"%s\" \"4\"\n      ;;\n    \"35\")\n      printf \"%s\" \"5\"\n      ;;\n    \"36\")\n      printf \"%s\" \"6\"\n      ;;\n    \"37\")\n      printf \"%s\" \"7\"\n      ;;\n    \"38\")\n      printf \"%s\" \"8\"\n      ;;\n    \"39\")\n      printf \"%s\" \"9\"\n      ;;\n    \"2d\")\n      printf \"%s\" \"-\"\n      ;;\n    \"5f\")\n      printf \"%s\" \"_\"\n      ;;\n    \"2e\")\n      printf \"%s\" \".\"\n      ;;\n    \"7e\")\n      printf \"%s\" \"~\"\n      ;;\n    #other hex\n    *)\n      if [ \"$_upper_hex\" = \"upper-hex\" ]; then\n        _hex_code=$(printf \"%s\" \"$_hex_code\" | _upper_case)\n      fi\n      printf '%%%s' \"$_hex_code\"\n      ;;\n    esac\n  done\n}\n\n_json_encode() {\n  _j_str=\"$(sed 's/\"/\\\\\"/g' | sed \"s/\\r/\\\\r/g\")\"\n  _debug3 \"_json_encode\"\n  _debug3 \"_j_str\" \"$_j_str\"\n  echo \"$_j_str\" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d \"\\r\\n\"\n}\n\n#from: http:\\/\\/  to http://\n_json_decode() {\n  _j_str=\"$(sed 's#\\\\/#/#g')\"\n  _debug3 \"_json_decode\"\n  _debug3 \"_j_str\" \"$_j_str\"\n  echo \"$_j_str\"\n}\n\n#options file\n_sed_i() {\n  options=\"$1\"\n  filename=\"$2\"\n  if [ -z \"$filename\" ]; then\n    _usage \"Usage:_sed_i options filename\"\n    return 1\n  fi\n  _debug2 options \"$options\"\n  if sed -h 2>&1 | grep \"\\-i\\[SUFFIX]\" >/dev/null 2>&1; then\n    _debug \"Using sed  -i\"\n    sed -i \"$options\" \"$filename\"\n  elif sed -h 2>&1 | grep \"\\-i extension\" >/dev/null 2>&1; then\n    _debug \"Using FreeBSD sed -i\"\n    sed -i \"\" \"$options\" \"$filename\"\n  else\n    _debug \"No -i support in sed\"\n    text=\"$(cat \"$filename\")\"\n    echo \"$text\" | sed \"$options\" >\"$filename\"\n  fi\n}\n\nif [ \"$(echo abc | egrep -o b 2>/dev/null)\" = \"b\" ]; then\n  __USE_EGREP=1\nelse\n  __USE_EGREP=\"\"\nfi\n\n_egrep_o() {\n  if [ \"$__USE_EGREP\" ]; then\n    egrep -o -- \"$1\" 2>/dev/null\n  else\n    sed -n 's/.*\\('\"$1\"'\\).*/\\1/p'\n  fi\n}\n\n#Usage: file startline endline\n_getfile() {\n  filename=\"$1\"\n  startline=\"$2\"\n  endline=\"$3\"\n  if [ -z \"$endline\" ]; then\n    _usage \"Usage: file startline endline\"\n    return 1\n  fi\n\n  i=\"$(grep -n -- \"$startline\" \"$filename\" | cut -d : -f 1)\"\n  if [ -z \"$i\" ]; then\n    _err \"Cannot find start line: $startline\"\n    return 1\n  fi\n  i=\"$(_math \"$i\" + 1)\"\n  _debug i \"$i\"\n\n  j=\"$(grep -n -- \"$endline\" \"$filename\" | cut -d : -f 1)\"\n  if [ -z \"$j\" ]; then\n    _err \"Cannot find end line: $endline\"\n    return 1\n  fi\n  j=\"$(_math \"$j\" - 1)\"\n  _debug j \"$j\"\n\n  sed -n \"$i,${j}p\" \"$filename\"\n\n}\n\n#Usage: multiline\n_base64() {\n  [ \"\" ] #urgly\n  if [ \"$1\" ]; then\n    _debug3 \"base64 multiline:'$1'\"\n    ${ACME_OPENSSL_BIN:-openssl} base64 -e\n  else\n    _debug3 \"base64 single line.\"\n    ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\\r\\n'\n  fi\n}\n\n#Usage: multiline\n_dbase64() {\n  if [ \"$1\" ]; then\n    ${ACME_OPENSSL_BIN:-openssl} base64 -d\n  else\n    ${ACME_OPENSSL_BIN:-openssl} base64 -d -A\n  fi\n}\n\n#file\n_checkcert() {\n  _cf=\"$1\"\n  if [ \"$DEBUG\" ]; then\n    ${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in \"$_cf\"\n  else\n    ${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in \"$_cf\" >/dev/null 2>&1\n  fi\n}\n\n#Usage: hashalg  [outputhex]\n#Output Base64-encoded digest\n_digest() {\n  alg=\"$1\"\n  if [ -z \"$alg\" ]; then\n    _usage \"Usage: _digest hashalg\"\n    return 1\n  fi\n\n  outputhex=\"$2\"\n\n  if [ \"$alg\" = \"sha3-256\" ] || [ \"$alg\" = \"sha256\" ] || [ \"$alg\" = \"sha1\" ] || [ \"$alg\" = \"md5\" ]; then\n    if [ \"$outputhex\" ]; then\n      ${ACME_OPENSSL_BIN:-openssl} dgst -\"$alg\" -hex | cut -d = -f 2 | tr -d ' '\n    else\n      ${ACME_OPENSSL_BIN:-openssl} dgst -\"$alg\" -binary | _base64\n    fi\n  else\n    _err \"$alg is not supported yet\"\n    return 1\n  fi\n\n}\n\n#Usage: hashalg  secret_hex  [outputhex]\n#Output binary hmac\n_hmac() {\n  alg=\"$1\"\n  secret_hex=\"$2\"\n  outputhex=\"$3\"\n\n  if [ -z \"$secret_hex\" ]; then\n    _usage \"Usage: _hmac hashalg secret [outputhex]\"\n    return 1\n  fi\n\n  if [ \"$alg\" = \"sha256\" ] || [ \"$alg\" = \"sha1\" ]; then\n    if [ \"$outputhex\" ]; then\n      (${ACME_OPENSSL_BIN:-openssl} dgst -\"$alg\" -mac HMAC -macopt \"hexkey:$secret_hex\" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -\"$alg\" -hmac \"$(printf \"%s\" \"$secret_hex\" | _h2b)\") | cut -d = -f 2 | tr -d ' '\n    else\n      ${ACME_OPENSSL_BIN:-openssl} dgst -\"$alg\" -mac HMAC -macopt \"hexkey:$secret_hex\" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -\"$alg\" -hmac \"$(printf \"%s\" \"$secret_hex\" | _h2b)\" -binary\n    fi\n  else\n    _err \"$alg is not supported yet\"\n    return 1\n  fi\n\n}\n\n#Usage: keyfile hashalg\n#Output: Base64-encoded signature value\n_sign() {\n  keyfile=\"$1\"\n  alg=\"$2\"\n  if [ -z \"$alg\" ]; then\n    _usage \"Usage: _sign keyfile hashalg\"\n    return 1\n  fi\n\n  _sign_openssl=\"${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile \"\n\n  if _isRSA \"$keyfile\" >/dev/null 2>&1; then\n    $_sign_openssl -$alg | _base64\n  elif _isEcc \"$keyfile\" >/dev/null 2>&1; then\n    if ! _signedECText=\"$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)\"; then\n      _err \"Sign failed: $_sign_openssl\"\n      _err \"Key file: $keyfile\"\n      _err \"Key content: $(wc -l <\"$keyfile\") lines\"\n      return 1\n    fi\n    _debug3 \"_signedECText\" \"$_signedECText\"\n    _ec_r=\"$(echo \"$_signedECText\" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d \"\\r\\n\")\"\n    _ec_s=\"$(echo \"$_signedECText\" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d \"\\r\\n\")\"\n    if [ \"$__ECC_KEY_LEN\" -eq \"256\" ]; then\n      while [ \"${#_ec_r}\" -lt \"64\" ]; do\n        _ec_r=\"0${_ec_r}\"\n      done\n      while [ \"${#_ec_s}\" -lt \"64\" ]; do\n        _ec_s=\"0${_ec_s}\"\n      done\n    fi\n    if [ \"$__ECC_KEY_LEN\" -eq \"384\" ]; then\n      while [ \"${#_ec_r}\" -lt \"96\" ]; do\n        _ec_r=\"0${_ec_r}\"\n      done\n      while [ \"${#_ec_s}\" -lt \"96\" ]; do\n        _ec_s=\"0${_ec_s}\"\n      done\n    fi\n    if [ \"$__ECC_KEY_LEN\" -eq \"512\" ]; then\n      while [ \"${#_ec_r}\" -lt \"132\" ]; do\n        _ec_r=\"0${_ec_r}\"\n      done\n      while [ \"${#_ec_s}\" -lt \"132\" ]; do\n        _ec_s=\"0${_ec_s}\"\n      done\n    fi\n    _debug3 \"_ec_r\" \"$_ec_r\"\n    _debug3 \"_ec_s\" \"$_ec_s\"\n    printf \"%s\" \"$_ec_r$_ec_s\" | _h2b | _base64\n  else\n    _err \"Unknown key file format.\"\n    return 1\n  fi\n\n}\n\n#keylength or isEcc flag (empty str => not ecc)\n_isEccKey() {\n  _length=\"$1\"\n\n  if [ -z \"$_length\" ]; then\n    return 1\n  fi\n\n  [ \"$_length\" != \"1024\" ] &&\n    [ \"$_length\" != \"2048\" ] &&\n    [ \"$_length\" != \"3072\" ] &&\n    [ \"$_length\" != \"4096\" ] &&\n    [ \"$_length\" != \"8192\" ]\n}\n\n# _createkey  2048|ec-256   file\n_createkey() {\n  length=\"$1\"\n  f=\"$2\"\n  _debug2 \"_createkey for file:$f\"\n  eccname=\"$length\"\n  if _startswith \"$length\" \"ec-\"; then\n    length=$(printf \"%s\" \"$length\" | cut -d '-' -f 2-100)\n\n    if [ \"$length\" = \"256\" ]; then\n      eccname=\"prime256v1\"\n    fi\n    if [ \"$length\" = \"384\" ]; then\n      eccname=\"secp384r1\"\n    fi\n    if [ \"$length\" = \"521\" ]; then\n      eccname=\"secp521r1\"\n    fi\n\n  fi\n\n  if [ -z \"$length\" ]; then\n    length=2048\n  fi\n\n  _debug \"Using length $length\"\n\n  if ! [ -e \"$f\" ]; then\n    if ! touch \"$f\" >/dev/null 2>&1; then\n      _f_path=\"$(dirname \"$f\")\"\n      _debug _f_path \"$_f_path\"\n      if ! mkdir -p \"$_f_path\"; then\n        _err \"Cannot create path: $_f_path\"\n        return 1\n      fi\n    fi\n    if ! touch \"$f\" >/dev/null 2>&1; then\n      return 1\n    fi\n    chmod 600 \"$f\"\n  fi\n\n  if _isEccKey \"$length\"; then\n    _debug \"Using EC name: $eccname\"\n    if _opkey=\"$(${ACME_OPENSSL_BIN:-openssl} ecparam -name \"$eccname\" -noout -genkey 2>/dev/null)\"; then\n      echo \"$_opkey\" >\"$f\"\n    else\n      _err \"Error encountered for ECC key named $eccname\"\n      return 1\n    fi\n  else\n    _debug \"Using RSA: $length\"\n    __traditional=\"\"\n    if _contains \"$(${ACME_OPENSSL_BIN:-openssl} help genrsa 2>&1)\" \"-traditional\"; then\n      __traditional=\"-traditional\"\n    fi\n    if _opkey=\"$(${ACME_OPENSSL_BIN:-openssl} genrsa $__traditional \"$length\" 2>/dev/null)\"; then\n      echo \"$_opkey\" >\"$f\"\n    else\n      _err \"Error encountered for RSA key of length $length\"\n      return 1\n    fi\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Key creation error.\"\n    return 1\n  fi\n}\n\n#domain\n_is_idn() {\n  _is_idn_d=\"$1\"\n  _debug2 _is_idn_d \"$_is_idn_d\"\n  _idn_temp=$(printf \"%s\" \"$_is_idn_d\" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_')\n  _debug2 _idn_temp \"$_idn_temp\"\n  [ \"$_idn_temp\" ]\n}\n\n#aa.com\n#aa.com,bb.com,cc.com\n_idn() {\n  __idn_d=\"$1\"\n  if ! _is_idn \"$__idn_d\"; then\n    printf \"%s\" \"$__idn_d\"\n    return 0\n  fi\n\n  if _exists idn; then\n    if _contains \"$__idn_d\" ','; then\n      _i_first=\"1\"\n      for f in $(echo \"$__idn_d\" | tr ',' ' '); do\n        [ -z \"$f\" ] && continue\n        if [ -z \"$_i_first\" ]; then\n          printf \"%s\" \",\"\n        else\n          _i_first=\"\"\n        fi\n        idn --quiet \"$f\" | tr -d \"\\r\\n\"\n      done\n    else\n      idn \"$__idn_d\" | tr -d \"\\r\\n\"\n    fi\n  else\n    _err \"Please install idn to process IDN names.\"\n  fi\n}\n\n#_createcsr  cn  san_list  keyfile csrfile conf acmeValidationv1 extendedUsage\n_createcsr() {\n  _debug _createcsr\n  domain=\"$1\"\n  domainlist=\"$2\"\n  csrkey=\"$3\"\n  csr=\"$4\"\n  csrconf=\"$5\"\n  acmeValidationv1=\"$6\"\n  extusage=\"$7\"\n  _debug2 domain \"$domain\"\n  _debug2 domainlist \"$domainlist\"\n  _debug2 csrkey \"$csrkey\"\n  _debug2 csr \"$csr\"\n  _debug2 csrconf \"$csrconf\"\n\n  printf \"[ req_distinguished_name ]\\n[ req ]\\ndistinguished_name = req_distinguished_name\\nreq_extensions = v3_req\\n[ v3_req ]\" >\"$csrconf\"\n\n  if [ \"$extusage\" ]; then\n    printf \"\\nextendedKeyUsage=$extusage\\n\" >>\"$csrconf\"\n  else\n    printf \"\\nextendedKeyUsage=serverAuth,clientAuth\\n\" >>\"$csrconf\"\n  fi\n\n  if [ \"$acmeValidationv1\" ]; then\n    domainlist=\"$(_idn \"$domainlist\")\"\n    _debug2 domainlist \"$domainlist\"\n    alt=\"\"\n    for dl in $(echo \"$domainlist\" | tr \",\" ' '); do\n      if [ \"$alt\" ]; then\n        alt=\"$alt,$(_getIdType \"$dl\" | _upper_case):$dl\"\n      else\n        alt=\"$(_getIdType \"$dl\" | _upper_case):$dl\"\n      fi\n    done\n    printf -- \"\\nsubjectAltName=$alt\" >>\"$csrconf\"\n  elif [ -z \"$domainlist\" ] || [ \"$domainlist\" = \"$NO_VALUE\" ]; then\n    #single domain\n    _info \"Single domain\" \"$domain\"\n    printf -- \"\\nsubjectAltName=$(_getIdType \"$domain\" | _upper_case):$(_idn \"$domain\")\" >>\"$csrconf\"\n  else\n    domainlist=\"$(_idn \"$domainlist\")\"\n    _debug2 domainlist \"$domainlist\"\n    alt=\"$(_getIdType \"$domain\" | _upper_case):$(_idn \"$domain\")\"\n    for dl in $(echo \"'$domainlist'\" | sed \"s/,/' '/g\"); do\n      dl=$(echo \"$dl\" | tr -d \"'\")\n      alt=\"$alt,$(_getIdType \"$dl\" | _upper_case):$dl\"\n    done\n    #multi\n    _info \"Multi domain\" \"$alt\"\n    printf -- \"\\nsubjectAltName=$alt\" >>\"$csrconf\"\n  fi\n  if [ \"$Le_OCSP_Staple\" = \"1\" ]; then\n    _savedomainconf Le_OCSP_Staple \"$Le_OCSP_Staple\"\n    printf -- \"\\nbasicConstraints = CA:FALSE\\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05\" >>\"$csrconf\"\n  fi\n\n  if [ \"$acmeValidationv1\" ]; then\n    printf \"\\n1.3.6.1.5.5.7.1.31=critical,DER:04:20:${acmeValidationv1}\" >>\"${csrconf}\"\n  fi\n\n  _csr_cn=\"$(_idn \"$domain\")\"\n  _debug2 _csr_cn \"$_csr_cn\"\n  if _contains \"$(uname -a)\" \"MINGW\"; then\n    if _isIP \"$_csr_cn\"; then\n      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key \"$csrkey\" -subj \"//O=$PROJECT_NAME\" -config \"$csrconf\" -out \"$csr\"\n    else\n      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key \"$csrkey\" -subj \"//CN=$_csr_cn\" -config \"$csrconf\" -out \"$csr\"\n    fi\n  else\n    if _isIP \"$_csr_cn\"; then\n      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key \"$csrkey\" -subj \"/O=$PROJECT_NAME\" -config \"$csrconf\" -out \"$csr\"\n    else\n      ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key \"$csrkey\" -subj \"/CN=$_csr_cn\" -config \"$csrconf\" -out \"$csr\"\n    fi\n  fi\n}\n\n#_signcsr key  csr  conf cert\n_signcsr() {\n  key=\"$1\"\n  csr=\"$2\"\n  conf=\"$3\"\n  cert=\"$4\"\n  _debug \"_signcsr\"\n\n  _msg=\"$(${ACME_OPENSSL_BIN:-openssl} x509 -req -days 365 -in \"$csr\" -signkey \"$key\" -extensions v3_req -extfile \"$conf\" -out \"$cert\" 2>&1)\"\n  _ret=\"$?\"\n  _debug \"$_msg\"\n  return $_ret\n}\n\n#_csrfile\n_readSubjectFromCSR() {\n  _csrfile=\"$1\"\n  if [ -z \"$_csrfile\" ]; then\n    _usage \"_readSubjectFromCSR mycsr.csr\"\n    return 1\n  fi\n  ${ACME_OPENSSL_BIN:-openssl} req -noout -in \"$_csrfile\" -subject | tr ',' \"\\n\" | _egrep_o \"CN *=.*\" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \\n'\n}\n\n#_csrfile\n#echo comma separated domain list\n_readSubjectAltNamesFromCSR() {\n  _csrfile=\"$1\"\n  if [ -z \"$_csrfile\" ]; then\n    _usage \"_readSubjectAltNamesFromCSR mycsr.csr\"\n    return 1\n  fi\n\n  _csrsubj=\"$(_readSubjectFromCSR \"$_csrfile\")\"\n  _debug _csrsubj \"$_csrsubj\"\n\n  _dnsAltnames=\"$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in \"$_csrfile\" | grep \"^ *DNS:.*\" | tr -d ' \\n')\"\n  _debug _dnsAltnames \"$_dnsAltnames\"\n\n  if _contains \"$_dnsAltnames,\" \"DNS:$_csrsubj,\"; then\n    _debug \"AltNames contains subject\"\n    _excapedAlgnames=\"$(echo \"$_dnsAltnames\" | tr '*' '#')\"\n    _debug _excapedAlgnames \"$_excapedAlgnames\"\n    _escapedSubject=\"$(echo \"$_csrsubj\" | tr '*' '#')\"\n    _debug _escapedSubject \"$_escapedSubject\"\n    _dnsAltnames=\"$(echo \"$_excapedAlgnames,\" | sed \"s/DNS:$_escapedSubject,//g\" | tr '#' '*' | sed \"s/,\\$//g\")\"\n    _debug _dnsAltnames \"$_dnsAltnames\"\n  else\n    _debug \"AltNames doesn't contain subject\"\n  fi\n\n  echo \"$_dnsAltnames\" | sed \"s/DNS://g\"\n}\n\n#_csrfile\n_readKeyLengthFromCSR() {\n  _csrfile=\"$1\"\n  if [ -z \"$_csrfile\" ]; then\n    _usage \"_readKeyLengthFromCSR mycsr.csr\"\n    return 1\n  fi\n\n  _outcsr=\"$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in \"$_csrfile\")\"\n  _debug2 _outcsr \"$_outcsr\"\n  if _contains \"$_outcsr\" \"Public Key Algorithm: id-ecPublicKey\"; then\n    _debug \"ECC CSR\"\n    echo \"$_outcsr\" | tr \"\\t\" \" \" | _egrep_o \"^ *ASN1 OID:.*\" | cut -d ':' -f 2 | tr -d ' '\n  else\n    _debug \"RSA CSR\"\n    _rkl=\"$(echo \"$_outcsr\" | tr \"\\t\" \" \" | _egrep_o \"^ *Public.Key:.*\" | cut -d '(' -f 2 | cut -d ' ' -f 1)\"\n    if [ \"$_rkl\" ]; then\n      echo \"$_rkl\"\n    else\n      echo \"$_outcsr\" | tr \"\\t\" \" \" | _egrep_o \"RSA Public.Key:.*\" | cut -d '(' -f 2 | cut -d ' ' -f 1\n    fi\n  fi\n}\n\n_ss() {\n  _port=\"$1\"\n\n  if _exists \"ss\"; then\n    _debug \"Using: ss\"\n    ss -ntpl 2>/dev/null | grep \":$_port \"\n    return 0\n  fi\n\n  if [ \"$(uname)\" = \"AIX\" ]; then\n    _debug \"Using: AIX netstat\"\n    netstat -an | grep \"^tcp\" | grep \"LISTEN\" | grep \"\\.$_port \"\n    return 0\n  fi\n\n  if _exists \"netstat\"; then\n    _debug \"Using: netstat\"\n    if netstat -help 2>&1 | grep \"\\-p proto\" >/dev/null; then\n      #for windows version netstat tool\n      netstat -an -p tcp | grep \"LISTENING\" | grep \":$_port \"\n    else\n      if netstat -help 2>&1 | grep \"\\-p protocol\" >/dev/null; then\n        netstat -an -p tcp | grep LISTEN | grep \":$_port \"\n      elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then\n        #for solaris\n        netstat -an -P tcp | grep \"\\.$_port \" | grep \"LISTEN\"\n      elif netstat -help 2>&1 | grep \"\\-p\" >/dev/null; then\n        #for full linux\n        netstat -ntpl | grep \":$_port \"\n      else\n        #for busybox (embedded linux; no pid support)\n        netstat -ntl 2>/dev/null | grep \":$_port \"\n      fi\n    fi\n    return 0\n  fi\n\n  return 1\n}\n\n#outfile key cert cacert [password [name [caname]]]\n_toPkcs() {\n  _cpfx=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  pfxPassword=\"$5\"\n  pfxName=\"$6\"\n  pfxCaname=\"$7\"\n\n  if [ \"$pfxCaname\" ]; then\n    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out \"$_cpfx\" -inkey \"$_ckey\" -in \"$_ccert\" -certfile \"$_cca\" -password \"pass:$pfxPassword\" -name \"$pfxName\" -caname \"$pfxCaname\"\n  elif [ \"$pfxName\" ]; then\n    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out \"$_cpfx\" -inkey \"$_ckey\" -in \"$_ccert\" -certfile \"$_cca\" -password \"pass:$pfxPassword\" -name \"$pfxName\"\n  elif [ \"$pfxPassword\" ]; then\n    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out \"$_cpfx\" -inkey \"$_ckey\" -in \"$_ccert\" -certfile \"$_cca\" -password \"pass:$pfxPassword\"\n  else\n    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out \"$_cpfx\" -inkey \"$_ckey\" -in \"$_ccert\" -certfile \"$_cca\"\n  fi\n  if [ \"$?\" = \"0\" ]; then\n    _savedomainconf \"Le_PFXPassword\" \"$pfxPassword\" \"base64\"\n  fi\n\n}\n\n#domain [password] [isEcc]\ntoPkcs() {\n  domain=\"$1\"\n  pfxPassword=\"$2\"\n  if [ -z \"$domain\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --to-pkcs12 --domain <domain.tld> [--password <password>] [--ecc]\"\n    return 1\n  fi\n\n  _isEcc=\"$3\"\n\n  _initpath \"$domain\" \"$_isEcc\"\n\n  _toPkcs \"$CERT_PFX_PATH\" \"$CERT_KEY_PATH\" \"$CERT_PATH\" \"$CA_CERT_PATH\" \"$pfxPassword\"\n\n  if [ \"$?\" = \"0\" ]; then\n    _info \"Success, PFX has been exported to: $CERT_PFX_PATH\"\n  fi\n\n}\n\n#domain [isEcc]\ntoPkcs8() {\n  domain=\"$1\"\n\n  if [ -z \"$domain\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --to-pkcs8 --domain <domain.tld> [--ecc]\"\n    return 1\n  fi\n\n  _isEcc=\"$2\"\n\n  _initpath \"$domain\" \"$_isEcc\"\n\n  ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in \"$CERT_KEY_PATH\" -out \"$CERT_PKCS8_PATH\"\n\n  if [ \"$?\" = \"0\" ]; then\n    _info \"Success, $CERT_PKCS8_PATH\"\n  fi\n\n}\n\n#[2048]\ncreateAccountKey() {\n  _info \"Creating account key\"\n  if [ -z \"$1\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --create-account-key [--accountkeylength <bits>]\"\n    return\n  fi\n\n  length=$1\n  _create_account_key \"$length\"\n\n}\n\n_create_account_key() {\n\n  length=$1\n\n  if [ -z \"$length\" ] || [ \"$length\" = \"$NO_VALUE\" ]; then\n    _debug \"Using default length $DEFAULT_ACCOUNT_KEY_LENGTH\"\n    length=\"$DEFAULT_ACCOUNT_KEY_LENGTH\"\n  fi\n\n  _debug length \"$length\"\n  _initpath\n\n  mkdir -p \"$CA_DIR\"\n  if [ -s \"$ACCOUNT_KEY_PATH\" ]; then\n    _info \"Account key exists, skipping\"\n    return 0\n  else\n    #generate account key\n    if _createkey \"$length\" \"$ACCOUNT_KEY_PATH\"; then\n      _info \"Account key creation OK.\"\n      return 0\n    else\n      _err \"Account key creation error.\"\n      return 1\n    fi\n  fi\n\n}\n\n#domain [length]\ncreateDomainKey() {\n  _info \"Creating domain key\"\n  if [ -z \"$1\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --create-domain-key --domain <domain.tld> [--keylength <bits>]\"\n    return\n  fi\n\n  domain=$1\n  _cdl=$2\n\n  if [ -z \"$_cdl\" ]; then\n    _debug \"Using DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH\"\n    _cdl=\"$DEFAULT_DOMAIN_KEY_LENGTH\"\n  fi\n\n  _initpath \"$domain\" \"$_cdl\"\n\n  if [ ! -f \"$CERT_KEY_PATH\" ] || [ ! -s \"$CERT_KEY_PATH\" ] || ([ \"$FORCE\" ] && ! [ \"$_ACME_IS_RENEW\" ]) || [ \"$Le_ForceNewDomainKey\" = \"1\" ]; then\n    if _createkey \"$_cdl\" \"$CERT_KEY_PATH\"; then\n      _savedomainconf Le_Keylength \"$_cdl\"\n      _info \"The domain key is here: $(__green $CERT_KEY_PATH)\"\n      return 0\n    else\n      _err \"Cannot create domain key\"\n      return 1\n    fi\n  else\n    if [ \"$_ACME_IS_RENEW\" ]; then\n      _info \"Domain key exists, skipping\"\n      return 0\n    else\n      _err \"Domain key exists, do you want to overwrite it?\"\n      _err \"If so, add '--force' and try again.\"\n      return 1\n    fi\n  fi\n\n}\n\n# domain  domainlist isEcc\ncreateCSR() {\n  _info \"Creating CSR\"\n  if [ -z \"$1\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --create-csr --domain <domain.tld> [--domain <domain2.tld> ...] [--ecc]\"\n    return\n  fi\n\n  domain=\"$1\"\n  domainlist=\"$2\"\n  _isEcc=\"$3\"\n\n  _initpath \"$domain\" \"$_isEcc\"\n\n  if [ -f \"$CSR_PATH\" ] && [ \"$_ACME_IS_RENEW\" ] && [ -z \"$FORCE\" ]; then\n    _info \"CSR exists, skipping\"\n    return\n  fi\n\n  if [ ! -f \"$CERT_KEY_PATH\" ]; then\n    _err \"This key file was not found: $CERT_KEY_PATH\"\n    _err \"Please create it first.\"\n    return 1\n  fi\n  _createcsr \"$domain\" \"$domainlist\" \"$CERT_KEY_PATH\" \"$CSR_PATH\" \"$DOMAIN_SSL_CONF\"\n\n}\n\n_url_replace() {\n  tr '/+' '_-' | tr -d '= '\n}\n\n#base64 string\n_durl_replace_base64() {\n  _l=$((${#1} % 4))\n  if [ $_l -eq 2 ]; then\n    _s=\"$1\"'=='\n  elif [ $_l -eq 3 ]; then\n    _s=\"$1\"'='\n  else\n    _s=\"$1\"\n  fi\n  echo \"$_s\" | tr '_-' '/+'\n}\n\n_time2str() {\n  #BSD\n  if date -u -r \"$1\" -j \"+%Y-%m-%dT%H:%M:%SZ\" 2>/dev/null; then\n    return\n  fi\n\n  #Linux\n  if date -u --date=@\"$1\" \"+%Y-%m-%dT%H:%M:%SZ\" 2>/dev/null; then\n    return\n  fi\n\n  #Omnios\n  if date -u -r \"$1\" +\"%Y-%m-%dT%H:%M:%SZ\" 2>/dev/null; then\n    return\n  fi\n\n  #Solaris\n  if printf \"%(%Y-%m-%dT%H:%M:%SZ)T\\n\" $1 2>/dev/null; then\n    return\n  fi\n\n  #Busybox\n  if echo \"$1\" | awk '{ print strftime(\"%Y-%m-%dT%H:%M:%SZ\", $0); }' 2>/dev/null; then\n    return\n  fi\n}\n\n_normalizeJson() {\n  sed \"s/\\\" *: *\\([\\\"{\\[]\\)/\\\":\\1/g\" | sed \"s/^ *\\([^ ]\\)/\\1/\" | tr -d \"\\r\\n\"\n}\n\n_stat() {\n  #Linux\n  if stat -c '%U:%G' \"$1\" 2>/dev/null; then\n    return\n  fi\n\n  #BSD\n  if stat -f '%Su:%Sg' \"$1\" 2>/dev/null; then\n    return\n  fi\n\n  return 1 #error, 'stat' not found\n}\n\n#keyfile\n_isRSA() {\n  keyfile=$1\n  if grep \"BEGIN RSA PRIVATE KEY\" \"$keyfile\" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in \"$keyfile\" -noout -text 2>&1 | grep \"^publicExponent:\" 2>&1 >/dev/null; then\n    return 0\n  fi\n  return 1\n}\n\n#keyfile\n_isEcc() {\n  keyfile=$1\n  if grep \"BEGIN EC PRIVATE KEY\" \"$keyfile\" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} ec -in \"$keyfile\" -noout -text 2>/dev/null | grep \"^NIST CURVE:\" 2>&1 >/dev/null; then\n    return 0\n  fi\n  return 1\n}\n\n#keyfile\n_calcjwk() {\n  keyfile=\"$1\"\n  if [ -z \"$keyfile\" ]; then\n    _usage \"Usage: _calcjwk keyfile\"\n    return 1\n  fi\n\n  if [ \"$JWK_HEADER\" ] && [ \"$__CACHED_JWK_KEY_FILE\" = \"$keyfile\" ]; then\n    _debug2 \"Use cached jwk for file: $__CACHED_JWK_KEY_FILE\"\n    return 0\n  fi\n\n  if _isRSA \"$keyfile\"; then\n    _debug \"RSA key\"\n    pub_exp=$(${ACME_OPENSSL_BIN:-openssl} rsa -in \"$keyfile\" -noout -text | grep \"^publicExponent:\" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1)\n    if [ \"${#pub_exp}\" = \"5\" ]; then\n      pub_exp=0$pub_exp\n    fi\n    _debug3 pub_exp \"$pub_exp\"\n\n    e=$(echo \"$pub_exp\" | _h2b | _base64)\n    _debug3 e \"$e\"\n\n    modulus=$(${ACME_OPENSSL_BIN:-openssl} rsa -in \"$keyfile\" -modulus -noout | cut -d '=' -f 2)\n    _debug3 modulus \"$modulus\"\n    n=\"$(printf \"%s\" \"$modulus\" | _h2b | _base64 | _url_replace)\"\n    _debug3 n \"$n\"\n\n    jwk='{\"e\": \"'$e'\", \"kty\": \"RSA\", \"n\": \"'$n'\"}'\n    _debug3 jwk \"$jwk\"\n\n    JWK_HEADER='{\"alg\": \"RS256\", \"jwk\": '$jwk'}'\n    JWK_HEADERPLACE_PART1='{\"nonce\": \"'\n    JWK_HEADERPLACE_PART2='\", \"alg\": \"RS256\"'\n  elif _isEcc \"$keyfile\"; then\n    _debug \"EC key\"\n    crv=\"$(${ACME_OPENSSL_BIN:-openssl} ec -in \"$keyfile\" -noout -text 2>/dev/null | grep \"^NIST CURVE:\" | cut -d \":\" -f 2 | tr -d \" \\r\\n\")\"\n    _debug3 crv \"$crv\"\n    __ECC_KEY_LEN=$(echo \"$crv\" | cut -d \"-\" -f 2)\n    if [ \"$__ECC_KEY_LEN\" = \"521\" ]; then\n      __ECC_KEY_LEN=512\n    fi\n    _debug3 __ECC_KEY_LEN \"$__ECC_KEY_LEN\"\n    if [ -z \"$crv\" ]; then\n      _debug \"Let's try ASN1 OID\"\n      crv_oid=\"$(${ACME_OPENSSL_BIN:-openssl} ec -in \"$keyfile\" -noout -text 2>/dev/null | grep \"^ASN1 OID:\" | cut -d \":\" -f 2 | tr -d \" \\r\\n\")\"\n      _debug3 crv_oid \"$crv_oid\"\n      case \"${crv_oid}\" in\n      \"prime256v1\")\n        crv=\"P-256\"\n        __ECC_KEY_LEN=256\n        ;;\n      \"secp384r1\")\n        crv=\"P-384\"\n        __ECC_KEY_LEN=384\n        ;;\n      \"secp521r1\")\n        crv=\"P-521\"\n        __ECC_KEY_LEN=512\n        ;;\n      *)\n        _err \"ECC oid: $crv_oid\"\n        return 1\n        ;;\n      esac\n      _debug3 crv \"$crv\"\n    fi\n\n    pubi=\"$(${ACME_OPENSSL_BIN:-openssl} ec -in \"$keyfile\" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)\"\n    pubi=$(_math \"$pubi\" + 1)\n    _debug3 pubi \"$pubi\"\n\n    pubj=\"$(${ACME_OPENSSL_BIN:-openssl} ec -in \"$keyfile\" -noout -text 2>/dev/null | grep -n \"ASN1 OID:\" | cut -d : -f 1)\"\n    pubj=$(_math \"$pubj\" - 1)\n    _debug3 pubj \"$pubj\"\n\n    pubtext=\"$(${ACME_OPENSSL_BIN:-openssl} ec -in \"$keyfile\" -noout -text 2>/dev/null | sed -n \"$pubi,${pubj}p\" | tr -d \" \\n\\r\")\"\n    _debug3 pubtext \"$pubtext\"\n\n    xlen=\"$(printf \"%s\" \"$pubtext\" | tr -d ':' | wc -c)\"\n    xlen=$(_math \"$xlen\" / 4)\n    _debug3 xlen \"$xlen\"\n\n    xend=$(_math \"$xlen\" + 1)\n    x=\"$(printf \"%s\" \"$pubtext\" | cut -d : -f 2-\"$xend\")\"\n    _debug3 x \"$x\"\n\n    x64=\"$(printf \"%s\" \"$x\" | tr -d : | _h2b | _base64 | _url_replace)\"\n    _debug3 x64 \"$x64\"\n\n    xend=$(_math \"$xend\" + 1)\n    y=\"$(printf \"%s\" \"$pubtext\" | cut -d : -f \"$xend\"-2048)\"\n    _debug3 y \"$y\"\n\n    y64=\"$(printf \"%s\" \"$y\" | tr -d : | _h2b | _base64 | _url_replace)\"\n    _debug3 y64 \"$y64\"\n\n    jwk='{\"crv\": \"'$crv'\", \"kty\": \"EC\", \"x\": \"'$x64'\", \"y\": \"'$y64'\"}'\n    _debug3 jwk \"$jwk\"\n\n    JWK_HEADER='{\"alg\": \"ES'$__ECC_KEY_LEN'\", \"jwk\": '$jwk'}'\n    JWK_HEADERPLACE_PART1='{\"nonce\": \"'\n    JWK_HEADERPLACE_PART2='\", \"alg\": \"ES'$__ECC_KEY_LEN'\"'\n  else\n    _err \"Only RSA or EC keys are supported. keyfile=$keyfile\"\n    _debug2 \"$(cat \"$keyfile\")\"\n    return 1\n  fi\n\n  _debug3 JWK_HEADER \"$JWK_HEADER\"\n  __CACHED_JWK_KEY_FILE=\"$keyfile\"\n}\n\n_time() {\n  date -u \"+%s\"\n}\n\n#support 2 formats:\n#    2022-04-01 08:10:33   to   1648800633\n#or  2022-04-01T08:10:33Z  to   1648800633\n_date2time() {\n  #Mac/BSD\n  if date -u -j -f \"%Y-%m-%d %H:%M:%S\" \"$(echo \"$1\" | tr -d \"Z\" | tr \"T\" ' ')\" +\"%s\" 2>/dev/null; then\n    return\n  fi\n  #Linux\n  if date -u -d \"$(echo \"$1\" | tr -d \"Z\" | tr \"T\" ' ')\" +\"%s\" 2>/dev/null; then\n    return\n  fi\n\n  #Solaris\n  if gdate -u -d \"$(echo \"$1\" | tr -d \"Z\" | tr \"T\" ' ')\" +\"%s\" 2>/dev/null; then\n    return\n  fi\n  #Omnios\n  if python3 -c \"import datetime; print(int(datetime.datetime.strptime(\\\"$1\\\", \\\"%Y-%m-%d %H:%M:%S\\\").replace(tzinfo=datetime.timezone.utc).timestamp()))\" 2>/dev/null; then\n    return\n  fi\n  #Omnios\n  if python3 -c \"import datetime; print(int(datetime.datetime.strptime(\\\"$1\\\", \\\"%Y-%m-%dT%H:%M:%SZ\\\").replace(tzinfo=datetime.timezone.utc).timestamp()))\" 2>/dev/null; then\n    return\n  fi\n  _err \"Cannot parse _date2time $1\"\n  return 1\n}\n\n_utc_date() {\n  date -u \"+%Y-%m-%d %H:%M:%S\"\n}\n\n_mktemp() {\n  if _exists mktemp; then\n    if mktemp 2>/dev/null; then\n      return 0\n    elif _contains \"$(mktemp 2>&1)\" \"-t prefix\" && mktemp -t \"$PROJECT_NAME\" 2>/dev/null; then\n      #for Mac osx\n      return 0\n    fi\n  fi\n  if [ -d \"/tmp\" ]; then\n    echo \"/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp\"\n    return 0\n  elif [ \"$LE_TEMP_DIR\" ] && mkdir -p \"$LE_TEMP_DIR\"; then\n    echo \"/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp\"\n    return 0\n  fi\n  _err \"Cannot create temp file.\"\n}\n\n#clear all the https envs to cause _inithttp() to run next time.\n_resethttp() {\n  __HTTP_INITIALIZED=\"\"\n  _ACME_CURL=\"\"\n  _ACME_WGET=\"\"\n  ACME_HTTP_NO_REDIRECTS=\"\"\n}\n\n_inithttp() {\n\n  if [ -z \"$HTTP_HEADER\" ] || ! touch \"$HTTP_HEADER\"; then\n    HTTP_HEADER=\"$(_mktemp)\"\n    _debug2 HTTP_HEADER \"$HTTP_HEADER\"\n  fi\n\n  if [ \"$__HTTP_INITIALIZED\" ]; then\n    if [ \"$_ACME_CURL$_ACME_WGET\" ]; then\n      _debug2 \"Http already initialized.\"\n      return 0\n    fi\n  fi\n\n  if [ -z \"$_ACME_CURL\" ] && _exists \"curl\"; then\n    _ACME_CURL=\"curl --silent --dump-header $HTTP_HEADER \"\n    if [ \"$ACME_USE_IPV6_REQUESTS\" ]; then\n      _ACME_CURL=\"$_ACME_CURL --ipv6 \"\n    elif [ \"$ACME_USE_IPV4_REQUESTS\" ]; then\n      _ACME_CURL=\"$_ACME_CURL --ipv4 \"\n    fi\n    if [ -z \"$ACME_HTTP_NO_REDIRECTS\" ]; then\n      _ACME_CURL=\"$_ACME_CURL -L \"\n    fi\n    if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge 2 ]; then\n      _CURL_DUMP=\"$(_mktemp)\"\n      _ACME_CURL=\"$_ACME_CURL --trace-ascii $_CURL_DUMP \"\n    fi\n\n    if [ \"$CA_PATH\" ]; then\n      _ACME_CURL=\"$_ACME_CURL --capath $CA_PATH \"\n    elif [ \"$CA_BUNDLE\" ]; then\n      _ACME_CURL=\"$_ACME_CURL --cacert $CA_BUNDLE \"\n    fi\n\n    if _contains \"$(curl --help 2>&1)\" \"--globoff\" || _contains \"$(curl --help curl 2>&1)\" \"--globoff\"; then\n      _ACME_CURL=\"$_ACME_CURL -g \"\n    fi\n\n    #don't use --fail-with-body\n    ##from curl 7.76: return fail on HTTP errors but keep the body\n    #if _contains \"$(curl --help http 2>&1)\" \"--fail-with-body\"; then\n    #  _ACME_CURL=\"$_ACME_CURL --fail-with-body \"\n    #fi\n  fi\n\n  if [ -z \"$_ACME_WGET\" ] && _exists \"wget\"; then\n    _ACME_WGET=\"wget -q\"\n    if [ \"$ACME_USE_IPV6_REQUESTS\" ]; then\n      _ACME_WGET=\"$_ACME_WGET --inet6-only \"\n    elif [ \"$ACME_USE_IPV4_REQUESTS\" ]; then\n      _ACME_WGET=\"$_ACME_WGET --inet4-only \"\n    fi\n    if [ \"$ACME_HTTP_NO_REDIRECTS\" ]; then\n      _ACME_WGET=\"$_ACME_WGET --max-redirect 0 \"\n    fi\n    if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n      if [ \"$_ACME_WGET\" ] && _contains \"$($_ACME_WGET --help 2>&1)\" \"--debug\"; then\n        _ACME_WGET=\"$_ACME_WGET -d \"\n      fi\n    fi\n    if [ \"$CA_PATH\" ]; then\n      _ACME_WGET=\"$_ACME_WGET --ca-directory=$CA_PATH \"\n    elif [ \"$CA_BUNDLE\" ]; then\n      _ACME_WGET=\"$_ACME_WGET --ca-certificate=$CA_BUNDLE \"\n    fi\n\n    #from wget 1.14: do not skip body on 404 error\n    if _contains \"$(wget --help 2>&1)\" \"--content-on-error\"; then\n      _ACME_WGET=\"$_ACME_WGET --content-on-error \"\n    fi\n  fi\n\n  __HTTP_INITIALIZED=1\n\n}\n\n# body  url [needbase64] [POST|PUT|DELETE] [ContentType]\n_post() {\n  body=\"$1\"\n  _post_url=\"$2\"\n  needbase64=\"$3\"\n  httpmethod=\"$4\"\n  _postContentType=\"$5\"\n\n  if [ -z \"$httpmethod\" ]; then\n    httpmethod=\"POST\"\n  fi\n  _debug $httpmethod\n  _debug \"_post_url\" \"$_post_url\"\n  _debug2 \"body\" \"$body\"\n  _debug2 \"_postContentType\" \"$_postContentType\"\n\n  _inithttp\n\n  if [ \"$_ACME_CURL\" ] && [ \"${ACME_USE_WGET:-0}\" = \"0\" ]; then\n    _CURL=\"$_ACME_CURL\"\n    if [ \"$HTTPS_INSECURE\" ]; then\n      _CURL=\"$_CURL --insecure  \"\n    fi\n    if [ \"$httpmethod\" = \"HEAD\" ]; then\n      _CURL=\"$_CURL -I  \"\n    fi\n    _debug \"_CURL\" \"$_CURL\"\n    if [ \"$needbase64\" ]; then\n      if [ \"$body\" ]; then\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"Content-Type: $_postContentType\" -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" --data \"$body\" \"$_post_url\" | _base64)\"\n        else\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" --data \"$body\" \"$_post_url\" | _base64)\"\n        fi\n      else\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"Content-Type: $_postContentType\" -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" \"$_post_url\" | _base64)\"\n        else\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" \"$_post_url\" | _base64)\"\n        fi\n      fi\n    else\n      if [ \"$body\" ]; then\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"Content-Type: $_postContentType\" -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" --data \"$body\" \"$_post_url\")\"\n        else\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" --data \"$body\" \"$_post_url\")\"\n        fi\n      else\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"Content-Type: $_postContentType\" -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" \"$_post_url\")\"\n        else\n          response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" \"$_post_url\")\"\n        fi\n      fi\n    fi\n    _ret=\"$?\"\n    if [ \"$_ret\" != \"0\" ]; then\n      _err \"Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret\"\n      if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n        _err \"Here is the curl dump log:\"\n        _err \"$(cat \"$_CURL_DUMP\")\"\n      fi\n    fi\n  elif [ \"$_ACME_WGET\" ]; then\n    _WGET=\"$_ACME_WGET\"\n    if [ \"$HTTPS_INSECURE\" ]; then\n      _WGET=\"$_WGET --no-check-certificate \"\n    fi\n    if [ \"$httpmethod\" = \"HEAD\" ]; then\n      _WGET=\"$_WGET --read-timeout=3.0  --tries=2  \"\n    fi\n    _debug \"_WGET\" \"$_WGET\"\n    if [ \"$needbase64\" ]; then\n      if [ \"$httpmethod\" = \"POST\" ]; then\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --header \"Content-Type: $_postContentType\" --post-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\" | _base64)\"\n        else\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --post-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\" | _base64)\"\n        fi\n      else\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --header \"Content-Type: $_postContentType\" --method $httpmethod --body-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\" | _base64)\"\n        else\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --method $httpmethod --body-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\" | _base64)\"\n        fi\n      fi\n    else\n      if [ \"$httpmethod\" = \"POST\" ]; then\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --header \"Content-Type: $_postContentType\" --post-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n        else\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --post-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n        fi\n      elif [ \"$httpmethod\" = \"HEAD\" ]; then\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_WGET --spider -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --header \"Content-Type: $_postContentType\" --post-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n        else\n          response=\"$($_WGET --spider -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --post-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n        fi\n      else\n        if [ \"$_postContentType\" ]; then\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --header \"Content-Type: $_postContentType\" --method $httpmethod --body-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n        else\n          response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --method $httpmethod --body-data=\"$body\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n        fi\n      fi\n    fi\n    _ret=\"$?\"\n    if [ \"$_ret\" = \"8\" ]; then\n      _ret=0\n      _debug \"wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later.\"\n    fi\n    if [ \"$_ret\" != \"0\" ]; then\n      _err \"Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret\"\n    fi\n    if _contains \"$_WGET\" \" -d \"; then\n      # Demultiplex wget debug output\n      cat \"$HTTP_HEADER\" >&2\n      _sed_i '/^[^ ][^ ]/d; /^ *$/d' \"$HTTP_HEADER\"\n    fi\n    # remove leading whitespaces from header to match curl format\n    _sed_i 's/^  //g' \"$HTTP_HEADER\"\n  else\n    _ret=\"$?\"\n    _err \"Neither curl nor wget have been found, cannot make $httpmethod request.\"\n  fi\n  _debug \"_ret\" \"$_ret\"\n  printf \"%s\" \"$response\"\n  return $_ret\n}\n\n# url getheader timeout\n_get() {\n  _debug GET\n  url=\"$1\"\n  onlyheader=\"$2\"\n  t=\"$3\"\n  _debug url \"$url\"\n  _debug \"timeout=$t\"\n\n  _inithttp\n\n  if [ \"$_ACME_CURL\" ] && [ \"${ACME_USE_WGET:-0}\" = \"0\" ]; then\n    _CURL=\"$_ACME_CURL\"\n    if [ \"$HTTPS_INSECURE\" ]; then\n      _CURL=\"$_CURL --insecure  \"\n    fi\n    if [ \"$t\" ]; then\n      _CURL=\"$_CURL --connect-timeout $t\"\n    fi\n    _debug \"_CURL\" \"$_CURL\"\n    if [ \"$onlyheader\" ]; then\n      $_CURL -I --user-agent \"$USER_AGENT\" -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" \"$url\"\n    else\n      $_CURL --user-agent \"$USER_AGENT\" -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" \"$url\"\n    fi\n    ret=$?\n    if [ \"$ret\" != \"0\" ]; then\n      _err \"Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret\"\n      if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n        _err \"Here is the curl dump log:\"\n        _err \"$(cat \"$_CURL_DUMP\")\"\n      fi\n    fi\n  elif [ \"$_ACME_WGET\" ]; then\n    _WGET=\"$_ACME_WGET\"\n    if [ \"$HTTPS_INSECURE\" ]; then\n      _WGET=\"$_WGET --no-check-certificate \"\n    fi\n    if [ \"$t\" ]; then\n      _WGET=\"$_WGET --timeout=$t\"\n    fi\n    _debug \"_WGET\" \"$_WGET\"\n    if [ \"$onlyheader\" ]; then\n      _wget_out=\"$($_WGET --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" -S -O /dev/null \"$url\" 2>&1)\"\n      if _contains \"$_WGET\" \" -d \"; then\n        # Demultiplex wget debug output\n        echo \"$_wget_out\" >&2\n        echo \"$_wget_out\" | sed '/^[^ ][^ ]/d; /^ *$/d; s/^  //g' -\n      fi\n    else\n      $_WGET --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" -S -O - \"$url\" 2>\"$HTTP_HEADER\"\n      if _contains \"$_WGET\" \" -d \"; then\n        # Demultiplex wget debug output\n        cat \"$HTTP_HEADER\" >&2\n        _sed_i '/^[^ ][^ ]/d; /^ *$/d' \"$HTTP_HEADER\"\n      fi\n      # remove leading whitespaces from header to match curl format\n      _sed_i 's/^  //g' \"$HTTP_HEADER\"\n    fi\n    ret=$?\n    if [ \"$ret\" = \"8\" ]; then\n      ret=0\n      _debug \"wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later.\"\n    fi\n    if [ \"$ret\" != \"0\" ]; then\n      _err \"Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret\"\n    fi\n  else\n    ret=$?\n    _err \"Neither curl nor wget have been found, cannot make GET request.\"\n  fi\n  _debug \"ret\" \"$ret\"\n  return $ret\n}\n\n_head_n() {\n  head -n \"$1\"\n}\n\n_tail_n() {\n  if _is_solaris; then\n    #fix for solaris\n    tail -\"$1\"\n  else\n    tail -n \"$1\"\n  fi\n}\n\n_tail_c() {\n  tail -c \"$1\" 2>/dev/null || tail -\"$1\"c\n}\n\n# url  payload needbase64  keyfile\n_send_signed_request() {\n  url=$1\n  payload=$2\n  needbase64=$3\n  keyfile=$4\n  if [ -z \"$keyfile\" ]; then\n    keyfile=\"$ACCOUNT_KEY_PATH\"\n  fi\n  _debug \"=======Sending Signed Request=======\"\n  _debug url \"$url\"\n  _debug payload \"$payload\"\n\n  if ! _calcjwk \"$keyfile\"; then\n    return 1\n  fi\n\n  __request_conent_type=\"$CONTENT_TYPE_JSON\"\n\n  payload64=$(printf \"%s\" \"$payload\" | _base64 | _url_replace)\n  _debug3 payload64 \"$payload64\"\n\n  MAX_REQUEST_RETRY_TIMES=20\n  _sleep_retry_sec=1\n  _request_retry_times=0\n  while [ \"${_request_retry_times}\" -lt \"$MAX_REQUEST_RETRY_TIMES\" ]; do\n    _request_retry_times=$(_math \"$_request_retry_times\" + 1)\n    _debug3 _request_retry_times \"$_request_retry_times\"\n    if [ -z \"$_CACHED_NONCE\" ]; then\n      _headers=\"\"\n      if [ \"$ACME_NEW_NONCE\" ]; then\n        _debug2 \"Get nonce with HEAD. ACME_NEW_NONCE\" \"$ACME_NEW_NONCE\"\n        nonceurl=\"$ACME_NEW_NONCE\"\n        if _post \"\" \"$nonceurl\" \"\" \"HEAD\" \"$__request_conent_type\" >/dev/null; then\n          _headers=\"$(cat \"$HTTP_HEADER\")\"\n          _debug2 _headers \"$_headers\"\n          _CACHED_NONCE=\"$(echo \"$_headers\" | grep -i \"Replay-Nonce:\" | _head_n 1 | tr -d \"\\r\\n \" | cut -d ':' -f 2 | cut -d , -f 1)\"\n        fi\n      fi\n      if [ -z \"$_CACHED_NONCE\" ]; then\n        _debug2 \"Get nonce with GET. ACME_DIRECTORY\" \"$ACME_DIRECTORY\"\n        nonceurl=\"$ACME_DIRECTORY\"\n        _headers=\"$(_get \"$nonceurl\" \"onlyheader\")\"\n        _debug2 _headers \"$_headers\"\n        _CACHED_NONCE=\"$(echo \"$_headers\" | grep -i \"Replay-Nonce:\" | _head_n 1 | tr -d \"\\r\\n \" | cut -d ':' -f 2)\"\n      fi\n      if [ -z \"$_CACHED_NONCE\" ] && [ \"$ACME_NEW_NONCE\" ]; then\n        _debug2 \"Get nonce with GET. ACME_NEW_NONCE\" \"$ACME_NEW_NONCE\"\n        nonceurl=\"$ACME_NEW_NONCE\"\n        _headers=\"$(_get \"$nonceurl\" \"onlyheader\")\"\n        _debug2 _headers \"$_headers\"\n        _CACHED_NONCE=\"$(echo \"$_headers\" | grep -i \"Replay-Nonce:\" | _head_n 1 | tr -d \"\\r\\n \" | cut -d ':' -f 2)\"\n      fi\n      if [ \"$?\" != \"0\" ]; then\n        _err \"Cannot connect to $nonceurl to get nonce.\"\n        return 1\n      fi\n    else\n      _debug2 \"Use _CACHED_NONCE\" \"$_CACHED_NONCE\"\n    fi\n    nonce=\"$_CACHED_NONCE\"\n    _debug2 nonce \"$nonce\"\n    if [ -z \"$nonce\" ]; then\n      _info \"Could not get nonce, let's try again.\"\n      _sleep 2\n      continue\n    fi\n\n    if [ \"$url\" = \"$ACME_NEW_ACCOUNT\" ]; then\n      protected=\"$JWK_HEADERPLACE_PART1$nonce\\\", \\\"url\\\": \\\"${url}$JWK_HEADERPLACE_PART2, \\\"jwk\\\": $jwk\"'}'\n    elif [ \"$url\" = \"$ACME_REVOKE_CERT\" ] && [ \"$keyfile\" != \"$ACCOUNT_KEY_PATH\" ]; then\n      protected=\"$JWK_HEADERPLACE_PART1$nonce\\\", \\\"url\\\": \\\"${url}$JWK_HEADERPLACE_PART2, \\\"jwk\\\": $jwk\"'}'\n    else\n      protected=\"$JWK_HEADERPLACE_PART1$nonce\\\", \\\"url\\\": \\\"${url}$JWK_HEADERPLACE_PART2, \\\"kid\\\": \\\"${ACCOUNT_URL}\\\"\"'}'\n    fi\n\n    _debug3 protected \"$protected\"\n\n    protected64=\"$(printf \"%s\" \"$protected\" | _base64 | _url_replace)\"\n    _debug3 protected64 \"$protected64\"\n\n    if ! _sig_t=\"$(printf \"%s\" \"$protected64.$payload64\" | _sign \"$keyfile\" \"sha256\")\"; then\n      _err \"Sign request failed.\"\n      return 1\n    fi\n    _debug3 _sig_t \"$_sig_t\"\n\n    sig=\"$(printf \"%s\" \"$_sig_t\" | _url_replace)\"\n    _debug3 sig \"$sig\"\n\n    body=\"{\\\"protected\\\": \\\"$protected64\\\", \\\"payload\\\": \\\"$payload64\\\", \\\"signature\\\": \\\"$sig\\\"}\"\n    _debug3 body \"$body\"\n\n    response=\"$(_post \"$body\" \"$url\" \"$needbase64\" \"POST\" \"$__request_conent_type\")\"\n    _CACHED_NONCE=\"\"\n\n    if [ \"$?\" != \"0\" ]; then\n      _err \"Cannot make POST request to $url\"\n      return 1\n    fi\n\n    responseHeaders=\"$(cat \"$HTTP_HEADER\")\"\n    _debug2 responseHeaders \"$responseHeaders\"\n\n    code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n    _debug code \"$code\"\n\n    _debug2 original \"$response\"\n    if echo \"$responseHeaders\" | grep -i \"Content-Type: *application/json\" >/dev/null 2>&1; then\n      response=\"$(echo \"$response\" | _json_decode | _normalizeJson)\"\n    fi\n    _debug2 response \"$response\"\n\n    _CACHED_NONCE=\"$(echo \"$responseHeaders\" | grep -i \"Replay-Nonce:\" | _head_n 1 | tr -d \"\\r\\n \" | cut -d ':' -f 2 | cut -d , -f 1)\"\n\n    if ! _startswith \"$code\" \"2\"; then\n      _body=\"$response\"\n      if [ \"$needbase64\" ]; then\n        _body=\"$(echo \"$_body\" | _dbase64 multiline)\"\n        _debug3 _body \"$_body\"\n      fi\n\n      _retryafter=$(echo \"$responseHeaders\" | grep -i \"^Retry-After *: *[0-9]\\+ *\" | cut -d : -f 2 | tr -d ' ' | tr -d '\\r')\n      if [ \"$code\" = '503' ]; then\n        _sleep_overload_retry_sec=$_retryafter\n        if [ -z \"$_sleep_overload_retry_sec\" ]; then\n          _sleep_overload_retry_sec=5\n        fi\n        if [ $_sleep_overload_retry_sec -le 600 ]; then\n          _info \"It seems the CA server is currently overloaded, let's wait and retry. Sleeping for $_sleep_overload_retry_sec seconds.\"\n          _sleep $_sleep_overload_retry_sec\n          continue\n        else\n          _info \"The retryafter=$_retryafter value is too large (> 600), will not retry anymore.\"\n        fi\n      fi\n      if _contains \"$_body\" \"JWS has invalid anti-replay nonce\" || _contains \"$_body\" \"JWS has an invalid anti-replay nonce\"; then\n        _info \"It seems the CA server is busy now, let's wait and retry. Sleeping for $_sleep_retry_sec seconds.\"\n        _CACHED_NONCE=\"\"\n        _sleep $_sleep_retry_sec\n        continue\n      fi\n      if _contains \"$_body\" \"The Replay Nonce is not recognized\"; then\n        _info \"The replay nonce is not valid, let's get a new one. Sleeping for $_sleep_retry_sec seconds.\"\n        _CACHED_NONCE=\"\"\n        _sleep $_sleep_retry_sec\n        continue\n      fi\n    fi\n    return 0\n  done\n  _info \"Giving up sending to CA server after $MAX_REQUEST_RETRY_TIMES retries.\"\n  return 1\n\n}\n\n#setopt \"file\"  \"opt\"  \"=\"  \"value\" [\";\"]\n_setopt() {\n  __conf=\"$1\"\n  __opt=\"$2\"\n  __sep=\"$3\"\n  __val=\"$4\"\n  __end=\"$5\"\n  if [ -z \"$__opt\" ]; then\n    _usage usage: _setopt '\"file\"  \"opt\"  \"=\"  \"value\" [\";\"]'\n    return\n  fi\n  if [ ! -f \"$__conf\" ]; then\n    touch \"$__conf\"\n    chmod 600 \"$__conf\"\n  fi\n  if [ -n \"$(_tail_c 1 <\"$__conf\")\" ]; then\n    echo >>\"$__conf\"\n  fi\n\n  if grep -n \"^$__opt$__sep\" \"$__conf\" >/dev/null; then\n    _debug3 OK\n    if _contains \"$__val\" \"&\"; then\n      __val=\"$(echo \"$__val\" | sed 's/&/\\\\&/g')\"\n    fi\n    if _contains \"$__val\" \"|\"; then\n      __val=\"$(echo \"$__val\" | sed 's/|/\\\\|/g')\"\n    fi\n    text=\"$(cat \"$__conf\")\"\n    printf -- \"%s\\n\" \"$text\" | sed \"s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|\" >\"$__conf\"\n\n  elif grep -n \"^#$__opt$__sep\" \"$__conf\" >/dev/null; then\n    if _contains \"$__val\" \"&\"; then\n      __val=\"$(echo \"$__val\" | sed 's/&/\\\\&/g')\"\n    fi\n    if _contains \"$__val\" \"|\"; then\n      __val=\"$(echo \"$__val\" | sed 's/|/\\\\|/g')\"\n    fi\n    text=\"$(cat \"$__conf\")\"\n    printf -- \"%s\\n\" \"$text\" | sed \"s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|\" >\"$__conf\"\n\n  else\n    _debug3 APP\n    echo \"$__opt$__sep$__val$__end\" >>\"$__conf\"\n  fi\n  _debug3 \"$(grep -n \"^$__opt$__sep\" \"$__conf\")\"\n}\n\n#_save_conf  file key  value base64encode\n#save to conf\n_save_conf() {\n  _s_c_f=\"$1\"\n  _sdkey=\"$2\"\n  _sdvalue=\"$3\"\n  _b64encode=\"$4\"\n  if [ \"$_sdvalue\" ] && [ \"$_b64encode\" ]; then\n    _sdvalue=\"${B64CONF_START}$(printf \"%s\" \"${_sdvalue}\" | _base64)${B64CONF_END}\"\n  fi\n  if [ \"$_s_c_f\" ]; then\n    _setopt \"$_s_c_f\" \"$_sdkey\" \"=\" \"'$_sdvalue'\"\n  else\n    _err \"Config file is empty, cannot save $_sdkey=$_sdvalue\"\n  fi\n}\n\n#_clear_conf file  key\n_clear_conf() {\n  _c_c_f=\"$1\"\n  _sdkey=\"$2\"\n  if [ \"$_c_c_f\" ]; then\n    _conf_data=\"$(cat \"$_c_c_f\")\"\n    echo \"$_conf_data\" | sed \"/^$_sdkey *=.*$/d\" >\"$_c_c_f\"\n  else\n    _err \"Config file is empty, cannot clear\"\n  fi\n}\n\n#_read_conf file  key\n_read_conf() {\n  _r_c_f=\"$1\"\n  _sdkey=\"$2\"\n  if [ -f \"$_r_c_f\" ]; then\n    _sdv=\"$(\n      eval \"$(grep \"^$_sdkey *=\" \"$_r_c_f\")\"\n      eval \"printf \\\"%s\\\" \\\"\\$$_sdkey\\\"\"\n    )\"\n    if _startswith \"$_sdv\" \"${B64CONF_START}\" && _endswith \"$_sdv\" \"${B64CONF_END}\"; then\n      _sdv=\"$(echo \"$_sdv\" | sed \"s/${B64CONF_START}//\" | sed \"s/${B64CONF_END}//\" | _dbase64)\"\n    fi\n    printf \"%s\" \"$_sdv\"\n  else\n    _debug \"Config file is empty, cannot read $_sdkey\"\n  fi\n}\n\n#_savedomainconf   key  value  base64encode\n#save to domain.conf\n_savedomainconf() {\n  _save_conf \"$DOMAIN_CONF\" \"$@\"\n}\n\n#_cleardomainconf   key\n_cleardomainconf() {\n  _clear_conf \"$DOMAIN_CONF\" \"$1\"\n}\n\n#_readdomainconf   key\n_readdomainconf() {\n  _read_conf \"$DOMAIN_CONF\" \"$1\"\n}\n\n#_migratedomainconf   oldkey  newkey  base64encode\n_migratedomainconf() {\n  _old_key=\"$1\"\n  _new_key=\"$2\"\n  _b64encode=\"$3\"\n  _old_value=$(_readdomainconf \"$_old_key\")\n  _cleardomainconf \"$_old_key\"\n  if [ -z \"$_old_value\" ]; then\n    return 1 # migrated failed: old value is empty\n  fi\n  _new_value=$(_readdomainconf \"$_new_key\")\n  if [ -n \"$_new_value\" ]; then\n    _debug \"Domain config new key exists, old key $_old_key='$_old_value' has been removed.\"\n    return 1 # migrated failed: old value replaced by new value\n  fi\n  _savedomainconf \"$_new_key\" \"$_old_value\" \"$_b64encode\"\n  _debug \"Domain config $_old_key has been migrated to $_new_key.\"\n}\n\n#_migratedeployconf   oldkey  newkey  base64encode\n_migratedeployconf() {\n  _migratedomainconf \"$1\" \"SAVED_$2\" \"$3\" ||\n    _migratedomainconf \"SAVED_$1\" \"SAVED_$2\" \"$3\" # try only when oldkey itself is not found\n}\n\n#key  value  base64encode\n_savedeployconf() {\n  _savedomainconf \"SAVED_$1\" \"$2\" \"$3\"\n  #remove later\n  _cleardomainconf \"$1\"\n}\n\n#key\n_getdeployconf() {\n  _rac_key=\"$1\"\n  _rac_value=\"$(eval echo \\$\"$_rac_key\")\"\n  if [ \"$_rac_value\" ]; then\n    if _startswith \"$_rac_value\" '\"' && _endswith \"$_rac_value\" '\"'; then\n      _debug2 \"trim quotation marks\"\n      eval $_rac_key=$_rac_value\n      export $_rac_key\n    fi\n    return 0 # do nothing\n  fi\n  _saved=\"$(_readdomainconf \"SAVED_$_rac_key\")\"\n  eval $_rac_key=\\$_saved\n  export $_rac_key\n}\n\n#_saveaccountconf  key  value  base64encode\n_saveaccountconf() {\n  _save_conf \"$ACCOUNT_CONF_PATH\" \"$@\"\n}\n\n#key  value base64encode\n_saveaccountconf_mutable() {\n  _save_conf \"$ACCOUNT_CONF_PATH\" \"SAVED_$1\" \"$2\" \"$3\"\n  #remove later\n  _clearaccountconf \"$1\"\n}\n\n#key\n_readaccountconf() {\n  _read_conf \"$ACCOUNT_CONF_PATH\" \"$1\"\n}\n\n#key\n_readaccountconf_mutable() {\n  _rac_key=\"$1\"\n  _readaccountconf \"SAVED_$_rac_key\"\n}\n\n#_clearaccountconf   key\n_clearaccountconf() {\n  _clear_conf \"$ACCOUNT_CONF_PATH\" \"$1\"\n}\n\n#key\n_clearaccountconf_mutable() {\n  _clearaccountconf \"SAVED_$1\"\n  #remove later\n  _clearaccountconf \"$1\"\n}\n\n#_savecaconf  key  value\n_savecaconf() {\n  _save_conf \"$CA_CONF\" \"$1\" \"$2\"\n}\n\n#_readcaconf   key\n_readcaconf() {\n  _read_conf \"$CA_CONF\" \"$1\"\n}\n\n#_clearaccountconf   key\n_clearcaconf() {\n  _clear_conf \"$CA_CONF\" \"$1\"\n}\n\n# content localaddress\n_startserver() {\n  content=\"$1\"\n  ncaddr=\"$2\"\n  _debug \"content\" \"$content\"\n  _debug \"ncaddr\" \"$ncaddr\"\n\n  _debug \"startserver: $$\"\n\n  _debug Le_HTTPPort \"$Le_HTTPPort\"\n  _debug Le_Listen_V4 \"$Le_Listen_V4\"\n  _debug Le_Listen_V6 \"$Le_Listen_V6\"\n\n  if _exists \"socat\"; then\n    _NC=\"socat\"\n    if [ \"$Le_Listen_V6\" ]; then\n      _NC=\"$_NC -6\"\n      SOCAT_OPTIONS=TCP6-LISTEN\n    elif [ \"$Le_Listen_V4\" ]; then\n      _NC=\"$_NC -4\"\n      SOCAT_OPTIONS=TCP4-LISTEN\n    else\n      SOCAT_OPTIONS=TCP-LISTEN\n    fi\n\n    if [ \"$DEBUG\" ] && [ \"$DEBUG\" -gt \"1\" ]; then\n      _NC=\"$_NC -d -d -v\"\n    fi\n\n    SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork\n\n    #Adding bind to local-address\n    if [ \"$ncaddr\" ]; then\n      SOCAT_OPTIONS=\"$SOCAT_OPTIONS,bind=${ncaddr}\"\n    fi\n\n    _content_len=\"$(printf \"%s\" \"$content\" | wc -c)\"\n    _debug _content_len \"$_content_len\"\n    _debug \"_NC\" \"$_NC $SOCAT_OPTIONS\"\n    export _SOCAT_ERR=\"$(_mktemp)\"\n    $_NC $SOCAT_OPTIONS SYSTEM:\"sleep 1; \\\necho 'HTTP/1.0 200 OK'; \\\necho 'Content-Length\\: $_content_len'; \\\necho ''; \\\nprintf '%s' '$content';\" 2>\"$_SOCAT_ERR\" &\n    serverproc=\"$!\"\n  else\n    _PYTHON=\"\"\n    if _exists \"python3\"; then\n      _PYTHON=\"python3\"\n    elif _exists \"python2\"; then\n      _PYTHON=\"python2\"\n    elif _exists \"python\"; then\n      _PYTHON=\"python\"\n    fi\n    if [ \"$_PYTHON\" ]; then\n      _debug \"Using python: $_PYTHON\"\n      _AF=\"socket.AF_INET\"\n      _BIND_ADDR=\"0.0.0.0\"\n      if [ \"$Le_Listen_V6\" ]; then\n        _AF=\"socket.AF_INET6\"\n        _BIND_ADDR=\"::\"\n      fi\n      if [ \"$ncaddr\" ]; then\n        _BIND_ADDR=\"$ncaddr\"\n      fi\n      export _SOCAT_ERR=\"$(_mktemp)\"\n      $_PYTHON -c \"import socket,sys;s=socket.socket($_AF,socket.SOCK_STREAM);s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);s.bind((sys.argv[2],int(sys.argv[1])));s.listen(5);res='HTTP/1.0 200 OK\\r\\nContent-Length: '+str(len(sys.argv[3]))+'\\r\\n\\r\\n'+sys.argv[3];\nwhile True:\n c,a=s.accept()\n c.sendall(res.encode() if hasattr(res, 'encode') else res)\n c.close()\" \"$Le_HTTPPort\" \"$_BIND_ADDR\" \"$content\" 2>\"$_SOCAT_ERR\" &\n      serverproc=\"$!\"\n      _NC=\"$_PYTHON\"\n    else\n      _err \"Please install socat or python first for standalone mode.\"\n      return 1\n    fi\n  fi\n\n  if [ -f \"$_SOCAT_ERR\" ]; then\n    if grep \"Permission denied\" \"$_SOCAT_ERR\" >/dev/null; then\n      _err \"$_NC: $(cat $_SOCAT_ERR)\"\n      _err \"Can not listen for user: $(whoami)\"\n      _err \"Maybe try with root again?\"\n      rm -f \"$_SOCAT_ERR\"\n      return 1\n    fi\n  fi\n}\n\n_stopserver() {\n  pid=\"$1\"\n  _debug \"pid\" \"$pid\"\n  if [ -z \"$pid\" ]; then\n    rm -f \"$_SOCAT_ERR\"\n    return\n  fi\n\n  kill $pid\n  rm -f \"$_SOCAT_ERR\"\n\n}\n\n# sleep sec\n_sleep() {\n  _sleep_sec=\"$1\"\n  if [ \"$__INTERACTIVE\" ]; then\n    _sleep_c=\"$_sleep_sec\"\n    while [ \"$_sleep_c\" -ge \"0\" ]; do\n      printf \"\\r      \\r\"\n      __green \"$_sleep_c\"\n      _sleep_c=\"$(_math \"$_sleep_c\" - 1)\"\n      sleep 1\n    done\n    printf \"\\r\"\n  else\n    sleep \"$_sleep_sec\"\n  fi\n}\n\n# _starttlsserver  san_a  san_b port content _ncaddr acmeValidationv1\n_starttlsserver() {\n  _info \"Starting tls server.\"\n  san_a=\"$1\"\n  san_b=\"$2\"\n  port=\"$3\"\n  content=\"$4\"\n  opaddr=\"$5\"\n  acmeValidationv1=\"$6\"\n\n  _debug san_a \"$san_a\"\n  _debug san_b \"$san_b\"\n  _debug port \"$port\"\n  _debug acmeValidationv1 \"$acmeValidationv1\"\n\n  #create key TLS_KEY\n  if ! _createkey \"2048\" \"$TLS_KEY\"; then\n    _err \"Error creating TLS validation key.\"\n    return 1\n  fi\n\n  #create csr\n  alt=\"$san_a\"\n  if [ \"$san_b\" ]; then\n    alt=\"$alt,$san_b\"\n  fi\n  if ! _createcsr \"tls.acme.sh\" \"$alt\" \"$TLS_KEY\" \"$TLS_CSR\" \"$TLS_CONF\" \"$acmeValidationv1\"; then\n    _err \"Error creating TLS validation CSR.\"\n    return 1\n  fi\n\n  #self signed\n  if ! _signcsr \"$TLS_KEY\" \"$TLS_CSR\" \"$TLS_CONF\" \"$TLS_CERT\"; then\n    _err \"Error creating TLS validation cert.\"\n    return 1\n  fi\n\n  __S_OPENSSL=\"${ACME_OPENSSL_BIN:-openssl} s_server -www -cert $TLS_CERT  -key $TLS_KEY \"\n  if [ \"$opaddr\" ]; then\n    __S_OPENSSL=\"$__S_OPENSSL -accept $opaddr:$port\"\n  else\n    __S_OPENSSL=\"$__S_OPENSSL -accept $port\"\n  fi\n\n  _debug Le_Listen_V4 \"$Le_Listen_V4\"\n  _debug Le_Listen_V6 \"$Le_Listen_V6\"\n  if [ \"$Le_Listen_V4\" ]; then\n    __S_OPENSSL=\"$__S_OPENSSL -4\"\n  elif [ \"$Le_Listen_V6\" ]; then\n    __S_OPENSSL=\"$__S_OPENSSL -6\"\n  fi\n\n  if [ \"$acmeValidationv1\" ]; then\n    __S_OPENSSL=\"$__S_OPENSSL -alpn acme-tls/1\"\n  fi\n\n  _debug \"$__S_OPENSSL\"\n  if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n    $__S_OPENSSL -tlsextdebug &\n  else\n    $__S_OPENSSL >/dev/null 2>&1 &\n  fi\n\n  serverproc=\"$!\"\n  sleep 1\n  _debug serverproc \"$serverproc\"\n}\n\n#file\n_readlink() {\n  _rf=\"$1\"\n  if ! readlink -f \"$_rf\" 2>/dev/null; then\n    if _startswith \"$_rf\" \"/\"; then\n      echo \"$_rf\"\n      return 0\n    fi\n    echo \"$(pwd)/$_rf\" | _conapath\n  fi\n}\n\n_conapath() {\n  sed \"s#/\\./#/#g\"\n}\n\n__initHome() {\n  if [ -z \"$_SCRIPT_HOME\" ]; then\n    if _exists readlink && _exists dirname; then\n      _debug \"Let's find the script directory.\"\n      _debug \"_SCRIPT_\" \"$_SCRIPT_\"\n      _script=\"$(_readlink \"$_SCRIPT_\")\"\n      _debug \"_script\" \"$_script\"\n      _script_home=\"$(dirname \"$_script\")\"\n      _debug \"_script_home\" \"$_script_home\"\n      if [ -d \"$_script_home\" ]; then\n        export _SCRIPT_HOME=\"$_script_home\"\n      else\n        _err \"It seems the script home is not correct: $_script_home\"\n      fi\n    fi\n  fi\n\n  #  if [ -z \"$LE_WORKING_DIR\" ]; then\n  #    if [ -f \"$DEFAULT_INSTALL_HOME/account.conf\" ]; then\n  #      _debug \"It seems that $PROJECT_NAME is already installed in $DEFAULT_INSTALL_HOME\"\n  #      LE_WORKING_DIR=\"$DEFAULT_INSTALL_HOME\"\n  #    else\n  #      LE_WORKING_DIR=\"$_SCRIPT_HOME\"\n  #    fi\n  #  fi\n\n  if [ -z \"$LE_WORKING_DIR\" ]; then\n    _debug \"Using default home: $DEFAULT_INSTALL_HOME\"\n    LE_WORKING_DIR=\"$DEFAULT_INSTALL_HOME\"\n  fi\n  export LE_WORKING_DIR\n\n  if [ -z \"$LE_CONFIG_HOME\" ]; then\n    LE_CONFIG_HOME=\"$LE_WORKING_DIR\"\n  fi\n  _debug \"Using config home: $LE_CONFIG_HOME\"\n  export LE_CONFIG_HOME\n\n  _DEFAULT_ACCOUNT_CONF_PATH=\"$LE_CONFIG_HOME/account.conf\"\n\n  if [ -z \"$ACCOUNT_CONF_PATH\" ]; then\n    if [ -f \"$_DEFAULT_ACCOUNT_CONF_PATH\" ]; then\n      . \"$_DEFAULT_ACCOUNT_CONF_PATH\"\n    fi\n  fi\n\n  if [ -z \"$ACCOUNT_CONF_PATH\" ]; then\n    ACCOUNT_CONF_PATH=\"$_DEFAULT_ACCOUNT_CONF_PATH\"\n  fi\n  _debug3 ACCOUNT_CONF_PATH \"$ACCOUNT_CONF_PATH\"\n  DEFAULT_LOG_FILE=\"$LE_CONFIG_HOME/$PROJECT_NAME.log\"\n\n  DEFAULT_CA_HOME=\"$LE_CONFIG_HOME/ca\"\n\n  if [ -z \"$LE_TEMP_DIR\" ]; then\n    LE_TEMP_DIR=\"$LE_CONFIG_HOME/tmp\"\n  fi\n}\n\n_clearAPI() {\n  ACME_NEW_ACCOUNT=\"\"\n  ACME_KEY_CHANGE=\"\"\n  ACME_NEW_AUTHZ=\"\"\n  ACME_NEW_ORDER=\"\"\n  ACME_REVOKE_CERT=\"\"\n  ACME_NEW_NONCE=\"\"\n  ACME_AGREEMENT=\"\"\n  ACME_RENEWAL_INFO=\"\"\n}\n\n#server\n_initAPI() {\n  _api_server=\"${1:-$ACME_DIRECTORY}\"\n  _debug \"_init API for server: $_api_server\"\n\n  MAX_API_RETRY_TIMES=10\n  _sleep_retry_sec=10\n  _request_retry_times=0\n  while [ -z \"$ACME_NEW_ACCOUNT\" ] && [ \"${_request_retry_times}\" -lt \"$MAX_API_RETRY_TIMES\" ]; do\n    _request_retry_times=$(_math \"$_request_retry_times\" + 1)\n    response=$(_get \"$_api_server\" \"\" 10)\n    if [ \"$?\" != \"0\" ]; then\n      _debug2 \"response\" \"$response\"\n      _info \"Cannot init API for: $_api_server.\"\n      _info \"Sleeping for $_sleep_retry_sec seconds and retrying.\"\n      _sleep \"$_sleep_retry_sec\"\n      continue\n    fi\n    response=$(echo \"$response\" | _json_decode)\n    _debug2 \"response\" \"$response\"\n\n    ACME_KEY_CHANGE=$(echo \"$response\" | _egrep_o 'keyChange\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_KEY_CHANGE\n\n    ACME_NEW_AUTHZ=$(echo \"$response\" | _egrep_o 'newAuthz\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_NEW_AUTHZ\n\n    ACME_NEW_ORDER=$(echo \"$response\" | _egrep_o 'newOrder\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_NEW_ORDER\n\n    ACME_NEW_ACCOUNT=$(echo \"$response\" | _egrep_o 'newAccount\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_NEW_ACCOUNT\n\n    ACME_REVOKE_CERT=$(echo \"$response\" | _egrep_o 'revokeCert\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_REVOKE_CERT\n\n    ACME_NEW_NONCE=$(echo \"$response\" | _egrep_o 'newNonce\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_NEW_NONCE\n\n    ACME_AGREEMENT=$(echo \"$response\" | _egrep_o 'termsOfService\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_AGREEMENT\n\n    ACME_RENEWAL_INFO=$(echo \"$response\" | _egrep_o 'renewalInfo\" *: *\"[^\"]*\"' | cut -d '\"' -f 3)\n    export ACME_RENEWAL_INFO\n\n    _debug \"ACME_KEY_CHANGE\" \"$ACME_KEY_CHANGE\"\n    _debug \"ACME_NEW_AUTHZ\" \"$ACME_NEW_AUTHZ\"\n    _debug \"ACME_NEW_ORDER\" \"$ACME_NEW_ORDER\"\n    _debug \"ACME_NEW_ACCOUNT\" \"$ACME_NEW_ACCOUNT\"\n    _debug \"ACME_REVOKE_CERT\" \"$ACME_REVOKE_CERT\"\n    _debug \"ACME_AGREEMENT\" \"$ACME_AGREEMENT\"\n    _debug \"ACME_NEW_NONCE\" \"$ACME_NEW_NONCE\"\n    _debug \"ACME_RENEWAL_INFO\" \"$ACME_RENEWAL_INFO\"\n    if [ \"$ACME_NEW_ACCOUNT\" ] && [ \"$ACME_NEW_ORDER\" ]; then\n      return 0\n    fi\n    _info \"Sleeping for $_sleep_retry_sec seconds and retrying.\"\n    _sleep \"$_sleep_retry_sec\"\n  done\n  if [ \"$ACME_NEW_ACCOUNT\" ] && [ \"$ACME_NEW_ORDER\" ]; then\n    return 0\n  fi\n  _err \"Cannot init API for $_api_server\"\n  return 1\n}\n\n_clearCA() {\n  export CA_CONF=\n  export ACCOUNT_KEY_PATH=\n  export ACCOUNT_JSON_PATH=\n}\n\n#[domain]  [keylength or isEcc flag]\n_initpath() {\n  domain=\"$1\"\n  _ilength=\"$2\"\n\n  __initHome\n\n  if [ -f \"$ACCOUNT_CONF_PATH\" ]; then\n    . \"$ACCOUNT_CONF_PATH\"\n  fi\n\n  if [ \"$_ACME_IN_CRON\" ]; then\n    if [ ! \"$_USER_PATH_EXPORTED\" ]; then\n      _USER_PATH_EXPORTED=1\n      export PATH=\"$USER_PATH:$PATH\"\n    fi\n  fi\n\n  if [ -z \"$CA_HOME\" ]; then\n    CA_HOME=\"$DEFAULT_CA_HOME\"\n  fi\n\n  if [ -z \"$ACME_DIRECTORY\" ]; then\n    if [ \"$STAGE\" ]; then\n      ACME_DIRECTORY=\"$DEFAULT_STAGING_CA\"\n      _info \"Using ACME_DIRECTORY: $ACME_DIRECTORY\"\n    else\n      default_acme_server=$(_readaccountconf \"DEFAULT_ACME_SERVER\")\n      _debug default_acme_server \"$default_acme_server\"\n      if [ \"$default_acme_server\" ]; then\n        ACME_DIRECTORY=\"$default_acme_server\"\n      else\n        ACME_DIRECTORY=\"$DEFAULT_CA\"\n      fi\n    fi\n  fi\n\n  _debug ACME_DIRECTORY \"$ACME_DIRECTORY\"\n  _ACME_SERVER_HOST=\"$(echo \"$ACME_DIRECTORY\" | cut -d : -f 2 | tr -s / | cut -d / -f 2)\"\n  _debug2 \"_ACME_SERVER_HOST\" \"$_ACME_SERVER_HOST\"\n\n  _ACME_SERVER_PATH=\"$(echo \"$ACME_DIRECTORY\" | cut -d : -f 2- | tr -s / | cut -d / -f 3-)\"\n  _debug2 \"_ACME_SERVER_PATH\" \"$_ACME_SERVER_PATH\"\n\n  CA_DIR=\"$CA_HOME/$_ACME_SERVER_HOST/$_ACME_SERVER_PATH\"\n  _DEFAULT_CA_CONF=\"$CA_DIR/ca.conf\"\n  if [ -z \"$CA_CONF\" ]; then\n    CA_CONF=\"$_DEFAULT_CA_CONF\"\n  fi\n  _debug3 CA_CONF \"$CA_CONF\"\n\n  _OLD_CADIR=\"$CA_HOME/$_ACME_SERVER_HOST\"\n  _OLD_ACCOUNT_KEY=\"$_OLD_CADIR/account.key\"\n  _OLD_ACCOUNT_JSON=\"$_OLD_CADIR/account.json\"\n  _OLD_CA_CONF=\"$_OLD_CADIR/ca.conf\"\n\n  _DEFAULT_ACCOUNT_KEY_PATH=\"$CA_DIR/account.key\"\n  _DEFAULT_ACCOUNT_JSON_PATH=\"$CA_DIR/account.json\"\n  if [ -z \"$ACCOUNT_KEY_PATH\" ]; then\n    ACCOUNT_KEY_PATH=\"$_DEFAULT_ACCOUNT_KEY_PATH\"\n    if [ -f \"$_OLD_ACCOUNT_KEY\" ] && ! [ -f \"$ACCOUNT_KEY_PATH\" ]; then\n      mkdir -p \"$CA_DIR\"\n      mv \"$_OLD_ACCOUNT_KEY\" \"$ACCOUNT_KEY_PATH\"\n    fi\n  fi\n\n  if [ -z \"$ACCOUNT_JSON_PATH\" ]; then\n    ACCOUNT_JSON_PATH=\"$_DEFAULT_ACCOUNT_JSON_PATH\"\n    if [ -f \"$_OLD_ACCOUNT_JSON\" ] && ! [ -f \"$ACCOUNT_JSON_PATH\" ]; then\n      mkdir -p \"$CA_DIR\"\n      mv \"$_OLD_ACCOUNT_JSON\" \"$ACCOUNT_JSON_PATH\"\n    fi\n  fi\n\n  if [ -f \"$_OLD_CA_CONF\" ] && ! [ -f \"$CA_CONF\" ]; then\n    mkdir -p \"$CA_DIR\"\n    mv \"$_OLD_CA_CONF\" \"$CA_CONF\"\n  fi\n\n  if [ -f \"$CA_CONF\" ]; then\n    . \"$CA_CONF\"\n  fi\n\n  if [ -z \"$ACME_DIR\" ]; then\n    ACME_DIR=\"/home/.acme\"\n  fi\n\n  if [ -z \"$APACHE_CONF_BACKUP_DIR\" ]; then\n    APACHE_CONF_BACKUP_DIR=\"$LE_CONFIG_HOME\"\n  fi\n\n  if [ -z \"$USER_AGENT\" ]; then\n    USER_AGENT=\"$DEFAULT_USER_AGENT\"\n  fi\n\n  if [ -z \"$HTTP_HEADER\" ]; then\n    HTTP_HEADER=\"$LE_CONFIG_HOME/http.header\"\n  fi\n\n  _DEFAULT_CERT_HOME=\"$LE_CONFIG_HOME\"\n  if [ -z \"$CERT_HOME\" ]; then\n    CERT_HOME=\"$_DEFAULT_CERT_HOME\"\n  fi\n\n  if [ -z \"$ACME_OPENSSL_BIN\" ] || [ ! -f \"$ACME_OPENSSL_BIN\" ] || [ ! -x \"$ACME_OPENSSL_BIN\" ]; then\n    ACME_OPENSSL_BIN=\"$DEFAULT_OPENSSL_BIN\"\n  fi\n\n  if [ -z \"$domain\" ]; then\n    return 0\n  fi\n\n  if [ -z \"$DOMAIN_PATH\" ]; then\n    domainhome=\"$CERT_HOME/$domain\"\n    domainhomeecc=\"$CERT_HOME/$domain$ECC_SUFFIX\"\n\n    DOMAIN_PATH=\"$domainhome\"\n\n    if _isEccKey \"$_ilength\"; then\n      DOMAIN_PATH=\"$domainhomeecc\"\n    elif [ -z \"$__SELECTED_RSA_KEY\" ]; then\n      if [ ! -d \"$domainhome\" ] && [ -d \"$domainhomeecc\" ]; then\n        _info \"The domain '$domain' seems to already have an ECC cert, let's use it.\"\n        DOMAIN_PATH=\"$domainhomeecc\"\n      fi\n    fi\n    _debug DOMAIN_PATH \"$DOMAIN_PATH\"\n    export DOMAIN_PATH\n  fi\n\n  if [ -z \"$DOMAIN_BACKUP_PATH\" ]; then\n    DOMAIN_BACKUP_PATH=\"$DOMAIN_PATH/backup\"\n  fi\n\n  if [ -z \"$DOMAIN_CONF\" ]; then\n    DOMAIN_CONF=\"$DOMAIN_PATH/$domain.conf\"\n  fi\n\n  if [ -z \"$DOMAIN_SSL_CONF\" ]; then\n    DOMAIN_SSL_CONF=\"$DOMAIN_PATH/$domain.csr.conf\"\n  fi\n\n  if [ -z \"$CSR_PATH\" ]; then\n    CSR_PATH=\"$DOMAIN_PATH/$domain.csr\"\n  fi\n  if [ -z \"$CERT_KEY_PATH\" ]; then\n    CERT_KEY_PATH=\"$DOMAIN_PATH/$domain.key\"\n  fi\n  if [ -z \"$CERT_PATH\" ]; then\n    CERT_PATH=\"$DOMAIN_PATH/$domain.cer\"\n  fi\n  if [ -z \"$CA_CERT_PATH\" ]; then\n    CA_CERT_PATH=\"$DOMAIN_PATH/ca.cer\"\n  fi\n  if [ -z \"$CERT_FULLCHAIN_PATH\" ]; then\n    CERT_FULLCHAIN_PATH=\"$DOMAIN_PATH/fullchain.cer\"\n  fi\n  if [ -z \"$CERT_PFX_PATH\" ]; then\n    CERT_PFX_PATH=\"$DOMAIN_PATH/$domain.pfx\"\n  fi\n  if [ -z \"$CERT_PKCS8_PATH\" ]; then\n    CERT_PKCS8_PATH=\"$DOMAIN_PATH/$domain.pkcs8\"\n  fi\n\n  if [ -z \"$TLS_CONF\" ]; then\n    TLS_CONF=\"$DOMAIN_PATH/tls.validation.conf\"\n  fi\n  if [ -z \"$TLS_CERT\" ]; then\n    TLS_CERT=\"$DOMAIN_PATH/tls.validation.cert\"\n  fi\n  if [ -z \"$TLS_KEY\" ]; then\n    TLS_KEY=\"$DOMAIN_PATH/tls.validation.key\"\n  fi\n  if [ -z \"$TLS_CSR\" ]; then\n    TLS_CSR=\"$DOMAIN_PATH/tls.validation.csr\"\n  fi\n\n}\n\n_apachePath() {\n  _APACHECTL=\"apachectl\"\n  if ! _exists apachectl; then\n    if _exists apache2ctl; then\n      _APACHECTL=\"apache2ctl\"\n    else\n      _err \"'apachectl not found. It seems that Apache is not installed or you are not root.'\"\n      _err \"Please use webroot mode to try again.\"\n      return 1\n    fi\n  fi\n\n  if ! $_APACHECTL -V >/dev/null; then\n    return 1\n  fi\n\n  if [ \"$APACHE_HTTPD_CONF\" ]; then\n    _saveaccountconf APACHE_HTTPD_CONF \"$APACHE_HTTPD_CONF\"\n    httpdconf=\"$APACHE_HTTPD_CONF\"\n    httpdconfname=\"$(basename \"$httpdconfname\")\"\n  else\n    httpdconfname=\"$($_APACHECTL -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '\"')\"\n    _debug httpdconfname \"$httpdconfname\"\n\n    if [ -z \"$httpdconfname\" ]; then\n      _err \"Cannot read Apache config file.\"\n      return 1\n    fi\n\n    if _startswith \"$httpdconfname\" '/'; then\n      httpdconf=\"$httpdconfname\"\n      httpdconfname=\"$(basename \"$httpdconfname\")\"\n    else\n      httpdroot=\"$($_APACHECTL -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '\"')\"\n      _debug httpdroot \"$httpdroot\"\n      httpdconf=\"$httpdroot/$httpdconfname\"\n      httpdconfname=\"$(basename \"$httpdconfname\")\"\n    fi\n  fi\n  _debug httpdconf \"$httpdconf\"\n  _debug httpdconfname \"$httpdconfname\"\n  if [ ! -f \"$httpdconf\" ]; then\n    _err \"Apache config file not found\" \"$httpdconf\"\n    return 1\n  fi\n  return 0\n}\n\n_restoreApache() {\n  if [ -z \"$usingApache\" ]; then\n    return 0\n  fi\n  _initpath\n  if ! _apachePath; then\n    return 1\n  fi\n\n  if [ ! -f \"$APACHE_CONF_BACKUP_DIR/$httpdconfname\" ]; then\n    _debug \"No config file to restore.\"\n    return 0\n  fi\n\n  cat \"$APACHE_CONF_BACKUP_DIR/$httpdconfname\" >\"$httpdconf\"\n  _debug \"Restored: $httpdconf.\"\n  if ! $_APACHECTL -t; then\n    _err \"Sorry, there's been an error restoring the Apache config. Please ask for support on $PROJECT.\"\n    return 1\n  fi\n  _debug \"Restored successfully.\"\n  rm -f \"$APACHE_CONF_BACKUP_DIR/$httpdconfname\"\n  return 0\n}\n\n_setApache() {\n  _initpath\n  if ! _apachePath; then\n    return 1\n  fi\n\n  #test the conf first\n  _info \"Checking if there is an error in the Apache config file before starting.\"\n\n  if ! $_APACHECTL -t >/dev/null; then\n    _err \"The Apache config file has errors, please fix them first then try again.\"\n    _err \"Don't worry, no changes to your system have been made.\"\n    return 1\n  else\n    _info \"OK\"\n  fi\n\n  #backup the conf\n  _debug \"Backing up Apache config file\" \"$httpdconf\"\n  if ! cp \"$httpdconf\" \"$APACHE_CONF_BACKUP_DIR/\"; then\n    _err \"Cannot backup Apache config file, aborting. Don't worry, the Apache config has not been changed.\"\n    _err \"This might be an $PROJECT_NAME bug, please open an issue on $PROJECT\"\n    return 1\n  fi\n  _info \"Config file $httpdconf has been backed up to $APACHE_CONF_BACKUP_DIR/$httpdconfname\"\n  _info \"In case an error causes it to not be restored automatically, you can restore it yourself.\"\n  _info \"You do not need to do anything on success, as the backup file will automatically be deleted.\"\n\n  #add alias\n\n  apacheVer=\"$($_APACHECTL -V | grep \"Server version:\" | cut -d : -f 2 | cut -d \" \" -f 2 | cut -d '/' -f 2)\"\n  _debug \"apacheVer\" \"$apacheVer\"\n  apacheMajor=\"$(echo \"$apacheVer\" | cut -d . -f 1)\"\n  apacheMinor=\"$(echo \"$apacheVer\" | cut -d . -f 2)\"\n\n  if [ \"$apacheVer\" ] && [ \"$apacheMajor$apacheMinor\" -ge \"24\" ]; then\n    echo \"\nAlias /.well-known/acme-challenge  $ACME_DIR\n\n<Directory $ACME_DIR >\nRequire all granted\n</Directory>\n  \" >>\"$httpdconf\"\n  else\n    echo \"\nAlias /.well-known/acme-challenge  $ACME_DIR\n\n<Directory $ACME_DIR >\nOrder allow,deny\nAllow from all\n</Directory>\n  \" >>\"$httpdconf\"\n  fi\n\n  _msg=\"$($_APACHECTL -t 2>&1)\"\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Sorry, an Apache config error has occurred\"\n    if _restoreApache; then\n      _err \"The Apache config file has been restored.\"\n    else\n      _err \"Sorry, the Apache config file cannot be restored, please open an issue on $PROJECT.\"\n    fi\n    return 1\n  fi\n\n  if [ ! -d \"$ACME_DIR\" ]; then\n    mkdir -p \"$ACME_DIR\"\n    chmod 755 \"$ACME_DIR\"\n  fi\n\n  if ! $_APACHECTL graceful; then\n    _err \"$_APACHECTL graceful error, please open an issue on $PROJECT.\"\n    _restoreApache\n    return 1\n  fi\n  usingApache=\"1\"\n  return 0\n}\n\n#find the real nginx conf file\n#backup\n#set the nginx conf\n#returns the real nginx conf file\n_setNginx() {\n  _d=\"$1\"\n  _croot=\"$2\"\n  _thumbpt=\"$3\"\n\n  FOUND_REAL_NGINX_CONF=\"\"\n  FOUND_REAL_NGINX_CONF_LN=\"\"\n  BACKUP_NGINX_CONF=\"\"\n  _debug _croot \"$_croot\"\n  _start_f=\"$(echo \"$_croot\" | cut -d : -f 2)\"\n  _debug _start_f \"$_start_f\"\n  if [ -z \"$_start_f\" ]; then\n    _debug \"Finding config using the nginx command\"\n    if [ -z \"$NGINX_CONF\" ]; then\n      if ! _exists \"nginx\"; then\n        _err \"nginx command not found.\"\n        return 1\n      fi\n      NGINX_CONF=\"$(nginx -V 2>&1 | _egrep_o \"\\-\\-conf-path=[^ ]* \" | tr -d \" \")\"\n      _debug NGINX_CONF \"$NGINX_CONF\"\n      NGINX_CONF=\"$(echo \"$NGINX_CONF\" | cut -d = -f 2)\"\n      _debug NGINX_CONF \"$NGINX_CONF\"\n      if [ -z \"$NGINX_CONF\" ]; then\n        _err \"Cannot find nginx config.\"\n        NGINX_CONF=\"\"\n        return 1\n      fi\n      if [ ! -f \"$NGINX_CONF\" ]; then\n        _err \"'$NGINX_CONF' doesn't exist.\"\n        NGINX_CONF=\"\"\n        return 1\n      fi\n      _debug \"Found nginx config file: $NGINX_CONF\"\n    fi\n    _start_f=\"$NGINX_CONF\"\n  fi\n  _debug \"Detecting nginx conf for $_d from: $_start_f\"\n  if ! _checkConf \"$_d\" \"$_start_f\"; then\n    _err \"Cannot find config file for domain $d\"\n    return 1\n  fi\n  _info \"Found config file: $FOUND_REAL_NGINX_CONF\"\n\n  _ln=$FOUND_REAL_NGINX_CONF_LN\n  _debug \"_ln\" \"$_ln\"\n\n  _lnn=$(_math $_ln + 1)\n  _debug _lnn \"$_lnn\"\n  _start_tag=\"$(sed -n \"$_lnn,${_lnn}p\" \"$FOUND_REAL_NGINX_CONF\")\"\n  _debug \"_start_tag\" \"$_start_tag\"\n  if [ \"$_start_tag\" = \"$NGINX_START\" ]; then\n    _info \"The domain $_d is already configured, skipping\"\n    FOUND_REAL_NGINX_CONF=\"\"\n    return 0\n  fi\n\n  mkdir -p \"$DOMAIN_BACKUP_PATH\"\n  _backup_conf=\"$DOMAIN_BACKUP_PATH/$_d.nginx.conf\"\n  _debug _backup_conf \"$_backup_conf\"\n  BACKUP_NGINX_CONF=\"$_backup_conf\"\n  _info \"Backing $FOUND_REAL_NGINX_CONF up to $_backup_conf\"\n  if ! cp \"$FOUND_REAL_NGINX_CONF\" \"$_backup_conf\"; then\n    _err \"Backup error.\"\n    FOUND_REAL_NGINX_CONF=\"\"\n    return 1\n  fi\n\n  if ! _exists \"nginx\"; then\n    _err \"nginx command not found.\"\n    return 1\n  fi\n  _info \"Checking the nginx config before setting up.\"\n  if ! nginx -t >/dev/null 2>&1; then\n    _err \"It seems that the nginx config is not correct, cannot continue.\"\n    return 1\n  fi\n\n  _info \"OK, setting up the nginx config file\"\n\n  if ! sed -n \"1,${_ln}p\" \"$_backup_conf\" >\"$FOUND_REAL_NGINX_CONF\"; then\n    cat \"$_backup_conf\" >\"$FOUND_REAL_NGINX_CONF\"\n    _err \"Error writing nginx config. Restoring it to its original version.\"\n    return 1\n  fi\n\n  echo \"$NGINX_START\nlocation ~ \\\"^/\\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\\$\\\" {\n  default_type text/plain;\n  return 200 \\\"\\$1.$_thumbpt\\\";\n}\n#NGINX_START\n\" >>\"$FOUND_REAL_NGINX_CONF\"\n\n  if ! sed -n \"${_lnn},99999p\" \"$_backup_conf\" >>\"$FOUND_REAL_NGINX_CONF\"; then\n    cat \"$_backup_conf\" >\"$FOUND_REAL_NGINX_CONF\"\n    _err \"Error writing nginx config. Restoring it to its original version.\"\n    return 1\n  fi\n  _debug3 \"Modified config:$(cat $FOUND_REAL_NGINX_CONF)\"\n  _info \"nginx config has been written, let's check it again.\"\n  if ! nginx -t >/dev/null 2>&1; then\n    _err \"There seems to be a problem with the nginx config, let's restore it to its original version.\"\n    cat \"$_backup_conf\" >\"$FOUND_REAL_NGINX_CONF\"\n    return 1\n  fi\n\n  _info \"Reloading nginx\"\n  if ! nginx -s reload >/dev/null 2>&1; then\n    _err \"There seems to be a problem with the nginx config, let's restore it to its original version.\"\n    cat \"$_backup_conf\" >\"$FOUND_REAL_NGINX_CONF\"\n    return 1\n  fi\n\n  return 0\n}\n\n#d , conf\n_checkConf() {\n  _d=\"$1\"\n  _c_file=\"$2\"\n  _debug \"Starting _checkConf from: $_c_file\"\n  if [ ! -f \"$2\" ] && ! echo \"$2\" | grep '*$' >/dev/null && echo \"$2\" | grep '*' >/dev/null; then\n    _debug \"wildcard\"\n    for _w_f in $2; do\n      if [ -f \"$_w_f\" ] && _checkConf \"$1\" \"$_w_f\"; then\n        return 0\n      fi\n    done\n    #not found\n    return 1\n  elif [ -f \"$2\" ]; then\n    _debug \"single\"\n    if _isRealNginxConf \"$1\" \"$2\"; then\n      _debug \"$2 found.\"\n      FOUND_REAL_NGINX_CONF=\"$2\"\n      return 0\n    fi\n    if cat \"$2\" | tr \"\\t\" \" \" | grep \"^ *include *.*;\" >/dev/null; then\n      _debug \"Trying include files\"\n      for included in $(cat \"$2\" | tr \"\\t\" \" \" | grep \"^ *include *.*;\" | sed \"s/include //\" | tr -d \" ;\"); do\n        _debug \"Checking included $included\"\n        if ! _startswith \"$included\" \"/\" && _exists dirname; then\n          _relpath=\"$(dirname \"$2\")\"\n          _debug \"_relpath\" \"$_relpath\"\n          included=\"$_relpath/$included\"\n        fi\n        if _checkConf \"$1\" \"$included\"; then\n          return 0\n        fi\n      done\n    fi\n    return 1\n  else\n    _debug \"$2 not found.\"\n    return 1\n  fi\n  return 1\n}\n\n#d , conf\n_isRealNginxConf() {\n  _debug \"_isRealNginxConf $1 $2\"\n  if [ -f \"$2\" ]; then\n    for _fln in $(tr \"\\t\" ' ' <\"$2\" | grep -n \"^ *server_name.* $1\" | cut -d : -f 1); do\n      _debug _fln \"$_fln\"\n      if [ \"$_fln\" ]; then\n        _start=$(tr \"\\t\" ' ' <\"$2\" | _head_n \"$_fln\" | grep -n \"^ *server *\" | grep -v server_name | _tail_n 1)\n        _debug \"_start\" \"$_start\"\n        _start_n=$(echo \"$_start\" | cut -d : -f 1)\n        _start_nn=$(_math $_start_n + 1)\n        _debug \"_start_n\" \"$_start_n\"\n        _debug \"_start_nn\" \"$_start_nn\"\n\n        _left=\"$(sed -n \"${_start_nn},99999p\" \"$2\")\"\n        _debug2 _left \"$_left\"\n        _end=\"$(echo \"$_left\" | tr \"\\t\" ' ' | grep -n \"^ *server *\" | grep -v server_name | _head_n 1)\"\n        _debug \"_end\" \"$_end\"\n        if [ \"$_end\" ]; then\n          _end_n=$(echo \"$_end\" | cut -d : -f 1)\n          _debug \"_end_n\" \"$_end_n\"\n          _seg_n=$(echo \"$_left\" | sed -n \"1,${_end_n}p\")\n        else\n          _seg_n=\"$_left\"\n        fi\n\n        _debug \"_seg_n\" \"$_seg_n\"\n\n        _skip_ssl=1\n        for _listen_i in $(echo \"$_seg_n\" | tr \"\\t\" ' ' | grep \"^ *listen\" | tr -d \" \"); do\n          if [ \"$_listen_i\" ]; then\n            if [ \"$(echo \"$_listen_i\" | _egrep_o \"listen.*ssl\")\" ]; then\n              _debug2 \"$_listen_i is ssl\"\n            else\n              _debug2 \"$_listen_i is plain text\"\n              _skip_ssl=\"\"\n              break\n            fi\n          fi\n        done\n\n        if [ \"$_skip_ssl\" = \"1\" ]; then\n          _debug \"ssl on, skip\"\n        else\n          FOUND_REAL_NGINX_CONF_LN=$_fln\n          _debug3 \"found FOUND_REAL_NGINX_CONF_LN\" \"$FOUND_REAL_NGINX_CONF_LN\"\n          return 0\n        fi\n      fi\n    done\n  fi\n  return 1\n}\n\n#restore all the nginx conf\n_restoreNginx() {\n  if [ -z \"$NGINX_RESTORE_VLIST\" ]; then\n    _debug \"No need to restore nginx config, skipping.\"\n    return\n  fi\n  _debug \"_restoreNginx\"\n  _debug \"NGINX_RESTORE_VLIST\" \"$NGINX_RESTORE_VLIST\"\n\n  for ng_entry in $(echo \"$NGINX_RESTORE_VLIST\" | tr \"$dvsep\" ' '); do\n    _debug \"ng_entry\" \"$ng_entry\"\n    _nd=$(echo \"$ng_entry\" | cut -d \"$sep\" -f 1)\n    _ngconf=$(echo \"$ng_entry\" | cut -d \"$sep\" -f 2)\n    _ngbackupconf=$(echo \"$ng_entry\" | cut -d \"$sep\" -f 3)\n    _info \"Restoring from $_ngbackupconf to $_ngconf\"\n    cat \"$_ngbackupconf\" >\"$_ngconf\"\n  done\n\n  _info \"Reloading nginx\"\n  if ! nginx -s reload >/dev/null; then\n    _err \"An error occurred while reloading nginx, please open an issue on $PROJECT.\"\n    return 1\n  fi\n  return 0\n}\n\n_clearup() {\n  _stopserver \"$serverproc\"\n  serverproc=\"\"\n  _restoreApache\n  _restoreNginx\n  _clearupdns\n  if [ -z \"$DEBUG\" ]; then\n    rm -f \"$TLS_CONF\"\n    rm -f \"$TLS_CERT\"\n    rm -f \"$TLS_KEY\"\n    rm -f \"$TLS_CSR\"\n  fi\n}\n\n_clearupdns() {\n  _debug \"_clearupdns\"\n  _debug \"dns_entries\" \"$dns_entries\"\n\n  if [ -z \"$dns_entries\" ]; then\n    _debug \"Skipping dns.\"\n    return\n  fi\n  _info \"Removing DNS records.\"\n\n  for entry in $dns_entries; do\n    d=$(_getfield \"$entry\" 1)\n    txtdomain=$(_getfield \"$entry\" 2)\n    aliasDomain=$(_getfield \"$entry\" 3)\n    _currentRoot=$(_getfield \"$entry\" 4)\n    txt=$(_getfield \"$entry\" 5)\n    d_api=$(_getfield \"$entry\" 6)\n    _debug \"d\" \"$d\"\n    _debug \"txtdomain\" \"$txtdomain\"\n    _debug \"aliasDomain\" \"$aliasDomain\"\n    _debug \"_currentRoot\" \"$_currentRoot\"\n    _debug \"txt\" \"$txt\"\n    _debug \"d_api\" \"$d_api\"\n    if [ \"$d_api\" = \"$txt\" ]; then\n      d_api=\"\"\n    fi\n\n    if [ -z \"$d_api\" ]; then\n      _info \"Domain API file was not found: $d_api\"\n      continue\n    fi\n\n    if [ \"$aliasDomain\" ]; then\n      txtdomain=\"$aliasDomain\"\n    fi\n\n    (\n      if ! . \"$d_api\"; then\n        _err \"Error loading file $d_api. Please check your API file and try again.\"\n        return 1\n      fi\n\n      rmcommand=\"${_currentRoot}_rm\"\n      if ! _exists \"$rmcommand\"; then\n        _err \"It seems that your API file doesn't define $rmcommand\"\n        return 1\n      fi\n      _info \"Removing txt: $txt for domain: $txtdomain\"\n      if ! $rmcommand \"$txtdomain\" \"$txt\"; then\n        _err \"Error removing txt for domain: $txtdomain\"\n        return 1\n      fi\n      _info \"Successfully removed\"\n    )\n\n  done\n}\n\n# webroot  removelevel tokenfile\n_clearupwebbroot() {\n  __webroot=\"$1\"\n  if [ -z \"$__webroot\" ]; then\n    _debug \"No webroot specified, skipping\"\n    return 0\n  fi\n\n  _rmpath=\"\"\n  if [ \"$2\" = '1' ]; then\n    _rmpath=\"$__webroot/.well-known\"\n  elif [ \"$2\" = '2' ]; then\n    _rmpath=\"$__webroot/.well-known/acme-challenge\"\n  elif [ \"$2\" = '3' ]; then\n    _rmpath=\"$__webroot/.well-known/acme-challenge/$3\"\n  else\n    _debug \"Skipping for removelevel: $2\"\n  fi\n\n  if [ \"$_rmpath\" ]; then\n    if [ \"$DEBUG\" ]; then\n      _debug \"Debugging, not removing: $_rmpath\"\n    else\n      rm -rf \"$_rmpath\"\n    fi\n  fi\n\n  return 0\n\n}\n\n_on_before_issue() {\n  _chk_web_roots=\"$1\"\n  _chk_main_domain=\"$2\"\n  _chk_alt_domains=\"$3\"\n  _chk_pre_hook=\"$4\"\n  _chk_local_addr=\"$5\"\n  _debug _on_before_issue\n  _debug _chk_main_domain \"$_chk_main_domain\"\n  _debug _chk_alt_domains \"$_chk_alt_domains\"\n  #run pre hook\n  if [ \"$_chk_pre_hook\" ]; then\n    _info \"Running pre hook:'$_chk_pre_hook'\"\n    if ! (\n      export Le_Domain=\"$_chk_main_domain\"\n      export Le_Alt=\"$_chk_alt_domains\"\n      cd \"$DOMAIN_PATH\" && eval \"$_chk_pre_hook\"\n    ); then\n      _err \"Error occurred when running pre hook.\"\n      return 1\n    fi\n  fi\n\n  if _hasfield \"$_chk_web_roots\" \"$NO_VALUE\" && [ \"$_chk_web_roots\" = \"$NO_VALUE\" ]; then\n    if ! _exists \"socat\" && ! _exists \"python\" && ! _exists \"python2\" && ! _exists \"python3\"; then\n      _err \"Please install socat or python tools first.\"\n      return 1\n    fi\n  fi\n\n  _debug Le_LocalAddress \"$_chk_local_addr\"\n\n  _index=1\n  _currentRoot=\"\"\n  _addrIndex=1\n  _w_index=1\n  while true; do\n    d=\"$(echo \"$_chk_main_domain,$_chk_alt_domains,\" | cut -d , -f \"$_w_index\")\"\n    _w_index=\"$(_math \"$_w_index\" + 1)\"\n    _debug d \"$d\"\n    if [ -z \"$d\" ]; then\n      break\n    fi\n    _debug \"Checking for domain\" \"$d\"\n    _currentRoot=\"$(_getfield \"$_chk_web_roots\" $_index)\"\n    _debug \"_currentRoot\" \"$_currentRoot\"\n    _index=$(_math $_index + 1)\n    _checkport=\"\"\n    if [ \"$_currentRoot\" = \"$NO_VALUE\" ]; then\n      _info \"Standalone mode.\"\n      if [ -z \"$Le_HTTPPort\" ]; then\n        Le_HTTPPort=80\n        _cleardomainconf \"Le_HTTPPort\"\n      else\n        _savedomainconf \"Le_HTTPPort\" \"$Le_HTTPPort\"\n      fi\n      _checkport=\"$Le_HTTPPort\"\n    elif [ \"$_currentRoot\" = \"$W_ALPN\" ]; then\n      _info \"Standalone alpn mode.\"\n      if [ -z \"$Le_TLSPort\" ]; then\n        Le_TLSPort=443\n      else\n        _savedomainconf \"Le_TLSPort\" \"$Le_TLSPort\"\n      fi\n      _checkport=\"$Le_TLSPort\"\n    fi\n\n    if [ \"$_checkport\" ]; then\n      _debug _checkport \"$_checkport\"\n      _checkaddr=\"$(_getfield \"$_chk_local_addr\" $_addrIndex)\"\n      _debug _checkaddr \"$_checkaddr\"\n\n      _addrIndex=\"$(_math $_addrIndex + 1)\"\n\n      _netprc=\"$(_ss \"$_checkport\" | grep \"$_checkport\")\"\n      netprc=\"$(echo \"$_netprc\" | grep \"$_checkaddr\")\"\n      if [ -z \"$netprc\" ]; then\n        netprc=\"$(echo \"$_netprc\" | grep \"$LOCAL_ANY_ADDRESS:$_checkport\")\"\n      fi\n      if [ \"$netprc\" ]; then\n        _err \"$netprc\"\n        _err \"tcp port $_checkport is already used by $(echo \"$netprc\" | cut -d : -f 4)\"\n        _err \"Please stop it first\"\n        return 1\n      fi\n    fi\n  done\n\n  if _hasfield \"$_chk_web_roots\" \"apache\"; then\n    if ! _setApache; then\n      _err \"Error setting up Apache. Please open an issue on $PROJECT.\"\n      return 1\n    fi\n  else\n    usingApache=\"\"\n  fi\n\n}\n\n_on_issue_err() {\n  _chk_post_hook=\"$1\"\n  _chk_vlist=\"$2\"\n  _debug _on_issue_err\n\n  if [ \"$LOG_FILE\" ]; then\n    _err \"Please check log file for more details: $LOG_FILE\"\n  else\n    _err \"Please add '--debug' or '--log' to see more information.\"\n    _err \"See: $_DEBUG_WIKI\"\n  fi\n\n  #run the post hook\n  if [ \"$_chk_post_hook\" ]; then\n    _info \"Running post hook: '$_chk_post_hook'\"\n    if ! (\n      cd \"$DOMAIN_PATH\" && eval \"$_chk_post_hook\"\n    ); then\n      _err \"Error encountered while running post hook.\"\n      return 1\n    fi\n  fi\n\n  #trigger the validation to flush the pending authz\n  _debug2 \"_chk_vlist\" \"$_chk_vlist\"\n  if [ \"$_chk_vlist\" ]; then\n    (\n      _debug2 \"start to deactivate authz\"\n      ventries=$(echo \"$_chk_vlist\" | tr \"$dvsep\" ' ')\n      for ventry in $ventries; do\n        d=$(echo \"$ventry\" | cut -d \"$sep\" -f 1)\n        keyauthorization=$(echo \"$ventry\" | cut -d \"$sep\" -f 2)\n        uri=$(echo \"$ventry\" | cut -d \"$sep\" -f 3)\n        vtype=$(echo \"$ventry\" | cut -d \"$sep\" -f 4)\n        _currentRoot=$(echo \"$ventry\" | cut -d \"$sep\" -f 5)\n        __trigger_validation \"$uri\" \"$keyauthorization\"\n      done\n    )\n  fi\n\n  if [ \"$_ACME_IS_RENEW\" = \"1\" ] && _hasfield \"$Le_Webroot\" \"$W_DNS\"; then\n    _err \"$_DNS_MANUAL_ERR\"\n  fi\n\n  if [ \"$DEBUG\" ] && [ \"$DEBUG\" -gt \"0\" ]; then\n    _debug \"$(_dlg_versions)\"\n  fi\n\n}\n\n_on_issue_success() {\n  _chk_post_hook=\"$1\"\n  _chk_renew_hook=\"$2\"\n  _debug _on_issue_success\n\n  #run the post hook\n  if [ \"$_chk_post_hook\" ]; then\n    _info \"Running post hook:'$_chk_post_hook'\"\n    if ! (\n      export CERT_PATH\n      export CERT_KEY_PATH\n      export CA_CERT_PATH\n      export CERT_FULLCHAIN_PATH\n      export Le_Domain=\"$_main_domain\"\n      cd \"$DOMAIN_PATH\" && eval \"$_chk_post_hook\"\n    ); then\n      _err \"Error encountered while running post hook.\"\n      return 1\n    fi\n  fi\n\n  #run renew hook\n  if [ \"$_ACME_IS_RENEW\" ] && [ \"$_chk_renew_hook\" ]; then\n    _info \"Running renew hook: '$_chk_renew_hook'\"\n    if ! (\n      export CERT_PATH\n      export CERT_KEY_PATH\n      export CA_CERT_PATH\n      export CERT_FULLCHAIN_PATH\n      export Le_Domain=\"$_main_domain\"\n      cd \"$DOMAIN_PATH\" && eval \"$_chk_renew_hook\"\n    ); then\n      _err \"Error encountered while running renew hook.\"\n      return 1\n    fi\n  fi\n\n  if _hasfield \"$Le_Webroot\" \"$W_DNS\" && [ -z \"$FORCE_DNS_MANUAL\" ]; then\n    _err \"$_DNS_MANUAL_WARN\"\n  fi\n\n}\n\n#account_key_length   eab-kid  eab-hmac-key\nregisteraccount() {\n  _account_key_length=\"$1\"\n  _eab_id=\"$2\"\n  _eab_hmac_key=\"$3\"\n  _initpath\n  _regAccount \"$_account_key_length\" \"$_eab_id\" \"$_eab_hmac_key\"\n}\n\n__calcAccountKeyHash() {\n  [ -f \"$ACCOUNT_KEY_PATH\" ] && _digest sha256 <\"$ACCOUNT_KEY_PATH\"\n}\n\n__calc_account_thumbprint() {\n  printf \"%s\" \"$jwk\" | tr -d ' ' | _digest \"sha256\" | _url_replace\n}\n\n_getAccountEmail() {\n  if [ \"$ACCOUNT_EMAIL\" ]; then\n    echo \"$ACCOUNT_EMAIL\"\n    return 0\n  fi\n  if [ -z \"$CA_EMAIL\" ]; then\n    CA_EMAIL=\"$(_readcaconf CA_EMAIL)\"\n  fi\n  if [ \"$CA_EMAIL\" ]; then\n    echo \"$CA_EMAIL\"\n    return 0\n  fi\n  _readaccountconf \"ACCOUNT_EMAIL\"\n}\n\n#keylength\n_regAccount() {\n  _initpath\n  _reg_length=\"$1\"\n  _eab_id=\"$2\"\n  _eab_hmac_key=\"$3\"\n  _debug3 _regAccount \"$_regAccount\"\n  _initAPI\n\n  mkdir -p \"$CA_DIR\"\n\n  if [ ! -f \"$ACCOUNT_KEY_PATH\" ]; then\n    if ! _create_account_key \"$_reg_length\"; then\n      _err \"Error creating account key.\"\n      return 1\n    fi\n  fi\n\n  if ! _calcjwk \"$ACCOUNT_KEY_PATH\"; then\n    return 1\n  fi\n  if [ \"$_eab_id\" ] && [ \"$_eab_hmac_key\" ]; then\n    _savecaconf CA_EAB_KEY_ID \"$_eab_id\"\n    _savecaconf CA_EAB_HMAC_KEY \"$_eab_hmac_key\"\n  fi\n  _eab_id=$(_readcaconf \"CA_EAB_KEY_ID\")\n  _eab_hmac_key=$(_readcaconf \"CA_EAB_HMAC_KEY\")\n  _secure_debug3 _eab_id \"$_eab_id\"\n  _secure_debug3 _eab_hmac_key \"$_eab_hmac_key\"\n  _email=\"$(_getAccountEmail)\"\n  if [ \"$_email\" ]; then\n    _savecaconf \"CA_EMAIL\" \"$_email\"\n  fi\n\n  if [ \"$ACME_DIRECTORY\" = \"$CA_ZEROSSL\" ]; then\n    if [ -z \"$_eab_id\" ] || [ -z \"$_eab_hmac_key\" ]; then\n      _info \"No EAB credentials found for ZeroSSL, let's obtain them\"\n      if [ -z \"$_email\" ]; then\n        _info \"$(__green \"$PROJECT_NAME is using ZeroSSL as default CA now.\")\"\n        _info \"$(__green \"Please update your account with an email address first.\")\"\n        _info \"$(__green \"$PROJECT_ENTRY --register-account -m my@example.com\")\"\n        _info \"See: $(__green \"$_ZEROSSL_WIKI\")\"\n        return 1\n      fi\n      _eabresp=$(_post \"email=$_email\" $_ZERO_EAB_ENDPOINT)\n      if [ \"$?\" != \"0\" ]; then\n        _debug2 \"$_eabresp\"\n        _err \"Cannot get EAB credentials from ZeroSSL.\"\n        return 1\n      fi\n      _secure_debug2 _eabresp \"$_eabresp\"\n      _eab_id=\"$(echo \"$_eabresp\" | tr ',}' '\\n\\n' | grep '\"eab_kid\"' | cut -d : -f 2 | tr -d '\"')\"\n      _secure_debug2 _eab_id \"$_eab_id\"\n      if [ -z \"$_eab_id\" ]; then\n        _err \"Cannot resolve _eab_id\"\n        return 1\n      fi\n      _eab_hmac_key=\"$(echo \"$_eabresp\" | tr ',}' '\\n\\n' | grep '\"eab_hmac_key\"' | cut -d : -f 2 | tr -d '\"')\"\n      _secure_debug2 _eab_hmac_key \"$_eab_hmac_key\"\n      if [ -z \"$_eab_hmac_key\" ]; then\n        _err \"Cannot resolve _eab_hmac_key\"\n        return 1\n      fi\n      _savecaconf CA_EAB_KEY_ID \"$_eab_id\"\n      _savecaconf CA_EAB_HMAC_KEY \"$_eab_hmac_key\"\n    fi\n  fi\n  if [ \"$_eab_id\" ] && [ \"$_eab_hmac_key\" ]; then\n    eab_protected=\"{\\\"alg\\\":\\\"HS256\\\",\\\"kid\\\":\\\"$_eab_id\\\",\\\"url\\\":\\\"${ACME_NEW_ACCOUNT}\\\"}\"\n    _debug3 eab_protected \"$eab_protected\"\n\n    eab_protected64=$(printf \"%s\" \"$eab_protected\" | _base64 | _url_replace)\n    _debug3 eab_protected64 \"$eab_protected64\"\n\n    eab_payload64=$(printf \"%s\" \"$jwk\" | _base64 | _url_replace)\n    _debug3 eab_payload64 \"$eab_payload64\"\n\n    eab_sign_t=\"$eab_protected64.$eab_payload64\"\n    _debug3 eab_sign_t \"$eab_sign_t\"\n\n    key_hex=\"$(_durl_replace_base64 \"$_eab_hmac_key\" | _dbase64 | _hex_dump | tr -d ' ')\"\n    _debug3 key_hex \"$key_hex\"\n\n    eab_signature=$(printf \"%s\" \"$eab_sign_t\" | _hmac sha256 $key_hex | _base64 | _url_replace)\n    _debug3 eab_signature \"$eab_signature\"\n\n    externalBinding=\",\\\"externalAccountBinding\\\":{\\\"protected\\\":\\\"$eab_protected64\\\", \\\"payload\\\":\\\"$eab_payload64\\\", \\\"signature\\\":\\\"$eab_signature\\\"}\"\n    _debug3 externalBinding \"$externalBinding\"\n  fi\n  if [ \"$_email\" ]; then\n    email_sg=\"\\\"contact\\\": [\\\"mailto:$_email\\\"], \"\n  fi\n  regjson=\"{$email_sg\\\"termsOfServiceAgreed\\\": true$externalBinding}\"\n\n  _info \"Registering account: $ACME_DIRECTORY\"\n\n  if ! _send_signed_request \"${ACME_NEW_ACCOUNT}\" \"$regjson\"; then\n    _err \"Error registering account: $response\"\n    return 1\n  fi\n\n  _eabAlreadyBound=\"\"\n  if [ \"$code\" = \"\" ] || [ \"$code\" = '201' ]; then\n    echo \"$response\" >\"$ACCOUNT_JSON_PATH\"\n    _info \"Registered\"\n  elif [ \"$code\" = '409' ] || [ \"$code\" = '200' ]; then\n    _info \"Already registered\"\n  elif [ \"$code\" = '400' ] && _contains \"$response\" 'The account is not awaiting external account binding'; then\n    _info \"EAB already registered\"\n    _eabAlreadyBound=1\n  else\n    _err \"Account registration error: $response\"\n    return 1\n  fi\n\n  if [ -z \"$_eabAlreadyBound\" ]; then\n    _debug2 responseHeaders \"$responseHeaders\"\n    _accUri=\"$(echo \"$responseHeaders\" | grep -i \"^Location:\" | _head_n 1 | cut -d ':' -f 2- | tr -d \"\\r\\n \")\"\n    _debug \"_accUri\" \"$_accUri\"\n    if [ -z \"$_accUri\" ]; then\n      _err \"Cannot find account id url.\"\n      _err \"$responseHeaders\"\n      return 1\n    fi\n    _savecaconf \"ACCOUNT_URL\" \"$_accUri\"\n  else\n    ACCOUNT_URL=\"$(_readcaconf ACCOUNT_URL)\"\n  fi\n  export ACCOUNT_URL=\"$_accUri\"\n\n  CA_KEY_HASH=\"$(__calcAccountKeyHash)\"\n  _debug \"Calc CA_KEY_HASH\" \"$CA_KEY_HASH\"\n  _savecaconf CA_KEY_HASH \"$CA_KEY_HASH\"\n\n  if [ \"$code\" = '403' ]; then\n    _err \"It seems that the account key has been deactivated, please use a new account key.\"\n    return 1\n  fi\n\n  ACCOUNT_THUMBPRINT=\"$(__calc_account_thumbprint)\"\n  _info \"ACCOUNT_THUMBPRINT\" \"$ACCOUNT_THUMBPRINT\"\n}\n\n#implement updateaccount\nupdateaccount() {\n  _initpath\n\n  if [ ! -f \"$ACCOUNT_KEY_PATH\" ]; then\n    _err \"Account key not found at: $ACCOUNT_KEY_PATH\"\n    return 1\n  fi\n\n  _accUri=$(_readcaconf \"ACCOUNT_URL\")\n  _debug _accUri \"$_accUri\"\n\n  if [ -z \"$_accUri\" ]; then\n    _err \"The account URL is empty, please run '--update-account' first to update the account info, then try again.\"\n    return 1\n  fi\n\n  if ! _calcjwk \"$ACCOUNT_KEY_PATH\"; then\n    return 1\n  fi\n  _initAPI\n\n  _email=\"$(_getAccountEmail)\"\n\n  if [ \"$_email\" ]; then\n    updjson='{\"contact\": [\"mailto:'$_email'\"]}'\n  else\n    updjson='{\"contact\": []}'\n  fi\n\n  _send_signed_request \"$_accUri\" \"$updjson\"\n\n  if [ \"$code\" = '200' ]; then\n    echo \"$response\" >\"$ACCOUNT_JSON_PATH\"\n    _info \"Account update success for $_accUri.\"\n\n    ACCOUNT_THUMBPRINT=\"$(__calc_account_thumbprint)\"\n    _info \"ACCOUNT_THUMBPRINT\" \"$ACCOUNT_THUMBPRINT\"\n  else\n    _info \"An error occurred and the account was not updated.\"\n    return 1\n  fi\n}\n\n#Implement deactivate account\ndeactivateaccount() {\n  _initpath\n\n  if [ ! -f \"$ACCOUNT_KEY_PATH\" ]; then\n    _err \"Account key not found at: $ACCOUNT_KEY_PATH\"\n    return 1\n  fi\n\n  _accUri=$(_readcaconf \"ACCOUNT_URL\")\n  _debug _accUri \"$_accUri\"\n\n  if [ -z \"$_accUri\" ]; then\n    _err \"The account URL is empty, please run '--update-account' first to update the account info, then try again.\"\n    return 1\n  fi\n\n  if ! _calcjwk \"$ACCOUNT_KEY_PATH\"; then\n    return 1\n  fi\n  _initAPI\n\n  _djson=\"{\\\"status\\\":\\\"deactivated\\\"}\"\n\n  if _send_signed_request \"$_accUri\" \"$_djson\" && _contains \"$response\" '\"deactivated\"'; then\n    _info \"Successfully deactivated account $_accUri.\"\n    _accid=$(echo \"$response\" | _egrep_o \"\\\"id\\\" *: *[^,]*,\" | cut -d : -f 2 | tr -d ' ,')\n  elif [ \"$code\" = \"403\" ]; then\n    _info \"The account is already deactivated.\"\n    _accid=$(_getfield \"$_accUri\" \"999\" \"/\")\n  else\n    _err \"Account deactivation failed for $_accUri.\"\n    return 1\n  fi\n\n  _debug \"Account id: $_accid\"\n  if [ \"$_accid\" ]; then\n    _deactivated_account_path=\"$CA_DIR/deactivated/$_accid\"\n    _debug _deactivated_account_path \"$_deactivated_account_path\"\n    if mkdir -p \"$_deactivated_account_path\"; then\n      _info \"Moving deactivated account info to $_deactivated_account_path/\"\n      mv \"$CA_CONF\" \"$_deactivated_account_path/\"\n      mv \"$ACCOUNT_JSON_PATH\" \"$_deactivated_account_path/\"\n      mv \"$ACCOUNT_KEY_PATH\" \"$_deactivated_account_path/\"\n    else\n      _err \"Cannot create dir: $_deactivated_account_path, try to remove the deactivated account key.\"\n      rm -f \"$CA_CONF\"\n      rm -f \"$ACCOUNT_JSON_PATH\"\n      rm -f \"$ACCOUNT_KEY_PATH\"\n    fi\n  fi\n}\n\n# domain folder  file\n_findHook() {\n  _hookdomain=\"$1\"\n  _hookcat=\"$2\"\n  _hookname=\"$3\"\n\n  if [ -f \"$_SCRIPT_HOME/$_hookcat/$_hookname\" ]; then\n    d_api=\"$_SCRIPT_HOME/$_hookcat/$_hookname\"\n  elif [ -f \"$_SCRIPT_HOME/$_hookcat/$_hookname.sh\" ]; then\n    d_api=\"$_SCRIPT_HOME/$_hookcat/$_hookname.sh\"\n  elif [ \"$_hookdomain\" ] && [ -f \"$LE_WORKING_DIR/$_hookdomain/$_hookname\" ]; then\n    d_api=\"$LE_WORKING_DIR/$_hookdomain/$_hookname\"\n  elif [ \"$_hookdomain\" ] && [ -f \"$LE_WORKING_DIR/$_hookdomain/$_hookname.sh\" ]; then\n    d_api=\"$LE_WORKING_DIR/$_hookdomain/$_hookname.sh\"\n  elif [ -f \"$LE_WORKING_DIR/$_hookname\" ]; then\n    d_api=\"$LE_WORKING_DIR/$_hookname\"\n  elif [ -f \"$LE_WORKING_DIR/$_hookname.sh\" ]; then\n    d_api=\"$LE_WORKING_DIR/$_hookname.sh\"\n  elif [ -f \"$LE_WORKING_DIR/$_hookcat/$_hookname\" ]; then\n    d_api=\"$LE_WORKING_DIR/$_hookcat/$_hookname\"\n  elif [ -f \"$LE_WORKING_DIR/$_hookcat/$_hookname.sh\" ]; then\n    d_api=\"$LE_WORKING_DIR/$_hookcat/$_hookname.sh\"\n  fi\n\n  printf \"%s\" \"$d_api\"\n}\n\n#domain\n__get_domain_new_authz() {\n  _gdnd=\"$1\"\n  _info \"Getting new-authz for domain\" \"$_gdnd\"\n  _initAPI\n  _Max_new_authz_retry_times=5\n  _authz_i=0\n  while [ \"$_authz_i\" -lt \"$_Max_new_authz_retry_times\" ]; do\n    _debug \"Trying new-authz, attempt number $_authz_i.\"\n    if ! _send_signed_request \"${ACME_NEW_AUTHZ}\" \"{\\\"resource\\\": \\\"new-authz\\\", \\\"identifier\\\": {\\\"type\\\": \\\"dns\\\", \\\"value\\\": \\\"$(_idn \"$_gdnd\")\\\"}}\"; then\n      _err \"Cannot get new authz for domain.\"\n      return 1\n    fi\n    if _contains \"$response\" \"No registration exists matching provided key\"; then\n      _err \"There has been an error, but it might now be resolved, please try again.\"\n      _err \"If you see this message for a second time, please report this as a bug: $(__green \"$PROJECT\")\"\n      _clearcaconf \"CA_KEY_HASH\"\n      break\n    fi\n    if ! _contains \"$response\" \"An error occurred while processing your request\"; then\n      _info \"new-authz request successful.\"\n      break\n    fi\n    _authz_i=\"$(_math \"$_authz_i\" + 1)\"\n    _info \"The server is busy, sleeping for $_authz_i seconds and retrying.\"\n    _sleep \"$_authz_i\"\n  done\n\n  if [ \"$_authz_i\" = \"$_Max_new_authz_retry_times\" ]; then\n    _err \"new-authz has been retried $_Max_new_authz_retry_times times, stopping.\"\n  fi\n\n  if [ \"$code\" ] && [ \"$code\" != '201' ]; then\n    _err \"new-authz error: $response\"\n    return 1\n  fi\n\n}\n\n#uri keyAuthorization\n__trigger_validation() {\n  _debug2 \"Trigger domain validation.\"\n  _t_url=\"$1\"\n  _debug2 _t_url \"$_t_url\"\n  _t_key_authz=\"$2\"\n  _debug2 _t_key_authz \"$_t_key_authz\"\n  _t_vtype=\"$3\"\n  _debug2 _t_vtype \"$_t_vtype\"\n\n  _send_signed_request \"$_t_url\" \"{}\"\n\n}\n\n#endpoint  domain type\n_ns_lookup_impl() {\n  _ns_ep=\"$1\"\n  _ns_domain=\"$2\"\n  _ns_type=\"$3\"\n  _debug2 \"_ns_ep\" \"$_ns_ep\"\n  _debug2 \"_ns_domain\" \"$_ns_domain\"\n  _debug2 \"_ns_type\" \"$_ns_type\"\n\n  response=\"$(_H1=\"accept: application/dns-json\" _get \"$_ns_ep?name=$_ns_domain&type=$_ns_type\")\"\n  _ret=$?\n  _debug2 \"response\" \"$response\"\n  if [ \"$_ret\" != \"0\" ]; then\n    return $_ret\n  fi\n  _answers=\"$(echo \"$response\" | tr '{}' '<>' | _egrep_o '\"Answer\":\\[[^]]*]' | tr '<>' '\\n\\n')\"\n  _debug2 \"_answers\" \"$_answers\"\n  echo \"$_answers\"\n}\n\n#domain, type\n_ns_lookup_cf() {\n  _cf_ld=\"$1\"\n  _cf_ld_type=\"$2\"\n  _cf_ep=\"https://cloudflare-dns.com/dns-query\"\n  _ns_lookup_impl \"$_cf_ep\" \"$_cf_ld\" \"$_cf_ld_type\"\n}\n\n#domain, type\n_ns_purge_cf() {\n  _cf_d=\"$1\"\n  _cf_d_type=\"$2\"\n  _debug \"Purging Cloudflare $_cf_d_type record for domain $_cf_d\"\n  _cf_purl=\"https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type\"\n  response=\"$(_post \"\" \"$_cf_purl\")\"\n  _debug2 response \"$response\"\n}\n\n#checks if cf server is available\n_ns_is_available_cf() {\n  if _get \"https://cloudflare-dns.com\" \"\" 10 >/dev/null; then\n    return 0\n  else\n    return 1\n  fi\n}\n\n_ns_is_available_google() {\n  if _get \"https://dns.google\" \"\" 10 >/dev/null; then\n    return 0\n  else\n    return 1\n  fi\n}\n\n#domain, type\n_ns_lookup_google() {\n  _cf_ld=\"$1\"\n  _cf_ld_type=\"$2\"\n  _cf_ep=\"https://dns.google/resolve\"\n  _ns_lookup_impl \"$_cf_ep\" \"$_cf_ld\" \"$_cf_ld_type\"\n}\n\n_ns_is_available_ali() {\n  if _get \"https://dns.alidns.com\" \"\" 10 >/dev/null; then\n    return 0\n  else\n    return 1\n  fi\n}\n\n#domain, type\n_ns_lookup_ali() {\n  _cf_ld=\"$1\"\n  _cf_ld_type=\"$2\"\n  _cf_ep=\"https://dns.alidns.com/resolve\"\n  _ns_lookup_impl \"$_cf_ep\" \"$_cf_ld\" \"$_cf_ld_type\"\n}\n\n_ns_is_available_dp() {\n  if _get \"https://doh.pub\" \"\" 10 >/dev/null; then\n    return 0\n  else\n    return 1\n  fi\n}\n\n#dnspod\n_ns_lookup_dp() {\n  _cf_ld=\"$1\"\n  _cf_ld_type=\"$2\"\n  _cf_ep=\"https://doh.pub/dns-query\"\n  _ns_lookup_impl \"$_cf_ep\" \"$_cf_ld\" \"$_cf_ld_type\"\n}\n\n_ns_select_doh() {\n  if [ -z \"$DOH_USE\" ]; then\n    _debug \"Detecting DNS server first.\"\n    if _ns_is_available_cf; then\n      _debug \"Using Cloudflare doh server\"\n      export DOH_USE=$DOH_CLOUDFLARE\n    elif _ns_is_available_google; then\n      _debug \"Using Google DOH server\"\n      export DOH_USE=$DOH_GOOGLE\n    elif _ns_is_available_ali; then\n      _debug \"Using Aliyun DOH server\"\n      export DOH_USE=$DOH_ALI\n    elif _ns_is_available_dp; then\n      _debug \"Using DNS POD DOH server\"\n      export DOH_USE=$DOH_DP\n    else\n      _err \"No DOH\"\n    fi\n  fi\n}\n\n#domain, type\n_ns_lookup() {\n  _ns_select_doh\n  if [ \"$DOH_USE\" = \"$DOH_CLOUDFLARE\" ] || [ -z \"$DOH_USE\" ]; then\n    _ns_lookup_cf \"$@\"\n  elif [ \"$DOH_USE\" = \"$DOH_GOOGLE\" ]; then\n    _ns_lookup_google \"$@\"\n  elif [ \"$DOH_USE\" = \"$DOH_ALI\" ]; then\n    _ns_lookup_ali \"$@\"\n  elif [ \"$DOH_USE\" = \"$DOH_DP\" ]; then\n    _ns_lookup_dp \"$@\"\n  else\n    _err \"Unknown DOH provider: DOH_USE=$DOH_USE\"\n  fi\n\n}\n\n#txtdomain, alias, txt\n__check_txt() {\n  _c_txtdomain=\"$1\"\n  _c_aliasdomain=\"$2\"\n  _c_txt=\"$3\"\n  _debug \"_c_txtdomain\" \"$_c_txtdomain\"\n  _debug \"_c_aliasdomain\" \"$_c_aliasdomain\"\n  _debug \"_c_txt\" \"$_c_txt\"\n  _ns_select_doh\n  _answers=\"$(_ns_lookup \"$_c_aliasdomain\" TXT)\"\n  _contains \"$_answers\" \"$_c_txt\"\n\n}\n\n#txtdomain\n__purge_txt() {\n  _p_txtdomain=\"$1\"\n  _debug _p_txtdomain \"$_p_txtdomain\"\n  if [ \"$DOH_USE\" = \"$DOH_CLOUDFLARE\" ] || [ -z \"$DOH_USE\" ]; then\n    _ns_purge_cf \"$_p_txtdomain\" \"TXT\"\n  else\n    _debug \"No purge API for this DOH API, just sleeping for 5 seconds\"\n    _sleep 5\n  fi\n\n}\n\n#wait and check each dns entries\n_check_dns_entries() {\n  _success_txt=\",\"\n  _end_time=\"$(_time)\"\n  _end_time=\"$(_math \"$_end_time\" + 1200)\" #let's check no more than 20 minutes.\n\n  while [ \"$(_time)\" -le \"$_end_time\" ]; do\n    _info \"You can use '--dnssleep' to disable public dns checks.\"\n    _info \"See: $_DNSCHECK_WIKI\"\n    _left=\"\"\n    for entry in $dns_entries; do\n      d=$(_getfield \"$entry\" 1)\n      txtdomain=$(_getfield \"$entry\" 2)\n      txtdomain=$(_idn \"$txtdomain\")\n      aliasDomain=$(_getfield \"$entry\" 3)\n      aliasDomain=$(_idn \"$aliasDomain\")\n      txt=$(_getfield \"$entry\" 5)\n      d_api=$(_getfield \"$entry\" 6)\n      _debug \"d\" \"$d\"\n      _debug \"txtdomain\" \"$txtdomain\"\n      _debug \"aliasDomain\" \"$aliasDomain\"\n      _debug \"txt\" \"$txt\"\n      _debug \"d_api\" \"$d_api\"\n      _info \"Checking $d for $aliasDomain\"\n      if _contains \"$_success_txt\" \",$txt,\"; then\n        _info \"Already succeeded, continuing.\"\n        continue\n      fi\n\n      if __check_txt \"$txtdomain\" \"$aliasDomain\" \"$txt\"; then\n        _info \"Success for domain $d '$aliasDomain'.\"\n        _success_txt=\"$_success_txt,$txt,\"\n        continue\n      fi\n      _left=1\n      _info \"Not valid yet, let's wait for 10 seconds then check the next one.\"\n      __purge_txt \"$txtdomain\"\n      if [ \"$txtdomain\" != \"$aliasDomain\" ]; then\n        __purge_txt \"$aliasDomain\"\n      fi\n      _sleep 10\n    done\n    if [ \"$_left\" ]; then\n      _info \"Let's wait for 10 seconds and check again\".\n      _sleep 10\n    else\n      _info \"All checks succeeded\"\n      return 0\n    fi\n  done\n  _info \"Timed out waiting for DNS.\"\n  return 1\n\n}\n\n#file\n_get_chain_issuers() {\n  _cfile=\"$1\"\n  if _contains \"$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)\" \"Usage: crl2pkcs7\" || _contains \"$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 2>&1)\" \"Usage: crl2pkcs7\" || _contains \"$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)\" \"unknown option help\"; then\n    ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep -i 'Issuer:' | _egrep_o \"CN *=[^,]*\" | cut -d = -f 2\n  else\n    _cindex=1\n    for _startn in $(grep -n -- \"$BEGIN_CERT\" \"$_cfile\" | cut -d : -f 1); do\n      _endn=\"$(grep -n -- \"$END_CERT\" \"$_cfile\" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)\"\n      _debug2 \"_startn\" \"$_startn\"\n      _debug2 \"_endn\" \"$_endn\"\n      if [ \"$DEBUG\" ]; then\n        _debug2 \"cert$_cindex\" \"$(sed -n \"$_startn,${_endn}p\" \"$_cfile\")\"\n      fi\n      sed -n \"$_startn,${_endn}p\" \"$_cfile\" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep 'Issuer:' | _egrep_o \"CN *=[^,]*\" | cut -d = -f 2 | sed \"s/ *\\(.*\\)/\\1/\"\n      _cindex=$(_math $_cindex + 1)\n    done\n  fi\n}\n\n#\n_get_chain_subjects() {\n  _cfile=\"$1\"\n  if _contains \"$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)\" \"Usage: crl2pkcs7\" || _contains \"$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 2>&1)\" \"Usage: crl2pkcs7\" || _contains \"$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)\" \"unknown option help\"; then\n    ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep -i 'Subject:' | _egrep_o \"CN *=[^,]*\" | cut -d = -f 2\n  else\n    _cindex=1\n    for _startn in $(grep -n -- \"$BEGIN_CERT\" \"$_cfile\" | cut -d : -f 1); do\n      _endn=\"$(grep -n -- \"$END_CERT\" \"$_cfile\" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)\"\n      _debug2 \"_startn\" \"$_startn\"\n      _debug2 \"_endn\" \"$_endn\"\n      if [ \"$DEBUG\" ]; then\n        _debug2 \"cert$_cindex\" \"$(sed -n \"$_startn,${_endn}p\" \"$_cfile\")\"\n      fi\n      sed -n \"$_startn,${_endn}p\" \"$_cfile\" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep -i 'Subject:' | _egrep_o \"CN *=[^,]*\" | cut -d = -f 2 | sed \"s/ *\\(.*\\)/\\1/\"\n      _cindex=$(_math $_cindex + 1)\n    done\n  fi\n}\n\n#cert  issuer\n_match_issuer() {\n  _cfile=\"$1\"\n  _missuer=\"$2\"\n  _fissuers=\"$(_get_chain_issuers $_cfile)\"\n  _debug2 _fissuers \"$_fissuers\"\n  _rootissuer=\"$(echo \"$_fissuers\" | _lower_case | _tail_n 1)\"\n  _debug2 _rootissuer \"$_rootissuer\"\n  _missuer=\"$(echo \"$_missuer\" | _lower_case)\"\n  _contains \"$_rootissuer\" \"$_missuer\"\n}\n\n#ip\n_isIPv4() {\n  for seg in $(echo \"$1\" | tr '.' ' '); do\n    _debug2 seg \"$seg\"\n    if [ \"$(echo \"$seg\" | tr -d '[0-9]')\" ]; then\n      #not all number\n      return 1\n    fi\n    if [ $seg -ge 0 ] && [ $seg -lt 256 ]; then\n      continue\n    fi\n    return 1\n  done\n  return 0\n}\n\n#ip6\n_isIPv6() {\n  _contains \"$1\" \":\"\n}\n\n#ip\n_isIP() {\n  _isIPv4 \"$1\" || _isIPv6 \"$1\"\n}\n\n#identifier\n_getIdType() {\n  if _isIP \"$1\"; then\n    echo \"$ID_TYPE_IP\"\n  else\n    echo \"$ID_TYPE_DNS\"\n  fi\n}\n\n# beginTime dateTo\n# beginTime is full string format(\"2022-04-01T08:10:33Z\"), beginTime can be empty, to use current time\n# dateTo can be ether in full string format(\"2022-04-01T08:10:33Z\") or in delta format(+5d or +20h)\n_convertValidaty() {\n  _beginTime=\"$1\"\n  _dateTo=\"$2\"\n  _debug2 \"_beginTime\" \"$_beginTime\"\n  _debug2 \"_dateTo\" \"$_dateTo\"\n\n  if _startswith \"$_dateTo\" \"+\"; then\n    _v_begin=$(_time)\n    if [ \"$_beginTime\" ]; then\n      _v_begin=\"$(_date2time \"$_beginTime\")\"\n    fi\n    _debug2 \"_v_begin\" \"$_v_begin\"\n    if _endswith \"$_dateTo\" \"h\"; then\n      _v_end=$(_math \"$_v_begin + 60 * 60 * $(echo \"$_dateTo\" | tr -d '+h')\")\n    elif _endswith \"$_dateTo\" \"d\"; then\n      _v_end=$(_math \"$_v_begin + 60 * 60 * 24 * $(echo \"$_dateTo\" | tr -d '+d')\")\n    else\n      _err \"Unrecognized format for _dateTo: $_dateTo\"\n      return 1\n    fi\n    _debug2 \"_v_end\" \"$_v_end\"\n    _time2str \"$_v_end\"\n  else\n    if [ \"$(_time)\" -gt \"$(_date2time \"$_dateTo\")\" ]; then\n      _err \"The validity end date is in the past: _dateTo = $_dateTo\"\n      return 1\n    fi\n    echo \"$_dateTo\"\n  fi\n}\n\n#webroot, domain domainlist  keylength\nissue() {\n  if [ -z \"$2\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --issue --domain <domain.tld> --webroot <directory>\"\n    return 1\n  fi\n  if [ -z \"$1\" ]; then\n    _usage \"Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc.\"\n    return 1\n  fi\n  _web_roots=\"$1\"\n  _main_domain=\"$2\"\n  _alt_domains=\"$3\"\n\n  if _contains \"$_main_domain\" \",\"; then\n    _main_domain=$(echo \"$2,$3\" | cut -d , -f 1)\n    _alt_domains=$(echo \"$2,$3\" | cut -d , -f 2- | sed \"s/,${NO_VALUE}$//\")\n  fi\n  _debug _main_domain \"$_main_domain\"\n  _debug _alt_domains \"$_alt_domains\"\n\n  _key_length=\"$4\"\n  _real_cert=\"$5\"\n  _real_key=\"$6\"\n  _real_ca=\"$7\"\n  _reload_cmd=\"$8\"\n  _real_fullchain=\"$9\"\n  _pre_hook=\"${10}\"\n  _post_hook=\"${11}\"\n  _renew_hook=\"${12}\"\n  _local_addr=\"${13}\"\n  _challenge_alias=\"${14}\"\n  _preferred_chain=\"${15}\"\n  _valid_from=\"${16}\"\n  _valid_to=\"${17}\"\n  _certificate_profile=\"${18}\"\n  _extended_key_usage=\"${19}\"\n\n  if [ -z \"$_ACME_IS_RENEW\" ]; then\n    _initpath \"$_main_domain\" \"$_key_length\"\n    mkdir -p \"$DOMAIN_PATH\"\n  elif ! _hasfield \"$_web_roots\" \"$W_DNS\"; then\n    Le_OrderFinalize=\"\"\n    Le_LinkOrder=\"\"\n    Le_LinkCert=\"\"\n  fi\n\n  if _hasfield \"$_web_roots\" \"$W_DNS\" && [ -z \"$FORCE_DNS_MANUAL\" ]; then\n    _err \"$_DNS_MANUAL_ERROR\"\n    return 1\n  fi\n\n  if [ -f \"$DOMAIN_CONF\" ]; then\n    Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime)\n    _debug Le_NextRenewTime \"$Le_NextRenewTime\"\n    if [ -z \"$FORCE\" ] && [ \"$Le_NextRenewTime\" ] && [ \"$(_time)\" -lt \"$Le_NextRenewTime\" ]; then\n      _valid_to_saved=$(_readdomainconf Le_Valid_To)\n      if [ \"$_valid_to_saved\" ] && ! _startswith \"$_valid_to_saved\" \"+\"; then\n        _info \"The domain is set to be valid to: $_valid_to_saved\"\n        _info \"It cannot be renewed automatically\"\n        _info \"See: $_VALIDITY_WIKI\"\n        return $RENEW_SKIP\n      fi\n      _saved_domain=$(_readdomainconf Le_Domain)\n      _debug _saved_domain \"$_saved_domain\"\n      _saved_alt=$(_readdomainconf Le_Alt)\n      _debug _saved_alt \"$_saved_alt\"\n      _normized_saved_domains=\"$(echo \"$_saved_domain,$_saved_alt\" | tr \",\" \"\\n\" | sort | tr '\\n' ',')\"\n      _debug _normized_saved_domains \"$_normized_saved_domains\"\n\n      _normized_domains=\"$(echo \"$_main_domain,$_alt_domains\" | tr \",\" \"\\n\" | sort | tr '\\n' ',')\"\n      _debug _normized_domains \"$_normized_domains\"\n\n      if [ \"$_normized_saved_domains\" = \"$_normized_domains\" ]; then\n        _info \"Domains not changed.\"\n        _info \"Skipping. Next renewal time is: $(__green \"$(_readdomainconf Le_NextRenewTimeStr)\")\"\n        _info \"Add '$(__red '--force')' to force renewal.\"\n        return $RENEW_SKIP\n      else\n        _info \"Domains have changed.\"\n      fi\n    fi\n  fi\n\n  _debug \"Using ACME_DIRECTORY: $ACME_DIRECTORY\"\n  if ! _initAPI; then\n    return 1\n  fi\n\n  _savedomainconf \"Le_Domain\" \"$_main_domain\"\n  _savedomainconf \"Le_Alt\" \"$_alt_domains\"\n  _savedomainconf \"Le_Webroot\" \"$_web_roots\"\n\n  _savedomainconf \"Le_PreHook\" \"$_pre_hook\" \"base64\"\n  _savedomainconf \"Le_PostHook\" \"$_post_hook\" \"base64\"\n  _savedomainconf \"Le_RenewHook\" \"$_renew_hook\" \"base64\"\n\n  if [ \"$_local_addr\" ]; then\n    _savedomainconf \"Le_LocalAddress\" \"$_local_addr\"\n  else\n    _cleardomainconf \"Le_LocalAddress\"\n  fi\n  if [ \"$_challenge_alias\" ]; then\n    _savedomainconf \"Le_ChallengeAlias\" \"$_challenge_alias\"\n  else\n    _cleardomainconf \"Le_ChallengeAlias\"\n  fi\n  if [ \"$_preferred_chain\" ]; then\n    _savedomainconf \"Le_Preferred_Chain\" \"$_preferred_chain\" \"base64\"\n  else\n    _cleardomainconf \"Le_Preferred_Chain\"\n  fi\n  if [ \"$_certificate_profile\" ]; then\n    _savedomainconf \"Le_Certificate_Profile\" \"$_certificate_profile\"\n  else\n    _cleardomainconf \"Le_Certificate_Profile\"\n  fi\n\n  Le_API=\"$ACME_DIRECTORY\"\n  _savedomainconf \"Le_API\" \"$Le_API\"\n\n  _info \"Using CA: $ACME_DIRECTORY\"\n  if [ \"$_alt_domains\" = \"$NO_VALUE\" ]; then\n    _alt_domains=\"\"\n  fi\n\n  if ! _on_before_issue \"$_web_roots\" \"$_main_domain\" \"$_alt_domains\" \"$_pre_hook\" \"$_local_addr\"; then\n    _err \"_on_before_issue.\"\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n\n  _saved_account_key_hash=\"$(_readcaconf \"CA_KEY_HASH\")\"\n  _debug2 _saved_account_key_hash \"$_saved_account_key_hash\"\n\n  if [ -z \"$ACCOUNT_URL\" ] || [ -z \"$_saved_account_key_hash\" ] || [ \"$_saved_account_key_hash\" != \"$(__calcAccountKeyHash)\" ]; then\n    if ! _regAccount \"$_accountkeylength\"; then\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n  else\n    _debug \"_saved_account_key_hash was not changed, skipping account registration.\"\n  fi\n\n  export Le_Next_Domain_Key=\"$CERT_KEY_PATH.next\"\n  if [ -f \"$CSR_PATH\" ] && [ ! -f \"$CERT_KEY_PATH\" ]; then\n    _info \"Signing from existing CSR.\"\n  else\n    # When renewing from an old version, the empty Le_Keylength means 2048.\n    # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over\n    # time but an empty value implies 2048 specifically.\n    _key=$(_readdomainconf Le_Keylength)\n    if [ -z \"$_key\" ]; then\n      _key=2048\n    fi\n    _debug \"Read key length: $_key\"\n    if [ ! -f \"$CERT_KEY_PATH\" ] || [ \"$_key_length\" != \"$_key\" ] || [ \"$Le_ForceNewDomainKey\" = \"1\" ]; then\n      if [ \"$Le_ForceNewDomainKey\" = \"1\" ] && [ -f \"$Le_Next_Domain_Key\" ]; then\n        _info \"Using pre-generated key: $Le_Next_Domain_Key\"\n        cat \"$Le_Next_Domain_Key\" >\"$CERT_KEY_PATH\"\n        echo \"\" >\"$Le_Next_Domain_Key\"\n      else\n        if ! createDomainKey \"$_main_domain\" \"$_key_length\"; then\n          _err \"Error creating domain key.\"\n          _clearup\n          _on_issue_err \"$_post_hook\"\n          return 1\n        fi\n      fi\n    fi\n    if [ \"$Le_ForceNewDomainKey\" ]; then\n      _info \"Generating next pre-generate key.\"\n      if [ ! -e \"$Le_Next_Domain_Key\" ]; then\n        touch \"$Le_Next_Domain_Key\"\n        chmod 600 \"$Le_Next_Domain_Key\"\n      fi\n      if ! _createkey \"$_key_length\" \"$Le_Next_Domain_Key\"; then\n        _err \"Cannot pre-generate domain key\"\n        return 1\n      fi\n    fi\n    _keyusage=\"$_extended_key_usage\"\n    if [ \"$Le_API\" = \"$CA_GOOGLE\" ] || [ \"$Le_API\" = \"$CA_GOOGLE_TEST\" ]; then\n      if [ -z \"$_keyusage\" ]; then\n        #https://github.com/acmesh-official/acme.sh/issues/6610\n        #google accepts serverauth only\n        _keyusage=\"serverAuth\"\n      fi\n    fi\n    if ! _createcsr \"$_main_domain\" \"$_alt_domains\" \"$CERT_KEY_PATH\" \"$CSR_PATH\" \"$DOMAIN_SSL_CONF\" \"\" \"$_keyusage\"; then\n      _err \"Error creating CSR.\"\n      _clearup\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n    if [ \"$_extended_key_usage\" ]; then\n      _savedomainconf \"Le_ExtKeyUse\" \"$_extended_key_usage\"\n    else\n      _cleardomainconf \"Le_ExtKeyUse\"\n    fi\n  fi\n\n  _savedomainconf \"Le_Keylength\" \"$_key_length\"\n\n  vlist=\"$Le_Vlist\"\n  _cleardomainconf \"Le_Vlist\"\n  _debug \"Getting domain auth token for each domain\"\n  sep='#'\n  dvsep=','\n  if [ -z \"$vlist\" ]; then\n    #make new order request\n    _identifiers=\"{\\\"type\\\":\\\"$(_getIdType \"$_main_domain\")\\\",\\\"value\\\":\\\"$(_idn \"$_main_domain\")\\\"}\"\n    _w_index=1\n    while true; do\n      d=\"$(echo \"$_alt_domains,\" | cut -d , -f \"$_w_index\")\"\n      _w_index=\"$(_math \"$_w_index\" + 1)\"\n      _debug d \"$d\"\n      if [ -z \"$d\" ]; then\n        break\n      fi\n      _identifiers=\"$_identifiers,{\\\"type\\\":\\\"$(_getIdType \"$d\")\\\",\\\"value\\\":\\\"$(_idn \"$d\")\\\"}\"\n    done\n    _debug2 _identifiers \"$_identifiers\"\n    _notBefore=\"\"\n    _notAfter=\"\"\n\n    if [ \"$_valid_from\" ]; then\n      _savedomainconf \"Le_Valid_From\" \"$_valid_from\"\n      _debug2 \"_valid_from\" \"$_valid_from\"\n      _notBefore=\"$(_convertValidaty \"\" \"$_valid_from\")\"\n      if [ \"$?\" != \"0\" ]; then\n        _err \"Cannot parse _valid_from: $_valid_from\"\n        return 1\n      fi\n      if [ \"$(_time)\" -gt \"$(_date2time \"$_notBefore\")\" ]; then\n        _notBefore=\"\"\n      fi\n    else\n      _cleardomainconf \"Le_Valid_From\"\n    fi\n    _debug2 _notBefore \"$_notBefore\"\n\n    if [ \"$_valid_to\" ]; then\n      _debug2 \"_valid_to\" \"$_valid_to\"\n      _savedomainconf \"Le_Valid_To\" \"$_valid_to\"\n      _notAfter=\"$(_convertValidaty \"$_notBefore\" \"$_valid_to\")\"\n      if [ \"$?\" != \"0\" ]; then\n        _err \"Cannot parse _valid_to: $_valid_to\"\n        return 1\n      fi\n    else\n      _cleardomainconf \"Le_Valid_To\"\n    fi\n    _debug2 \"_notAfter\" \"$_notAfter\"\n\n    _newOrderObj=\"{\\\"identifiers\\\": [$_identifiers]\"\n    if [ \"$_notBefore\" ]; then\n      _newOrderObj=\"$_newOrderObj,\\\"notBefore\\\": \\\"$_notBefore\\\"\"\n    fi\n    if [ \"$_notAfter\" ]; then\n      _newOrderObj=\"$_newOrderObj,\\\"notAfter\\\": \\\"$_notAfter\\\"\"\n    fi\n    if [ \"$_certificate_profile\" ]; then\n      _newOrderObj=\"$_newOrderObj,\\\"profile\\\": \\\"$_certificate_profile\\\"\"\n    fi\n    _debug \"STEP 1, Ordering a Certificate\"\n    if ! _send_signed_request \"$ACME_NEW_ORDER\" \"$_newOrderObj}\"; then\n      _err \"Error creating new order.\"\n      _clearup\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n    if _contains \"$response\" \"invalid\"; then\n      if echo \"$response\" | _normalizeJson | grep '\"status\":\"invalid\"' >/dev/null 2>&1; then\n        _err \"Create new order with invalid status.\"\n        _err \"$response\"\n        _clearup\n        _on_issue_err \"$_post_hook\"\n        return 1\n      fi\n    fi\n\n    Le_LinkOrder=\"$(echo \"$responseHeaders\" | grep -i '^Location.*$' | _tail_n 1 | tr -d \"\\r\\n \" | cut -d \":\" -f 2-)\"\n    _debug Le_LinkOrder \"$Le_LinkOrder\"\n    Le_OrderFinalize=\"$(echo \"$response\" | _egrep_o '\"finalize\" *: *\"[^\"]*\"' | cut -d '\"' -f 4)\"\n    _debug Le_OrderFinalize \"$Le_OrderFinalize\"\n    if [ -z \"$Le_OrderFinalize\" ]; then\n      _err \"Error creating new order. Le_OrderFinalize not found. $response\"\n      _clearup\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n\n    #for dns manual mode\n    _savedomainconf \"Le_OrderFinalize\" \"$Le_OrderFinalize\"\n\n    _authorizations_seg=\"$(echo \"$response\" | _json_decode | _egrep_o '\"authorizations\" *: *\\[[^\\[]*\\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '\"')\"\n    _debug2 _authorizations_seg \"$_authorizations_seg\"\n    if [ -z \"$_authorizations_seg\" ]; then\n      _err \"_authorizations_seg not found.\"\n      _clearup\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n\n    _debug \"STEP 2, Get the authorizations of each domain\"\n    #domain and authz map\n    _authorizations_map=\"\"\n    for _authz_url in $(echo \"$_authorizations_seg\" | tr ',' ' '); do\n      _debug2 \"_authz_url\" \"$_authz_url\"\n      if ! _send_signed_request \"$_authz_url\"; then\n        _err \"Error getting authz.\"\n        _err \"_authorizations_seg\" \"$_authorizations_seg\"\n        _err \"_authz_url\" \"$_authz_url\"\n        _err \"$response\"\n        _clearup\n        _on_issue_err \"$_post_hook\"\n        return 1\n      fi\n\n      response=\"$(echo \"$response\" | _normalizeJson)\"\n      _debug2 response \"$response\"\n      if echo \"$response\" | grep '\"status\":\"invalid\"' >/dev/null 2>&1; then\n        _err \"get authz objec with invalid status, please try again later.\"\n        _err \"_authorizations_seg\" \"$_authorizations_seg\"\n        _err \"$response\"\n        _clearup\n        _on_issue_err \"$_post_hook\"\n        return 1\n      fi\n      _d=\"$(echo \"$response\" | _egrep_o '\"value\" *: *\"[^\"]*\"' | cut -d : -f 2- | tr -d ' \"')\"\n      if _contains \"$response\" \"\\\"wildcard\\\" *: *true\"; then\n        _d=\"*.$_d\"\n      fi\n      _debug2 _d \"$_d\"\n      _authorizations_map=\"$_d,$response#$_authz_url\n$_authorizations_map\"\n    done\n\n    _debug2 _authorizations_map \"$_authorizations_map\"\n\n    _index=0\n    _currentRoot=\"\"\n    _w_index=1\n    while true; do\n      d=\"$(echo \"$_main_domain,$_alt_domains,\" | cut -d , -f \"$_w_index\")\"\n      _w_index=\"$(_math \"$_w_index\" + 1)\"\n      _debug d \"$d\"\n      if [ -z \"$d\" ]; then\n        break\n      fi\n      _info \"Getting webroot for domain\" \"$d\"\n      _index=$(_math $_index + 1)\n      _w=\"$(echo $_web_roots | cut -d , -f $_index)\"\n      _debug _w \"$_w\"\n      if [ \"$_w\" ]; then\n        _currentRoot=\"$_w\"\n      fi\n      _debug \"_currentRoot\" \"$_currentRoot\"\n\n      vtype=\"$VTYPE_HTTP\"\n      #todo, v2 wildcard force to use dns\n      if _startswith \"$_currentRoot\" \"$W_DNS\"; then\n        vtype=\"$VTYPE_DNS\"\n      fi\n\n      if [ \"$_currentRoot\" = \"$W_ALPN\" ]; then\n        vtype=\"$VTYPE_ALPN\"\n      fi\n\n      _idn_d=\"$(_idn \"$d\")\"\n      _candidates=\"$(echo \"$_authorizations_map\" | grep -i \"^$_idn_d,\")\"\n      _debug2 _candidates \"$_candidates\"\n      if [ \"$(echo \"$_candidates\" | wc -l)\" -gt 1 ]; then\n        for _can in $_candidates; do\n          if _startswith \"$(echo \"$_can\" | tr '.' '|')\" \"$(echo \"$_idn_d\" | tr '.' '|'),\"; then\n            _candidates=\"$_can\"\n            break\n          fi\n        done\n      fi\n      response=\"$(echo \"$_candidates\" | sed \"s/$_idn_d,//\")\"\n      _debug2 \"response\" \"$response\"\n      if [ -z \"$response\" ]; then\n        _err \"Error getting authz.\"\n        _err \"_authorizations_map\" \"$_authorizations_map\"\n        _clearup\n        _on_issue_err \"$_post_hook\"\n        return 1\n      fi\n      _authz_url=\"$(echo \"$_candidates\" | sed \"s/$_idn_d,//\" | _egrep_o \"#.*\" | sed \"s/^#//\")\"\n      _debug _authz_url \"$_authz_url\"\n      if [ -z \"$thumbprint\" ]; then\n        thumbprint=\"$(__calc_account_thumbprint)\"\n      fi\n\n      keyauthorization=\"\"\n\n      if echo \"$response\" | grep '\"status\":\"valid\"' >/dev/null 2>&1; then\n        _debug \"$d is already valid.\"\n        keyauthorization=\"$STATE_VERIFIED\"\n        _debug keyauthorization \"$keyauthorization\"\n      fi\n\n      # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018\n      entry=\"$(echo \"$response\" | sed s/'\"error\":{}'/'\"error\":null'/ | _egrep_o '[^\\{]*\"type\":\"'$vtype'\"[^\\}]*')\"\n      _debug entry \"$entry\"\n\n      if [ -z \"$keyauthorization\" -a -z \"$entry\" ]; then\n        _err \"Cannot get domain token entry $d for $vtype\"\n        _supported_vtypes=\"$(echo \"$response\" | _egrep_o \"\\\"challenges\\\":\\[[^]]*]\" | tr '{' \"\\n\" | grep type | cut -d '\"' -f 4 | tr \"\\n\" ' ')\"\n        if [ \"$_supported_vtypes\" ]; then\n          _err \"Supported validation types are: $_supported_vtypes, but you specified: $vtype\"\n        fi\n        _clearup\n        _on_issue_err \"$_post_hook\"\n        return 1\n      fi\n\n      if [ -z \"$keyauthorization\" ]; then\n        token=\"$(echo \"$entry\" | _egrep_o '\"token\":\"[^\"]*' | cut -d : -f 2 | tr -d '\"')\"\n        _debug token \"$token\"\n\n        if [ -z \"$token\" ]; then\n          _err \"Cannot get domain token $entry\"\n          _clearup\n          _on_issue_err \"$_post_hook\"\n          return 1\n        fi\n\n        uri=\"$(echo \"$entry\" | _egrep_o '\"url\":\"[^\"]*' | cut -d '\"' -f 4 | _head_n 1)\"\n\n        _debug uri \"$uri\"\n\n        if [ -z \"$uri\" ]; then\n          _err \"Cannot get domain URI $entry\"\n          _clearup\n          _on_issue_err \"$_post_hook\"\n          return 1\n        fi\n        keyauthorization=\"$token.$thumbprint\"\n        _debug keyauthorization \"$keyauthorization\"\n      fi\n\n      dvlist=\"$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url\"\n      _debug dvlist \"$dvlist\"\n\n      vlist=\"$vlist$dvlist$dvsep\"\n\n    done\n    _debug vlist \"$vlist\"\n    #add entry\n    dns_entries=\"\"\n    dnsadded=\"\"\n    ventries=$(echo \"$vlist\" | tr \"$dvsep\" ' ')\n    _alias_index=1\n    for ventry in $ventries; do\n      d=$(echo \"$ventry\" | cut -d \"$sep\" -f 1)\n      keyauthorization=$(echo \"$ventry\" | cut -d \"$sep\" -f 2)\n      vtype=$(echo \"$ventry\" | cut -d \"$sep\" -f 4)\n      _currentRoot=$(echo \"$ventry\" | cut -d \"$sep\" -f 5)\n      _authz_url=$(echo \"$ventry\" | cut -d \"$sep\" -f 6)\n      _debug d \"$d\"\n      if [ \"$keyauthorization\" = \"$STATE_VERIFIED\" ]; then\n        _debug \"$d has already been verified, skipping $vtype.\"\n        _alias_index=\"$(_math \"$_alias_index\" + 1)\"\n        continue\n      fi\n\n      if [ \"$vtype\" = \"$VTYPE_DNS\" ]; then\n        dnsadded='0'\n        _dns_root_d=\"$d\"\n        if _startswith \"$_dns_root_d\" \"*.\"; then\n          _dns_root_d=\"$(echo \"$_dns_root_d\" | sed 's/*.//')\"\n        fi\n        _d_alias=\"$(_getfield \"$_challenge_alias\" \"$_alias_index\")\"\n        test \"$_d_alias\" = \"$NO_VALUE\" && _d_alias=\"\"\n        _alias_index=\"$(_math \"$_alias_index\" + 1)\"\n        _debug \"_d_alias\" \"$_d_alias\"\n        if [ \"$_d_alias\" ]; then\n          if _startswith \"$_d_alias\" \"$DNS_ALIAS_PREFIX\"; then\n            txtdomain=\"$(echo \"$_d_alias\" | sed \"s/$DNS_ALIAS_PREFIX//\")\"\n          else\n            txtdomain=\"_acme-challenge.$_d_alias\"\n          fi\n          dns_entry=\"${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$txtdomain$dvsep$_currentRoot\"\n        else\n          txtdomain=\"_acme-challenge.$_dns_root_d\"\n          dns_entry=\"${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$dvsep$_currentRoot\"\n        fi\n\n        _debug txtdomain \"$txtdomain\"\n        txt=\"$(printf \"%s\" \"$keyauthorization\" | _digest \"sha256\" | _url_replace)\"\n        _debug txt \"$txt\"\n\n        d_api=\"$(_findHook \"$_dns_root_d\" $_SUB_FOLDER_DNSAPI \"$_currentRoot\")\"\n        _debug d_api \"$d_api\"\n\n        dns_entry=\"$dns_entry$dvsep$txt${dvsep}$d_api\"\n        _debug2 dns_entry \"$dns_entry\"\n        if [ \"$d_api\" ]; then\n          _debug \"Found domain API file: $d_api\"\n        else\n          if [ \"$_currentRoot\" != \"$W_DNS\" ]; then\n            _err \"Cannot find DNS API hook for: $_currentRoot\"\n            _info \"You need to add the TXT record manually.\"\n          fi\n          _info \"$(__red \"Add the following TXT record:\")\"\n          _info \"$(__red \"Domain: '$(__green \"$txtdomain\")'\")\"\n          _info \"$(__red \"TXT value: '$(__green \"$txt\")'\")\"\n          _info \"$(__red \"Please make sure to prepend '_acme-challenge.' to your domain\")\"\n          _info \"$(__red \"so that the resulting subdomain is: $txtdomain\")\"\n          continue\n        fi\n\n        (\n          if ! . \"$d_api\"; then\n            _err \"Error loading file $d_api. Please check your API file and try again.\"\n            return 1\n          fi\n\n          addcommand=\"${_currentRoot}_add\"\n          if ! _exists \"$addcommand\"; then\n            _err \"It seems that your API file is incorrect. Make sure it has a function named: $addcommand\"\n            return 1\n          fi\n          _info \"Adding TXT value: $txt for domain: $txtdomain\"\n          if ! $addcommand \"$txtdomain\" \"$txt\"; then\n            _err \"Error adding TXT record to domain: $txtdomain\"\n            return 1\n          fi\n          _info \"The TXT record has been successfully added.\"\n        )\n\n        if [ \"$?\" != \"0\" ]; then\n          _on_issue_err \"$_post_hook\" \"$vlist\"\n          _clearup\n          return 1\n        fi\n        dns_entries=\"$dns_entries$dns_entry\n\"\n        _debug2 \"$dns_entries\"\n        dnsadded='1'\n      fi\n    done\n\n    if [ \"$dnsadded\" = '0' ]; then\n      _savedomainconf \"Le_Vlist\" \"$vlist\"\n      _debug \"DNS record not yet added. Will save to $DOMAIN_CONF and exit.\"\n      _err \"Please add the TXT records to the domains, and re-run with --renew.\"\n      _on_issue_err \"$_post_hook\"\n      _clearup\n      # If asked to be in manual DNS mode, flag this exit with a separate\n      # error so it can be distinguished from other failures.\n      return $CODE_DNS_MANUAL\n    fi\n\n  fi\n\n  if [ \"$dns_entries\" ]; then\n    if [ -z \"$Le_DNSSleep\" ]; then\n      _info \"Let's check each DNS record now. Sleeping for 20 seconds first.\"\n      _sleep 20\n      if ! _check_dns_entries; then\n        _err \"Error checking DNS.\"\n        _on_issue_err \"$_post_hook\"\n        _clearup\n        return 1\n      fi\n    else\n      _savedomainconf \"Le_DNSSleep\" \"$Le_DNSSleep\"\n      _info \"Sleeping for $(__green $Le_DNSSleep) seconds to wait for the the TXT records to take effect\"\n      _sleep \"$Le_DNSSleep\"\n    fi\n  fi\n\n  NGINX_RESTORE_VLIST=\"\"\n  _debug \"OK, let's start verification\"\n\n  _ncIndex=1\n  ventries=$(echo \"$vlist\" | tr \"$dvsep\" ' ')\n  for ventry in $ventries; do\n    d=$(echo \"$ventry\" | cut -d \"$sep\" -f 1)\n    keyauthorization=$(echo \"$ventry\" | cut -d \"$sep\" -f 2)\n    uri=$(echo \"$ventry\" | cut -d \"$sep\" -f 3)\n    vtype=$(echo \"$ventry\" | cut -d \"$sep\" -f 4)\n    _currentRoot=$(echo \"$ventry\" | cut -d \"$sep\" -f 5)\n    _authz_url=$(echo \"$ventry\" | cut -d \"$sep\" -f 6)\n    if [ \"$keyauthorization\" = \"$STATE_VERIFIED\" ]; then\n      _info \"$d is already verified, skipping $vtype.\"\n      continue\n    fi\n\n    _info \"Verifying: $d\"\n    _debug \"d\" \"$d\"\n    _debug \"keyauthorization\" \"$keyauthorization\"\n    _debug \"uri\" \"$uri\"\n    _debug \"_authz_url\" \"$_authz_url\"\n    removelevel=\"\"\n    token=\"$(printf \"%s\" \"$keyauthorization\" | cut -d '.' -f 1)\"\n\n    _debug \"_currentRoot\" \"$_currentRoot\"\n\n    if [ \"$vtype\" = \"$VTYPE_HTTP\" ]; then\n      if [ \"$_currentRoot\" = \"$NO_VALUE\" ]; then\n        _info \"Standalone mode server\"\n        _ncaddr=\"$(_getfield \"$_local_addr\" \"$_ncIndex\")\"\n        _ncIndex=\"$(_math $_ncIndex + 1)\"\n        _startserver \"$keyauthorization\" \"$_ncaddr\"\n        if [ \"$?\" != \"0\" ]; then\n          _clearup\n          _on_issue_err \"$_post_hook\" \"$vlist\"\n          return 1\n        fi\n        sleep 1\n        _debug serverproc \"$serverproc\"\n      elif [ \"$_currentRoot\" = \"$MODE_STATELESS\" ]; then\n        _info \"Stateless mode for domain: $d\"\n        _sleep 1\n      elif _startswith \"$_currentRoot\" \"$NGINX\"; then\n        _info \"Nginx mode for domain: $d\"\n        #set up nginx server\n        FOUND_REAL_NGINX_CONF=\"\"\n        BACKUP_NGINX_CONF=\"\"\n        if ! _setNginx \"$d\" \"$_currentRoot\" \"$thumbprint\"; then\n          _clearup\n          _on_issue_err \"$_post_hook\" \"$vlist\"\n          return 1\n        fi\n\n        if [ \"$FOUND_REAL_NGINX_CONF\" ]; then\n          _realConf=\"$FOUND_REAL_NGINX_CONF\"\n          _backup=\"$BACKUP_NGINX_CONF\"\n          _debug _realConf \"$_realConf\"\n          NGINX_RESTORE_VLIST=\"$d$sep$_realConf$sep$_backup$dvsep$NGINX_RESTORE_VLIST\"\n        fi\n        _sleep 1\n      else\n        if [ \"$_currentRoot\" = \"apache\" ]; then\n          wellknown_path=\"$ACME_DIR\"\n        else\n          wellknown_path=\"$_currentRoot/.well-known/acme-challenge\"\n          if [ ! -d \"$_currentRoot/.well-known\" ]; then\n            removelevel='1'\n          elif [ ! -d \"$_currentRoot/.well-known/acme-challenge\" ]; then\n            removelevel='2'\n          else\n            removelevel='3'\n          fi\n        fi\n\n        _debug wellknown_path \"$wellknown_path\"\n\n        _debug \"Writing token: $token to $wellknown_path/$token\"\n\n        # Ensure .well-known is visible to web server user/group\n        # https://github.com/Neilpang/acme.sh/pull/32\n        if ! (umask ugo+rx &&\n          mkdir -p \"$wellknown_path\" &&\n          printf \"%s\" \"$keyauthorization\" >\"$wellknown_path/$token\"); then\n          _err \"$d: Cannot write token to file: $wellknown_path/$token\"\n          _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n          _clearup\n          _on_issue_err \"$_post_hook\" \"$vlist\"\n          return 1\n        fi\n        if ! chmod a+r \"$wellknown_path/$token\"; then\n          _debug \"chmod failed, will just continue.\"\n        fi\n      fi\n    elif [ \"$vtype\" = \"$VTYPE_ALPN\" ]; then\n      acmevalidationv1=\"$(printf \"%s\" \"$keyauthorization\" | _digest \"sha256\" \"hex\")\"\n      _debug acmevalidationv1 \"$acmevalidationv1\"\n      if ! _starttlsserver \"$d\" \"\" \"$Le_TLSPort\" \"$keyauthorization\" \"$_ncaddr\" \"$acmevalidationv1\"; then\n        _err \"Error starting TLS server.\"\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        _clearup\n        _on_issue_err \"$_post_hook\" \"$vlist\"\n        return 1\n      fi\n    fi\n\n    if ! __trigger_validation \"$uri\" \"$keyauthorization\" \"$vtype\"; then\n      _err \"$d: Cannot get challenge: $response\"\n      _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n      _clearup\n      _on_issue_err \"$_post_hook\" \"$vlist\"\n      return 1\n    fi\n\n    if [ \"$code\" ] && [ \"$code\" != '202' ]; then\n      if [ \"$code\" = '200' ]; then\n        _debug \"Trigger validation code: $code\"\n      else\n        _err \"$d: Challenge error: $response\"\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        _clearup\n        _on_issue_err \"$_post_hook\" \"$vlist\"\n        return 1\n      fi\n    fi\n\n    waittimes=0\n    if [ -z \"$MAX_RETRY_TIMES\" ]; then\n      MAX_RETRY_TIMES=30\n    fi\n\n    _debug \"Let's check the authz status\"\n    while true; do\n      waittimes=$(_math \"$waittimes\" + 1)\n      if [ \"$waittimes\" -ge \"$MAX_RETRY_TIMES\" ]; then\n        _err \"$d: Timeout\"\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        _clearup\n        _on_issue_err \"$_post_hook\" \"$vlist\"\n        return 1\n      fi\n\n      _debug2 original \"$response\"\n\n      response=\"$(echo \"$response\" | _normalizeJson)\"\n      _debug2 response \"$response\"\n\n      status=$(echo \"$response\" | _egrep_o '\"status\":\"[^\"]*' | cut -d : -f 2 | tr -d '\"')\n      _debug2 status \"$status\"\n      if _contains \"$status\" \"invalid\"; then\n        error=\"$(echo \"$response\" | _egrep_o '\"error\":\\{[^\\}]*')\"\n        _debug2 error \"$error\"\n        errordetail=\"$(echo \"$error\" | _egrep_o '\"detail\": *\"[^\"]*' | cut -d '\"' -f 4)\"\n        _debug2 errordetail \"$errordetail\"\n        if [ \"$errordetail\" ]; then\n          _err \"$d: Invalid status. Verification error details: $errordetail\"\n        else\n          _err \"$d: Invalid status, Verification error: $error\"\n        fi\n        if [ \"$DEBUG\" ]; then\n          if [ \"$vtype\" = \"$VTYPE_HTTP\" ]; then\n            _debug \"Debug: GET token URL.\"\n            if _isIPv6 \"$d\"; then\n              host=\"[$d]\"\n            else\n              host=\"$d\"\n            fi\n            _get \"http://$host/.well-known/acme-challenge/$token\" \"\" 1\n          fi\n        fi\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        _clearup\n        _on_issue_err \"$_post_hook\" \"$vlist\"\n        return 1\n      fi\n\n      if _contains \"$status\" \"valid\"; then\n        _info \"$(__green Success)\"\n        _stopserver \"$serverproc\"\n        serverproc=\"\"\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        break\n      fi\n\n      if _contains \"$status\" \"pending\"; then\n        _info \"Pending. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)\"\n      elif _contains \"$status\" \"processing\"; then\n        _info \"Processing. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)\"\n      else\n        _err \"$d: Unknown status: $status. Verification error: $response\"\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        _clearup\n        _on_issue_err \"$_post_hook\" \"$vlist\"\n        return 1\n      fi\n      _debug \"Sleep 2 seconds before verifying again\"\n      _sleep 2\n      _debug \"Checking\"\n\n      _send_signed_request \"$_authz_url\"\n\n      if [ \"$?\" != \"0\" ]; then\n        _err \"$d: Invalid code. Verification error: $response\"\n        _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n        _clearup\n        _on_issue_err \"$_post_hook\" \"$vlist\"\n        return 1\n      fi\n      _retryafter=$(echo \"$responseHeaders\" | grep -i \"^Retry-After *: *[0-9]\\+ *\" | cut -d : -f 2 | tr -d ' ' | tr -d '\\r')\n      _sleep_overload_retry_sec=$_retryafter\n      if [ \"$_sleep_overload_retry_sec\" ]; then\n        if [ $_sleep_overload_retry_sec -le 600 ]; then\n          _sleep $_sleep_overload_retry_sec\n        else\n          _info \"The retryafter=$_retryafter value is too large (> 600), will not retry anymore.\"\n          _clearupwebbroot \"$_currentRoot\" \"$removelevel\" \"$token\"\n          _clearup\n          _on_issue_err \"$_post_hook\" \"$vlist\"\n          return 1\n        fi\n      fi\n    done\n\n  done\n\n  _clearup\n  _info \"Verification finished, beginning signing.\"\n  der=\"$(_getfile \"${CSR_PATH}\" \"${BEGIN_CSR}\" \"${END_CSR}\" | tr -d \"\\r\\n\" | _url_replace)\"\n\n  _info \"Let's finalize the order.\"\n  _info \"Le_OrderFinalize\" \"$Le_OrderFinalize\"\n  if ! _send_signed_request \"${Le_OrderFinalize}\" \"{\\\"csr\\\": \\\"$der\\\"}\"; then\n    _err \"Signing failed.\"\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n  if [ \"$code\" != \"200\" ]; then\n    _err \"Signing failed. Finalize code was not 200.\"\n    _err \"$response\"\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n  if [ -z \"$Le_LinkOrder\" ]; then\n    Le_LinkOrder=\"$(echo \"$responseHeaders\" | grep -i '^Location.*$' | _tail_n 1 | tr -d \"\\r\\n \\t\" | cut -d \":\" -f 2-)\"\n  fi\n\n  _savedomainconf \"Le_LinkOrder\" \"$Le_LinkOrder\"\n\n  _link_cert_retry=0\n  _MAX_CERT_RETRY=30\n  while [ \"$_link_cert_retry\" -lt \"$_MAX_CERT_RETRY\" ]; do\n    if _contains \"$response\" \"\\\"status\\\":\\\"valid\\\"\"; then\n      _debug \"Order status is valid.\"\n      Le_LinkCert=\"$(echo \"$response\" | _egrep_o '\"certificate\" *: *\"[^\"]*\"' | cut -d '\"' -f 4)\"\n      _debug Le_LinkCert \"$Le_LinkCert\"\n      if [ -z \"$Le_LinkCert\" ]; then\n        _err \"A signing error occurred: could not find Le_LinkCert\"\n        _err \"$response\"\n        _on_issue_err \"$_post_hook\"\n        return 1\n      fi\n      break\n    elif _contains \"$response\" \"\\\"ready\\\"\"; then\n      _info \"Order status is 'ready', let's sleep and retry.\"\n      _retryafter=$(echo \"$responseHeaders\" | grep -i \"^Retry-After *:\" | cut -d : -f 2 | tr -d ' ' | tr -d '\\r')\n      _debug \"_retryafter\" \"$_retryafter\"\n      if [ \"$_retryafter\" ]; then\n        _info \"Sleeping for $_retryafter seconds then retrying\"\n        _sleep $_retryafter\n      else\n        _sleep 2\n      fi\n    elif _contains \"$response\" \"\\\"processing\\\"\"; then\n      _info \"Order status is 'processing', let's sleep and retry.\"\n      _retryafter=$(echo \"$responseHeaders\" | grep -i \"^Retry-After *:\" | cut -d : -f 2 | tr -d ' ' | tr -d '\\r')\n      _debug \"_retryafter\" \"$_retryafter\"\n      if [ \"$_retryafter\" ]; then\n        _info \"Sleeping for $_retryafter seconds then retrying\"\n        _sleep $_retryafter\n      else\n        _sleep 2\n      fi\n    else\n      _err \"Signing error: wrong status\"\n      _err \"$response\"\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n    #the order is processing, so we are going to poll order status\n    if [ -z \"$Le_LinkOrder\" ]; then\n      _err \"Signing error: could not get order link location header\"\n      _err \"responseHeaders\" \"$responseHeaders\"\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n    _info \"Polling order status: $Le_LinkOrder\"\n    if ! _send_signed_request \"$Le_LinkOrder\"; then\n      _err \"Signing failed. Could not make POST request to Le_LinkOrder for cert: $Le_LinkOrder.\"\n      _err \"$response\"\n      _on_issue_err \"$_post_hook\"\n      return 1\n    fi\n    _link_cert_retry=\"$(_math $_link_cert_retry + 1)\"\n  done\n\n  if [ -z \"$Le_LinkCert\" ]; then\n    _err \"Signing failed. Could not get Le_LinkCert, and stopped retrying after reaching the retry limit.\"\n    _err \"$response\"\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n  _info \"Downloading cert.\"\n  _info \"Le_LinkCert\" \"$Le_LinkCert\"\n  if ! _send_signed_request \"$Le_LinkCert\"; then\n    _err \"Signing failed. Could not download cert: $Le_LinkCert.\"\n    _err \"$response\"\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n\n  echo \"$response\" >\"$CERT_PATH\"\n  _split_cert_chain \"$CERT_PATH\" \"$CERT_FULLCHAIN_PATH\" \"$CA_CERT_PATH\"\n  if [ -z \"$_preferred_chain\" ]; then\n    _preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN)\n  fi\n  if [ \"$_preferred_chain\" ] && [ -f \"$CERT_FULLCHAIN_PATH\" ]; then\n    if [ \"$DEBUG\" ]; then\n      _debug \"Default chain issuers: \" \"$(_get_chain_issuers \"$CERT_FULLCHAIN_PATH\")\"\n    fi\n    if ! _match_issuer \"$CERT_FULLCHAIN_PATH\" \"$_preferred_chain\"; then\n      rels=\"$(echo \"$responseHeaders\" | tr -d ' <>' | grep -i \"^link:\" | grep -i 'rel=\"alternate\"' | cut -d : -f 2- | cut -d ';' -f 1)\"\n      _debug2 \"rels\" \"$rels\"\n      for rel in $rels; do\n        _info \"Trying rel: $rel\"\n        if ! _send_signed_request \"$rel\"; then\n          _err \"Signing failed, could not download cert: $rel\"\n          _err \"$response\"\n          continue\n        fi\n        _relcert=\"$CERT_PATH.alt\"\n        _relfullchain=\"$CERT_FULLCHAIN_PATH.alt\"\n        _relca=\"$CA_CERT_PATH.alt\"\n        echo \"$response\" >\"$_relcert\"\n        _split_cert_chain \"$_relcert\" \"$_relfullchain\" \"$_relca\"\n        if [ \"$DEBUG\" ]; then\n          _debug \"rel chain issuers: \" \"$(_get_chain_issuers \"$_relfullchain\")\"\n        fi\n        if _match_issuer \"$_relfullchain\" \"$_preferred_chain\"; then\n          _info \"Matched issuer in: $rel\"\n          cat $_relcert >\"$CERT_PATH\"\n          cat $_relfullchain >\"$CERT_FULLCHAIN_PATH\"\n          cat $_relca >\"$CA_CERT_PATH\"\n          rm -f \"$_relcert\"\n          rm -f \"$_relfullchain\"\n          rm -f \"$_relca\"\n          break\n        fi\n        rm -f \"$_relcert\"\n        rm -f \"$_relfullchain\"\n        rm -f \"$_relca\"\n      done\n    fi\n  fi\n\n  _debug \"Le_LinkCert\" \"$Le_LinkCert\"\n  _savedomainconf \"Le_LinkCert\" \"$Le_LinkCert\"\n\n  if [ -z \"$Le_LinkCert\" ] || ! _checkcert \"$CERT_PATH\"; then\n    response=\"$(echo \"$response\" | _dbase64 \"multiline\" | tr -d '\\0' | _normalizeJson)\"\n    _err \"Signing failed: $(echo \"$response\" | _egrep_o '\"detail\":\"[^\"]*\"')\"\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n\n  if [ \"$Le_LinkCert\" ]; then\n    _info \"$(__green \"Cert success.\")\"\n    cat \"$CERT_PATH\"\n\n    _info \"Your cert is in: $(__green \"$CERT_PATH\")\"\n\n    if [ -f \"$CERT_KEY_PATH\" ]; then\n      _info \"Your cert key is in: $(__green \"$CERT_KEY_PATH\")\"\n    fi\n\n    if [ ! \"$USER_PATH\" ] || [ ! \"$_ACME_IN_CRON\" ]; then\n      USER_PATH=\"$PATH\"\n      _saveaccountconf \"USER_PATH\" \"$USER_PATH\"\n    fi\n  fi\n\n  [ -f \"$CA_CERT_PATH\" ] && _info \"The intermediate CA cert is in: $(__green \"$CA_CERT_PATH\")\"\n  [ -f \"$CERT_FULLCHAIN_PATH\" ] && _info \"And the full-chain cert is in: $(__green \"$CERT_FULLCHAIN_PATH\")\"\n  if [ \"$Le_ForceNewDomainKey\" ] && [ -e \"$Le_Next_Domain_Key\" ]; then\n    _info \"Your pre-generated key for future cert key changes is in: $(__green \"$Le_Next_Domain_Key\")\"\n  fi\n\n  Le_CertCreateTime=$(_time)\n  _savedomainconf \"Le_CertCreateTime\" \"$Le_CertCreateTime\"\n\n  Le_CertCreateTimeStr=$(_time2str \"$Le_CertCreateTime\")\n  _savedomainconf \"Le_CertCreateTimeStr\" \"$Le_CertCreateTimeStr\"\n\n  if [ -z \"$Le_RenewalDays\" ] || [ \"$Le_RenewalDays\" -lt \"0\" ]; then\n    Le_RenewalDays=\"$DEFAULT_RENEW\"\n  else\n    _savedomainconf \"Le_RenewalDays\" \"$Le_RenewalDays\"\n  fi\n\n  if [ \"$CA_BUNDLE\" ]; then\n    _saveaccountconf CA_BUNDLE \"$CA_BUNDLE\"\n  else\n    _clearaccountconf \"CA_BUNDLE\"\n  fi\n\n  if [ \"$CA_PATH\" ]; then\n    _saveaccountconf CA_PATH \"$CA_PATH\"\n  else\n    _clearaccountconf \"CA_PATH\"\n  fi\n\n  if [ \"$HTTPS_INSECURE\" ]; then\n    _saveaccountconf HTTPS_INSECURE \"$HTTPS_INSECURE\"\n  else\n    _clearaccountconf \"HTTPS_INSECURE\"\n  fi\n\n  if [ \"$Le_Listen_V4\" ]; then\n    _savedomainconf \"Le_Listen_V4\" \"$Le_Listen_V4\"\n    _cleardomainconf Le_Listen_V6\n  elif [ \"$Le_Listen_V6\" ]; then\n    _savedomainconf \"Le_Listen_V6\" \"$Le_Listen_V6\"\n    _cleardomainconf Le_Listen_V4\n  fi\n\n  if [ \"$Le_ForceNewDomainKey\" = \"1\" ]; then\n    _savedomainconf \"Le_ForceNewDomainKey\" \"$Le_ForceNewDomainKey\"\n  else\n    _cleardomainconf Le_ForceNewDomainKey\n  fi\n  if [ \"$_notAfter\" ]; then\n    Le_NextRenewTime=$(_date2time \"$_notAfter\")\n    Le_NextRenewTimeStr=\"$_notAfter\"\n    if [ \"$_valid_to\" ] && ! _startswith \"$_valid_to\" \"+\"; then\n      _info \"The domain is set to be valid until: $_valid_to\"\n      _info \"It cannot be renewed automatically\"\n      _info \"See: $_VALIDITY_WIKI\"\n    else\n      _now=$(_time)\n      _debug2 \"_now\" \"$_now\"\n      _lifetime=$(_math $Le_NextRenewTime - $_now)\n      _debug2 \"_lifetime\" \"$_lifetime\"\n      if [ $_lifetime -gt 86400 ]; then\n        #if lifetime is logner than one day, it will renew one day before\n        Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400)\n        Le_NextRenewTimeStr=$(_time2str \"$Le_NextRenewTime\")\n      else\n        #if lifetime is less than 24 hours, it will renew one hour before\n        Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600)\n        Le_NextRenewTimeStr=$(_time2str \"$Le_NextRenewTime\")\n      fi\n    fi\n  else\n    Le_NextRenewTime=$(_math \"$Le_CertCreateTime\" + \"$Le_RenewalDays\" \\* 24 \\* 60 \\* 60)\n    Le_NextRenewTime=$(_math \"$Le_NextRenewTime\" - 86400)\n    Le_NextRenewTimeStr=$(_time2str \"$Le_NextRenewTime\")\n  fi\n  _savedomainconf \"Le_NextRenewTimeStr\" \"$Le_NextRenewTimeStr\"\n  _savedomainconf \"Le_NextRenewTime\" \"$Le_NextRenewTime\"\n\n  #convert to pkcs12\n  Le_PFXPassword=\"$(_readdomainconf Le_PFXPassword)\"\n  if [ \"$Le_PFXPassword\" ]; then\n    _toPkcs \"$CERT_PFX_PATH\" \"$CERT_KEY_PATH\" \"$CERT_PATH\" \"$CA_CERT_PATH\" \"$Le_PFXPassword\"\n  fi\n\n  if [ \"$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain\" ]; then\n    _savedomainconf \"Le_RealCertPath\" \"$_real_cert\"\n    _savedomainconf \"Le_RealCACertPath\" \"$_real_ca\"\n    _savedomainconf \"Le_RealKeyPath\" \"$_real_key\"\n    _savedomainconf \"Le_ReloadCmd\" \"$_reload_cmd\" \"base64\"\n    _savedomainconf \"Le_RealFullChainPath\" \"$_real_fullchain\"\n    if ! _installcert \"$_main_domain\" \"$_real_cert\" \"$_real_key\" \"$_real_ca\" \"$_real_fullchain\" \"$_reload_cmd\"; then\n      return 1\n    fi\n  fi\n\n  if ! _on_issue_success \"$_post_hook\" \"$_renew_hook\"; then\n    _err \"Error calling hook.\"\n    return 1\n  fi\n}\n\n#in_out_cert   out_fullchain   out_ca\n_split_cert_chain() {\n  _certf=\"$1\"\n  _fullchainf=\"$2\"\n  _caf=\"$3\"\n  if [ \"$(grep -- \"$BEGIN_CERT\" \"$_certf\" | wc -l)\" -gt \"1\" ]; then\n    _debug \"Found cert chain\"\n    cat \"$_certf\" >\"$_fullchainf\"\n    _end_n=\"$(grep -n -- \"$END_CERT\" \"$_fullchainf\" | _head_n 1 | cut -d : -f 1)\"\n    _debug _end_n \"$_end_n\"\n    sed -n \"1,${_end_n}p\" \"$_fullchainf\" >\"$_certf\"\n    _end_n=\"$(_math $_end_n + 1)\"\n    sed -n \"${_end_n},9999p\" \"$_fullchainf\" >\"$_caf\"\n  fi\n}\n\n#domain  [isEcc] [server]\nrenew() {\n  Le_Domain=\"$1\"\n  if [ -z \"$Le_Domain\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --renew --domain <domain.tld> [--ecc] [--server server]\"\n    return 1\n  fi\n\n  _isEcc=\"$2\"\n  _renewServer=\"$3\"\n  _debug \"_renewServer\" \"$_renewServer\"\n\n  _initpath \"$Le_Domain\" \"$_isEcc\"\n\n  _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}\n  _info \"$(__green \"Renewing: '$Le_Domain'\")\"\n  if [ ! -f \"$DOMAIN_CONF\" ]; then\n    _info \"'$Le_Domain' is not an issued domain, skipping.\"\n    return $RENEW_SKIP\n  fi\n\n  if [ \"$Le_RenewalDays\" ]; then\n    _savedomainconf Le_RenewalDays \"$Le_RenewalDays\"\n  fi\n\n  . \"$DOMAIN_CONF\"\n  _debug Le_API \"$Le_API\"\n\n  case \"$Le_API\" in\n  \"$CA_LETSENCRYPT_V2_TEST\")\n    _info \"Switching back to $CA_LETSENCRYPT_V2\"\n    Le_API=\"$CA_LETSENCRYPT_V2\"\n    ;;\n  \"$CA_GOOGLE_TEST\")\n    _info \"Switching back to $CA_GOOGLE\"\n    Le_API=\"$CA_GOOGLE\"\n    ;;\n  esac\n\n  if [ \"$_server\" ]; then\n    Le_API=\"$_server\"\n  fi\n  _info \"Renewing using Le_API=$Le_API\"\n\n  _clearAPI\n  _clearCA\n  export ACME_DIRECTORY=\"$Le_API\"\n\n  #reload ca configs\n  _debug2 \"initpath again.\"\n  _initpath \"$Le_Domain\" \"$_isEcc\"\n\n  if [ -z \"$FORCE\" ] && [ \"$Le_NextRenewTime\" ] && [ \"$(_time)\" -lt \"$Le_NextRenewTime\" ]; then\n    _info \"Skipping. Next renewal time is: $(__green \"$Le_NextRenewTimeStr\")\"\n    _info \"Add '$(__red '--force')' to force renewal.\"\n    if [ -z \"$_ACME_IN_RENEWALL\" ]; then\n      if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then\n        _send_notify \"Renew $Le_Domain skipped\" \"Good, the cert is skipped.\" \"$NOTIFY_HOOK\" \"$RENEW_SKIP\"\n      fi\n    fi\n    return \"$RENEW_SKIP\"\n  fi\n\n  if [ \"$_ACME_IN_CRON\" = \"1\" ] && [ -z \"$Le_CertCreateTime\" ]; then\n    _info \"Skipping invalid cert for: $Le_Domain\"\n    return $RENEW_SKIP\n  fi\n\n  _ACME_IS_RENEW=\"1\"\n  Le_ReloadCmd=\"$(_readdomainconf Le_ReloadCmd)\"\n  Le_PreHook=\"$(_readdomainconf Le_PreHook)\"\n  Le_PostHook=\"$(_readdomainconf Le_PostHook)\"\n  Le_RenewHook=\"$(_readdomainconf Le_RenewHook)\"\n  Le_Preferred_Chain=\"$(_readdomainconf Le_Preferred_Chain)\"\n  Le_Certificate_Profile=\"$(_readdomainconf Le_Certificate_Profile)\"\n  Le_Valid_From=\"$(_readdomainconf Le_Valid_From)\"\n  Le_Valid_To=\"$(_readdomainconf Le_Valid_To)\"\n  Le_ExtKeyUse=\"$(_readdomainconf Le_ExtKeyUse)\"\n\n  # When renewing from an old version, the empty Le_Keylength means 2048.\n  # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over\n  # time but an empty value implies 2048 specifically.\n  Le_Keylength=\"$(_readdomainconf Le_Keylength)\"\n  if [ -z \"$Le_Keylength\" ]; then\n    Le_Keylength=2048\n  fi\n  if [ \"$CA_LETSENCRYPT_V2\" = \"$Le_API\" ]; then\n    #letsencrypt doesn't support ocsp anymore\n    if [ \"$Le_OCSP_Staple\" ]; then\n      export Le_OCSP_Staple=\"\"\n      _cleardomainconf Le_OCSP_Staple\n    fi\n  fi\n  issue \"$Le_Webroot\" \"$Le_Domain\" \"$Le_Alt\" \"$Le_Keylength\" \"$Le_RealCertPath\" \"$Le_RealKeyPath\" \"$Le_RealCACertPath\" \"$Le_ReloadCmd\" \"$Le_RealFullChainPath\" \"$Le_PreHook\" \"$Le_PostHook\" \"$Le_RenewHook\" \"$Le_LocalAddress\" \"$Le_ChallengeAlias\" \"$Le_Preferred_Chain\" \"$Le_Valid_From\" \"$Le_Valid_To\" \"$Le_Certificate_Profile\" \"$Le_ExtKeyUse\"\n  res=\"$?\"\n  if [ \"$res\" != \"0\" ]; then\n    return \"$res\"\n  fi\n\n  if [ \"$Le_DeployHook\" ]; then\n    _deploy \"$Le_Domain\" \"$Le_DeployHook\"\n    res=\"$?\"\n  fi\n\n  _ACME_IS_RENEW=\"\"\n  if [ -z \"$_ACME_IN_RENEWALL\" ]; then\n    if [ \"$res\" = \"0\" ]; then\n      if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then\n        _send_notify \"Renew $d success\" \"Good, the cert is renewed.\" \"$NOTIFY_HOOK\" 0\n      fi\n    else\n      if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then\n        _send_notify \"Renew $d error\" \"There is an error.\" \"$NOTIFY_HOOK\" 1\n      fi\n    fi\n  fi\n\n  return \"$res\"\n}\n\n#renewAll  [stopRenewOnError] [server]\nrenewAll() {\n  _initpath\n  _clearCA\n  _stopRenewOnError=\"$1\"\n  _debug \"_stopRenewOnError\" \"$_stopRenewOnError\"\n\n  _server=\"$2\"\n  _debug \"_server\" \"$_server\"\n\n  _ret=\"0\"\n  _success_msg=\"\"\n  _error_msg=\"\"\n  _skipped_msg=\"\"\n  _error_level=$NOTIFY_LEVEL_SKIP\n  _notify_code=$RENEW_SKIP\n  _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}\n  _debug \"_set_level\" \"$_set_level\"\n  export _ACME_IN_RENEWALL=1\n  for di in \"${CERT_HOME}\"/*[.:]*/; do\n    _debug di \"$di\"\n    if ! [ -d \"$di\" ]; then\n      _debug \"Not a directory, skipping: $di\"\n      continue\n    fi\n    d=$(basename \"$di\")\n    _debug d \"$d\"\n    (\n      if _endswith \"$d\" \"$ECC_SUFFIX\"; then\n        _isEcc=$(echo \"$d\" | cut -d \"$ECC_SEP\" -f 2)\n        d=$(echo \"$d\" | cut -d \"$ECC_SEP\" -f 1)\n      fi\n      renew \"$d\" \"$_isEcc\" \"$_server\"\n    )\n    rc=\"$?\"\n    _debug \"Return code: $rc\"\n    if [ \"$rc\" = \"0\" ]; then\n      if [ $_error_level -gt $NOTIFY_LEVEL_RENEW ]; then\n        _error_level=\"$NOTIFY_LEVEL_RENEW\"\n        _notify_code=0\n      fi\n\n      if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then\n        if [ \"$NOTIFY_MODE\" = \"$NOTIFY_MODE_CERT\" ]; then\n          _send_notify \"Renew $d success\" \"Good, the cert is renewed.\" \"$NOTIFY_HOOK\" 0\n        fi\n      fi\n\n      _success_msg=\"${_success_msg}    $d\n\"\n    elif [ \"$rc\" = \"$RENEW_SKIP\" ]; then\n      if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then\n        _error_level=\"$NOTIFY_LEVEL_SKIP\"\n        _notify_code=$RENEW_SKIP\n      fi\n\n      if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then\n        if [ \"$NOTIFY_MODE\" = \"$NOTIFY_MODE_CERT\" ]; then\n          _send_notify \"Renew $d skipped\" \"Good, the cert is skipped.\" \"$NOTIFY_HOOK\" \"$RENEW_SKIP\"\n        fi\n      fi\n\n      _info \"Skipped $d\"\n      _skipped_msg=\"${_skipped_msg}    $d\n\"\n    else\n      if [ $_error_level -gt $NOTIFY_LEVEL_ERROR ]; then\n        _error_level=\"$NOTIFY_LEVEL_ERROR\"\n        _notify_code=1\n      fi\n\n      if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then\n        if [ \"$NOTIFY_MODE\" = \"$NOTIFY_MODE_CERT\" ]; then\n          _send_notify \"Renew $d error\" \"There is an error.\" \"$NOTIFY_HOOK\" 1\n        fi\n      fi\n\n      _error_msg=\"${_error_msg}    $d\n\"\n      if [ \"$_stopRenewOnError\" ]; then\n        _err \"Error renewing $d, stopping.\"\n        _ret=\"$rc\"\n        break\n      else\n        _ret=\"$rc\"\n        _err \"Error renewing $d.\"\n      fi\n    fi\n  done\n  _debug _error_level \"$_error_level\"\n  _debug _set_level \"$_set_level\"\n  if [ $_error_level -le $_set_level ]; then\n    if [ -z \"$NOTIFY_MODE\" ] || [ \"$NOTIFY_MODE\" = \"$NOTIFY_MODE_BULK\" ]; then\n      _msg_subject=\"Renew\"\n      if [ \"$_error_msg\" ]; then\n        _msg_subject=\"${_msg_subject} Error\"\n        _msg_data=\"Errored certs:\n${_error_msg}\n\"\n      fi\n      if [ \"$_success_msg\" ]; then\n        _msg_subject=\"${_msg_subject} Success\"\n        _msg_data=\"${_msg_data}Successful certs:\n${_success_msg}\n\"\n      fi\n      if [ \"$_skipped_msg\" ]; then\n        _msg_subject=\"${_msg_subject} Skipped\"\n        _msg_data=\"${_msg_data}Skipped certs:\n${_skipped_msg}\n\"\n      fi\n\n      _send_notify \"$_msg_subject\" \"$_msg_data\" \"$NOTIFY_HOOK\" \"$_notify_code\"\n    fi\n  fi\n\n  return \"$_ret\"\n}\n\n#csr webroot\nsigncsr() {\n  _csrfile=\"$1\"\n  _csrW=\"$2\"\n  if [ -z \"$_csrfile\" ] || [ -z \"$_csrW\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --sign-csr --csr <csr-file> --webroot <directory>\"\n    return 1\n  fi\n\n  _real_cert=\"$3\"\n  _real_key=\"$4\"\n  _real_ca=\"$5\"\n  _reload_cmd=\"$6\"\n  _real_fullchain=\"$7\"\n  _pre_hook=\"${8}\"\n  _post_hook=\"${9}\"\n  _renew_hook=\"${10}\"\n  _local_addr=\"${11}\"\n  _challenge_alias=\"${12}\"\n  _preferred_chain=\"${13}\"\n  _valid_f=\"${14}\"\n  _valid_t=\"${15}\"\n  _cert_prof=\"${16}\"\n  _en_key_usage=\"${17}\"\n\n  _csrsubj=$(_readSubjectFromCSR \"$_csrfile\")\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Cannot read subject from CSR: $_csrfile\"\n    return 1\n  fi\n  _debug _csrsubj \"$_csrsubj\"\n  if _contains \"$_csrsubj\" ' ' || ! _contains \"$_csrsubj\" '.'; then\n    _info \"It seems that the subject $_csrsubj is not a valid domain name. Dropping it.\"\n    _csrsubj=\"\"\n  fi\n\n  _csrdomainlist=$(_readSubjectAltNamesFromCSR \"$_csrfile\")\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Cannot read domain list from CSR: $_csrfile\"\n    return 1\n  fi\n  _debug \"_csrdomainlist\" \"$_csrdomainlist\"\n\n  if [ -z \"$_csrsubj\" ]; then\n    _csrsubj=\"$(_getfield \"$_csrdomainlist\" 1)\"\n    _debug _csrsubj \"$_csrsubj\"\n    _csrdomainlist=\"$(echo \"$_csrdomainlist\" | cut -d , -f 2-)\"\n    _debug \"_csrdomainlist\" \"$_csrdomainlist\"\n  fi\n\n  if [ -z \"$_csrsubj\" ]; then\n    _err \"Cannot read subject from CSR: $_csrfile\"\n    return 1\n  fi\n\n  _csrkeylength=$(_readKeyLengthFromCSR \"$_csrfile\")\n  if [ \"$?\" != \"0\" ] || [ -z \"$_csrkeylength\" ]; then\n    _err \"Cannot read key length from CSR: $_csrfile\"\n    return 1\n  fi\n\n  _initpath \"$_csrsubj\" \"$_csrkeylength\"\n  mkdir -p \"$DOMAIN_PATH\"\n\n  _info \"Copying CSR to: $CSR_PATH\"\n  cp \"$_csrfile\" \"$CSR_PATH\"\n\n  issue \"$_csrW\" \"$_csrsubj\" \"$_csrdomainlist\" \"$_csrkeylength\" \"$_real_cert\" \"$_real_key\" \"$_real_ca\" \"$_reload_cmd\" \"$_real_fullchain\" \"$_pre_hook\" \"$_post_hook\" \"$_renew_hook\" \"$_local_addr\" \"$_challenge_alias\" \"$_preferred_chain\" \"$_valid_f\" \"$_valid_t\" \"$_cert_prof\" \"$_en_key_usage\"\n\n}\n\nshowcsr() {\n  _csrfile=\"$1\"\n  _csrd=\"$2\"\n  if [ -z \"$_csrfile\" ] && [ -z \"$_csrd\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --show-csr --csr <csr-file>\"\n    return 1\n  fi\n\n  _initpath\n\n  _csrsubj=$(_readSubjectFromCSR \"$_csrfile\")\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Cannot read subject from CSR: $_csrfile\"\n    return 1\n  fi\n  if [ -z \"$_csrsubj\" ]; then\n    _info \"The subject is empty\"\n  fi\n\n  _info \"Subject=$_csrsubj\"\n\n  _csrdomainlist=$(_readSubjectAltNamesFromCSR \"$_csrfile\")\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Cannot read domain list from CSR: $_csrfile\"\n    return 1\n  fi\n  _debug \"_csrdomainlist\" \"$_csrdomainlist\"\n\n  _info \"SubjectAltNames=$_csrdomainlist\"\n\n  _csrkeylength=$(_readKeyLengthFromCSR \"$_csrfile\")\n  if [ \"$?\" != \"0\" ] || [ -z \"$_csrkeylength\" ]; then\n    _err \"Cannot read key length from CSR: $_csrfile\"\n    return 1\n  fi\n  _info \"KeyLength=$_csrkeylength\"\n}\n\n#listraw  domain\nlist() {\n  _raw=\"$1\"\n  _domain=\"$2\"\n  _initpath\n\n  _sep=\"|\"\n  if [ \"$_raw\" ]; then\n    if [ -z \"$_domain\" ]; then\n      printf \"%s\\n\" \"Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Profile${_sep}CA${_sep}Created${_sep}Renew\"\n    fi\n    for di in \"${CERT_HOME}\"/*.* \"${CERT_HOME}\"/*:*; do\n      [ -d \"$di\" ] || continue\n      d=$(basename \"$di\")\n      _debug d \"$d\"\n      (\n        if _endswith \"$d\" \"$ECC_SUFFIX\"; then\n          _isEcc=\"ecc\"\n          d=$(echo \"$d\" | cut -d \"$ECC_SEP\" -f 1)\n        fi\n        DOMAIN_CONF=\"$di/$d.conf\"\n        if [ -f \"$DOMAIN_CONF\" ]; then\n          . \"$DOMAIN_CONF\"\n          _ca=\"$(_getCAShortName \"$Le_API\")\"\n          if [ -z \"$_domain\" ]; then\n            printf \"%s\\n\" \"$Le_Domain${_sep}\\\"$Le_Keylength\\\"${_sep}$Le_Alt${_sep}$Le_Certificate_Profile${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr\"\n          else\n            if [ \"$_domain\" = \"$d\" ]; then\n              cat \"$DOMAIN_CONF\"\n            fi\n          fi\n        fi\n      )\n    done\n  else\n    if _exists column; then\n      list \"raw\" \"$_domain\" | column -t -s \"$_sep\"\n    else\n      list \"raw\" \"$_domain\" | tr \"$_sep\" '\\t'\n    fi\n  fi\n\n}\n\nlist_profiles() {\n  _initpath\n  _initAPI\n\n  _l_server_url=\"$ACME_DIRECTORY\"\n  _l_server_name=\"$(_getCAShortName \"$_l_server_url\")\"\n  _info \"Fetching profiles from $_l_server_name ($_l_server_url)...\"\n\n  response=$(_get \"$_l_server_url\" \"\" 10)\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Failed to connect to CA directory: $_l_server_url\"\n    return 1\n  fi\n\n  normalized_response=$(echo \"$response\" | _normalizeJson)\n  profiles_json=$(echo \"$normalized_response\" | _egrep_o '\"profiles\" *: *\\{[^\\}]*\\}')\n\n  if [ -z \"$profiles_json\" ]; then\n    _info \"The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint.\"\n    return 0\n  fi\n\n  # Strip the outer layer to get the key-value pairs\n  profiles_kv=$(echo \"$profiles_json\" | sed 's/\"profiles\" *: *{//' | sed 's/}$//' | tr ',' '\\n')\n\n  printf \"\\n%-15s %s\\n\" \"name\" \"info\"\n  printf -- \"--------------------------------------------------------------------\\n\"\n\n  _old_IFS=\"$IFS\"\n  IFS='\n'\n  for pair in $profiles_kv; do\n    # Trim quotes and whitespace\n    _name=$(echo \"$pair\" | cut -d: -f1 | tr -d '\" \\t')\n    _info_url=$(echo \"$pair\" | cut -d: -f2- | sed 's/^ *//' | tr -d '\"')\n    printf \"%-15s %s\\n\" \"$_name\" \"$_info_url\"\n  done\n  IFS=\"$_old_IFS\"\n\n  return 0\n}\n\n_deploy() {\n  _d=\"$1\"\n  _hooks=\"$2\"\n\n  for _d_api in $(echo \"$_hooks\" | tr ',' \" \"); do\n    _deployApi=\"$(_findHook \"$_d\" $_SUB_FOLDER_DEPLOY \"$_d_api\")\"\n    if [ -z \"$_deployApi\" ]; then\n      _err \"The deploy hook $_d_api was not found.\"\n      return 1\n    fi\n    _debug _deployApi \"$_deployApi\"\n\n    if ! (\n      if ! . \"$_deployApi\"; then\n        _err \"Error loading file $_deployApi. Please check your API file and try again.\"\n        return 1\n      fi\n\n      d_command=\"${_d_api}_deploy\"\n      if ! _exists \"$d_command\"; then\n        _err \"It seems that your API file is not correct. Make sure it has a function named: $d_command\"\n        return 1\n      fi\n\n      if ! $d_command \"$_d\" \"$CERT_KEY_PATH\" \"$CERT_PATH\" \"$CA_CERT_PATH\" \"$CERT_FULLCHAIN_PATH\" \"$CERT_PFX_PATH\"; then\n        _err \"Error deploying for domain: $_d\"\n        return 1\n      fi\n    ); then\n      _err \"Error encountered while deploying.\"\n      return 1\n    else\n      _info \"$(__green Success)\"\n    fi\n  done\n}\n\n#domain hooks\ndeploy() {\n  _d=\"$1\"\n  _hooks=\"$2\"\n  _isEcc=\"$3\"\n  if [ -z \"$_hooks\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --deploy --domain <domain.tld> --deploy-hook <hookname> [--ecc] \"\n    return 1\n  fi\n\n  _initpath \"$_d\" \"$_isEcc\"\n  if [ ! -d \"$DOMAIN_PATH\" ]; then\n    _err \"The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install.\"\n    _err \"Cannot find path: '$DOMAIN_PATH'\"\n    return 1\n  fi\n\n  _debug2 DOMAIN_CONF \"$DOMAIN_CONF\"\n  . \"$DOMAIN_CONF\"\n\n  _savedomainconf Le_DeployHook \"$_hooks\"\n\n  _deploy \"$_d\" \"$_hooks\"\n}\n\ninstallcert() {\n  _main_domain=\"$1\"\n  if [ -z \"$_main_domain\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --install-cert --domain <domain.tld> [--ecc] [--cert-file <file>] [--key-file <file>] [--ca-file <file>] [ --reloadcmd <command>] [--fullchain-file <file>]\"\n    return 1\n  fi\n\n  _real_cert=\"$2\"\n  _real_key=\"$3\"\n  _real_ca=\"$4\"\n  _reload_cmd=\"$5\"\n  _real_fullchain=\"$6\"\n  _isEcc=\"$7\"\n\n  _initpath \"$_main_domain\" \"$_isEcc\"\n  if [ ! -d \"$DOMAIN_PATH\" ]; then\n    _err \"The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install.\"\n    _err \"Cannot find path: '$DOMAIN_PATH'\"\n    return 1\n  fi\n\n  _savedomainconf \"Le_RealCertPath\" \"$_real_cert\"\n  _savedomainconf \"Le_RealCACertPath\" \"$_real_ca\"\n  _savedomainconf \"Le_RealKeyPath\" \"$_real_key\"\n  _savedomainconf \"Le_ReloadCmd\" \"$_reload_cmd\" \"base64\"\n  _savedomainconf \"Le_RealFullChainPath\" \"$_real_fullchain\"\n  export Le_ForceNewDomainKey=\"$(_readdomainconf Le_ForceNewDomainKey)\"\n  export Le_Next_Domain_Key\n  _installcert \"$_main_domain\" \"$_real_cert\" \"$_real_key\" \"$_real_ca\" \"$_real_fullchain\" \"$_reload_cmd\"\n}\n\n#domain  cert  key  ca  fullchain reloadcmd backup-prefix\n_installcert() {\n  _main_domain=\"$1\"\n  _real_cert=\"$2\"\n  _real_key=\"$3\"\n  _real_ca=\"$4\"\n  _real_fullchain=\"$5\"\n  _reload_cmd=\"$6\"\n  _backup_prefix=\"$7\"\n\n  if [ \"$_real_cert\" = \"$NO_VALUE\" ]; then\n    _real_cert=\"\"\n  fi\n  if [ \"$_real_key\" = \"$NO_VALUE\" ]; then\n    _real_key=\"\"\n  fi\n  if [ \"$_real_ca\" = \"$NO_VALUE\" ]; then\n    _real_ca=\"\"\n  fi\n  if [ \"$_reload_cmd\" = \"$NO_VALUE\" ]; then\n    _reload_cmd=\"\"\n  fi\n  if [ \"$_real_fullchain\" = \"$NO_VALUE\" ]; then\n    _real_fullchain=\"\"\n  fi\n\n  _backup_path=\"$DOMAIN_BACKUP_PATH/$_backup_prefix\"\n  mkdir -p \"$_backup_path\"\n\n  if [ \"$_real_cert\" ]; then\n    _info \"Installing cert to: $_real_cert\"\n    if [ -f \"$_real_cert\" ] && [ ! \"$_ACME_IS_RENEW\" ]; then\n      cp \"$_real_cert\" \"$_backup_path/cert.bak\"\n    fi\n    if [ \"$CERT_PATH\" != \"$_real_cert\" ]; then\n      cat \"$CERT_PATH\" >\"$_real_cert\" || return 1\n    fi\n  fi\n\n  if [ \"$_real_ca\" ]; then\n    _info \"Installing CA to: $_real_ca\"\n    if [ \"$_real_ca\" = \"$_real_cert\" ]; then\n      echo \"\" >>\"$_real_ca\"\n      cat \"$CA_CERT_PATH\" >>\"$_real_ca\" || return 1\n    else\n      if [ -f \"$_real_ca\" ] && [ ! \"$_ACME_IS_RENEW\" ]; then\n        cp \"$_real_ca\" \"$_backup_path/ca.bak\"\n      fi\n      if [ \"$CA_CERT_PATH\" != \"$_real_ca\" ]; then\n        cat \"$CA_CERT_PATH\" >\"$_real_ca\" || return 1\n      fi\n    fi\n  fi\n\n  if [ \"$_real_key\" ]; then\n    _info \"Installing key to: $_real_key\"\n    if [ -f \"$_real_key\" ] && [ ! \"$_ACME_IS_RENEW\" ]; then\n      cp \"$_real_key\" \"$_backup_path/key.bak\"\n    fi\n    if [ \"$CERT_KEY_PATH\" != \"$_real_key\" ]; then\n      if [ -f \"$_real_key\" ]; then\n        cat \"$CERT_KEY_PATH\" >\"$_real_key\" || return 1\n      else\n        touch \"$_real_key\" || return 1\n        chmod 600 \"$_real_key\"\n        cat \"$CERT_KEY_PATH\" >\"$_real_key\" || return 1\n      fi\n    fi\n  fi\n\n  if [ \"$_real_fullchain\" ]; then\n    _info \"Installing full chain to: $_real_fullchain\"\n    if [ -f \"$_real_fullchain\" ] && [ ! \"$_ACME_IS_RENEW\" ]; then\n      cp \"$_real_fullchain\" \"$_backup_path/fullchain.bak\"\n    fi\n    if [ \"$_real_fullchain\" != \"$CERT_FULLCHAIN_PATH\" ]; then\n      cat \"$CERT_FULLCHAIN_PATH\" >\"$_real_fullchain\" || return 1\n    fi\n  fi\n\n  if [ \"$_reload_cmd\" ]; then\n    _info \"Running reload cmd: $_reload_cmd\"\n    if (\n      export CERT_PATH\n      export CERT_KEY_PATH\n      export CA_CERT_PATH\n      export CERT_FULLCHAIN_PATH\n      export Le_Domain=\"$_main_domain\"\n      export Le_ForceNewDomainKey\n      export Le_Next_Domain_Key\n      cd \"$DOMAIN_PATH\" && eval \"$_reload_cmd\"\n    ); then\n      _info \"$(__green \"Reload successful\")\"\n    else\n      _err \"Reload error for: $_main_domain\"\n    fi\n  fi\n\n}\n\n__read_password() {\n  unset _pp\n  prompt=\"Enter Password:\"\n  while IFS= read -p \"$prompt\" -r -s -n 1 char; do\n    if [ \"$char\" = $'\\0' ]; then\n      break\n    fi\n    prompt='*'\n    _pp=\"$_pp$char\"\n  done\n  echo \"$_pp\"\n}\n\n_install_win_taskscheduler() {\n  _lesh=\"$1\"\n  _centry=\"$2\"\n  _randomminute=\"$3\"\n  if ! _exists cygpath; then\n    _err \"cygpath not found\"\n    return 1\n  fi\n  if ! _exists schtasks; then\n    _err \"schtasks.exe was not found, are you on Windows?\"\n    return 1\n  fi\n  _winbash=\"$(cygpath -w $(which bash))\"\n  _debug _winbash \"$_winbash\"\n  if [ -z \"$_winbash\" ]; then\n    _err \"Cannot find bash path\"\n    return 1\n  fi\n  _myname=\"$(whoami)\"\n  _debug \"_myname\" \"$_myname\"\n  if [ -z \"$_myname\" ]; then\n    _err \"Can not find own username\"\n    return 1\n  fi\n  _debug \"_lesh\" \"$_lesh\"\n\n  _info \"To install the scheduler task to your Windows account, you must input your Windows password.\"\n  _info \"$PROJECT_NAME will not save your password.\"\n  _info \"Please input your Windows password for: $(__green \"$_myname\")\"\n  _password=\"$(__read_password)\"\n  #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' \"$_WINDOWS_SCHEDULER_NAME\" '/F' '/ST' \"00:$_randomminute\" '/RU' \"$_myname\" '/RP' \"$_password\" '/TR' \"$_winbash -l -c '$_lesh --cron --home \\\"$LE_WORKING_DIR\\\" $_centry'\" >/dev/null\n  echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' \"$_WINDOWS_SCHEDULER_NAME\" '/F' '/ST' \"00:$_randomminute\" '/RU' \"$_myname\" '/RP' \"$_password\" '/TR' \"\\\"$_winbash -l -c '$_lesh --cron --home \\\"$LE_WORKING_DIR\\\" $_centry'\\\"\" | cmd.exe >/dev/null\n  echo\n\n}\n\n_uninstall_win_taskscheduler() {\n  if ! _exists schtasks; then\n    _err \"schtasks.exe was not found, are you on Windows?\"\n    return 1\n  fi\n  if ! echo SCHTASKS /query /tn \"$_WINDOWS_SCHEDULER_NAME\" | cmd.exe >/dev/null; then\n    _debug \"scheduler $_WINDOWS_SCHEDULER_NAME was not found.\"\n  else\n    _info \"Removing $_WINDOWS_SCHEDULER_NAME\"\n    echo SCHTASKS /delete /f /tn \"$_WINDOWS_SCHEDULER_NAME\" | cmd.exe >/dev/null\n  fi\n}\n\n#confighome\ninstallcronjob() {\n  _c_home=\"$1\"\n  _initpath\n  _CRONTAB=\"crontab\"\n  if [ -f \"$LE_WORKING_DIR/$PROJECT_ENTRY\" ]; then\n    lesh=\"\\\"$LE_WORKING_DIR\\\"/$PROJECT_ENTRY\"\n  else\n    _debug \"_SCRIPT_\" \"$_SCRIPT_\"\n    _script=\"$(_readlink \"$_SCRIPT_\")\"\n    _debug _script \"$_script\"\n    if [ -f \"$_script\" ]; then\n      _info \"Using the current script from: $_script\"\n      lesh=\"$_script\"\n    else\n      _err \"Cannot install cronjob, $PROJECT_ENTRY not found.\"\n      return 1\n    fi\n  fi\n  if [ \"$_c_home\" ]; then\n    _c_entry=\"--config-home \\\"$_c_home\\\" \"\n  fi\n  _t=$(_time)\n  random_minute=$(_math $_t % 60)\n  random_hour=$(_math $_t / 60 % 24)\n\n  if ! _exists \"$_CRONTAB\" && _exists \"fcrontab\"; then\n    _CRONTAB=\"fcrontab\"\n  fi\n\n  if ! _exists \"$_CRONTAB\"; then\n    if _exists cygpath && _exists schtasks.exe; then\n      _info \"It seems you are on Windows, let's install the Windows scheduler task.\"\n      if _install_win_taskscheduler \"$lesh\" \"$_c_entry\" \"$random_minute\"; then\n        _info \"Successfully installed Windows scheduler task.\"\n        return 0\n      else\n        _err \"Failed to install Windows scheduler task.\"\n        return 1\n      fi\n    fi\n    _err \"crontab/fcrontab doesn't exist, so we cannot install cron jobs.\"\n    _err \"Your certs will not be renewed automatically.\"\n    _err \"You must add your own cron job to call '$PROJECT_ENTRY --cron' every day.\"\n    return 1\n  fi\n  _info \"Installing cron job\"\n  if ! $_CRONTAB -l | grep \"$PROJECT_ENTRY --cron\"; then\n    if _exists uname && uname -a | grep SunOS >/dev/null; then\n      _CRONTAB_STDIN=\"$_CRONTAB --\"\n    else\n      _CRONTAB_STDIN=\"$_CRONTAB -\"\n    fi\n    $_CRONTAB -l | {\n      cat\n      echo \"$random_minute $random_hour * * * $lesh --cron --home \\\"$LE_WORKING_DIR\\\" $_c_entry> /dev/null\"\n    } | $_CRONTAB_STDIN\n  fi\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Failed to install cron job. You need to manually renew your certs.\"\n    _err \"Alternatively, you can add a cron job by yourself:\"\n    _err \"$lesh --cron --home \\\"$LE_WORKING_DIR\\\" > /dev/null\"\n    return 1\n  fi\n}\n\nuninstallcronjob() {\n  _CRONTAB=\"crontab\"\n  if ! _exists \"$_CRONTAB\" && _exists \"fcrontab\"; then\n    _CRONTAB=\"fcrontab\"\n  fi\n\n  if ! _exists \"$_CRONTAB\"; then\n    if _exists cygpath && _exists schtasks.exe; then\n      _info \"It seems you are on Windows, let's uninstall the Windows scheduler task.\"\n      if _uninstall_win_taskscheduler; then\n        _info \"Successfully uninstalled Windows scheduler task.\"\n        return 0\n      else\n        _err \"Failed to uninstall Windows scheduler task.\"\n        return 1\n      fi\n    fi\n    return\n  fi\n  _info \"Removing cron job\"\n  cr=\"$($_CRONTAB -l | grep \"$PROJECT_ENTRY --cron\")\"\n  if [ \"$cr\" ]; then\n    if _exists uname && uname -a | grep SunOS >/dev/null; then\n      $_CRONTAB -l | sed \"/$PROJECT_ENTRY --cron/d\" | $_CRONTAB --\n    else\n      $_CRONTAB -l | sed \"/$PROJECT_ENTRY --cron/d\" | $_CRONTAB -\n    fi\n    LE_WORKING_DIR=\"$(echo \"$cr\" | cut -d ' ' -f 9 | tr -d '\"')\"\n    _info LE_WORKING_DIR \"$LE_WORKING_DIR\"\n    if _contains \"$cr\" \"--config-home\"; then\n      LE_CONFIG_HOME=\"$(echo \"$cr\" | cut -d ' ' -f 11 | tr -d '\"')\"\n      _debug LE_CONFIG_HOME \"$LE_CONFIG_HOME\"\n    fi\n  fi\n  _initpath\n\n}\n\n#domain  isECC  revokeReason\nrevoke() {\n  Le_Domain=\"$1\"\n  if [ -z \"$Le_Domain\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --revoke --domain <domain.tld> [--ecc]\"\n    return 1\n  fi\n\n  _isEcc=\"$2\"\n  _reason=\"$3\"\n  if [ -z \"$_reason\" ]; then\n    _reason=\"0\"\n  fi\n  _initpath \"$Le_Domain\" \"$_isEcc\"\n  if [ ! -f \"$DOMAIN_CONF\" ]; then\n    _err \"$Le_Domain is not an issued domain, skipping.\"\n    return 1\n  fi\n\n  if [ ! -f \"$CERT_PATH\" ]; then\n    _err \"Cert for $Le_Domain $CERT_PATH was not found, skipping.\"\n    return 1\n  fi\n\n  . \"$DOMAIN_CONF\"\n  _debug Le_API \"$Le_API\"\n\n  if [ \"$Le_API\" ]; then\n    if [ \"$Le_API\" != \"$ACME_DIRECTORY\" ]; then\n      _clearAPI\n    fi\n    export ACME_DIRECTORY=\"$Le_API\"\n    #reload ca configs\n    ACCOUNT_KEY_PATH=\"\"\n    ACCOUNT_JSON_PATH=\"\"\n    CA_CONF=\"\"\n    _debug3 \"initpath again.\"\n    _initpath \"$Le_Domain\" \"$_isEcc\"\n    _initAPI\n  fi\n\n  cert=\"$(_getfile \"${CERT_PATH}\" \"${BEGIN_CERT}\" \"${END_CERT}\" | tr -d \"\\r\\n\" | _url_replace)\"\n\n  if [ -z \"$cert\" ]; then\n    _err \"Cert for $Le_Domain is empty, skipping.\"\n    return 1\n  fi\n\n  _initAPI\n\n  data=\"{\\\"certificate\\\": \\\"$cert\\\",\\\"reason\\\":$_reason}\"\n\n  uri=\"${ACME_REVOKE_CERT}\"\n\n  _info \"Trying account key first.\"\n  if _send_signed_request \"$uri\" \"$data\" \"\" \"$ACCOUNT_KEY_PATH\"; then\n    if [ -z \"$response\" ]; then\n      _info \"Successfully revoked.\"\n      rm -f \"$CERT_PATH\"\n      cat \"$CERT_KEY_PATH\" >\"$CERT_KEY_PATH.revoked\"\n      cat \"$CSR_PATH\" >\"$CSR_PATH.revoked\"\n      return 0\n    else\n      _err \"Error revoking.\"\n      _debug \"$response\"\n    fi\n  fi\n\n  if [ -f \"$CERT_KEY_PATH\" ]; then\n    _info \"Trying domain key.\"\n    if _send_signed_request \"$uri\" \"$data\" \"\" \"$CERT_KEY_PATH\"; then\n      if [ -z \"$response\" ]; then\n        _info \"Successfully revoked.\"\n        rm -f \"$CERT_PATH\"\n        cat \"$CERT_KEY_PATH\" >\"$CERT_KEY_PATH.revoked\"\n        cat \"$CSR_PATH\" >\"$CSR_PATH.revoked\"\n        return 0\n      else\n        _err \"Error revoking using domain key.\"\n        _err \"$response\"\n      fi\n    fi\n  else\n    _info \"Domain key file doesn't exist.\"\n  fi\n  return 1\n}\n\n#domain  ecc\nremove() {\n  Le_Domain=\"$1\"\n  if [ -z \"$Le_Domain\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --remove --domain <domain.tld> [--ecc]\"\n    return 1\n  fi\n\n  _isEcc=\"$2\"\n\n  _initpath \"$Le_Domain\" \"$_isEcc\"\n  _removed_conf=\"$DOMAIN_CONF.removed\"\n  if [ ! -f \"$DOMAIN_CONF\" ]; then\n    if [ -f \"$_removed_conf\" ]; then\n      _err \"$Le_Domain has already been removed. You can remove the folder by yourself: $DOMAIN_PATH\"\n    else\n      _err \"$Le_Domain is not an issued domain, skipping.\"\n    fi\n    return 1\n  fi\n\n  if mv \"$DOMAIN_CONF\" \"$_removed_conf\"; then\n    _info \"$Le_Domain has been removed. The key and cert files are in $(__green $DOMAIN_PATH)\"\n    _info \"You can remove them by yourself.\"\n    return 0\n  else\n    _err \"Failed to remove $Le_Domain.\"\n    return 1\n  fi\n}\n\n#domain vtype\n_deactivate() {\n  _d_domain=\"$1\"\n  _d_type=\"$2\"\n  _initpath \"$_d_domain\" \"$_d_type\"\n\n  . \"$DOMAIN_CONF\"\n  _debug Le_API \"$Le_API\"\n\n  if [ \"$Le_API\" ]; then\n    if [ \"$Le_API\" != \"$ACME_DIRECTORY\" ]; then\n      _clearAPI\n    fi\n    export ACME_DIRECTORY=\"$Le_API\"\n    #reload ca configs\n    ACCOUNT_KEY_PATH=\"\"\n    ACCOUNT_JSON_PATH=\"\"\n    CA_CONF=\"\"\n    _debug3 \"initpath again.\"\n    _initpath \"$Le_Domain\" \"$_d_type\"\n    _initAPI\n  fi\n\n  _identifiers=\"{\\\"type\\\":\\\"$(_getIdType \"$_d_domain\")\\\",\\\"value\\\":\\\"$_d_domain\\\"}\"\n  if ! _send_signed_request \"$ACME_NEW_ORDER\" \"{\\\"identifiers\\\": [$_identifiers]}\"; then\n    _err \"Cannot get new order for domain.\"\n    return 1\n  fi\n  _authorizations_seg=\"$(echo \"$response\" | _egrep_o '\"authorizations\" *: *\\[[^\\]*\\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '\"')\"\n  _debug2 _authorizations_seg \"$_authorizations_seg\"\n  if [ -z \"$_authorizations_seg\" ]; then\n    _err \"_authorizations_seg not found.\"\n    _clearup\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n\n  authzUri=\"$_authorizations_seg\"\n  _debug2 \"authzUri\" \"$authzUri\"\n  if ! _send_signed_request \"$authzUri\"; then\n    _err \"Error making GET request for authz.\"\n    _err \"_authorizations_seg\" \"$_authorizations_seg\"\n    _err \"authzUri\" \"$authzUri\"\n    _clearup\n    _on_issue_err \"$_post_hook\"\n    return 1\n  fi\n\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  _debug2 response \"$response\"\n  _URL_NAME=\"url\"\n\n  entries=\"$(echo \"$response\" | tr '][' '==' | _egrep_o \"challenges\\\": *=[^=]*=\" | tr '}{' '\\n\\n' | grep \"\\\"status\\\": *\\\"valid\\\"\")\"\n  if [ -z \"$entries\" ]; then\n    _info \"No valid entries found.\"\n    if [ -z \"$thumbprint\" ]; then\n      thumbprint=\"$(__calc_account_thumbprint)\"\n    fi\n    _debug \"Trigger validation.\"\n    vtype=\"$(_getIdType \"$_d_domain\")\"\n    # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018\n    entry=\"$(echo \"$response\" | sed s/'\"error\":{}'/'\"error\":null'/ | _egrep_o '[^\\{]*\"type\":\"'$vtype'\"[^\\}]*')\"\n    _debug entry \"$entry\"\n    if [ -z \"$entry\" ]; then\n      _err \"$d: Cannot get domain token\"\n      return 1\n    fi\n    token=\"$(echo \"$entry\" | _egrep_o '\"token\":\"[^\"]*' | cut -d : -f 2 | tr -d '\"')\"\n    _debug token \"$token\"\n\n    uri=\"$(echo \"$entry\" | _egrep_o \"\\\"$_URL_NAME\\\":\\\"[^\\\"]*\" | cut -d : -f 2,3 | tr -d '\"')\"\n    _debug uri \"$uri\"\n\n    keyauthorization=\"$token.$thumbprint\"\n    _debug keyauthorization \"$keyauthorization\"\n    __trigger_validation \"$uri\" \"$keyauthorization\"\n\n  fi\n\n  _d_i=0\n  _d_max_retry=$(echo \"$entries\" | wc -l)\n  while [ \"$_d_i\" -lt \"$_d_max_retry\" ]; do\n    _info \"Deactivating $_d_domain\"\n    _d_i=\"$(_math $_d_i + 1)\"\n    entry=\"$(echo \"$entries\" | sed -n \"${_d_i}p\")\"\n    _debug entry \"$entry\"\n\n    if [ -z \"$entry\" ]; then\n      _info \"No more valid entries found.\"\n      break\n    fi\n\n    _vtype=\"$(echo \"$entry\" | _egrep_o '\"type\": *\"[^\"]*\"' | cut -d : -f 2 | tr -d '\"')\"\n    _debug _vtype \"$_vtype\"\n    _info \"Found $_vtype\"\n\n    uri=\"$(echo \"$entry\" | _egrep_o \"\\\"$_URL_NAME\\\":\\\"[^\\\"]*\\\"\" | tr -d '\" ' | cut -d : -f 2-)\"\n    _debug uri \"$uri\"\n\n    if [ \"$_d_type\" ] && [ \"$_d_type\" != \"$_vtype\" ]; then\n      _info \"Skipping $_vtype\"\n      continue\n    fi\n\n    _info \"Deactivating $_vtype\"\n\n    _djson=\"{\\\"status\\\":\\\"deactivated\\\"}\"\n\n    if _send_signed_request \"$authzUri\" \"$_djson\" && _contains \"$response\" '\"deactivated\"'; then\n      _info \"Successfully deactivated $_vtype.\"\n    else\n      _err \"Could not deactivate $_vtype.\"\n      break\n    fi\n\n  done\n  _debug \"$_d_i\"\n  if [ \"$_d_i\" -eq \"$_d_max_retry\" ]; then\n    _info \"Successfully deactivated!\"\n  else\n    _err \"Deactivation failed.\"\n  fi\n\n}\n\ndeactivate() {\n  _d_domain_list=\"$1\"\n  _d_type=\"$2\"\n  _initpath\n  _initAPI\n  _debug _d_domain_list \"$_d_domain_list\"\n  if [ -z \"$(echo $_d_domain_list | cut -d , -f 1)\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --deactivate --domain <domain.tld> [--domain <domain2.tld> ...]\"\n    return 1\n  fi\n  for _d_dm in $(echo \"$_d_domain_list\" | tr ',' ' '); do\n    if [ -z \"$_d_dm\" ] || [ \"$_d_dm\" = \"$NO_VALUE\" ]; then\n      continue\n    fi\n    if ! _deactivate \"$_d_dm\" \"$_d_type\"; then\n      return 1\n    fi\n  done\n}\n\n#cert\n_getAKI() {\n  _cert=\"$1\"\n  openssl x509 -in \"$_cert\" -text -noout | grep \"X509v3 Authority Key Identifier\" -A 1 | _tail_n 1 | tr -d ' :'\n}\n\n#cert\n_getSerial() {\n  _cert=\"$1\"\n  openssl x509 -in \"$_cert\" -serial -noout | cut -d = -f 2\n}\n\n#cert\n_get_ARI() {\n  _cert=\"$1\"\n  _aki=$(_getAKI \"$_cert\")\n  _ser=$(_getSerial \"$_cert\")\n  _debug2 \"_aki\" \"$_aki\"\n  _debug2 \"_ser\" \"$_ser\"\n\n  _akiurl=\"$(echo \"$_aki\" | _h2b | _base64 | tr -d = | _url_encode)\"\n  _debug2 \"_akiurl\" \"$_akiurl\"\n  _serurl=\"$(echo \"$_ser\" | _h2b | _base64 | tr -d = | _url_encode)\"\n  _debug2 \"_serurl\" \"$_serurl\"\n\n  _ARI_URL=\"$ACME_RENEWAL_INFO/$_akiurl.$_serurl\"\n  _get \"$_ARI_URL\"\n\n}\n\n# Detect profile file if not specified as environment variable\n_detect_profile() {\n  if [ -n \"$PROFILE\" -a -f \"$PROFILE\" ]; then\n    echo \"$PROFILE\"\n    return\n  fi\n\n  DETECTED_PROFILE=''\n  SHELLTYPE=\"$(basename \"/$SHELL\")\"\n\n  if [ \"$SHELLTYPE\" = \"bash\" ]; then\n    if [ -f \"$HOME/.bashrc\" ]; then\n      DETECTED_PROFILE=\"$HOME/.bashrc\"\n    elif [ -f \"$HOME/.bash_profile\" ]; then\n      DETECTED_PROFILE=\"$HOME/.bash_profile\"\n    fi\n  elif [ \"$SHELLTYPE\" = \"zsh\" ]; then\n    DETECTED_PROFILE=\"$HOME/.zshrc\"\n  fi\n\n  if [ -z \"$DETECTED_PROFILE\" ]; then\n    if [ -f \"$HOME/.profile\" ]; then\n      DETECTED_PROFILE=\"$HOME/.profile\"\n    elif [ -f \"$HOME/.bashrc\" ]; then\n      DETECTED_PROFILE=\"$HOME/.bashrc\"\n    elif [ -f \"$HOME/.bash_profile\" ]; then\n      DETECTED_PROFILE=\"$HOME/.bash_profile\"\n    elif [ -f \"$HOME/.zshrc\" ]; then\n      DETECTED_PROFILE=\"$HOME/.zshrc\"\n    fi\n  fi\n\n  echo \"$DETECTED_PROFILE\"\n}\n\n_initconf() {\n  _initpath\n  if [ ! -f \"$ACCOUNT_CONF_PATH\" ]; then\n    echo \"\n\n#LOG_FILE=\\\"$DEFAULT_LOG_FILE\\\"\n#LOG_LEVEL=1\n\n#AUTO_UPGRADE=\\\"1\\\"\n\n#NO_TIMESTAMP=1\n\n    \" >\"$ACCOUNT_CONF_PATH\"\n    chmod 600 \"$ACCOUNT_CONF_PATH\"\n  fi\n}\n\n# nocron\n_precheck() {\n  _nocron=\"$1\"\n\n  if ! _exists \"curl\" && ! _exists \"wget\"; then\n    _err \"Please install curl or wget first to enable access to HTTP resources.\"\n    return 1\n  fi\n\n  if [ -z \"$_nocron\" ]; then\n    if ! _exists \"crontab\" && ! _exists \"fcrontab\"; then\n      if _exists cygpath && _exists schtasks.exe; then\n        _info \"It seems you are on Windows, we will install the Windows scheduler task.\"\n      else\n        _err \"It is recommended to install crontab first. Try to install 'cron', 'crontab', 'crontabs' or 'vixie-cron'.\"\n        _err \"We need to set a cron job to renew the certs automatically.\"\n        _err \"Otherwise, your certs will not be able to be renewed automatically.\"\n        if [ -z \"$FORCE\" ]; then\n          _err \"Please add '--force' and try install again to go without crontab.\"\n          _err \"./$PROJECT_ENTRY --install --force\"\n          return 1\n        fi\n      fi\n    fi\n  fi\n\n  if ! _exists \"${ACME_OPENSSL_BIN:-openssl}\"; then\n    _err \"Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN\"\n    _err \"We need openssl to generate keys.\"\n    return 1\n  fi\n\n  if ! _exists \"socat\" && ! _exists \"python\" && ! _exists \"python2\" && ! _exists \"python3\"; then\n    _err \"It is recommended to install socat or python first.\"\n    _err \"We use socat or python for the standalone server, which is used for standalone mode.\"\n    _err \"If you don't want to use standalone mode, you may ignore this warning.\"\n  fi\n\n  return 0\n}\n\n_setShebang() {\n  _file=\"$1\"\n  _shebang=\"$2\"\n  if [ -z \"$_shebang\" ]; then\n    _usage \"Usage: file shebang\"\n    return 1\n  fi\n  cp \"$_file\" \"$_file.tmp\"\n  echo \"$_shebang\" >\"$_file\"\n  sed -n 2,99999p \"$_file.tmp\" >>\"$_file\"\n  rm -f \"$_file.tmp\"\n}\n\n#confighome\n_installalias() {\n  _c_home=\"$1\"\n  _initpath\n\n  _envfile=\"$LE_WORKING_DIR/$PROJECT_ENTRY.env\"\n  if [ \"$_upgrading\" ] && [ \"$_upgrading\" = \"1\" ]; then\n    echo \"$(cat \"$_envfile\")\" | sed \"s|^LE_WORKING_DIR.*$||\" >\"$_envfile\"\n    echo \"$(cat \"$_envfile\")\" | sed \"s|^alias le.*$||\" >\"$_envfile\"\n    echo \"$(cat \"$_envfile\")\" | sed \"s|^alias le.sh.*$||\" >\"$_envfile\"\n  fi\n\n  if [ \"$_c_home\" ]; then\n    _c_entry=\" --config-home '$_c_home'\"\n  fi\n\n  _setopt \"$_envfile\" \"export LE_WORKING_DIR\" \"=\" \"\\\"$LE_WORKING_DIR\\\"\"\n  if [ \"$_c_home\" ]; then\n    _setopt \"$_envfile\" \"export LE_CONFIG_HOME\" \"=\" \"\\\"$LE_CONFIG_HOME\\\"\"\n  else\n    _sed_i \"/^export LE_CONFIG_HOME/d\" \"$_envfile\"\n  fi\n  _setopt \"$_envfile\" \"alias $PROJECT_ENTRY\" \"=\" \"\\\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\\\"\"\n\n  _profile=\"$(_detect_profile)\"\n  if [ \"$_profile\" ]; then\n    _debug \"Found profile: $_profile\"\n    _info \"Installing alias to '$_profile'\"\n    _setopt \"$_profile\" \". \\\"$_envfile\\\"\"\n    _info \"Close and reopen your terminal to start using $PROJECT_NAME\"\n  else\n    _info \"No profile has been found, you will need to change your working directory to $LE_WORKING_DIR to use $PROJECT_NAME\"\n  fi\n\n  #for csh\n  _cshfile=\"$LE_WORKING_DIR/$PROJECT_ENTRY.csh\"\n  _csh_profile=\"$HOME/.cshrc\"\n  if [ -f \"$_csh_profile\" ]; then\n    _info \"Installing alias to '$_csh_profile'\"\n    _setopt \"$_cshfile\" \"setenv LE_WORKING_DIR\" \" \" \"\\\"$LE_WORKING_DIR\\\"\"\n    if [ \"$_c_home\" ]; then\n      _setopt \"$_cshfile\" \"setenv LE_CONFIG_HOME\" \" \" \"\\\"$LE_CONFIG_HOME\\\"\"\n    else\n      _sed_i \"/^setenv LE_CONFIG_HOME/d\" \"$_cshfile\"\n    fi\n    _setopt \"$_cshfile\" \"alias $PROJECT_ENTRY\" \" \" \"\\\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\\\"\"\n    _setopt \"$_csh_profile\" \"source \\\"$_cshfile\\\"\"\n  fi\n\n  #for tcsh\n  _tcsh_profile=\"$HOME/.tcshrc\"\n  if [ -f \"$_tcsh_profile\" ]; then\n    _info \"Installing alias to '$_tcsh_profile'\"\n    _setopt \"$_cshfile\" \"setenv LE_WORKING_DIR\" \" \" \"\\\"$LE_WORKING_DIR\\\"\"\n    if [ \"$_c_home\" ]; then\n      _setopt \"$_cshfile\" \"setenv LE_CONFIG_HOME\" \" \" \"\\\"$LE_CONFIG_HOME\\\"\"\n    fi\n    _setopt \"$_cshfile\" \"alias $PROJECT_ENTRY\" \" \" \"\\\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\\\"\"\n    _setopt \"$_tcsh_profile\" \"source \\\"$_cshfile\\\"\"\n  fi\n\n}\n\n# nocron confighome noprofile accountemail\ninstall() {\n\n  if [ -z \"$LE_WORKING_DIR\" ]; then\n    LE_WORKING_DIR=\"$DEFAULT_INSTALL_HOME\"\n  fi\n\n  _nocron=\"$1\"\n  _c_home=\"$2\"\n  _noprofile=\"$3\"\n  _accountemail=\"$4\"\n\n  if ! _initpath; then\n    _err \"Install failed.\"\n    return 1\n  fi\n  if [ \"$_nocron\" ]; then\n    _debug \"Skipping cron job installation\"\n  fi\n\n  if [ \"$_ACME_IN_CRON\" != \"1\" ]; then\n    if ! _precheck \"$_nocron\"; then\n      _err \"Pre-check failed, cannot install.\"\n      return 1\n    fi\n  fi\n\n  if [ -z \"$_c_home\" ] && [ \"$LE_CONFIG_HOME\" != \"$LE_WORKING_DIR\" ]; then\n    _info \"Using config home: $LE_CONFIG_HOME\"\n    _c_home=\"$LE_CONFIG_HOME\"\n  fi\n\n  #convert from le\n  if [ -d \"$HOME/.le\" ]; then\n    for envfile in \"le.env\" \"le.sh.env\"; do\n      if [ -f \"$HOME/.le/$envfile\" ]; then\n        if grep \"le.sh\" \"$HOME/.le/$envfile\" >/dev/null; then\n          _upgrading=\"1\"\n          _info \"You are upgrading from le.sh\"\n          _info \"Renaming \\\"$HOME/.le\\\" to $LE_WORKING_DIR\"\n          mv \"$HOME/.le\" \"$LE_WORKING_DIR\"\n          mv \"$LE_WORKING_DIR/$envfile\" \"$LE_WORKING_DIR/$PROJECT_ENTRY.env\"\n          break\n        fi\n      fi\n    done\n  fi\n\n  _info \"Installing to $LE_WORKING_DIR\"\n\n  if [ ! -d \"$LE_WORKING_DIR\" ]; then\n    if ! mkdir -p \"$LE_WORKING_DIR\"; then\n      _err \"Cannot create working dir: $LE_WORKING_DIR\"\n      return 1\n    fi\n\n    chmod 700 \"$LE_WORKING_DIR\"\n  fi\n\n  if [ ! -d \"$LE_CONFIG_HOME\" ]; then\n    if ! mkdir -p \"$LE_CONFIG_HOME\"; then\n      _err \"Cannot create config dir: $LE_CONFIG_HOME\"\n      return 1\n    fi\n\n    chmod 700 \"$LE_CONFIG_HOME\"\n  fi\n\n  cp \"$PROJECT_ENTRY\" \"$LE_WORKING_DIR/\" && chmod +x \"$LE_WORKING_DIR/$PROJECT_ENTRY\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Installation failed, cannot copy $PROJECT_ENTRY\"\n    return 1\n  fi\n\n  _info \"Installed to $LE_WORKING_DIR/$PROJECT_ENTRY\"\n\n  if [ \"$_ACME_IN_CRON\" != \"1\" ] && [ -z \"$_noprofile\" ]; then\n    _installalias \"$_c_home\"\n  fi\n\n  for subf in $_SUB_FOLDERS; do\n    if [ -d \"$subf\" ]; then\n      mkdir -p \"$LE_WORKING_DIR/$subf\"\n      cp \"$subf\"/* \"$LE_WORKING_DIR\"/\"$subf\"/\n    fi\n  done\n\n  if [ ! -f \"$ACCOUNT_CONF_PATH\" ]; then\n    _initconf\n  fi\n\n  if [ \"$_DEFAULT_ACCOUNT_CONF_PATH\" != \"$ACCOUNT_CONF_PATH\" ]; then\n    _setopt \"$_DEFAULT_ACCOUNT_CONF_PATH\" \"ACCOUNT_CONF_PATH\" \"=\" \"\\\"$ACCOUNT_CONF_PATH\\\"\"\n  fi\n\n  if [ \"$_DEFAULT_CERT_HOME\" != \"$CERT_HOME\" ]; then\n    _saveaccountconf \"CERT_HOME\" \"$CERT_HOME\"\n  fi\n\n  if [ \"$_DEFAULT_ACCOUNT_KEY_PATH\" != \"$ACCOUNT_KEY_PATH\" ]; then\n    _saveaccountconf \"ACCOUNT_KEY_PATH\" \"$ACCOUNT_KEY_PATH\"\n  fi\n\n  if [ -z \"$_nocron\" ]; then\n    installcronjob \"$_c_home\"\n  fi\n\n  if [ -z \"$NO_DETECT_SH\" ]; then\n    #Modify shebang\n    if _exists bash; then\n      _bash_path=\"$(bash -c \"command -v bash 2>/dev/null\")\"\n      if [ -z \"$_bash_path\" ]; then\n        _bash_path=\"$(bash -c 'echo $SHELL')\"\n      fi\n    fi\n    if [ \"$_bash_path\" ]; then\n      _info \"bash has been found. Changing the shebang to use bash as preferred.\"\n      _shebang='#!'\"$_bash_path\"\n      _setShebang \"$LE_WORKING_DIR/$PROJECT_ENTRY\" \"$_shebang\"\n      for subf in $_SUB_FOLDERS; do\n        if [ -d \"$LE_WORKING_DIR/$subf\" ]; then\n          for _apifile in \"$LE_WORKING_DIR/$subf/\"*.sh; do\n            _setShebang \"$_apifile\" \"$_shebang\"\n          done\n        fi\n      done\n    fi\n  fi\n\n  if [ \"$_accountemail\" ]; then\n    _saveaccountconf \"ACCOUNT_EMAIL\" \"$_accountemail\"\n  fi\n  _saveaccountconf \"UPGRADE_HASH\" \"$(_getUpgradeHash)\"\n  _info OK\n}\n\n# nocron\nuninstall() {\n  _nocron=\"$1\"\n  if [ -z \"$_nocron\" ]; then\n    uninstallcronjob\n  fi\n  _initpath\n\n  _uninstallalias\n\n  rm -f \"$LE_WORKING_DIR/$PROJECT_ENTRY\"\n  _info \"The keys and certs are in \\\"$(__green \"$LE_CONFIG_HOME\")\\\". You can remove them by yourself.\"\n\n}\n\n_uninstallalias() {\n  _initpath\n\n  _profile=\"$(_detect_profile)\"\n  if [ \"$_profile\" ]; then\n    _info \"Uninstalling alias from: '$_profile'\"\n    text=\"$(cat \"$_profile\")\"\n    echo \"$text\" | sed \"s|^.*\\\"$LE_WORKING_DIR/$PROJECT_NAME.env\\\"$||\" >\"$_profile\"\n  fi\n\n  _csh_profile=\"$HOME/.cshrc\"\n  if [ -f \"$_csh_profile\" ]; then\n    _info \"Uninstalling alias from: '$_csh_profile'\"\n    text=\"$(cat \"$_csh_profile\")\"\n    echo \"$text\" | sed \"s|^.*\\\"$LE_WORKING_DIR/$PROJECT_NAME.csh\\\"$||\" >\"$_csh_profile\"\n  fi\n\n  _tcsh_profile=\"$HOME/.tcshrc\"\n  if [ -f \"$_tcsh_profile\" ]; then\n    _info \"Uninstalling alias from: '$_csh_profile'\"\n    text=\"$(cat \"$_tcsh_profile\")\"\n    echo \"$text\" | sed \"s|^.*\\\"$LE_WORKING_DIR/$PROJECT_NAME.csh\\\"$||\" >\"$_tcsh_profile\"\n  fi\n\n}\n\ncron() {\n  export _ACME_IN_CRON=1\n  _initpath\n  _info \"$(__green \"===Starting cron===\")\"\n  if [ \"$AUTO_UPGRADE\" = \"1\" ]; then\n    export LE_WORKING_DIR\n    (\n      if ! upgrade; then\n        _err \"Cron: Upgrade failed!\"\n        return 1\n      fi\n    )\n    . \"$LE_WORKING_DIR/$PROJECT_ENTRY\" >/dev/null\n\n    if [ -t 1 ]; then\n      __INTERACTIVE=\"1\"\n    fi\n\n    _info \"Automatically upgraded to: $VER\"\n  fi\n  renewAll\n  _ret=\"$?\"\n  _ACME_IN_CRON=\"\"\n  _info \"$(__green \"===End cron===\")\"\n  exit $_ret\n}\n\nversion() {\n  echo \"$PROJECT\"\n  echo \"v$VER\"\n}\n\n# subject content hooks code\n_send_notify() {\n  _nsubject=\"$1\"\n  _ncontent=\"$2\"\n  _nhooks=\"$3\"\n  _nerror=\"$4\"\n\n  if [ \"$NOTIFY_LEVEL\" = \"$NOTIFY_LEVEL_DISABLE\" ]; then\n    _debug \"The NOTIFY_LEVEL is $NOTIFY_LEVEL, which means it's disabled, so will just return.\"\n    return 0\n  fi\n\n  if [ -z \"$_nhooks\" ]; then\n    _debug \"The NOTIFY_HOOK is empty, will just return.\"\n    return 0\n  fi\n\n  _nsource=\"$NOTIFY_SOURCE\"\n  if [ -z \"$_nsource\" ]; then\n    _nsource=\"$(uname -n)\"\n  fi\n\n  _nsubject=\"$_nsubject by $_nsource\"\n\n  _send_err=0\n  for _n_hook in $(echo \"$_nhooks\" | tr ',' \" \"); do\n    _n_hook_file=\"$(_findHook \"\" $_SUB_FOLDER_NOTIFY \"$_n_hook\")\"\n    _info \"Sending via: $_n_hook\"\n    _debug \"Found $_n_hook_file for $_n_hook\"\n    if [ -z \"$_n_hook_file\" ]; then\n      _err \"Cannot find the hook file for $_n_hook\"\n      continue\n    fi\n    if ! (\n      if ! . \"$_n_hook_file\"; then\n        _err \"Error loading file $_n_hook_file. Please check your API file and try again.\"\n        return 1\n      fi\n\n      d_command=\"${_n_hook}_send\"\n      if ! _exists \"$d_command\"; then\n        _err \"It seems that your API file is not correct. Make sure it has a function named: $d_command\"\n        return 1\n      fi\n\n      if ! $d_command \"$_nsubject\" \"$_ncontent\" \"$_nerror\"; then\n        _err \"Error sending message using $d_command\"\n        return 1\n      fi\n\n      return 0\n    ); then\n      _err \"Error setting $_n_hook_file.\"\n      _send_err=1\n    else\n      _info \"$_n_hook $(__green Success)\"\n    fi\n  done\n  return $_send_err\n\n}\n\n# hook\n_set_notify_hook() {\n  _nhooks=\"$1\"\n\n  _test_subject=\"Hello, this is a notification from $PROJECT_NAME\"\n  _test_content=\"If you receive this message, your notification works.\"\n\n  _send_notify \"$_test_subject\" \"$_test_content\" \"$_nhooks\" 0\n\n}\n\n#[hook] [level] [mode]\nsetnotify() {\n  _nhook=\"$1\"\n  _nlevel=\"$2\"\n  _nmode=\"$3\"\n  _nsource=\"$4\"\n\n  _initpath\n\n  if [ -z \"$_nhook$_nlevel$_nmode$_nsource\" ]; then\n    _usage \"Usage: $PROJECT_ENTRY --set-notify [--notify-hook <hookname>] [--notify-level <0|1|2|3>] [--notify-mode <0|1>] [--notify-source <hostname>]\"\n    _usage \"$_NOTIFY_WIKI\"\n    return 1\n  fi\n\n  if [ \"$_nlevel\" ]; then\n    _info \"Set notify level to: $_nlevel\"\n    export \"NOTIFY_LEVEL=$_nlevel\"\n    _saveaccountconf \"NOTIFY_LEVEL\" \"$NOTIFY_LEVEL\"\n  fi\n\n  if [ \"$_nmode\" ]; then\n    _info \"Set notify mode to: $_nmode\"\n    export \"NOTIFY_MODE=$_nmode\"\n    _saveaccountconf \"NOTIFY_MODE\" \"$NOTIFY_MODE\"\n  fi\n\n  if [ \"$_nsource\" ]; then\n    _info \"Set notify source to: $_nsource\"\n    export \"NOTIFY_SOURCE=$_nsource\"\n    _saveaccountconf \"NOTIFY_SOURCE\" \"$NOTIFY_SOURCE\"\n  fi\n\n  if [ \"$_nhook\" ]; then\n    _info \"Set notify hook to: $_nhook\"\n    if [ \"$_nhook\" = \"$NO_VALUE\" ]; then\n      _info \"Clearing notify hook\"\n      _clearaccountconf \"NOTIFY_HOOK\"\n    else\n      if _set_notify_hook \"$_nhook\"; then\n        export NOTIFY_HOOK=\"$_nhook\"\n        _saveaccountconf \"NOTIFY_HOOK\" \"$NOTIFY_HOOK\"\n        return 0\n      else\n        _err \"Cannot set notify hook to: $_nhook\"\n        return 1\n      fi\n    fi\n  fi\n\n}\n\nshowhelp() {\n  _initpath\n  version\n  echo \"Usage: $PROJECT_ENTRY <command> ... [parameters ...]\nCommands:\n  -h, --help               Show this help message.\n  -v, --version            Show version info.\n  --install                Install $PROJECT_NAME to your system.\n  --uninstall              Uninstall $PROJECT_NAME, and uninstall the cron job.\n  --upgrade                Upgrade $PROJECT_NAME to the latest code from $PROJECT.\n  --issue                  Issue a cert.\n  --deploy                 Deploy the cert to your server.\n  -i, --install-cert       Install the issued cert to Apache/nginx or any other server.\n  -r, --renew              Renew a cert.\n  --renew-all              Renew all the certs.\n  --revoke                 Revoke a cert.\n  --remove                 Remove the cert from list of certs known to $PROJECT_NAME.\n  --list                   List all the certs.\n  --info                   Show the $PROJECT_NAME configs, or the configs for a domain with [-d domain] parameter.\n  --to-pkcs12              Export the certificate and key to a pfx file.\n  --to-pkcs8               Convert to pkcs8 format.\n  --sign-csr               Issue a cert from an existing csr.\n  --show-csr               Show the content of a csr.\n  -ccr, --create-csr       Create CSR, professional use.\n  --create-domain-key      Create an domain private key, professional use.\n  --update-account         Update account info.\n  --register-account       Register account key.\n  --deactivate-account     Deactivate the account.\n  --create-account-key     Create an account private key, professional use.\n  --install-cronjob        Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.\n  --uninstall-cronjob      Uninstall the cron job. The 'uninstall' command can do this automatically.\n  --cron                   Run cron job to renew all the certs.\n  --set-notify             Set the cron notification hook, level or mode.\n  --deactivate             Deactivate the domain authz, professional use.\n  --set-default-ca         Used with '--server', Set the default CA to use.\n                           See: $_SERVER_WIKI\n  --set-default-chain      Set the default preferred chain for a CA.\n                           See: $_PREFERRED_CHAIN_WIKI\n\n\nParameters:\n  -d, --domain <domain.tld>         Specifies a domain, used to issue, renew or revoke etc.\n  --challenge-alias <domain.tld>    The challenge domain alias for DNS alias mode.\n                                      See: $_DNS_ALIAS_WIKI\n\n  --domain-alias <domain.tld>       The domain alias for DNS alias mode.\n                                      See: $_DNS_ALIAS_WIKI\n\n  --preferred-chain <chain>         If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.\n                                      If no match, the default offered chain will be used. (default: empty)\n                                      See: $_PREFERRED_CHAIN_WIKI\n\n  --cert-profile, --certificate-profile <profile>  If the CA offers profiles, select the desired profile\n                                      See: $_PROFILESELECTION_WIKI\n\n  --valid-to    <date-time>         Request the NotAfter field of the cert.\n                                      See: $_VALIDITY_WIKI\n  --valid-from  <date-time>         Request the NotBefore field of the cert.\n                                      See: $_VALIDITY_WIKI\n\n  -f, --force                       Force install, force cert renewal or override sudo restrictions.\n  --staging, --test                 Use staging server, for testing.\n  --debug [0|1|2|3]                 Output debug info. Defaults to $DEBUG_LEVEL_DEFAULT if argument is omitted.\n  --output-insecure                 Output all the sensitive messages.\n                                      By default all the credentials/sensitive messages are hidden from the output/debug/log for security.\n  -w, --webroot <directory>         Specifies the web root folder for web root mode.\n  --standalone                      Use standalone mode.\n  --alpn                            Use standalone alpn mode.\n  --stateless                       Use stateless mode.\n                                      See: $_STATELESS_WIKI\n\n  --apache                          Use Apache mode.\n  --dns [dns_hook]                  Use dns manual mode or dns api. Defaults to manual mode when argument is omitted.\n                                      See: $_DNS_API_WIKI\n\n  --dnssleep <seconds>              The time in seconds to wait for all the txt records to propagate in dns api mode.\n                                      It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.\n  -k, --keylength <bits>            Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.\n  -ak, --accountkeylength <bits>    Specifies the account key length: 2048, 3072, 4096\n  --log [file]                      Specifies the log file. Defaults to \\\"$DEFAULT_LOG_FILE\\\" if argument is omitted.\n  --log-level <1|2>                 Specifies the log level, default is $DEFAULT_LOG_LEVEL.\n  --syslog <0|3|6|7>                Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.\n  --eab-kid <eab_key_id>            Key Identifier for External Account Binding.\n  --eab-hmac-key <eab_hmac_key>     HMAC key for External Account Binding.\n\n\n  These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert:\n\n  --cert-file <file>                Path to copy the cert file to after issue/renew.\n  --key-file <file>                 Path to copy the key file to after issue/renew.\n  --ca-file <file>                  Path to copy the intermediate cert file to after issue/renew.\n  --fullchain-file <file>           Path to copy the fullchain cert file to after issue/renew.\n  --reloadcmd <command>             Command to execute after issue/renew to reload the server.\n\n  --server <server_uri>             ACME Directory Resource URI. (default: $DEFAULT_CA)\n                                      See: $_SERVER_WIKI\n\n  --accountconf <file>              Specifies a customized account config file.\n  --home <directory>                Specifies the home dir for $PROJECT_NAME.\n  --cert-home <directory>           Specifies the home dir to save all the certs.\n  --config-home <directory>         Specifies the home dir to save all the configurations.\n  --useragent <string>              Specifies the user agent string. it will be saved for future use too.\n  -m, --email <email>               Specifies the account email, only valid for the '--install' and '--update-account' command.\n  --accountkey <file>               Specifies the account key path, only valid for the '--install' command.\n  --days <ndays>                    Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.\n  --httpport <port>                 Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.\n  --tlsport <port>                  Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.\n  --local-address <ip>              Specifies the standalone/tls server listening address, in case you have multiple ip addresses.\n  --listraw                         Only used for '--list' command, list the certs in raw format.\n  -se, --stop-renew-on-error        Only valid for '--renew-all' command. Stop if one cert has error in renewal.\n  --insecure                        Do not check the server certificate, in some devices, the api server's certificate may not be trusted.\n  --ca-bundle <file>                Specifies the path to the CA certificate bundle to verify api server's certificate.\n  --ca-path <directory>             Specifies directory containing CA certificates in PEM format, used by wget or curl.\n  --no-cron                         Only valid for '--install' command, which means: do not install the default cron job.\n                                      In this case, the certs will not be renewed automatically.\n  --no-profile                      Only valid for '--install' command, which means: do not install aliases to user profile.\n  --no-color                        Do not output color text.\n  --force-color                     Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.\n  --ecc                             Specifies use of the ECC cert. Only valid for '--install-cert', '--renew', '--remove ', '--revoke',\n                                      '--deploy', '--to-pkcs8', '--to-pkcs12' and '--create-csr'.\n  --csr <file>                      Specifies the input csr.\n  --pre-hook <command>              Command to be run before obtaining any certificates.\n  --post-hook <command>             Command to be run after attempting to obtain/renew certificates. Runs regardless of whether obtain/renew succeeded or failed.\n  --renew-hook <command>            Command to be run after each successfully renewed certificate.\n  --deploy-hook <hookname>          The hook file to deploy cert\n  --extended-key-usage <string>     Manually define the CSR extended key usage value. The default is serverAuth,clientAuth.\n  --ocsp, --ocsp-must-staple        Generate OCSP-Must-Staple extension.\n  --always-force-new-domain-key     Generate new domain key on renewal. Otherwise, the domain key is not changed by default.\n  --auto-upgrade [0|1]              Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted.\n  --listen-v4                       Force standalone/tls server to listen at ipv4.\n  --listen-v6                       Force standalone/tls server to listen at ipv6.\n  --request-v4                      Force client requests to use ipv4 to connect to the CA server.\n  --request-v6                      Force client requests to use ipv6 to connect to the CA server.\n  --openssl-bin <file>              Specifies a custom openssl bin location.\n  --use-wget                        Force to use wget, if you have both curl and wget installed.\n  --yes-I-know-dns-manual-mode-enough-go-ahead-please  Force use of dns manual mode.\n                                      See:  $_DNS_MANUAL_WIKI\n\n  -b, --branch <branch>             Only valid for '--upgrade' command, specifies the branch name to upgrade to.\n  --notify-level <0|1|2|3>          Set the notification level:  Default value is $NOTIFY_LEVEL_DEFAULT.\n                                      0: disabled, no notification will be sent.\n                                      1: send notifications only when there is an error.\n                                      2: send notifications when a cert is successfully renewed, or there is an error.\n                                      3: send notifications when a cert is skipped, renewed, or error.\n  --notify-mode <0|1>               Set notification mode. Default value is $NOTIFY_MODE_DEFAULT.\n                                      0: Bulk mode. Send all the domain's notifications in one message(mail).\n                                      1: Cert mode. Send a message for every single cert.\n  --notify-hook <hookname>          Set the notify hook\n  --notify-source <server name>     Set the server name in the notification message\n  --revoke-reason <0-10>            The reason for revocation, can be used in conjunction with the '--revoke' command.\n                                      See: $_REVOKE_WIKI\n\n  --password <password>             Add a password to exported pfx file. Use with --to-pkcs12.\n\n\n\"\n}\n\ninstallOnline() {\n  _info \"Installing from online archive.\"\n\n  _branch=\"$BRANCH\"\n  if [ -z \"$_branch\" ]; then\n    _branch=\"master\"\n  fi\n\n  target=\"$PROJECT/archive/$_branch.tar.gz\"\n  _info \"Downloading $target\"\n  localname=\"$_branch.tar.gz\"\n  if ! _get \"$target\" >$localname; then\n    _err \"Download error.\"\n    return 1\n  fi\n  (\n    _info \"Extracting $localname\"\n    if ! (tar xzf $localname || gtar xzf $localname); then\n      _err \"Extraction error.\"\n      exit 1\n    fi\n\n    cd \"$PROJECT_NAME-$_branch\"\n    chmod +x $PROJECT_ENTRY\n    if ./$PROJECT_ENTRY --install \"$@\"; then\n      _info \"Install success!\"\n    fi\n\n    cd ..\n\n    rm -rf \"$PROJECT_NAME-$_branch\"\n    rm -f \"$localname\"\n  )\n}\n\n_getRepoHash() {\n  _hash_path=$1\n  shift\n  _hash_url=\"${PROJECT_API:-https://api.github.com/repos/acmesh-official}/$PROJECT_NAME/git/refs/$_hash_path\"\n  _get \"$_hash_url\" \"\" 30 | tr -d \"\\r\\n\" | tr '{},' '\\n\\n\\n' | grep '\"sha\":' | cut -d '\"' -f 4\n}\n\n_getUpgradeHash() {\n  _b=\"$BRANCH\"\n  if [ -z \"$_b\" ]; then\n    _b=\"master\"\n  fi\n  _hash=$(_getRepoHash \"heads/$_b\")\n  if [ -z \"$_hash\" ]; then _hash=$(_getRepoHash \"tags/$_b\"); fi\n  echo $_hash\n}\n\nupgrade() {\n  if (\n    _initpath\n    [ -z \"$FORCE\" ] && [ \"$(_getUpgradeHash)\" = \"$(_readaccountconf \"UPGRADE_HASH\")\" ] && _info \"Already up to date!\" && exit 0\n    export LE_WORKING_DIR\n    cd \"$LE_WORKING_DIR\"\n    installOnline \"--nocron\" \"--noprofile\"\n  ); then\n    _info \"Upgrade successful!\"\n    exit 0\n  else\n    _err \"Upgrade failed!\"\n    exit 1\n  fi\n}\n\n_processAccountConf() {\n  if [ \"$_useragent\" ]; then\n    _saveaccountconf \"USER_AGENT\" \"$_useragent\"\n  elif [ \"$USER_AGENT\" ] && [ \"$USER_AGENT\" != \"$DEFAULT_USER_AGENT\" ]; then\n    _saveaccountconf \"USER_AGENT\" \"$USER_AGENT\"\n  fi\n\n  if [ \"$_openssl_bin\" ]; then\n    _saveaccountconf \"ACME_OPENSSL_BIN\" \"$_openssl_bin\"\n  elif [ \"$ACME_OPENSSL_BIN\" ] && [ \"$ACME_OPENSSL_BIN\" != \"$DEFAULT_OPENSSL_BIN\" ]; then\n    _saveaccountconf \"ACME_OPENSSL_BIN\" \"$ACME_OPENSSL_BIN\"\n  fi\n\n  if [ \"$_auto_upgrade\" ]; then\n    _saveaccountconf \"AUTO_UPGRADE\" \"$_auto_upgrade\"\n  elif [ \"$AUTO_UPGRADE\" ]; then\n    _saveaccountconf \"AUTO_UPGRADE\" \"$AUTO_UPGRADE\"\n  fi\n\n  if [ \"$_use_wget\" ]; then\n    _saveaccountconf \"ACME_USE_WGET\" \"$_use_wget\"\n  elif [ \"$ACME_USE_WGET\" ]; then\n    _saveaccountconf \"ACME_USE_WGET\" \"$ACME_USE_WGET\"\n  fi\n\n  if [ \"$_request_v6\" ]; then\n    _saveaccountconf \"ACME_USE_IPV6_REQUESTS\" \"$_request_v6\"\n    _clearaccountconf \"ACME_USE_IPV4_REQUESTS\"\n    ACME_USE_IPV4_REQUESTS=\n  elif [ \"$_request_v4\" ]; then\n    _saveaccountconf \"ACME_USE_IPV4_REQUESTS\" \"$_request_v4\"\n    _clearaccountconf \"ACME_USE_IPV6_REQUESTS\"\n    ACME_USE_IPV6_REQUESTS=\n  elif [ \"$ACME_USE_IPV6_REQUESTS\" ]; then\n    _saveaccountconf \"ACME_USE_IPV6_REQUESTS\" \"$ACME_USE_IPV6_REQUESTS\"\n    _clearaccountconf \"ACME_USE_IPV4_REQUESTS\"\n    ACME_USE_IPV4_REQUESTS=\n  elif [ \"$ACME_USE_IPV4_REQUESTS\" ]; then\n    _saveaccountconf \"ACME_USE_IPV4_REQUESTS\" \"$ACME_USE_IPV4_REQUESTS\"\n    _clearaccountconf \"ACME_USE_IPV6_REQUESTS\"\n    ACME_USE_IPV6_REQUESTS=\n  fi\n\n}\n\n_checkSudo() {\n  if [ -z \"$__INTERACTIVE\" ]; then\n    #don't check if it's not in an interactive shell\n    return 0\n  fi\n  if [ \"$SUDO_GID\" ] && [ \"$SUDO_COMMAND\" ] && [ \"$SUDO_USER\" ] && [ \"$SUDO_UID\" ]; then\n    if [ \"$SUDO_USER\" = \"root\" ] && [ \"$SUDO_UID\" = \"0\" ]; then\n      #it's root using sudo, no matter it's using sudo or not, just fine\n      return 0\n    fi\n    if [ -n \"$SUDO_COMMAND\" ]; then\n      #it's a normal user doing \"sudo su\", or `sudo -i` or `sudo -s`, or `sudo su acmeuser1`\n      _endswith \"$SUDO_COMMAND\" /bin/su || _contains \"$SUDO_COMMAND\" \"/bin/su \" || grep \"^$SUDO_COMMAND\\$\" /etc/shells >/dev/null 2>&1\n      return $?\n    fi\n    #otherwise\n    return 1\n  fi\n  return 0\n}\n\n#server  #keylength\n_selectServer() {\n  _server=\"$1\"\n  _skeylength=\"$2\"\n  _server_lower=\"$(echo \"$_server\" | _lower_case)\"\n  _sindex=0\n  for snames in $CA_NAMES; do\n    snames=\"$(echo \"$snames\" | _lower_case)\"\n    _sindex=\"$(_math $_sindex + 1)\"\n    _debug2 \"_selectServer try snames\" \"$snames\"\n    for sname in $(echo \"$snames\" | tr ',' ' '); do\n      if [ \"$_server_lower\" = \"$sname\" ]; then\n        _debug2 \"_selectServer match $sname\"\n        _serverdir=\"$(_getfield \"$CA_SERVERS\" $_sindex)\"\n        if [ \"$_serverdir\" = \"$CA_SSLCOM_RSA\" ] && _isEccKey \"$_skeylength\"; then\n          _serverdir=\"$CA_SSLCOM_ECC\"\n        fi\n        _debug \"Selected server: $_serverdir\"\n        ACME_DIRECTORY=\"$_serverdir\"\n        export ACME_DIRECTORY\n        return\n      fi\n    done\n  done\n  ACME_DIRECTORY=\"$_server\"\n  export ACME_DIRECTORY\n}\n\n#url\n_getCAShortName() {\n  caurl=\"$1\"\n  if [ -z \"$caurl\" ]; then\n    #use letsencrypt as default value if the Le_API is empty\n    #this case can only come from the old upgrading.\n    caurl=\"$CA_LETSENCRYPT_V2\"\n  fi\n  if [ \"$CA_SSLCOM_ECC\" = \"$caurl\" ]; then\n    caurl=\"$CA_SSLCOM_RSA\" #just hack to get the short name\n  fi\n  caurl_lower=\"$(echo $caurl | _lower_case)\"\n  _sindex=0\n  for surl in $(echo \"$CA_SERVERS\" | _lower_case | tr , ' '); do\n    _sindex=\"$(_math $_sindex + 1)\"\n    if [ \"$caurl_lower\" = \"$surl\" ]; then\n      _nindex=0\n      for snames in $CA_NAMES; do\n        _nindex=\"$(_math $_nindex + 1)\"\n        if [ $_nindex -ge $_sindex ]; then\n          _getfield \"$snames\" 1\n          return\n        fi\n      done\n    fi\n  done\n  echo \"$caurl\"\n}\n\n#set default ca to $ACME_DIRECTORY\nsetdefaultca() {\n  if [ -z \"$ACME_DIRECTORY\" ]; then\n    _err \"Please provide a --server parameter.\"\n    return 1\n  fi\n  _saveaccountconf \"DEFAULT_ACME_SERVER\" \"$ACME_DIRECTORY\"\n  _info \"Changed default CA to: $(__green \"$ACME_DIRECTORY\")\"\n}\n\n#preferred-chain\nsetdefaultchain() {\n  _initpath\n  _preferred_chain=\"$1\"\n  if [ -z \"$_preferred_chain\" ]; then\n    _err \"Please provide a value for '--preferred-chain'.\"\n    return 1\n  fi\n  mkdir -p \"$CA_DIR\"\n  _savecaconf \"DEFAULT_PREFERRED_CHAIN\" \"$_preferred_chain\"\n}\n\n#domain ecc\ninfo() {\n  _domain=\"$1\"\n  _ecc=\"$2\"\n  _initpath\n  if [ -z \"$_domain\" ]; then\n    _debug \"Show global configs\"\n    echo \"LE_WORKING_DIR=$LE_WORKING_DIR\"\n    echo \"LE_CONFIG_HOME=$LE_CONFIG_HOME\"\n    cat \"$ACCOUNT_CONF_PATH\"\n  else\n    _debug \"Show domain configs\"\n    (\n      _initpath \"$_domain\" \"$_ecc\"\n      echo \"DOMAIN_CONF=$DOMAIN_CONF\"\n      for seg in $(cat $DOMAIN_CONF | cut -d = -f 1); do\n        echo \"$seg=$(_readdomainconf \"$seg\")\"\n      done\n    )\n  fi\n}\n\n_process() {\n  _CMD=\"\"\n  _domain=\"\"\n  _altdomains=\"$NO_VALUE\"\n  _webroot=\"\"\n  _challenge_alias=\"\"\n  _keylength=\"$DEFAULT_DOMAIN_KEY_LENGTH\"\n  _accountkeylength=\"$DEFAULT_ACCOUNT_KEY_LENGTH\"\n  _cert_file=\"\"\n  _key_file=\"\"\n  _ca_file=\"\"\n  _fullchain_file=\"\"\n  _reloadcmd=\"\"\n  _password=\"\"\n  _accountconf=\"\"\n  _useragent=\"\"\n  _accountemail=\"\"\n  _accountkey=\"\"\n  _certhome=\"\"\n  _confighome=\"\"\n  _httpport=\"\"\n  _tlsport=\"\"\n  _dnssleep=\"\"\n  _listraw=\"\"\n  _stopRenewOnError=\"\"\n  #_insecure=\"\"\n  _ca_bundle=\"\"\n  _ca_path=\"\"\n  _nocron=\"\"\n  _noprofile=\"\"\n  _ecc=\"\"\n  _csr=\"\"\n  _pre_hook=\"\"\n  _post_hook=\"\"\n  _renew_hook=\"\"\n  _deploy_hook=\"\"\n  _logfile=\"\"\n  _log=\"\"\n  _local_address=\"\"\n  _log_level=\"\"\n  _auto_upgrade=\"\"\n  _request_v4=\"\"\n  _request_v6=\"\"\n  _listen_v4=\"\"\n  _listen_v6=\"\"\n  _openssl_bin=\"\"\n  _syslog=\"\"\n  _use_wget=\"\"\n  _server=\"\"\n  _notify_hook=\"\"\n  _notify_level=\"\"\n  _notify_mode=\"\"\n  _notify_source=\"\"\n  _revoke_reason=\"\"\n  _eab_kid=\"\"\n  _eab_hmac_key=\"\"\n  _preferred_chain=\"\"\n  _valid_from=\"\"\n  _valid_to=\"\"\n  _certificate_profile=\"\"\n  _extended_key_usage=\"\"\n  while [ ${#} -gt 0 ]; do\n    case \"${1}\" in\n\n    --help | -h)\n      showhelp\n      return\n      ;;\n    --version | -v)\n      version\n      return\n      ;;\n    --install)\n      _CMD=\"install\"\n      ;;\n    --install-online)\n      shift\n      installOnline \"$@\"\n      return\n      ;;\n    --uninstall)\n      _CMD=\"uninstall\"\n      ;;\n    --upgrade)\n      _CMD=\"upgrade\"\n      ;;\n    --issue)\n      _CMD=\"issue\"\n      ;;\n    --deploy)\n      _CMD=\"deploy\"\n      ;;\n    --sign-csr | --signcsr)\n      _CMD=\"signcsr\"\n      ;;\n    --show-csr | --showcsr)\n      _CMD=\"showcsr\"\n      ;;\n    -i | --install-cert | --installcert)\n      _CMD=\"installcert\"\n      ;;\n    --renew | -r)\n      _CMD=\"renew\"\n      ;;\n    --renew-all | --renewAll | --renewall)\n      _CMD=\"renewAll\"\n      ;;\n    --revoke)\n      _CMD=\"revoke\"\n      ;;\n    --remove)\n      _CMD=\"remove\"\n      ;;\n    --list)\n      _CMD=\"list\"\n      ;;\n    --info)\n      _CMD=\"info\"\n      ;;\n    --install-cronjob | --installcronjob)\n      _CMD=\"installcronjob\"\n      ;;\n    --uninstall-cronjob | --uninstallcronjob)\n      _CMD=\"uninstallcronjob\"\n      ;;\n    --cron)\n      _CMD=\"cron\"\n      ;;\n    --to-pkcs12 | --to-pkcs | --toPkcs)\n      _CMD=\"toPkcs\"\n      ;;\n    --to-pkcs8 | --toPkcs8)\n      _CMD=\"toPkcs8\"\n      ;;\n    --create-account-key | --createAccountKey | --createaccountkey | -cak)\n      _CMD=\"createAccountKey\"\n      ;;\n    --create-domain-key | --createDomainKey | --createdomainkey | -cdk)\n      _CMD=\"createDomainKey\"\n      ;;\n    -ccr | --create-csr | --createCSR | --createcsr)\n      _CMD=\"createCSR\"\n      ;;\n    --deactivate)\n      _CMD=\"deactivate\"\n      ;;\n    --update-account | --updateaccount)\n      _CMD=\"updateaccount\"\n      ;;\n    --register-account | --registeraccount)\n      _CMD=\"registeraccount\"\n      ;;\n    --deactivate-account)\n      _CMD=\"deactivateaccount\"\n      ;;\n    --set-notify)\n      _CMD=\"setnotify\"\n      ;;\n    --set-default-ca)\n      _CMD=\"setdefaultca\"\n      ;;\n    --set-default-chain)\n      _CMD=\"setdefaultchain\"\n      ;;\n    --list-profiles)\n      _CMD=\"list_profiles\"\n      ;;\n    -d | --domain)\n      _dvalue=\"$2\"\n\n      if [ \"$_dvalue\" ]; then\n        if _startswith \"$_dvalue\" \"-\"; then\n          _err \"'$_dvalue' is not a valid domain for parameter '$1'\"\n          return 1\n        fi\n        if _is_idn \"$_dvalue\" && ! _exists idn; then\n          _err \"It seems that $_dvalue is an IDN (Internationalized Domain Names), please install the 'idn' command first.\"\n          return 1\n        fi\n\n        if [ -z \"$_domain\" ]; then\n          _domain=\"$_dvalue\"\n        else\n          if [ \"$_altdomains\" = \"$NO_VALUE\" ]; then\n            _altdomains=\"$_dvalue\"\n          else\n            _altdomains=\"$_altdomains,$_dvalue\"\n          fi\n        fi\n      fi\n\n      shift\n      ;;\n\n    -f | --force)\n      FORCE=\"1\"\n      ;;\n    --staging | --test)\n      STAGE=\"1\"\n      ;;\n    --server)\n      _server=\"$2\"\n      shift\n      ;;\n    --debug)\n      if [ -z \"$2\" ] || _startswith \"$2\" \"-\"; then\n        DEBUG=\"$DEBUG_LEVEL_DEFAULT\"\n      else\n        DEBUG=\"$2\"\n        shift\n      fi\n      ;;\n    --output-insecure)\n      export OUTPUT_INSECURE=1\n      ;;\n    -w | --webroot)\n      wvalue=\"$2\"\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      shift\n      ;;\n    --challenge-alias)\n      cvalue=\"$2\"\n      _challenge_alias=\"$_challenge_alias$cvalue,\"\n      shift\n      ;;\n    --domain-alias)\n      cvalue=\"$DNS_ALIAS_PREFIX$2\"\n      _challenge_alias=\"$_challenge_alias$cvalue,\"\n      shift\n      ;;\n    --standalone)\n      wvalue=\"$NO_VALUE\"\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      ;;\n    --alpn)\n      wvalue=\"$W_ALPN\"\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      ;;\n    --stateless)\n      wvalue=\"$MODE_STATELESS\"\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      ;;\n    --local-address)\n      lvalue=\"$2\"\n      _local_address=\"$_local_address$lvalue,\"\n      shift\n      ;;\n    --apache)\n      wvalue=\"apache\"\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      ;;\n    --nginx)\n      wvalue=\"$NGINX\"\n      if [ \"$2\" ] && ! _startswith \"$2\" \"-\"; then\n        wvalue=\"$NGINX$2\"\n        shift\n      fi\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      ;;\n    --dns)\n      wvalue=\"$W_DNS\"\n      if [ \"$2\" ] && ! _startswith \"$2\" \"-\"; then\n        wvalue=\"$2\"\n        shift\n      fi\n      if [ -z \"$_webroot\" ]; then\n        _webroot=\"$wvalue\"\n      else\n        _webroot=\"$_webroot,$wvalue\"\n      fi\n      ;;\n    --dnssleep)\n      _dnssleep=\"$2\"\n      Le_DNSSleep=\"$_dnssleep\"\n      shift\n      ;;\n    --keylength | -k)\n      _keylength=\"$2\"\n      shift\n      if [ \"$_keylength\" ] && ! _isEccKey \"$_keylength\"; then\n        export __SELECTED_RSA_KEY=1\n      fi\n      ;;\n    -ak | --accountkeylength)\n      _accountkeylength=\"$2\"\n      shift\n      ;;\n    --cert-file | --certpath)\n      _cert_file=\"$2\"\n      shift\n      ;;\n    --key-file | --keypath)\n      _key_file=\"$2\"\n      shift\n      ;;\n    --ca-file | --capath)\n      _ca_file=\"$2\"\n      shift\n      ;;\n    --fullchain-file | --fullchainpath)\n      _fullchain_file=\"$2\"\n      shift\n      ;;\n    --reloadcmd | --reloadCmd)\n      _reloadcmd=\"$2\"\n      shift\n      ;;\n    --password)\n      _password=\"$2\"\n      shift\n      ;;\n    --accountconf)\n      _accountconf=\"$2\"\n      ACCOUNT_CONF_PATH=\"$_accountconf\"\n      shift\n      ;;\n    --home)\n      export LE_WORKING_DIR=\"$(echo \"$2\" | sed 's|/$||')\"\n      shift\n      ;;\n    --cert-home | --certhome)\n      _certhome=\"$2\"\n      export CERT_HOME=\"$_certhome\"\n      shift\n      ;;\n    --config-home)\n      _confighome=\"$2\"\n      export LE_CONFIG_HOME=\"$_confighome\"\n      shift\n      ;;\n    --useragent)\n      _useragent=\"$2\"\n      USER_AGENT=\"$_useragent\"\n      shift\n      ;;\n    -m | --email | --accountemail)\n      _accountemail=\"$2\"\n      export ACCOUNT_EMAIL=\"$_accountemail\"\n      shift\n      ;;\n    --accountkey)\n      _accountkey=\"$2\"\n      ACCOUNT_KEY_PATH=\"$_accountkey\"\n      shift\n      ;;\n    --days)\n      _days=\"$2\"\n      Le_RenewalDays=\"$_days\"\n      shift\n      ;;\n    --valid-from)\n      _valid_from=\"$2\"\n      shift\n      ;;\n    --valid-to)\n      _valid_to=\"$2\"\n      shift\n      ;;\n    --certificate-profile | --cert-profile)\n      _certificate_profile=\"$2\"\n      shift\n      ;;\n    --httpport)\n      _httpport=\"$2\"\n      Le_HTTPPort=\"$_httpport\"\n      shift\n      ;;\n    --tlsport)\n      _tlsport=\"$2\"\n      Le_TLSPort=\"$_tlsport\"\n      shift\n      ;;\n    --listraw)\n      _listraw=\"raw\"\n      ;;\n    -se | --stop-renew-on-error | --stopRenewOnError | --stoprenewonerror)\n      _stopRenewOnError=\"1\"\n      ;;\n    --insecure)\n      #_insecure=\"1\"\n      HTTPS_INSECURE=\"1\"\n      ;;\n    --ca-bundle)\n      _ca_bundle=\"$(_readlink \"$2\")\"\n      CA_BUNDLE=\"$_ca_bundle\"\n      shift\n      ;;\n    --ca-path)\n      _ca_path=\"$2\"\n      CA_PATH=\"$_ca_path\"\n      shift\n      ;;\n    --no-cron | --nocron)\n      _nocron=\"1\"\n      ;;\n    --no-profile | --noprofile)\n      _noprofile=\"1\"\n      ;;\n    --no-color)\n      export ACME_NO_COLOR=1\n      ;;\n    --force-color)\n      export ACME_FORCE_COLOR=1\n      ;;\n    --ecc)\n      _ecc=\"isEcc\"\n      ;;\n    --csr)\n      _csr=\"$2\"\n      shift\n      ;;\n    --pre-hook)\n      _pre_hook=\"$2\"\n      shift\n      ;;\n    --post-hook)\n      _post_hook=\"$2\"\n      shift\n      ;;\n    --renew-hook)\n      _renew_hook=\"$2\"\n      shift\n      ;;\n    --deploy-hook)\n      if [ -z \"$2\" ] || _startswith \"$2\" \"-\"; then\n        _usage \"Please specify a value for '--deploy-hook'\"\n        return 1\n      fi\n      _deploy_hook=\"$_deploy_hook$2,\"\n      shift\n      ;;\n    --extended-key-usage)\n      _extended_key_usage=\"$2\"\n      shift\n      ;;\n    --ocsp-must-staple | --ocsp)\n      Le_OCSP_Staple=\"1\"\n      ;;\n    --always-force-new-domain-key)\n      if [ -z \"$2\" ] || _startswith \"$2\" \"-\"; then\n        Le_ForceNewDomainKey=1\n      else\n        Le_ForceNewDomainKey=\"$2\"\n        shift\n      fi\n      ;;\n    --yes-I-know-dns-manual-mode-enough-go-ahead-please)\n      export FORCE_DNS_MANUAL=1\n      ;;\n    --log | --logfile)\n      _log=\"1\"\n      _logfile=\"$2\"\n      if _startswith \"$_logfile\" '-'; then\n        _logfile=\"\"\n      else\n        shift\n      fi\n      LOG_FILE=\"$_logfile\"\n      if [ -z \"$LOG_LEVEL\" ]; then\n        LOG_LEVEL=\"$DEFAULT_LOG_LEVEL\"\n      fi\n      ;;\n    --log-level)\n      _log_level=\"$2\"\n      LOG_LEVEL=\"$_log_level\"\n      shift\n      ;;\n    --syslog)\n      if ! _startswith \"$2\" '-'; then\n        _syslog=\"$2\"\n        shift\n      fi\n      if [ -z \"$_syslog\" ]; then\n        _syslog=\"$SYSLOG_LEVEL_DEFAULT\"\n      fi\n      ;;\n    --auto-upgrade)\n      _auto_upgrade=\"$2\"\n      if [ -z \"$_auto_upgrade\" ] || _startswith \"$_auto_upgrade\" '-'; then\n        _auto_upgrade=\"1\"\n      else\n        shift\n      fi\n      AUTO_UPGRADE=\"$_auto_upgrade\"\n      ;;\n    --request-v4)\n      _request_v4=\"1\"\n      ACME_USE_IPV4_REQUESTS=\"1\"\n      _request_v6=\"\"\n      ACME_USE_IPV6_REQUESTS=\"\"\n      ;;\n    --request-v6)\n      _request_v6=\"1\"\n      ACME_USE_IPV6_REQUESTS=\"1\"\n      _request_v4=\"\"\n      ACME_USE_IPV4_REQUESTS=\"\"\n      ;;\n    --listen-v4)\n      _listen_v4=\"1\"\n      Le_Listen_V4=\"$_listen_v4\"\n      ;;\n    --listen-v6)\n      _listen_v6=\"1\"\n      Le_Listen_V6=\"$_listen_v6\"\n      ;;\n    --openssl-bin)\n      _openssl_bin=\"$2\"\n      ACME_OPENSSL_BIN=\"$_openssl_bin\"\n      shift\n      ;;\n    --use-wget)\n      _use_wget=\"1\"\n      ACME_USE_WGET=\"1\"\n      ;;\n    --branch | -b)\n      export BRANCH=\"$2\"\n      shift\n      ;;\n    --notify-hook)\n      _nhook=\"$2\"\n      if _startswith \"$_nhook\" \"-\"; then\n        _err \"'$_nhook' is not a hook name for '$1'\"\n        return 1\n      fi\n      if [ \"$_notify_hook\" ]; then\n        _notify_hook=\"$_notify_hook,$_nhook\"\n      else\n        _notify_hook=\"$_nhook\"\n      fi\n      shift\n      ;;\n    --notify-level)\n      _nlevel=\"$2\"\n      if _startswith \"$_nlevel\" \"-\"; then\n        _err \"'$_nlevel' is not an integer for '$1'\"\n        return 1\n      fi\n      _notify_level=\"$_nlevel\"\n      shift\n      ;;\n    --notify-mode)\n      _nmode=\"$2\"\n      if _startswith \"$_nmode\" \"-\"; then\n        _err \"'$_nmode' is not an integer for '$1'\"\n        return 1\n      fi\n      _notify_mode=\"$_nmode\"\n      shift\n      ;;\n    --notify-source)\n      _nsource=\"$2\"\n      if _startswith \"$_nsource\" \"-\"; then\n        _err \"'$_nsource' is not a valid host name for '$1'\"\n        return 1\n      fi\n      _notify_source=\"$_nsource\"\n      shift\n      ;;\n    --revoke-reason)\n      _revoke_reason=\"$2\"\n      if _startswith \"$_revoke_reason\" \"-\"; then\n        _err \"'$_revoke_reason' is not an integer for '$1'\"\n        return 1\n      fi\n      shift\n      ;;\n    --eab-kid)\n      _eab_kid=\"$2\"\n      shift\n      ;;\n    --eab-hmac-key)\n      _eab_hmac_key=\"$2\"\n      shift\n      ;;\n    --preferred-chain)\n      _preferred_chain=\"$2\"\n      shift\n      ;;\n    *)\n      _err \"Unknown parameter: $1\"\n      return 1\n      ;;\n    esac\n\n    shift 1\n  done\n\n  if [ \"$_server\" ]; then\n    _selectServer \"$_server\" \"${_ecc:-$_keylength}\"\n    _server=\"$ACME_DIRECTORY\"\n  fi\n\n  if [ \"${_CMD}\" != \"install\" ]; then\n    if [ \"$__INTERACTIVE\" ] && ! _checkSudo; then\n      if [ -z \"$FORCE\" ]; then\n        #Use \"echo\" here, instead of _info. it's too early\n        echo \"It seems that you are using sudo, please read this page first:\"\n        echo \"$_SUDO_WIKI\"\n        return 1\n      fi\n    fi\n    __initHome\n    if [ \"$_log\" ]; then\n      if [ -z \"$_logfile\" ]; then\n        _logfile=\"$DEFAULT_LOG_FILE\"\n      fi\n    fi\n    if [ \"$_logfile\" ]; then\n      _saveaccountconf \"LOG_FILE\" \"$_logfile\"\n      LOG_FILE=\"$_logfile\"\n    fi\n\n    if [ \"$_log_level\" ]; then\n      _saveaccountconf \"LOG_LEVEL\" \"$_log_level\"\n      LOG_LEVEL=\"$_log_level\"\n    fi\n\n    if [ \"$_syslog\" ]; then\n      if _exists logger; then\n        if [ \"$_syslog\" = \"0\" ]; then\n          _clearaccountconf \"SYS_LOG\"\n        else\n          _saveaccountconf \"SYS_LOG\" \"$_syslog\"\n        fi\n        SYS_LOG=\"$_syslog\"\n      else\n        _err \"The 'logger' command was not found, cannot enable syslog.\"\n        _clearaccountconf \"SYS_LOG\"\n        SYS_LOG=\"\"\n      fi\n    fi\n\n    _processAccountConf\n  fi\n\n  _debug2 LE_WORKING_DIR \"$LE_WORKING_DIR\"\n\n  if [ \"$DEBUG\" ]; then\n    version\n    if [ \"$_server\" ]; then\n      _debug \"Using server: $_server\"\n    fi\n  fi\n  _debug \"Running cmd: ${_CMD}\"\n  case \"${_CMD}\" in\n  install) install \"$_nocron\" \"$_confighome\" \"$_noprofile\" \"$_accountemail\" ;;\n  uninstall) uninstall \"$_nocron\" ;;\n  upgrade) upgrade ;;\n  issue)\n    issue \"$_webroot\" \"$_domain\" \"$_altdomains\" \"$_keylength\" \"$_cert_file\" \"$_key_file\" \"$_ca_file\" \"$_reloadcmd\" \"$_fullchain_file\" \"$_pre_hook\" \"$_post_hook\" \"$_renew_hook\" \"$_local_address\" \"$_challenge_alias\" \"$_preferred_chain\" \"$_valid_from\" \"$_valid_to\" \"$_certificate_profile\" \"$_extended_key_usage\"\n    ;;\n  deploy)\n    deploy \"$_domain\" \"$_deploy_hook\" \"$_ecc\"\n    ;;\n  signcsr)\n    signcsr \"$_csr\" \"$_webroot\" \"$_cert_file\" \"$_key_file\" \"$_ca_file\" \"$_reloadcmd\" \"$_fullchain_file\" \"$_pre_hook\" \"$_post_hook\" \"$_renew_hook\" \"$_local_address\" \"$_challenge_alias\" \"$_preferred_chain\" \"$_valid_from\" \"$_valid_to\" \"$_certificate_profile\" \"$_extended_key_usage\"\n    ;;\n  showcsr)\n    showcsr \"$_csr\" \"$_domain\"\n    ;;\n  installcert)\n    installcert \"$_domain\" \"$_cert_file\" \"$_key_file\" \"$_ca_file\" \"$_reloadcmd\" \"$_fullchain_file\" \"$_ecc\"\n    ;;\n  renew)\n    renew \"$_domain\" \"$_ecc\" \"$_server\"\n    ;;\n  renewAll)\n    renewAll \"$_stopRenewOnError\" \"$_server\"\n    ;;\n  revoke)\n    revoke \"$_domain\" \"$_ecc\" \"$_revoke_reason\"\n    ;;\n  remove)\n    remove \"$_domain\" \"$_ecc\"\n    ;;\n  deactivate)\n    deactivate \"$_domain,$_altdomains\"\n    ;;\n  registeraccount)\n    registeraccount \"$_accountkeylength\" \"$_eab_kid\" \"$_eab_hmac_key\"\n    ;;\n  updateaccount)\n    updateaccount\n    ;;\n  deactivateaccount)\n    deactivateaccount\n    ;;\n  list)\n    list \"$_listraw\" \"$_domain\"\n    ;;\n  info)\n    info \"$_domain\" \"$_ecc\"\n    ;;\n  installcronjob) installcronjob \"$_confighome\" ;;\n  uninstallcronjob) uninstallcronjob ;;\n  cron) cron ;;\n  toPkcs)\n    toPkcs \"$_domain\" \"$_password\" \"$_ecc\"\n    ;;\n  toPkcs8)\n    toPkcs8 \"$_domain\" \"$_ecc\"\n    ;;\n  createAccountKey)\n    createAccountKey \"$_accountkeylength\"\n    ;;\n  createDomainKey)\n    createDomainKey \"$_domain\" \"$_keylength\"\n    ;;\n  createCSR)\n    createCSR \"$_domain\" \"$_altdomains\" \"$_ecc\"\n    ;;\n  setnotify)\n    setnotify \"$_notify_hook\" \"$_notify_level\" \"$_notify_mode\" \"$_notify_source\"\n    ;;\n  setdefaultca)\n    setdefaultca\n    ;;\n  setdefaultchain)\n    setdefaultchain \"$_preferred_chain\"\n    ;;\n  list_profiles)\n    list_profiles\n    ;;\n  *)\n    if [ \"$_CMD\" ]; then\n      _err \"Invalid command: $_CMD\"\n    fi\n    showhelp\n    return 1\n    ;;\n  esac\n  _ret=\"$?\"\n  if [ \"$_ret\" != \"0\" ]; then\n    return $_ret\n  fi\n\n  if [ \"${_CMD}\" = \"install\" ]; then\n    if [ \"$_log\" ]; then\n      if [ -z \"$LOG_FILE\" ]; then\n        LOG_FILE=\"$DEFAULT_LOG_FILE\"\n      fi\n      _saveaccountconf \"LOG_FILE\" \"$LOG_FILE\"\n    fi\n\n    if [ \"$_log_level\" ]; then\n      _saveaccountconf \"LOG_LEVEL\" \"$_log_level\"\n    fi\n\n    if [ \"$_syslog\" ]; then\n      if _exists logger; then\n        if [ \"$_syslog\" = \"0\" ]; then\n          _clearaccountconf \"SYS_LOG\"\n        else\n          _saveaccountconf \"SYS_LOG\" \"$_syslog\"\n        fi\n      else\n        _err \"The 'logger' command was not found, cannot enable syslog.\"\n        _clearaccountconf \"SYS_LOG\"\n        SYS_LOG=\"\"\n      fi\n    fi\n\n    _processAccountConf\n  fi\n\n}\n\nmain() {\n  [ -z \"$1\" ] && showhelp && return\n  if _startswith \"$1\" '-'; then _process \"$@\"; else \"$@\"; fi\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "deploy/README.md",
    "content": "# Using deploy api\n\ndeploy hook usage:\n\nhttps://github.com/acmesh-official/acme.sh/wiki/deployhooks\n\n"
  },
  {
    "path": "deploy/ali_cdn.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034,SC2154\n\n# Script to create certificate to Alibaba Cloud CDN\n#\n# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun\n#\n# This deployment required following variables\n# export Ali_Key=\"ALIACCESSKEY\"\n# export Ali_Secret=\"ALISECRETKEY\"\n# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi\n#\n# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates\n# export DEPLOY_ALI_CDN_DOMAIN=\"cdn.example.com\"\n# If you have multiple CDN domains using the same certificate, just\n# export DEPLOY_ALI_CDN_DOMAIN=\"cdn1.example.com cdn2.example.com\"\n#\n# For DCDN, see ali_dcdn deploy hook\n\nAli_CDN_API=\"https://cdn.aliyuncs.com/\"\n\nali_cdn_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # Load dnsapi/dns_ali.sh to reduce the duplicated codes\n  # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276\n  dnsapi_ali=\"$(_findHook \"$_cdomain\" \"$_SUB_FOLDER_DNSAPI\" dns_ali)\"\n  # shellcheck source=/dev/null\n  if ! . \"$dnsapi_ali\"; then\n    _err \"Error loading file $dnsapi_ali. Please check your API file and try again.\"\n    return 1\n  fi\n\n  _prepare_ali_credentials || return 1\n\n  _getdeployconf DEPLOY_ALI_CDN_DOMAIN\n  if [ \"$DEPLOY_ALI_CDN_DOMAIN\" ]; then\n    _savedeployconf DEPLOY_ALI_CDN_DOMAIN \"$DEPLOY_ALI_CDN_DOMAIN\"\n  else\n    DEPLOY_ALI_CDN_DOMAIN=\"$_cdomain\"\n  fi\n\n  # read cert and key files and urlencode both\n  _cert=$(_url_encode upper-hex <\"$_cfullchain\")\n  _key=$(_url_encode upper-hex <\"$_ckey\")\n\n  _debug2 _cert \"$_cert\"\n  _debug2 _key \"$_key\"\n\n  ## update domain ssl config\n  for domain in $DEPLOY_ALI_CDN_DOMAIN; do\n    _set_cdn_domain_ssl_certificate_query \"$domain\" \"$_cert\" \"$_key\"\n    if _ali_rest \"Set CDN domain SSL certificate for $domain\" \"\" POST; then\n      _info \"Domain $domain certificate has been deployed successfully\"\n    fi\n  done\n\n  return 0\n}\n\n# domain pub pri\n_set_cdn_domain_ssl_certificate_query() {\n  endpoint=$Ali_CDN_API\n  query=''\n  query=$query'AccessKeyId='$Ali_Key\n  query=$query'&Action=SetCdnDomainSSLCertificate'\n  query=$query'&CertType=upload'\n  query=$query'&DomainName='$1\n  query=$query'&Format=json'\n  query=$query'&SSLPri='$3\n  query=$query'&SSLProtocol=on'\n  query=$query'&SSLPub='$2\n  query=$query'&SignatureMethod=HMAC-SHA1'\n  query=$query\"&SignatureNonce=$(_ali_nonce)\"\n  query=$query'&SignatureVersion=1.0'\n  query=$query'&Timestamp='$(_ali_timestamp)\n  query=$query'&Version=2018-05-10'\n}\n"
  },
  {
    "path": "deploy/ali_dcdn.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034,SC2154\n\n# Script to create certificate to Alibaba Cloud DCDN\n#\n# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun\n#\n# This deployment required following variables\n# export Ali_Key=\"ALIACCESSKEY\"\n# export Ali_Secret=\"ALISECRETKEY\"\n# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi\n#\n# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates\n# export DEPLOY_ALI_DCDN_DOMAIN=\"dcdn.example.com\"\n# If you have multiple CDN domains using the same certificate, just\n# export DEPLOY_ALI_DCDN_DOMAIN=\"dcdn1.example.com dcdn2.example.com\"\n#\n# For regular CDN, see ali_cdn deploy hook\n\nAli_DCDN_API=\"https://dcdn.aliyuncs.com/\"\n\nali_dcdn_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # Load dnsapi/dns_ali.sh to reduce the duplicated codes\n  # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276\n  dnsapi_ali=\"$(_findHook \"$_cdomain\" \"$_SUB_FOLDER_DNSAPI\" dns_ali)\"\n  # shellcheck source=/dev/null\n  if ! . \"$dnsapi_ali\"; then\n    _err \"Error loading file $dnsapi_ali. Please check your API file and try again.\"\n    return 1\n  fi\n\n  _prepare_ali_credentials || return 1\n\n  _getdeployconf DEPLOY_ALI_DCDN_DOMAIN\n  if [ \"$DEPLOY_ALI_DCDN_DOMAIN\" ]; then\n    _savedeployconf DEPLOY_ALI_DCDN_DOMAIN \"$DEPLOY_ALI_DCDN_DOMAIN\"\n  else\n    DEPLOY_ALI_DCDN_DOMAIN=\"$_cdomain\"\n  fi\n\n  # read cert and key files and urlencode both\n  _cert=$(_url_encode upper-hex <\"$_cfullchain\")\n  _key=$(_url_encode upper-hex <\"$_ckey\")\n\n  _debug2 _cert \"$_cert\"\n  _debug2 _key \"$_key\"\n\n  ## update domain ssl config\n  for domain in $DEPLOY_ALI_DCDN_DOMAIN; do\n    _set_dcdn_domain_ssl_certificate_query \"$domain\" \"$_cert\" \"$_key\"\n    if _ali_rest \"Set DCDN domain SSL certificate for $domain\" \"\" POST; then\n      _info \"Domain $domain certificate has been deployed successfully\"\n    fi\n  done\n\n  return 0\n}\n\n# domain pub pri\n_set_dcdn_domain_ssl_certificate_query() {\n  endpoint=$Ali_DCDN_API\n  query=''\n  query=$query'AccessKeyId='$Ali_Key\n  query=$query'&Action=SetDcdnDomainSSLCertificate'\n  query=$query'&CertType=upload'\n  query=$query'&DomainName='$1\n  query=$query'&Format=json'\n  query=$query'&SSLPri='$3\n  query=$query'&SSLProtocol=on'\n  query=$query'&SSLPub='$2\n  query=$query'&SignatureMethod=HMAC-SHA1'\n  query=$query\"&SignatureNonce=$(_ali_nonce)\"\n  query=$query'&SignatureVersion=1.0'\n  query=$query'&Timestamp='$(_ali_timestamp)\n  query=$query'&Version=2018-01-15'\n}\n"
  },
  {
    "path": "deploy/apache.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to apache server.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\napache_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"Deploy cert to apache server, Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/cachefly.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificate to CacheFly\n# https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post\n\n# This deployment required following variables\n# export CACHEFLY_TOKEN=\"Your CacheFly API Token\"\n\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nCACHEFLY_API_BASE=\"https://api.cachefly.com/api/2.5\"\n\ncachefly_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$CACHEFLY_TOKEN\" ]; then\n    _err \"CACHEFLY_TOKEN is not defined.\"\n    return 1\n  else\n    _savedomainconf CACHEFLY_TOKEN \"$CACHEFLY_TOKEN\"\n  fi\n\n  _info \"Deploying certificate to CacheFly...\"\n\n  ## upload certificate\n  string_fullchain=$(sed 's/$/\\\\n/' \"$_cfullchain\" | tr -d '\\n')\n  string_key=$(sed 's/$/\\\\n/' \"$_ckey\" | tr -d '\\n')\n\n  _request_body=\"{\\\"certificate\\\":\\\"$string_fullchain\\\",\\\"certificateKey\\\":\\\"$string_key\\\"}\"\n  _debug _request_body \"$_request_body\"\n  _debug CACHEFLY_TOKEN \"$CACHEFLY_TOKEN\"\n  export _H1=\"Authorization: Bearer $CACHEFLY_TOKEN\"\n  _response=$(_post \"$_request_body\" \"$CACHEFLY_API_BASE/certificates\" \"\" \"POST\" \"application/json\")\n\n  if _contains \"$_response\" \"message\"; then\n    _err \"Error in deploying $_cdomain certificate to CacheFly.\"\n    _err \"$_response\"\n    return 1\n  fi\n  _debug response \"$_response\"\n  _info \"Domain $_cdomain certificate successfully deployed to CacheFly.\"\n  return 0\n}\n"
  },
  {
    "path": "deploy/cleverreach.sh",
    "content": "#!/usr/bin/env sh\n# Here is the script to deploy the cert to your CleverReach Account using the CleverReach REST API.\n# Your OAuth needs the right scope, please contact CleverReach support for that.\n#\n# Written by Jan-Philipp Benecke <github@bnck.me>\n# Public domain, 2020\n#\n# Following environment variables must be set:\n#\n#export DEPLOY_CLEVERREACH_CLIENT_ID=myid\n#export DEPLOY_CLEVERREACH_CLIENT_SECRET=mysecret\n\ncleverreach_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _rest_endpoint=\"https://rest.cleverreach.com\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf DEPLOY_CLEVERREACH_CLIENT_ID\n  _getdeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET\n  _getdeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID\n\n  if [ -z \"${DEPLOY_CLEVERREACH_CLIENT_ID}\" ]; then\n    _err \"CleverReach Client ID is not found, please define DEPLOY_CLEVERREACH_CLIENT_ID.\"\n    return 1\n  fi\n  if [ -z \"${DEPLOY_CLEVERREACH_CLIENT_SECRET}\" ]; then\n    _err \"CleverReach client secret is not found, please define DEPLOY_CLEVERREACH_CLIENT_SECRET.\"\n    return 1\n  fi\n\n  _savedeployconf DEPLOY_CLEVERREACH_CLIENT_ID \"${DEPLOY_CLEVERREACH_CLIENT_ID}\"\n  _savedeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET \"${DEPLOY_CLEVERREACH_CLIENT_SECRET}\"\n  _savedeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID \"${DEPLOY_CLEVERREACH_SUBCLIENT_ID}\"\n\n  _info \"Obtaining a CleverReach access token\"\n\n  _data=\"{\\\"grant_type\\\": \\\"client_credentials\\\", \\\"client_id\\\": \\\"${DEPLOY_CLEVERREACH_CLIENT_ID}\\\", \\\"client_secret\\\": \\\"${DEPLOY_CLEVERREACH_CLIENT_SECRET}\\\"}\"\n  _auth_result=\"$(_post \"$_data\" \"$_rest_endpoint/oauth/token.php\" \"\" \"POST\" \"application/json\")\"\n\n  _debug _data \"$_data\"\n  _debug _auth_result \"$_auth_result\"\n\n  _regex=\".*\\\"access_token\\\":\\\"\\([-._0-9A-Za-z]*\\)\\\".*$\"\n  _debug _regex \"$_regex\"\n  _access_token=$(echo \"$_auth_result\" | _json_decode | sed -n \"s/$_regex/\\1/p\")\n\n  _debug _subclient \"${DEPLOY_CLEVERREACH_SUBCLIENT_ID}\"\n\n  if [ -n \"${DEPLOY_CLEVERREACH_SUBCLIENT_ID}\" ]; then\n    _info \"Obtaining token for sub-client ${DEPLOY_CLEVERREACH_SUBCLIENT_ID}\"\n    export _H1=\"Authorization: Bearer ${_access_token}\"\n    _subclient_token_result=\"$(_get \"$_rest_endpoint/v3/clients/$DEPLOY_CLEVERREACH_SUBCLIENT_ID/token\")\"\n    _access_token=$(echo \"$_subclient_token_result\" | sed -n \"s/\\\"//p\")\n\n    _debug _subclient_token_result \"$_access_token\"\n\n    _info \"Destroying parent token at CleverReach, as it not needed anymore\"\n    _destroy_result=\"$(_post \"\" \"$_rest_endpoint/v3/oauth/token.json\" \"\" \"DELETE\" \"application/json\")\"\n    _debug _destroy_result \"$_destroy_result\"\n  fi\n\n  _info \"Uploading certificate and key to CleverReach\"\n\n  _certData=\"{\\\"cert\\\":\\\"$(_json_encode <\"$_cfullchain\")\\\", \\\"key\\\":\\\"$(_json_encode <\"$_ckey\")\\\"}\"\n  export _H1=\"Authorization: Bearer ${_access_token}\"\n  _add_cert_result=\"$(_post \"$_certData\" \"$_rest_endpoint/v3/ssl\" \"\" \"POST\" \"application/json\")\"\n\n  if [ -z \"${DEPLOY_CLEVERREACH_SUBCLIENT_ID}\" ]; then\n    _info \"Destroying token at CleverReach, as it not needed anymore\"\n    _destroy_result=\"$(_post \"\" \"$_rest_endpoint/v3/oauth/token.json\" \"\" \"DELETE\" \"application/json\")\"\n    _debug _destroy_result \"$_destroy_result\"\n  fi\n\n  if ! echo \"$_add_cert_result\" | grep '\"error\":' >/dev/null; then\n    _info \"Uploaded certificate successfully\"\n    return 0\n  else\n    _debug _add_cert_result \"$_add_cert_result\"\n    _err \"Unable to update certificate\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "deploy/consul.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert to hashicorp consul using curl\n# (https://www.consul.io/)\n#\n# it requires following environment variables:\n#\n# CONSUL_PREFIX - this contains the prefix path in consul\n# CONSUL_HTTP_ADDR - consul requires this to find your consul server\n#\n# additionally, you need to ensure that CONSUL_HTTP_TOKEN is available\n# to access the consul server\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nconsul_deploy() {\n\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # validate required env vars\n  _getdeployconf CONSUL_PREFIX\n  if [ -z \"$CONSUL_PREFIX\" ]; then\n    _err \"CONSUL_PREFIX needs to be defined (contains prefix path in vault)\"\n    return 1\n  fi\n  _savedeployconf CONSUL_PREFIX \"$CONSUL_PREFIX\"\n\n  _getdeployconf CONSUL_HTTP_ADDR\n  if [ -z \"$CONSUL_HTTP_ADDR\" ]; then\n    _err \"CONSUL_HTTP_ADDR needs to be defined (contains consul connection address)\"\n    return 1\n  fi\n  _savedeployconf CONSUL_HTTP_ADDR \"$CONSUL_HTTP_ADDR\"\n\n  CONSUL_CMD=$(command -v consul)\n\n  # force CLI, but the binary does not exist => error\n  if [ -n \"$USE_CLI\" ] && [ -z \"$CONSUL_CMD\" ]; then\n    _err \"Cannot find the consul binary!\"\n    return 1\n  fi\n\n  # use the CLI first\n  if [ -n \"$USE_CLI\" ] || [ -n \"$CONSUL_CMD\" ]; then\n    _info \"Found consul binary, deploying with CLI\"\n    consul_deploy_cli \"$CONSUL_CMD\" \"$CONSUL_PREFIX\"\n  else\n    _info \"Did not find consul binary, deploying with API\"\n    consul_deploy_api \"$CONSUL_HTTP_ADDR\" \"$CONSUL_PREFIX\" \"$CONSUL_HTTP_TOKEN\"\n  fi\n}\n\nconsul_deploy_api() {\n  CONSUL_HTTP_ADDR=\"$1\"\n  CONSUL_PREFIX=\"$2\"\n  CONSUL_HTTP_TOKEN=\"$3\"\n\n  URL=\"$CONSUL_HTTP_ADDR/v1/kv/$CONSUL_PREFIX\"\n  export _H1=\"X-Consul-Token: $CONSUL_HTTP_TOKEN\"\n\n  if [ -n \"$FABIO\" ]; then\n    _post \"$(cat \"$_cfullchain\")\" \"$URL/${_cdomain}-cert.pem\" '' \"PUT\" || return 1\n    _post \"$(cat \"$_ckey\")\" \"$URL/${_cdomain}-key.pem\" '' \"PUT\" || return 1\n  else\n    _post \"$(cat \"$_ccert\")\" \"$URL/${_cdomain}/cert.pem\" '' \"PUT\" || return 1\n    _post \"$(cat \"$_ckey\")\" \"$URL/${_cdomain}/cert.key\" '' \"PUT\" || return 1\n    _post \"$(cat \"$_cca\")\" \"$URL/${_cdomain}/chain.pem\" '' \"PUT\" || return 1\n    _post \"$(cat \"$_cfullchain\")\" \"$URL/${_cdomain}/fullchain.pem\" '' \"PUT\" || return 1\n  fi\n}\n\nconsul_deploy_cli() {\n  CONSUL_CMD=\"$1\"\n  CONSUL_PREFIX=\"$2\"\n\n  if [ -n \"$FABIO\" ]; then\n    $CONSUL_CMD kv put \"${CONSUL_PREFIX}/${_cdomain}-cert.pem\" @\"$_cfullchain\" || return 1\n    $CONSUL_CMD kv put \"${CONSUL_PREFIX}/${_cdomain}-key.pem\" @\"$_ckey\" || return 1\n  else\n    $CONSUL_CMD kv put \"${CONSUL_PREFIX}/${_cdomain}/cert.pem\" value=@\"$_ccert\" || return 1\n    $CONSUL_CMD kv put \"${CONSUL_PREFIX}/${_cdomain}/cert.key\" value=@\"$_ckey\" || return 1\n    $CONSUL_CMD kv put \"${CONSUL_PREFIX}/${_cdomain}/chain.pem\" value=@\"$_cca\" || return 1\n    $CONSUL_CMD kv put \"${CONSUL_PREFIX}/${_cdomain}/fullchain.pem\" value=@\"$_cfullchain\" || return 1\n  fi\n}\n"
  },
  {
    "path": "deploy/cpanel_uapi.sh",
    "content": "#!/usr/bin/env sh\n# Here is the script to deploy the cert to your cpanel using the cpanel API.\n# Uses command line uapi.  --user option is needed only if run as root.\n# Returns 0 when success.\n#\n# Configure DEPLOY_CPANEL_AUTO_<...> options to enable or restrict automatic\n# detection of deployment targets through UAPI (if not set, defaults below are used.)\n# - ENABLED : 'true' for multi-site / wildcard capability; otherwise single-site mode.\n# - NOMATCH : 'true' to allow deployment to sites that do not match the certificate.\n# - INCLUDE : Comma-separated list - sites must match this field.\n# - EXCLUDE : Comma-separated list - sites must NOT match this field.\n# INCLUDE/EXCLUDE both support non-lexical, glob-style matches using '*'\n#\n# Please note that I am no longer using Github. If you want to report an issue\n# or contact me, visit https://forum.webseodesigners.com/web-design-seo-and-hosting-f16/\n#\n# Written by Santeri Kannisto <santeri.kannisto@webseodesigners.com>\n# Public domain, 2017-2018\n#\n# export DEPLOY_CPANEL_USER=myusername\n# export DEPLOY_CPANEL_AUTO_ENABLED='true'\n# export DEPLOY_CPANEL_AUTO_NOMATCH='false'\n# export DEPLOY_CPANEL_AUTO_INCLUDE='*'\n# export DEPLOY_CPANEL_AUTO_EXCLUDE=''\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\ncpanel_uapi_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  # re-declare vars inherited from acme.sh but not passed to make ShellCheck happy\n  : \"${Le_Alt:=\"\"}\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if ! _exists uapi; then\n    _err \"The command uapi is not found.\"\n    return 1\n  fi\n\n  # declare useful constants\n  uapi_error_response='status: 0'\n\n  # read cert and key files and urlencode both\n  _cert=$(_url_encode <\"$_ccert\")\n  _key=$(_url_encode <\"$_ckey\")\n\n  _debug2 _cert \"$_cert\"\n  _debug2 _key \"$_key\"\n\n  if [ \"$(id -u)\" = 0 ]; then\n    _getdeployconf DEPLOY_CPANEL_USER\n    # fallback to _readdomainconf for old installs\n    if [ -z \"${DEPLOY_CPANEL_USER:=$(_readdomainconf DEPLOY_CPANEL_USER)}\" ]; then\n      _err \"It seems that you are root, please define the target user name: export DEPLOY_CPANEL_USER=username\"\n      return 1\n    fi\n    _debug DEPLOY_CPANEL_USER \"$DEPLOY_CPANEL_USER\"\n    _savedeployconf DEPLOY_CPANEL_USER \"$DEPLOY_CPANEL_USER\"\n\n    _uapi_user=\"$DEPLOY_CPANEL_USER\"\n  fi\n\n  # Load all AUTO envars and set defaults - see above for usage\n  __cpanel_initautoparam ENABLED 'true'\n  __cpanel_initautoparam NOMATCH 'false'\n  __cpanel_initautoparam INCLUDE '*'\n  __cpanel_initautoparam EXCLUDE ''\n\n  # Auto mode\n  if [ \"$DEPLOY_CPANEL_AUTO_ENABLED\" = \"true\" ]; then\n    # call API for site config\n    _response=$(uapi DomainInfo list_domains)\n    # exit if error in response\n    if [ -z \"$_response\" ] || [ \"${_response#*\"$uapi_error_response\"}\" != \"$_response\" ]; then\n      _err \"Error in deploying certificate - cannot retrieve sitelist:\"\n      _err \"\\n$_response\"\n      return 1\n    fi\n\n    # parse response to create site list\n    sitelist=$(__cpanel_parse_response \"$_response\")\n    _debug \"UAPI sites found: $sitelist\"\n\n    # filter sitelist using configured domains\n    # skip if NOMATCH is \"true\"\n    if [ \"$DEPLOY_CPANEL_AUTO_NOMATCH\" = \"true\" ]; then\n      _debug \"DEPLOY_CPANEL_AUTO_NOMATCH is true\"\n      _info \"UAPI nomatch mode is enabled - Will not validate sites are valid for the certificate\"\n    else\n      _debug \"DEPLOY_CPANEL_AUTO_NOMATCH is false\"\n      d=\"$(echo \"${Le_Alt},\" | sed -e \"s/^$_cdomain,//\" -e \"s/,$_cdomain,/,/\")\"\n      d=\"$(echo \"$_cdomain,$d\" | tr ',' '\\n' | sed -e 's/\\./\\\\./g' -e 's/\\*/\\[\\^\\.\\]\\*/g')\"\n      sitelist=\"$(echo \"$sitelist\" | grep -ix \"$d\")\"\n      _debug2 \"Matched UAPI sites: $sitelist\"\n    fi\n\n    # filter sites that do not match $DEPLOY_CPANEL_AUTO_INCLUDE\n    _info \"Applying sitelist filter DEPLOY_CPANEL_AUTO_INCLUDE: $DEPLOY_CPANEL_AUTO_INCLUDE\"\n    sitelist=\"$(echo \"$sitelist\" | grep -ix \"$(echo \"$DEPLOY_CPANEL_AUTO_INCLUDE\" | tr ',' '\\n' | sed -e 's/\\./\\\\./g' -e 's/\\*/\\.\\*/g')\")\"\n    _debug2 \"Remaining sites: $sitelist\"\n\n    # filter sites that match $DEPLOY_CPANEL_AUTO_EXCLUDE\n    _info \"Applying sitelist filter DEPLOY_CPANEL_AUTO_EXCLUDE: $DEPLOY_CPANEL_AUTO_EXCLUDE\"\n    sitelist=\"$(echo \"$sitelist\" | grep -vix \"$(echo \"$DEPLOY_CPANEL_AUTO_EXCLUDE\" | tr ',' '\\n' | sed -e 's/\\./\\\\./g' -e 's/\\*/\\.\\*/g')\")\"\n    _debug2 \"Remaining sites: $sitelist\"\n\n    # counter for success / failure check\n    successes=0\n    if [ -n \"$sitelist\" ]; then\n      sitetotal=\"$(echo \"$sitelist\" | wc -l)\"\n      _debug \"$sitetotal sites to deploy\"\n    else\n      sitetotal=0\n      _debug \"No sites to deploy\"\n    fi\n\n    # for each site: call uapi to publish cert and log result. Only return failure if all fail\n    for site in $sitelist; do\n      # call uapi to publish cert, check response for errors and log them.\n      if [ -n \"$_uapi_user\" ]; then\n        _response=$(uapi --user=\"$_uapi_user\" SSL install_ssl domain=\"$site\" cert=\"$_cert\" key=\"$_key\")\n      else\n        _response=$(uapi SSL install_ssl domain=\"$site\" cert=\"$_cert\" key=\"$_key\")\n      fi\n      if [ \"${_response#*\"$uapi_error_response\"}\" != \"$_response\" ]; then\n        _err \"Error in deploying certificate to $site:\"\n        _err \"$_response\"\n      else\n        successes=$((successes + 1))\n        _debug \"$_response\"\n        _info \"Succcessfully deployed to $site\"\n      fi\n    done\n\n    # Raise error if all updates fail\n    if [ \"$sitetotal\" -gt 0 ] && [ \"$successes\" -eq 0 ]; then\n      _err \"Could not deploy to any of $sitetotal sites via UAPI\"\n      _debug \"successes: $successes, sitetotal: $sitetotal\"\n      return 1\n    fi\n\n    _info \"Successfully deployed certificate to $successes of $sitetotal sites via UAPI\"\n    return 0\n  else\n    # \"classic\" mode - will only try to deploy to the primary domain; will not check UAPI first\n    if [ -n \"$_uapi_user\" ]; then\n      _response=$(uapi --user=\"$_uapi_user\" SSL install_ssl domain=\"$_cdomain\" cert=\"$_cert\" key=\"$_key\")\n    else\n      _response=$(uapi SSL install_ssl domain=\"$_cdomain\" cert=\"$_cert\" key=\"$_key\")\n    fi\n\n    if [ \"${_response#*\"$uapi_error_response\"}\" != \"$_response\" ]; then\n      _err \"Error in deploying certificate:\"\n      _err \"$_response\"\n      return 1\n    fi\n\n    _debug response \"$_response\"\n    _info \"Certificate successfully deployed\"\n    return 0\n  fi\n}\n\n########  Private functions #####################\n\n# Internal utility to process YML from UAPI - looks at main_domain, sub_domains, addon domains and parked domains\n#[response]\n__cpanel_parse_response() {\n  if [ $# -gt 0 ]; then resp=\"$*\"; else resp=\"$(cat)\"; fi\n\n  echo \"$resp\" |\n    sed -En \\\n      -e 's/\\r$//' \\\n      -e 's/^( *)([_.[:alnum:]]+) *: *(.*)/\\1,\\2,\\3/p' \\\n      -e 's/^( *)- (.*)/\\1,-,\\2/p' |\n    awk -F, '{\n      level = length($1)/2;\n      section[level] = $2;\n      for (i in section) {if (i > level) {delete section[i]}}\n      if (length($3) > 0) {\n        prefix=\"\";\n        for (i=0; i < level; i++)\n          { prefix = (prefix)(section[i])(\"/\") }\n        printf(\"%s%s=%s\\n\", prefix, $2, $3);\n      }\n    }' |\n    sed -En -e 's/^result\\/data\\/(main_domain|sub_domains\\/-|addon_domains\\/-|parked_domains\\/-)=(.*)$/\\2/p'\n}\n\n# Load parameter by prefix+name - fallback to default if not set, and save to config\n#pname pdefault\n__cpanel_initautoparam() {\n  pname=\"$1\"\n  pdefault=\"$2\"\n  pkey=\"DEPLOY_CPANEL_AUTO_$pname\"\n\n  _getdeployconf \"$pkey\"\n  [ -n \"$(eval echo \"\\\"\\$$pkey\\\"\")\" ] || eval \"$pkey=\\\"$pdefault\\\"\"\n  _debug2 \"$pkey\" \"$(eval echo \"\\\"\\$$pkey\\\"\")\"\n  _savedeployconf \"$pkey\" \"$(eval echo \"\\\"\\$$pkey\\\"\")\"\n}\n"
  },
  {
    "path": "deploy/directadmin.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificate to DirectAdmin\n# https://docs.directadmin.com/directadmin/customizing-workflow/api-all-about.html#creating-a-login-key\n# https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs\n\n# This deployment required following variables\n# export DirectAdmin_SCHEME=\"https\" # Optional, https or http, defaults to https\n# export DirectAdmin_ENDPOINT=\"example.com:2222\"\n# export DirectAdmin_USERNAME=\"Your DirectAdmin Username\"\n# export DirectAdmin_KEY=\"Your DirectAdmin Login Key or Password\"\n# export DirectAdmin_MAIN_DOMAIN=\"Your DirectAdmin Main Domain, NOT Subdomain\"\n\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\ndirectadmin_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$DirectAdmin_ENDPOINT\" ]; then\n    _err \"DirectAdmin_ENDPOINT is not defined.\"\n    return 1\n  else\n    _savedomainconf DirectAdmin_ENDPOINT \"$DirectAdmin_ENDPOINT\"\n  fi\n  if [ -z \"$DirectAdmin_USERNAME\" ]; then\n    _err \"DirectAdmin_USERNAME is not defined.\"\n    return 1\n  else\n    _savedomainconf DirectAdmin_USERNAME \"$DirectAdmin_USERNAME\"\n  fi\n  if [ -z \"$DirectAdmin_KEY\" ]; then\n    _err \"DirectAdmin_KEY is not defined.\"\n    return 1\n  else\n    _savedomainconf DirectAdmin_KEY \"$DirectAdmin_KEY\"\n  fi\n  if [ -z \"$DirectAdmin_MAIN_DOMAIN\" ]; then\n    _err \"DirectAdmin_MAIN_DOMAIN is not defined.\"\n    return 1\n  else\n    _savedomainconf DirectAdmin_MAIN_DOMAIN \"$DirectAdmin_MAIN_DOMAIN\"\n  fi\n\n  # Optional SCHEME\n  _getdeployconf DirectAdmin_SCHEME\n  # set default values for DirectAdmin_SCHEME\n  [ -n \"${DirectAdmin_SCHEME}\" ] || DirectAdmin_SCHEME=\"https\"\n\n  _info \"Deploying certificate to DirectAdmin...\"\n\n  # upload certificate\n  string_cfullchain=$(sed 's/$/\\\\n/' \"$_cfullchain\" | tr -d '\\n')\n  string_key=$(sed 's/$/\\\\n/' \"$_ckey\" | tr -d '\\n')\n\n  _request_body=\"{\\\"domain\\\":\\\"$DirectAdmin_MAIN_DOMAIN\\\",\\\"action\\\":\\\"save\\\",\\\"type\\\":\\\"paste\\\",\\\"certificate\\\":\\\"$string_key\\n$string_cfullchain\\n\\\"}\"\n  _debug _request_body \"$_request_body\"\n  _debug DirectAdmin_ENDPOINT \"$DirectAdmin_ENDPOINT\"\n  _debug DirectAdmin_USERNAME \"$DirectAdmin_USERNAME\"\n  _debug DirectAdmin_KEY \"$DirectAdmin_KEY\"\n  _debug DirectAdmin_MAIN_DOMAIN \"$DirectAdmin_MAIN_DOMAIN\"\n  _response=$(_post \"$_request_body\" \"$DirectAdmin_SCHEME://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL\" \"\" \"POST\" \"application/json\")\n\n  if _contains \"$_response\" \"error=1\"; then\n    _err \"Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN.\"\n    _err \"$_response\"\n    return 1\n  fi\n\n  _info \"$_response\"\n  _info \"Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN.\"\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/docker.sh",
    "content": "#!/usr/bin/env sh\n\n#DEPLOY_DOCKER_CONTAINER_LABEL=\"xxxxxxx\"\n\n#DEPLOY_DOCKER_CONTAINER_KEY_FILE=\"/path/to/key.pem\"\n#DEPLOY_DOCKER_CONTAINER_CERT_FILE=\"/path/to/cert.pem\"\n#DEPLOY_DOCKER_CONTAINER_CA_FILE=\"/path/to/ca.pem\"\n#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE=\"/path/to/fullchain.pem\"\n#DEPLOY_DOCKER_CONTAINER_RELOAD_CMD=\"service nginx force-reload\"\n\n_DEPLOY_DOCKER_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers\"\n\n_DOCKER_HOST_DEFAULT=\"/var/run/docker.sock\"\n\ndocker_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _cpfx=\"$6\"\n  _debug _cdomain \"$_cdomain\"\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL\n  _debug2 DEPLOY_DOCKER_CONTAINER_LABEL \"$DEPLOY_DOCKER_CONTAINER_LABEL\"\n  if [ -z \"$DEPLOY_DOCKER_CONTAINER_LABEL\" ]; then\n    _err \"The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container.\"\n    _err \"See: $_DEPLOY_DOCKER_WIKI\"\n  fi\n\n  _savedeployconf DEPLOY_DOCKER_CONTAINER_LABEL \"$DEPLOY_DOCKER_CONTAINER_LABEL\"\n\n  if [ \"$DOCKER_HOST\" ]; then\n    _saveaccountconf DOCKER_HOST \"$DOCKER_HOST\"\n  fi\n\n  if _exists docker && docker version | grep -i docker >/dev/null; then\n    _info \"Using docker command\"\n    export _USE_DOCKER_COMMAND=1\n  else\n    export _USE_DOCKER_COMMAND=\n  fi\n\n  export _USE_UNIX_SOCKET=\n  if [ -z \"$_USE_DOCKER_COMMAND\" ]; then\n    export _USE_REST=\n    if [ \"$DOCKER_HOST\" ]; then\n      _debug \"Try use docker host: $DOCKER_HOST\"\n      export _USE_REST=1\n    else\n      export _DOCKER_SOCK=\"$_DOCKER_HOST_DEFAULT\"\n      _debug \"Try use $_DOCKER_SOCK\"\n      if [ ! -e \"$_DOCKER_SOCK\" ] || [ ! -w \"$_DOCKER_SOCK\" ]; then\n        _err \"$_DOCKER_SOCK is not available\"\n        return 1\n      fi\n      export _USE_UNIX_SOCKET=1\n      if ! _exists \"curl\"; then\n        _err \"Please install curl first.\"\n        _err \"We need curl to work.\"\n        return 1\n      fi\n      if ! _check_curl_version; then\n        return 1\n      fi\n    fi\n  fi\n\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE\n  _debug2 DEPLOY_DOCKER_CONTAINER_KEY_FILE \"$DEPLOY_DOCKER_CONTAINER_KEY_FILE\"\n  if [ \"$DEPLOY_DOCKER_CONTAINER_KEY_FILE\" ]; then\n    _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE \"$DEPLOY_DOCKER_CONTAINER_KEY_FILE\"\n  fi\n\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE\n  _debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE \"$DEPLOY_DOCKER_CONTAINER_CERT_FILE\"\n  if [ \"$DEPLOY_DOCKER_CONTAINER_CERT_FILE\" ]; then\n    _savedeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE \"$DEPLOY_DOCKER_CONTAINER_CERT_FILE\"\n  fi\n\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE\n  _debug2 DEPLOY_DOCKER_CONTAINER_CA_FILE \"$DEPLOY_DOCKER_CONTAINER_CA_FILE\"\n  if [ \"$DEPLOY_DOCKER_CONTAINER_CA_FILE\" ]; then\n    _savedeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE \"$DEPLOY_DOCKER_CONTAINER_CA_FILE\"\n  fi\n\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE\n  _debug2 DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE \"$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE\"\n  if [ \"$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE\" ]; then\n    _savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE \"$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE\"\n  fi\n\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE\n  _debug2 DEPLOY_DOCKER_CONTAINER_PFX_FILE \"$DEPLOY_DOCKER_CONTAINER_PFX_FILE\"\n  if [ \"$DEPLOY_DOCKER_CONTAINER_PFX_FILE\" ]; then\n    _savedeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE \"$DEPLOY_DOCKER_CONTAINER_PFX_FILE\"\n  fi\n\n  _getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\n  _debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD \"$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\"\n  if [ \"$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\" ]; then\n    _savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD \"$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\" \"base64\"\n  fi\n\n  _cid=\"$(_get_id \"$DEPLOY_DOCKER_CONTAINER_LABEL\")\"\n  _info \"Container id: $_cid\"\n  if [ -z \"$_cid\" ]; then\n    _err \"can not find container id\"\n    return 1\n  fi\n\n  if [ \"$DEPLOY_DOCKER_CONTAINER_KEY_FILE\" ]; then\n    if ! _docker_cp \"$_cid\" \"$_ckey\" \"$DEPLOY_DOCKER_CONTAINER_KEY_FILE\"; then\n      return 1\n    fi\n  fi\n\n  if [ \"$DEPLOY_DOCKER_CONTAINER_CERT_FILE\" ]; then\n    if ! _docker_cp \"$_cid\" \"$_ccert\" \"$DEPLOY_DOCKER_CONTAINER_CERT_FILE\"; then\n      return 1\n    fi\n  fi\n\n  if [ \"$DEPLOY_DOCKER_CONTAINER_CA_FILE\" ]; then\n    if ! _docker_cp \"$_cid\" \"$_cca\" \"$DEPLOY_DOCKER_CONTAINER_CA_FILE\"; then\n      return 1\n    fi\n  fi\n\n  if [ \"$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE\" ]; then\n    if ! _docker_cp \"$_cid\" \"$_cfullchain\" \"$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE\"; then\n      return 1\n    fi\n  fi\n\n  if [ \"$DEPLOY_DOCKER_CONTAINER_PFX_FILE\" ]; then\n    if ! _docker_cp \"$_cid\" \"$_cpfx\" \"$DEPLOY_DOCKER_CONTAINER_PFX_FILE\"; then\n      return 1\n    fi\n  fi\n\n  if [ \"$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\" ]; then\n    _info \"Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\"\n    if ! _docker_exec \"$_cid\" \"$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD\"; then\n      return 1\n    fi\n  fi\n  return 0\n}\n\n#label\n_get_id() {\n  _label=\"$1\"\n  if [ \"$_USE_DOCKER_COMMAND\" ]; then\n    docker ps -f label=\"$_label\" --format \"{{.ID}}\"\n  elif [ \"$_USE_REST\" ]; then\n    _err \"Not implemented yet.\"\n    return 1\n  elif [ \"$_USE_UNIX_SOCKET\" ]; then\n    _req=\"{\\\"label\\\":[\\\"$_label\\\"]}\"\n    _debug2 _req \"$_req\"\n    _req=\"$(printf \"%s\" \"$_req\" | _url_encode)\"\n    _debug2 _req \"$_req\"\n    listjson=\"$(_curl_unix_sock \"${_DOCKER_SOCK:-$_DOCKER_HOST_DEFAULT}\" GET \"/containers/json?filters=$_req\")\"\n    _debug2 \"listjson\" \"$listjson\"\n    echo \"$listjson\" | tr '{,' '\\n' | grep -i '\"id\":' | _head_n 1 | cut -d '\"' -f 4\n  else\n    _err \"Not implemented yet.\"\n    return 1\n  fi\n}\n\n#id  cmd\n_docker_exec() {\n  _eargs=\"$*\"\n  _debug2 \"_docker_exec $_eargs\"\n  _dcid=\"$1\"\n  shift\n  if [ \"$_USE_DOCKER_COMMAND\" ]; then\n    docker exec -i \"$_dcid\" sh -c \"$*\"\n  elif [ \"$_USE_REST\" ]; then\n    _err \"Not implemented yet.\"\n    return 1\n  elif [ \"$_USE_UNIX_SOCKET\" ]; then\n    _cmd=\"$*\"\n    #_cmd=\"$(printf \"%s\" \"$_cmd\" | sed 's/ /\",\"/g')\"\n    _debug2 _cmd \"$_cmd\"\n    #create exec instance:\n    cjson=\"$(_curl_unix_sock \"$_DOCKER_SOCK\" POST \"/containers/$_dcid/exec\" \"{\\\"Cmd\\\": [\\\"sh\\\", \\\"-c\\\", \\\"$_cmd\\\"]}\")\"\n    _debug2 cjson \"$cjson\"\n    execid=\"$(echo \"$cjson\" | cut -d '\"' -f 4)\"\n    _debug execid \"$execid\"\n    ejson=\"$(_curl_unix_sock \"$_DOCKER_SOCK\" POST \"/exec/$execid/start\" \"{\\\"Detach\\\": false,\\\"Tty\\\": false}\")\"\n    _debug2 ejson \"$ejson\"\n    if [ \"$ejson\" ]; then\n      _err \"$ejson\"\n      return 1\n    fi\n  else\n    _err \"Not implemented yet.\"\n    return 1\n  fi\n}\n\n#id from  to\n_docker_cp() {\n  _dcid=\"$1\"\n  _from=\"$2\"\n  _to=\"$3\"\n  _info \"Copying file from $_from to $_to\"\n  _dir=\"$(dirname \"$_to\")\"\n  _debug2 _dir \"$_dir\"\n  if ! _docker_exec \"$_dcid\" mkdir -p \"$_dir\"; then\n    _err \"Can not create dir: $_dir\"\n    return 1\n  fi\n  if [ \"$_USE_DOCKER_COMMAND\" ]; then\n    if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n      _docker_exec \"$_dcid\" tee \"$_to\" <\"$_from\"\n    else\n      _docker_exec \"$_dcid\" tee \"$_to\" <\"$_from\" >/dev/null\n    fi\n    if [ \"$?\" = \"0\" ]; then\n      _info \"Success\"\n      return 0\n    else\n      _info \"Error\"\n      return 1\n    fi\n  elif [ \"$_USE_REST\" ]; then\n    _err \"Not implemented yet.\"\n    return 1\n  elif [ \"$_USE_UNIX_SOCKET\" ]; then\n    _frompath=\"$_from\"\n    if _startswith \"$_frompath\" '/'; then\n      _frompath=\"$(echo \"$_from\" | cut -b 2-)\" #remove the first '/' char\n    fi\n    _debug2 \"_frompath\" \"$_frompath\"\n    _toname=\"$(basename \"$_to\")\"\n    _debug2 \"_toname\" \"$_toname\"\n    _debug2 \"_from\" \"$_from\"\n    if ! tar --transform=\"s,$(printf \"%s\" \"$_frompath\" | tr '*' .),$_toname,\" -cz \"$_from\" 2>/dev/null | _curl_unix_sock \"$_DOCKER_SOCK\" PUT \"/containers/$_dcid/archive?noOverwriteDirNonDir=1&path=$(printf \"%s\" \"$_dir\" | _url_encode)\" '@-' \"Content-Type: application/octet-stream\"; then\n      _err \"copy error\"\n      return 1\n    fi\n    return 0\n  else\n    _err \"Not implemented yet.\"\n    return 1\n  fi\n\n}\n\n#sock method  endpoint data content-type\n_curl_unix_sock() {\n  _socket=\"$1\"\n  _method=\"$2\"\n  _endpoint=\"$3\"\n  _data=\"$4\"\n  _ctype=\"$5\"\n  if [ -z \"$_ctype\" ]; then\n    _ctype=\"Content-Type: application/json\"\n  fi\n  _debug _data \"$_data\"\n  _debug2 \"url\" \"http://localhost$_endpoint\"\n  if [ \"$_CURL_NO_HOST\" ]; then\n    _cux_url=\"http:$_endpoint\"\n  else\n    _cux_url=\"http://localhost$_endpoint\"\n  fi\n\n  if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n    curl -vvv --silent --unix-socket \"$_socket\" -X \"$_method\" --data-binary \"$_data\" --header \"$_ctype\" \"$_cux_url\"\n  else\n    curl --silent --unix-socket \"$_socket\" -X \"$_method\" --data-binary \"$_data\" --header \"$_ctype\" \"$_cux_url\"\n  fi\n\n}\n\n_check_curl_version() {\n  _cversion=\"$(curl -V | grep '^curl ' | cut -d ' ' -f 2)\"\n  _debug2 \"_cversion\" \"$_cversion\"\n\n  _major=\"$(_getfield \"$_cversion\" 1 '.')\"\n  _debug2 \"_major\" \"$_major\"\n\n  _minor=\"$(_getfield \"$_cversion\" 2 '.')\"\n  _debug2 \"_minor\" \"$_minor\"\n\n  if [ \"$_major\" -ge \"8\" ]; then\n    #ok\n    return 0\n  fi\n  if [ \"$_major\" = \"7\" ]; then\n    if [ \"$_minor\" -lt \"40\" ]; then\n      _err \"curl v$_cversion doesn't support unit socket\"\n      _err \"Please upgrade to curl 7.40 or later.\"\n      return 1\n    fi\n    if [ \"$_minor\" -lt \"50\" ]; then\n      _debug \"Use short host name\"\n      export _CURL_NO_HOST=1\n    else\n      export _CURL_NO_HOST=\n    fi\n    return 0\n  else\n    _err \"curl v$_cversion doesn't support unit socket\"\n    _err \"Please upgrade to curl 7.40 or later.\"\n    return 1\n  fi\n\n}\n"
  },
  {
    "path": "deploy/dovecot.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to dovecot server.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\ndovecot_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/edgio.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert to edgio using its API\n# https://docs.edg.io/guides/v7/develop/rest_api/authentication\n# https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts\n\n# This deployment required following variables\n# export EDGIO_CLIENT_ID=\"Your Edgio Client ID\"\n# export EDGIO_CLIENT_SECRET=\"Your Edgio Client Secret\"\n# export EDGIO_ENVIRONMENT_ID=\"Your Edgio Environment ID\"\n\n# If have more than one Environment ID\n# export EDGIO_ENVIRONMENT_ID=\"ENVIRONMENT_ID_1 ENVIRONMENT_ID_2\"\n\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nedgio_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$EDGIO_CLIENT_ID\" ]; then\n    _err \"EDGIO_CLIENT_ID is not defined.\"\n    return 1\n  else\n    _savedomainconf EDGIO_CLIENT_ID \"$EDGIO_CLIENT_ID\"\n  fi\n\n  if [ -z \"$EDGIO_CLIENT_SECRET\" ]; then\n    _err \"EDGIO_CLIENT_SECRET is not defined.\"\n    return 1\n  else\n    _savedomainconf EDGIO_CLIENT_SECRET \"$EDGIO_CLIENT_SECRET\"\n  fi\n\n  if [ -z \"$EDGIO_ENVIRONMENT_ID\" ]; then\n    _err \"EDGIO_ENVIRONMENT_ID is not defined.\"\n    return 1\n  else\n    _savedomainconf EDGIO_ENVIRONMENT_ID \"$EDGIO_ENVIRONMENT_ID\"\n  fi\n\n  _info \"Getting access token\"\n  _data=\"client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config\"\n  _debug Get_access_token_data \"$_data\"\n  _response=$(_post \"$_data\" \"https://id.edgio.app/connect/token\" \"\" \"POST\" \"application/x-www-form-urlencoded\")\n  _debug Get_access_token_response \"$_response\"\n  _access_token=$(echo \"$_response\" | _json_decode | _egrep_o '\"access_token\":\"[^\"]*' | cut -d : -f 2 | tr -d '\"')\n  _debug _access_token \"$_access_token\"\n  if [ -z \"$_access_token\" ]; then\n    _err \"Error in getting access token\"\n    return 1\n  fi\n\n  _info \"Uploading certificate\"\n  string_ccert=$(sed 's/$/\\\\n/' \"$_ccert\" | tr -d '\\n')\n  string_cca=$(sed 's/$/\\\\n/' \"$_cca\" | tr -d '\\n')\n  string_key=$(sed 's/$/\\\\n/' \"$_ckey\" | tr -d '\\n')\n\n  for ENVIRONMENT_ID in $EDGIO_ENVIRONMENT_ID; do\n    _data=\"{\\\"environment_id\\\":\\\"$ENVIRONMENT_ID\\\",\\\"primary_cert\\\":\\\"$string_ccert\\\",\\\"intermediate_cert\\\":\\\"$string_cca\\\",\\\"private_key\\\":\\\"$string_key\\\"}\"\n    _debug Upload_certificate_data \"$_data\"\n    _H1=\"Authorization: Bearer $_access_token\"\n    _response=$(_post \"$_data\" \"https://edgioapis.com/config/v0.1/tls-certs\" \"\" \"POST\" \"application/json\")\n    if _contains \"$_response\" \"message\"; then\n      _err \"Error in deploying $_cdomain certificate to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID.\"\n      _err \"$_response\"\n      return 1\n    fi\n    _debug Upload_certificate_response \"$_response\"\n    _info \"Domain $_cdomain certificate successfully deployed to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID.\"\n  done\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/exim4.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to exim4 server.\n\n#returns 0 means success, otherwise error.\n\n#DEPLOY_EXIM4_CONF=\"/etc/exim/exim.conf\"\n#DEPLOY_EXIM4_RELOAD=\"service exim4 restart\"\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nexim4_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _ssl_path=\"/etc/acme.sh/exim4\"\n  if ! mkdir -p \"$_ssl_path\"; then\n    _err \"Can not create folder:$_ssl_path\"\n    return 1\n  fi\n\n  _info \"Copying key and cert\"\n  _real_key=\"$_ssl_path/exim4.key\"\n  if ! cat \"$_ckey\" >\"$_real_key\"; then\n    _err \"Error: write key file to: $_real_key\"\n    return 1\n  fi\n  _real_fullchain=\"$_ssl_path/exim4.pem\"\n  if ! cat \"$_cfullchain\" >\"$_real_fullchain\"; then\n    _err \"Error: write key file to: $_real_fullchain\"\n    return 1\n  fi\n\n  DEFAULT_EXIM4_RELOAD=\"service exim4 restart\"\n  _reload=\"${DEPLOY_EXIM4_RELOAD:-$DEFAULT_EXIM4_RELOAD}\"\n\n  if [ -z \"$IS_RENEW\" ]; then\n    DEFAULT_EXIM4_CONF=\"/etc/exim/exim.conf\"\n    if [ ! -f \"$DEFAULT_EXIM4_CONF\" ]; then\n      DEFAULT_EXIM4_CONF=\"/etc/exim4/exim4.conf.template\"\n    fi\n    _exim4_conf=\"${DEPLOY_EXIM4_CONF:-$DEFAULT_EXIM4_CONF}\"\n    _debug _exim4_conf \"$_exim4_conf\"\n    if [ ! -f \"$_exim4_conf\" ]; then\n      if [ -z \"$DEPLOY_EXIM4_CONF\" ]; then\n        _err \"exim4 conf is not found, please define DEPLOY_EXIM4_CONF\"\n        return 1\n      else\n        _err \"It seems that the specified exim4 conf is not valid, please check.\"\n        return 1\n      fi\n    fi\n    if [ ! -w \"$_exim4_conf\" ]; then\n      _err \"The file $_exim4_conf is not writable, please change the permission.\"\n      return 1\n    fi\n    _backup_conf=\"$DOMAIN_BACKUP_PATH/exim4.conf.bak\"\n    _info \"Backup $_exim4_conf to $_backup_conf\"\n    cp \"$_exim4_conf\" \"$_backup_conf\"\n\n    _info \"Modify exim4 conf: $_exim4_conf\"\n    if _setopt \"$_exim4_conf\" \"tls_certificate\" \"=\" \"$_real_fullchain\" &&\n      _setopt \"$_exim4_conf\" \"tls_privatekey\" \"=\" \"$_real_key\"; then\n      _info \"Set config success!\"\n    else\n      _err \"Config exim4 server error, please report bug to us.\"\n      _info \"Restoring exim4 conf\"\n      if cat \"$_backup_conf\" >\"$_exim4_conf\"; then\n        _info \"Restore conf success\"\n        eval \"$_reload\"\n      else\n        _err \"Oops, error restore exim4 conf, please report bug to us.\"\n      fi\n      return 1\n    fi\n  fi\n\n  _info \"Run reload: $_reload\"\n  if eval \"$_reload\"; then\n    _info \"Reload success!\"\n    if [ \"$DEPLOY_EXIM4_CONF\" ]; then\n      _savedomainconf DEPLOY_EXIM4_CONF \"$DEPLOY_EXIM4_CONF\"\n    else\n      _cleardomainconf DEPLOY_EXIM4_CONF\n    fi\n    if [ \"$DEPLOY_EXIM4_RELOAD\" ]; then\n      _savedomainconf DEPLOY_EXIM4_RELOAD \"$DEPLOY_EXIM4_RELOAD\"\n    else\n      _cleardomainconf DEPLOY_EXIM4_RELOAD\n    fi\n    return 0\n  else\n    _err \"Reload error, restoring\"\n    if cat \"$_backup_conf\" >\"$_exim4_conf\"; then\n      _info \"Restore conf success\"\n      eval \"$_reload\"\n    else\n      _err \"Oops, error restore exim4 conf, please report bug to us.\"\n    fi\n    return 1\n  fi\n\n}\n"
  },
  {
    "path": "deploy/fritzbox.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to an AVM FRITZ!Box router.\n\n#returns 0 means success, otherwise error.\n\n#DEPLOY_FRITZBOX_USERNAME=\"username\"\n#DEPLOY_FRITZBOX_PASSWORD=\"password\"\n#DEPLOY_FRITZBOX_URL=\"https://fritz.box\"\n\n# Kudos to wikrie at Github for his FRITZ!Box update script:\n# https://gist.github.com/wikrie/f1d5747a714e0a34d0582981f7cb4cfb\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nfritzbox_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if ! _exists iconv; then\n    if ! _exists uconv; then\n      if ! _exists perl; then\n        _err \"iconv or uconv or perl not found\"\n        return 1\n      fi\n    fi\n  fi\n\n  # Clear traces of incorrectly stored values\n  _clearaccountconf DEPLOY_FRITZBOX_USERNAME\n  _clearaccountconf DEPLOY_FRITZBOX_PASSWORD\n  _clearaccountconf DEPLOY_FRITZBOX_URL\n\n  # Read config from saved values or env\n  _getdeployconf DEPLOY_FRITZBOX_USERNAME\n  _getdeployconf DEPLOY_FRITZBOX_PASSWORD\n  _getdeployconf DEPLOY_FRITZBOX_URL\n\n  _debug DEPLOY_FRITZBOX_URL \"$DEPLOY_FRITZBOX_URL\"\n  _debug DEPLOY_FRITZBOX_USERNAME \"$DEPLOY_FRITZBOX_USERNAME\"\n  _secure_debug DEPLOY_FRITZBOX_PASSWORD \"$DEPLOY_FRITZBOX_PASSWORD\"\n\n  if [ -z \"$DEPLOY_FRITZBOX_USERNAME\" ]; then\n    _err \"FRITZ!Box username is not found, please define DEPLOY_FRITZBOX_USERNAME.\"\n    return 1\n  fi\n  if [ -z \"$DEPLOY_FRITZBOX_PASSWORD\" ]; then\n    _err \"FRITZ!Box password is not found, please define DEPLOY_FRITZBOX_PASSWORD.\"\n    return 1\n  fi\n  if [ -z \"$DEPLOY_FRITZBOX_URL\" ]; then\n    _err \"FRITZ!Box url is not found, please define DEPLOY_FRITZBOX_URL.\"\n    return 1\n  fi\n\n  # Save current values\n  _savedeployconf DEPLOY_FRITZBOX_USERNAME \"$DEPLOY_FRITZBOX_USERNAME\"\n  _savedeployconf DEPLOY_FRITZBOX_PASSWORD \"$DEPLOY_FRITZBOX_PASSWORD\"\n  _savedeployconf DEPLOY_FRITZBOX_URL \"$DEPLOY_FRITZBOX_URL\"\n\n  # Do not check for a valid SSL certificate, because initially the cert is not valid, so it could not install the LE generated certificate\n  export HTTPS_INSECURE=1\n\n  _info \"Log in to the FRITZ!Box\"\n  _fritzbox_challenge=\"$(_get \"${DEPLOY_FRITZBOX_URL}/login_sid.lua\" | sed -e 's/^.*<Challenge>//' -e 's/<\\/Challenge>.*$//')\"\n  if _exists iconv; then\n    _fritzbox_hash=\"$(printf \"%s-%s\" \"${_fritzbox_challenge}\" \"${DEPLOY_FRITZBOX_PASSWORD}\" | iconv -f ASCII -t UTF16LE | _digest md5 hex)\"\n  elif _exists uconv; then\n    _fritzbox_hash=\"$(printf \"%s-%s\" \"${_fritzbox_challenge}\" \"${DEPLOY_FRITZBOX_PASSWORD}\" | uconv -f ASCII -t UTF16LE | _digest md5 hex)\"\n  else\n    _fritzbox_hash=\"$(printf \"%s-%s\" \"${_fritzbox_challenge}\" \"${DEPLOY_FRITZBOX_PASSWORD}\" | perl -p -e 'use Encode qw/encode/; print encode(\"UTF-16LE\",\"$_\"); $_=\"\";' | _digest md5 hex)\"\n  fi\n  _fritzbox_sid=\"$(_get \"${DEPLOY_FRITZBOX_URL}/login_sid.lua?sid=0000000000000000&username=${DEPLOY_FRITZBOX_USERNAME}&response=${_fritzbox_challenge}-${_fritzbox_hash}\" | sed -e 's/^.*<SID>//' -e 's/<\\/SID>.*$//')\"\n\n  if [ -z \"${_fritzbox_sid}\" ] || [ \"${_fritzbox_sid}\" = \"0000000000000000\" ]; then\n    _err \"Logging in to the FRITZ!Box failed. Please check username, password and URL.\"\n    return 1\n  fi\n\n  _info \"Generate form POST request\"\n  _post_request=\"$(_mktemp)\"\n  _post_boundary=\"---------------------------$(date +%Y%m%d%H%M%S)\"\n  # _CERTPASSWORD_ is unset because Let's Encrypt certificates don't have a password. But if they ever do, here's the place to use it!\n  _CERTPASSWORD_=\n  {\n    printf -- \"--\"\n    printf -- \"%s\\r\\n\" \"${_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"sid\\\"\\r\\n\\r\\n%s\\r\\n\" \"${_fritzbox_sid}\"\n    printf -- \"--\"\n    printf -- \"%s\\r\\n\" \"${_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"BoxCertPassword\\\"\\r\\n\\r\\n%s\\r\\n\" \"${_CERTPASSWORD_}\"\n    printf -- \"--\"\n    printf -- \"%s\\r\\n\" \"${_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"BoxCertImportFile\\\"; filename=\\\"BoxCert.pem\\\"\\r\\n\"\n    printf \"Content-Type: application/octet-stream\\r\\n\\r\\n\"\n    cat \"${_ckey}\" \"${_cfullchain}\"\n    printf \"\\r\\n\"\n    printf -- \"--\"\n    printf -- \"%s--\" \"${_post_boundary}\"\n  } >>\"${_post_request}\"\n\n  _info \"Upload certificate to the FRITZ!Box\"\n\n  export _H1=\"Content-type: multipart/form-data boundary=${_post_boundary}\"\n  _post \"$(cat \"${_post_request}\")\" \"${DEPLOY_FRITZBOX_URL}/cgi-bin/firmwarecfg\" | grep SSL\n\n  retval=$?\n  if [ $retval = 0 ]; then\n    _info \"Upload successful\"\n  else\n    _err \"Upload failed\"\n  fi\n  rm \"${_post_request}\"\n\n  return $retval\n}\n"
  },
  {
    "path": "deploy/gcore_cdn.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is the script to deploy the cert to G-Core CDN service (https://gcore.com/) using the G-Core Labs API (https://apidocs.gcore.com/cdn).\n# Returns 0 when success.\n#\n# Written by temoffey <temofffey@gmail.com>\n# Public domain, 2019\n# Update by DreamOfIce <admin@dreamofice.cn> in 2023\n\n#export DEPLOY_GCORE_CDN_USERNAME=myusername\n#export DEPLOY_GCORE_CDN_PASSWORD=mypassword\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\n\ngcore_cdn_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _fullchain=$(tr '\\r\\n' '*#' <\"$_cfullchain\" | sed 's/*#/#/g;s/##/#/g;s/#/\\\\n/g')\n  _key=$(tr '\\r\\n' '*#' <\"$_ckey\" | sed 's/*#/#/g;s/#/\\\\n/g')\n\n  _debug _fullchain \"$_fullchain\"\n  _debug _key \"$_key\"\n\n  if [ -z \"$DEPLOY_GCORE_CDN_USERNAME\" ]; then\n    if [ -z \"$Le_Deploy_gcore_cdn_username\" ]; then\n      _err \"Please define the target username: export DEPLOY_GCORE_CDN_USERNAME=username\"\n      return 1\n    fi\n  else\n    Le_Deploy_gcore_cdn_username=\"$DEPLOY_GCORE_CDN_USERNAME\"\n    _savedomainconf Le_Deploy_gcore_cdn_username \"$Le_Deploy_gcore_cdn_username\"\n  fi\n\n  if [ -z \"$DEPLOY_GCORE_CDN_PASSWORD\" ]; then\n    if [ -z \"$Le_Deploy_gcore_cdn_password\" ]; then\n      _err \"Please define the target password: export DEPLOY_GCORE_CDN_PASSWORD=password\"\n      return 1\n    fi\n  else\n    Le_Deploy_gcore_cdn_password=\"$DEPLOY_GCORE_CDN_PASSWORD\"\n    _savedomainconf Le_Deploy_gcore_cdn_password \"$Le_Deploy_gcore_cdn_password\"\n  fi\n\n  _info \"Get authorization token\"\n  _request=\"{\\\"username\\\":\\\"$Le_Deploy_gcore_cdn_username\\\",\\\"password\\\":\\\"$Le_Deploy_gcore_cdn_password\\\"}\"\n  _debug _request \"$_request\"\n  export _H1=\"Content-Type:application/json\"\n  _response=$(_post \"$_request\" \"https://api.gcore.com/auth/jwt/login\")\n  _debug _response \"$_response\"\n  _regex=\".*\\\"access\\\":\\\"\\([-._0-9A-Za-z]*\\)\\\".*$\"\n  _debug _regex \"$_regex\"\n  _token=$(echo \"$_response\" | sed -n \"s/$_regex/\\1/p\")\n  _debug _token \"$_token\"\n\n  if [ -z \"$_token\" ]; then\n    _err \"Error G-Core Labs API authorization\"\n    return 1\n  fi\n\n  _info \"Find CDN resource with cname $_cdomain\"\n  export _H2=\"Authorization:Bearer $_token\"\n  _response=$(_get \"https://api.gcore.com/cdn/resources\")\n  _debug _response \"$_response\"\n  _regex=\"\\\"primary_resource\\\":null},\"\n  _debug _regex \"$_regex\"\n  _response=$(echo \"$_response\" | sed \"s/$_regex/$_regex\\n/g\")\n  _debug _response \"$_response\"\n  _regex=\"^.*\\\"cname\\\":\\\"$_cdomain\\\".*$\"\n  _debug _regex \"$_regex\"\n  _resource=$(echo \"$_response\" | _egrep_o \"$_regex\")\n  _debug _resource \"$_resource\"\n  _regex=\".*\\\"id\\\":\\([0-9]*\\).*$\"\n  _debug _regex \"$_regex\"\n  _resourceId=$(echo \"$_resource\" | sed -n \"s/$_regex/\\1/p\")\n  _debug _resourceId \"$_resourceId\"\n  _regex=\".*\\\"sslData\\\":\\([0-9]*\\).*$\"\n  _debug _regex \"$_regex\"\n  _sslDataOld=$(echo \"$_resource\" | sed -n \"s/$_regex/\\1/p\")\n  _debug _sslDataOld \"$_sslDataOld\"\n  _regex=\".*\\\"originGroup\\\":\\([0-9]*\\).*$\"\n  _debug _regex \"$_regex\"\n  _originGroup=$(echo \"$_resource\" | sed -n \"s/$_regex/\\1/p\")\n  _debug _originGroup \"$_originGroup\"\n\n  if [ -z \"$_resourceId\" ] || [ -z \"$_originGroup\" ]; then\n    _err \"Not found CDN resource with cname $_cdomain\"\n    return 1\n  fi\n\n  _info \"Add new SSL certificate\"\n  _date=$(date \"+%d.%m.%Y %H:%M:%S\")\n  _request=\"{\\\"name\\\":\\\"$_cdomain ($_date)\\\",\\\"sslCertificate\\\":\\\"$_fullchain\\\",\\\"sslPrivateKey\\\":\\\"$_key\\\"}\"\n  _debug _request \"$_request\"\n  _response=$(_post \"$_request\" \"https://api.gcore.com/cdn/sslData\")\n  _debug _response \"$_response\"\n  _regex=\".*\\\"id\\\":\\([0-9]*\\).*$\"\n  _debug _regex \"$_regex\"\n  _sslDataAdd=$(echo \"$_response\" | sed -n \"s/$_regex/\\1/p\")\n  _debug _sslDataAdd \"$_sslDataAdd\"\n\n  if [ -z \"$_sslDataAdd\" ]; then\n    _err \"Error new SSL certificate add\"\n    return 1\n  fi\n\n  _info \"Update CDN resource\"\n  _request=\"{\\\"originGroup\\\":$_originGroup,\\\"sslData\\\":$_sslDataAdd}\"\n  _debug _request \"$_request\"\n  _response=$(_post \"$_request\" \"https://api.gcore.com/cdn/resources/$_resourceId\" '' \"PUT\")\n  _debug _response \"$_response\"\n  _regex=\".*\\\"sslData\\\":\\([0-9]*\\).*$\"\n  _debug _regex \"$_regex\"\n  _sslDataNew=$(echo \"$_response\" | sed -n \"s/$_regex/\\1/p\")\n  _debug _sslDataNew \"$_sslDataNew\"\n\n  if [ \"$_sslDataNew\" != \"$_sslDataAdd\" ]; then\n    _err \"Error CDN resource update\"\n    return 1\n  fi\n\n  if [ -z \"$_sslDataOld\" ] || [ \"$_sslDataOld\" = \"null\" ]; then\n    _info \"Not found old SSL certificate\"\n  else\n    _info \"Delete old SSL certificate\"\n    _response=$(_post '' \"https://api.gcore.com/cdn/sslData/$_sslDataOld\" '' \"DELETE\")\n    _debug _response \"$_response\"\n  fi\n\n  _info \"Certificate successfully deployed\"\n  return 0\n}\n"
  },
  {
    "path": "deploy/gitlab.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificate to a Gitlab hosted page\n\n# The following variables exported from environment will be used.\n# If not set then values previously saved in domain.conf file are used.\n\n# All the variables are required\n\n# export GITLAB_TOKEN=\"xxxxxxx\"\n# export GITLAB_PROJECT_ID=012345\n# export GITLAB_DOMAIN=\"mydomain.com\"\n\ngitlab_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$GITLAB_TOKEN\" ]; then\n    if [ -z \"$Le_Deploy_gitlab_token\" ]; then\n      _err \"GITLAB_TOKEN not defined.\"\n      return 1\n    fi\n  else\n    Le_Deploy_gitlab_token=\"$GITLAB_TOKEN\"\n    _savedomainconf Le_Deploy_gitlab_token \"$Le_Deploy_gitlab_token\"\n  fi\n\n  if [ -z \"$GITLAB_PROJECT_ID\" ]; then\n    if [ -z \"$Le_Deploy_gitlab_project_id\" ]; then\n      _err \"GITLAB_PROJECT_ID not defined.\"\n      return 1\n    fi\n  else\n    Le_Deploy_gitlab_project_id=\"$GITLAB_PROJECT_ID\"\n    _savedomainconf Le_Deploy_gitlab_project_id \"$Le_Deploy_gitlab_project_id\"\n  fi\n\n  if [ -z \"$GITLAB_DOMAIN\" ]; then\n    if [ -z \"$Le_Deploy_gitlab_domain\" ]; then\n      _err \"GITLAB_DOMAIN not defined.\"\n      return 1\n    fi\n  else\n    Le_Deploy_gitlab_domain=\"$GITLAB_DOMAIN\"\n    _savedomainconf Le_Deploy_gitlab_domain \"$Le_Deploy_gitlab_domain\"\n  fi\n\n  string_fullchain=$(_url_encode <\"$_cfullchain\")\n  string_key=$(_url_encode <\"$_ckey\")\n\n  body=\"certificate=$string_fullchain&key=$string_key\"\n\n  export _H1=\"PRIVATE-TOKEN: $Le_Deploy_gitlab_token\"\n\n  gitlab_url=\"https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain\"\n\n  _response=$(_post \"$body\" \"$gitlab_url\" 0 PUT | _dbase64 \"multiline\")\n\n  error_response=\"error\"\n\n  if test \"${_response#*\"$error_response\"}\" != \"$_response\"; then\n    _err \"Error in deploying certificate:\"\n    _err \"$_response\"\n    return 1\n  fi\n\n  _debug response \"$_response\"\n  _info \"Certificate successfully deployed\"\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/haproxy.sh",
    "content": "#!/usr/bin/env sh\n\n# Script for acme.sh to deploy certificates to haproxy\n#\n# The following variables can be exported:\n#\n# export DEPLOY_HAPROXY_PEM_NAME=\"${domain}.pem\"\n#\n# Defines the name of the PEM file.\n# Defaults to \"<domain>.pem\"\n#\n# export DEPLOY_HAPROXY_PEM_PATH=\"/etc/haproxy\"\n#\n# Defines location of PEM file for HAProxy.\n# Defaults to /etc/haproxy\n#\n# export DEPLOY_HAPROXY_RELOAD=\"systemctl reload haproxy\"\n#\n# OPTIONAL: Reload command used post deploy\n# This defaults to be a no-op (ie \"true\").\n# It is strongly recommended to set this something that makes sense\n# for your distro.\n#\n# export DEPLOY_HAPROXY_ISSUER=\"no\"\n#\n# OPTIONAL: Places CA file as \"${DEPLOY_HAPROXY_PEM}.issuer\"\n# Note: Required for OCSP stapling to work\n#\n# export DEPLOY_HAPROXY_BUNDLE=\"no\"\n#\n# OPTIONAL: Deploy this certificate as part of a multi-cert bundle\n# This adds a suffix to the certificate based on the certificate type\n# eg RSA certificates will have .rsa as a suffix to the file name\n# HAProxy will load all certificates and provide one or the other\n# depending on client capabilities\n# Note: This functionality requires HAProxy was compiled against\n# a version of OpenSSL that supports this.\n#\n# export DEPLOY_HAPROXY_HOT_UPDATE=\"yes\"\n# export DEPLOY_HAPROXY_STATS_SOCKET=\"UNIX:/run/haproxy/admin.sock\"\n#\n# OPTIONAL: Deploy the certificate over the HAProxy stats socket without\n# needing to reload HAProxy. Default is \"no\".\n#\n# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat\n# address format.\n#\n# export DEPLOY_HAPROXY_MASTER_CLI=\"UNIX:/run/haproxy-master.sock\"\n#\n# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE=\"yes\" instead\n# of a stats socket, use this variable.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nhaproxy_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _cmdpfx=\"\"\n\n  # Some defaults\n  DEPLOY_HAPROXY_PEM_PATH_DEFAULT=\"/etc/haproxy\"\n  DEPLOY_HAPROXY_PEM_NAME_DEFAULT=\"${_cdomain}.pem\"\n  DEPLOY_HAPROXY_BUNDLE_DEFAULT=\"no\"\n  DEPLOY_HAPROXY_ISSUER_DEFAULT=\"no\"\n  DEPLOY_HAPROXY_RELOAD_DEFAULT=\"true\"\n  DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT=\"no\"\n  DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT=\"UNIX:/run/haproxy/admin.sock\"\n\n  _debug _cdomain \"${_cdomain}\"\n  _debug _ckey \"${_ckey}\"\n  _debug _ccert \"${_ccert}\"\n  _debug _cca \"${_cca}\"\n  _debug _cfullchain \"${_cfullchain}\"\n\n  # PEM_PATH is optional. If not provided then assume \"${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_PEM_PATH\n  _debug2 DEPLOY_HAPROXY_PEM_PATH \"${DEPLOY_HAPROXY_PEM_PATH}\"\n  if [ -n \"${DEPLOY_HAPROXY_PEM_PATH}\" ]; then\n    Le_Deploy_haproxy_pem_path=\"${DEPLOY_HAPROXY_PEM_PATH}\"\n    _savedomainconf Le_Deploy_haproxy_pem_path \"${Le_Deploy_haproxy_pem_path}\"\n  elif [ -z \"${Le_Deploy_haproxy_pem_path}\" ]; then\n    Le_Deploy_haproxy_pem_path=\"${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}\"\n  fi\n\n  # Ensure PEM_PATH exists\n  if [ -d \"${Le_Deploy_haproxy_pem_path}\" ]; then\n    _debug \"PEM_PATH ${Le_Deploy_haproxy_pem_path} exists\"\n  else\n    _err \"PEM_PATH ${Le_Deploy_haproxy_pem_path} does not exist\"\n    return 1\n  fi\n\n  # PEM_NAME is optional. If not provided then assume \"${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_PEM_NAME\n  _debug2 DEPLOY_HAPROXY_PEM_NAME \"${DEPLOY_HAPROXY_PEM_NAME}\"\n  if [ -n \"${DEPLOY_HAPROXY_PEM_NAME}\" ]; then\n    Le_Deploy_haproxy_pem_name=\"${DEPLOY_HAPROXY_PEM_NAME}\"\n    _savedomainconf Le_Deploy_haproxy_pem_name \"${Le_Deploy_haproxy_pem_name}\"\n  elif [ -z \"${Le_Deploy_haproxy_pem_name}\" ]; then\n    Le_Deploy_haproxy_pem_name=\"${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}\"\n    # We better not have '*' as the first character\n    if [ \"${Le_Deploy_haproxy_pem_name%%\"${Le_Deploy_haproxy_pem_name#?}\"}\" = '*' ]; then\n      # removes the first characters and add a _ instead\n      Le_Deploy_haproxy_pem_name=\"_${Le_Deploy_haproxy_pem_name#?}\"\n    fi\n  fi\n\n  # BUNDLE is optional. If not provided then assume \"${DEPLOY_HAPROXY_BUNDLE_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_BUNDLE\n  _debug2 DEPLOY_HAPROXY_BUNDLE \"${DEPLOY_HAPROXY_BUNDLE}\"\n  if [ -n \"${DEPLOY_HAPROXY_BUNDLE}\" ]; then\n    Le_Deploy_haproxy_bundle=\"${DEPLOY_HAPROXY_BUNDLE}\"\n    _savedomainconf Le_Deploy_haproxy_bundle \"${Le_Deploy_haproxy_bundle}\"\n  elif [ -z \"${Le_Deploy_haproxy_bundle}\" ]; then\n    Le_Deploy_haproxy_bundle=\"${DEPLOY_HAPROXY_BUNDLE_DEFAULT}\"\n  fi\n\n  # ISSUER is optional. If not provided then assume \"${DEPLOY_HAPROXY_ISSUER_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_ISSUER\n  _debug2 DEPLOY_HAPROXY_ISSUER \"${DEPLOY_HAPROXY_ISSUER}\"\n  if [ -n \"${DEPLOY_HAPROXY_ISSUER}\" ]; then\n    Le_Deploy_haproxy_issuer=\"${DEPLOY_HAPROXY_ISSUER}\"\n    _savedomainconf Le_Deploy_haproxy_issuer \"${Le_Deploy_haproxy_issuer}\"\n  elif [ -z \"${Le_Deploy_haproxy_issuer}\" ]; then\n    Le_Deploy_haproxy_issuer=\"${DEPLOY_HAPROXY_ISSUER_DEFAULT}\"\n  fi\n\n  # RELOAD is optional. If not provided then assume \"${DEPLOY_HAPROXY_RELOAD_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_RELOAD\n  _debug2 DEPLOY_HAPROXY_RELOAD \"${DEPLOY_HAPROXY_RELOAD}\"\n  if [ -n \"${DEPLOY_HAPROXY_RELOAD}\" ]; then\n    Le_Deploy_haproxy_reload=\"${DEPLOY_HAPROXY_RELOAD}\"\n    _savedomainconf Le_Deploy_haproxy_reload \"${Le_Deploy_haproxy_reload}\"\n  elif [ -z \"${Le_Deploy_haproxy_reload}\" ]; then\n    Le_Deploy_haproxy_reload=\"${DEPLOY_HAPROXY_RELOAD_DEFAULT}\"\n  fi\n\n  # HOT_UPDATE is optional. If not provided then assume \"${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_HOT_UPDATE\n  _debug2 DEPLOY_HAPROXY_HOT_UPDATE \"${DEPLOY_HAPROXY_HOT_UPDATE}\"\n  if [ -n \"${DEPLOY_HAPROXY_HOT_UPDATE}\" ]; then\n    Le_Deploy_haproxy_hot_update=\"${DEPLOY_HAPROXY_HOT_UPDATE}\"\n    _savedomainconf Le_Deploy_haproxy_hot_update \"${Le_Deploy_haproxy_hot_update}\"\n  elif [ -z \"${Le_Deploy_haproxy_hot_update}\" ]; then\n    Le_Deploy_haproxy_hot_update=\"${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}\"\n  fi\n\n  # STATS_SOCKET is optional. If not provided then assume \"${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}\"\n  _getdeployconf DEPLOY_HAPROXY_STATS_SOCKET\n  _debug2 DEPLOY_HAPROXY_STATS_SOCKET \"${DEPLOY_HAPROXY_STATS_SOCKET}\"\n  if [ -n \"${DEPLOY_HAPROXY_STATS_SOCKET}\" ]; then\n    Le_Deploy_haproxy_stats_socket=\"${DEPLOY_HAPROXY_STATS_SOCKET}\"\n    _savedomainconf Le_Deploy_haproxy_stats_socket \"${Le_Deploy_haproxy_stats_socket}\"\n  elif [ -z \"${Le_Deploy_haproxy_stats_socket}\" ]; then\n    Le_Deploy_haproxy_stats_socket=\"${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}\"\n  fi\n\n  # MASTER_CLI is optional. No defaults are used. When the master CLI is used,\n  # all commands are sent with a prefix.\n  _getdeployconf DEPLOY_HAPROXY_MASTER_CLI\n  _debug2 DEPLOY_HAPROXY_MASTER_CLI \"${DEPLOY_HAPROXY_MASTER_CLI}\"\n  if [ -n \"${DEPLOY_HAPROXY_MASTER_CLI}\" ]; then\n    Le_Deploy_haproxy_stats_socket=\"${DEPLOY_HAPROXY_MASTER_CLI}\"\n    _savedomainconf Le_Deploy_haproxy_stats_socket \"${Le_Deploy_haproxy_stats_socket}\"\n    _cmdpfx=\"@1 \" # command prefix used for master CLI only.\n  fi\n\n  # Set the suffix depending if we are creating a bundle or not\n  if [ \"${Le_Deploy_haproxy_bundle}\" = \"yes\" ]; then\n    _info \"Bundle creation requested\"\n    # Initialise $Le_Keylength if its not already set\n    if [ -z \"${Le_Keylength}\" ]; then\n      Le_Keylength=\"\"\n    fi\n    if _isEccKey \"${Le_Keylength}\"; then\n      _info \"ECC key type detected\"\n      _suffix=\".ecdsa\"\n    else\n      _info \"RSA key type detected\"\n      _suffix=\".rsa\"\n    fi\n  else\n    _suffix=\"\"\n  fi\n  _debug _suffix \"${_suffix}\"\n\n  # Set variables for later\n  _pem=\"${Le_Deploy_haproxy_pem_path}/${Le_Deploy_haproxy_pem_name}${_suffix}\"\n  _issuer=\"${_pem}.issuer\"\n  _ocsp=\"${_pem}.ocsp\"\n  _reload=\"${Le_Deploy_haproxy_reload}\"\n  _statssock=\"${Le_Deploy_haproxy_stats_socket}\"\n\n  _info \"Deploying PEM file\"\n  # Create a temporary PEM file\n  _temppem=\"$(_mktemp)\"\n  _debug _temppem \"${_temppem}\"\n  cat \"${_ccert}\" \"${_cca}\" \"${_ckey}\" | grep . >\"${_temppem}\"\n  _ret=\"$?\"\n\n  # Check that we could create the temporary file\n  if [ \"${_ret}\" != \"0\" ]; then\n    _err \"Error code ${_ret} returned during PEM file creation\"\n    [ -f \"${_temppem}\" ] && rm -f \"${_temppem}\"\n    return ${_ret}\n  fi\n\n  # Move PEM file into place\n  _info \"Moving new certificate into place\"\n  _debug _pem \"${_pem}\"\n  cat \"${_temppem}\" >\"${_pem}\"\n  _ret=$?\n\n  # Clean up temp file\n  [ -f \"${_temppem}\" ] && rm -f \"${_temppem}\"\n\n  # Deal with any failure of moving PEM file into place\n  if [ \"${_ret}\" != \"0\" ]; then\n    _err \"Error code ${_ret} returned while moving new certificate into place\"\n    return ${_ret}\n  fi\n\n  # Update .issuer file if requested\n  if [ \"${Le_Deploy_haproxy_issuer}\" = \"yes\" ]; then\n    _info \"Updating .issuer file\"\n    _debug _issuer \"${_issuer}\"\n    cat \"${_cca}\" >\"${_issuer}\"\n    _ret=\"$?\"\n\n    if [ \"${_ret}\" != \"0\" ]; then\n      _err \"Error code ${_ret} returned while copying issuer/CA certificate into place\"\n      return ${_ret}\n    fi\n  else\n    [ -f \"${_issuer}\" ] && _err \"Issuer file update not requested but .issuer file exists\"\n  fi\n\n  # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option\n  if [ -z \"${Le_OCSP_Staple}\" ]; then\n    Le_OCSP_Staple=\"0\"\n  fi\n  if [ \"${Le_OCSP_Staple}\" = \"1\" ]; then\n    _info \"Updating OCSP stapling info\"\n    _debug _ocsp \"${_ocsp}\"\n    _info \"Extracting OCSP URL\"\n    _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in \"${_pem}\")\n    _debug _ocsp_url \"${_ocsp_url}\"\n\n    # Only process OCSP if URL was present\n    if [ \"${_ocsp_url}\" != \"\" ]; then\n      # Extract the hostname from the OCSP URL\n      _info \"Extracting OCSP URL\"\n      _ocsp_host=$(echo \"${_ocsp_url}\" | cut -d/ -f3)\n      _debug _ocsp_host \"${_ocsp_host}\"\n\n      # Only process the certificate if we have a .issuer file\n      if [ -r \"${_issuer}\" ]; then\n        # Check if issuer cert is also a root CA cert\n        _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in \"${_issuer}\" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)\n        _debug _subjectdn \"${_subjectdn}\"\n        _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in \"${_issuer}\" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)\n        _debug _issuerdn \"${_issuerdn}\"\n        _info \"Requesting OCSP response\"\n        # If the issuer is a CA cert then our command line has \"-CAfile\" added\n        if [ \"${_subjectdn}\" = \"${_issuerdn}\" ]; then\n          _cafile_argument=\"-CAfile \\\"${_issuer}\\\"\"\n        else\n          _cafile_argument=\"\"\n        fi\n        _debug _cafile_argument \"${_cafile_argument}\"\n        # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed\n        _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)\n        _debug _openssl_version \"${_openssl_version}\"\n        _openssl_major=$(echo \"${_openssl_version}\" | cut -d '.' -f1)\n        _openssl_minor=$(echo \"${_openssl_version}\" | cut -d '.' -f2)\n        if [ \"${_openssl_major}\" -eq \"1\" ] && [ \"${_openssl_minor}\" -ge \"1\" ] || [ \"${_openssl_major}\" -ge \"2\" ]; then\n          _header_sep=\"=\"\n        else\n          _header_sep=\" \"\n        fi\n        # Request the OCSP response from the issuer and store it\n        _openssl_ocsp_cmd=\"${ACME_OPENSSL_BIN:-openssl} ocsp \\\n          -issuer \\\"${_issuer}\\\" \\\n          -cert \\\"${_pem}\\\" \\\n          -url \\\"${_ocsp_url}\\\" \\\n          -header Host${_header_sep}\\\"${_ocsp_host}\\\" \\\n          -respout \\\"${_ocsp}\\\" \\\n          -verify_other \\\"${_issuer}\\\" \\\n          ${_cafile_argument} \\\n          | grep -q \\\"${_pem}: good\\\"\"\n        _debug _openssl_ocsp_cmd \"${_openssl_ocsp_cmd}\"\n        eval \"${_openssl_ocsp_cmd}\"\n        _ret=$?\n      else\n        # Non fatal: No issuer file was present so no OCSP stapling file created\n        _err \"OCSP stapling in use but no .issuer file was present\"\n      fi\n    else\n      # Non fatal: No OCSP url was found int the certificate\n      _err \"OCSP update requested but no OCSP URL was found in certificate\"\n    fi\n\n    # Non fatal: Check return code of openssl command\n    if [ \"${_ret}\" != \"0\" ]; then\n      _err \"Updating OCSP stapling failed with return code ${_ret}\"\n    fi\n  else\n    # An OCSP file was already present but certificate did not have OCSP extension\n    if [ -f \"${_ocsp}\" ]; then\n      _err \"OCSP was not requested but .ocsp file exists.\"\n      # Could remove the file at this step, although HAProxy just ignores it in this case\n      # rm -f \"${_ocsp}\" || _err \"Problem removing stale .ocsp file\"\n    fi\n  fi\n\n  if [ \"${Le_Deploy_haproxy_hot_update}\" = \"yes\" ]; then\n    # set the socket name for messages\n    if [ -n \"${_cmdpfx}\" ]; then\n      _socketname=\"master CLI\"\n    else\n      _socketname=\"stats socket\"\n    fi\n\n    # Update certificate over HAProxy stats socket or master CLI.\n    if _exists socat; then\n      # look for the certificate on the stats socket, to chose between updating or creating one\n      _socat_cert_cmd=\"echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'\"\n      _debug _socat_cert_cmd \"${_socat_cert_cmd}\"\n      eval \"${_socat_cert_cmd}\"\n      _ret=$?\n      if [ \"${_ret}\" != \"0\" ]; then\n        _newcert=\"1\"\n        _info \"Creating new certificate '${_pem}' over HAProxy ${_socketname}.\"\n        # certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.\n        _socat_crtlist_show_cmd=\"echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'\"\n        _debug _socat_crtlist_show_cmd \"${_socat_crtlist_show_cmd}\"\n        eval \"${_socat_crtlist_show_cmd}\"\n        _ret=$?\n        if [ \"${_ret}\" != \"0\" ]; then\n          _err \"Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'\"\n          return \"${_ret}\"\n        fi\n        # create a new certificate\n        _socat_new_cmd=\"echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'\"\n        _debug _socat_new_cmd \"${_socat_new_cmd}\"\n        eval \"${_socat_new_cmd}\"\n        _ret=$?\n        if [ \"${_ret}\" != \"0\" ]; then\n          _err \"Couldn't create '${_pem}' in haproxy\"\n          return \"${_ret}\"\n        fi\n      else\n        _info \"Update existing certificate '${_pem}' over HAProxy ${_socketname}.\"\n      fi\n      _socat_cert_set_cmd=\"echo -e '${_cmdpfx}set ssl cert ${_pem} <<\\n$(cat \"${_pem}\")\\n' | socat '${_statssock}' - | grep -q 'Transaction created'\"\n      _secure_debug _socat_cert_set_cmd \"${_socat_cert_set_cmd}\"\n      eval \"${_socat_cert_set_cmd}\"\n      _ret=$?\n      if [ \"${_ret}\" != \"0\" ]; then\n        _err \"Can't update '${_pem}' in haproxy\"\n        return \"${_ret}\"\n      fi\n      _socat_cert_commit_cmd=\"echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'\"\n      _debug _socat_cert_commit_cmd \"${_socat_cert_commit_cmd}\"\n      eval \"${_socat_cert_commit_cmd}\"\n      _ret=$?\n      if [ \"${_ret}\" != \"0\" ]; then\n        _err \"Can't commit '${_pem}' in haproxy\"\n        return ${_ret}\n      fi\n      if [ \"${_newcert}\" = \"1\" ]; then\n        # if this is a new certificate, it needs to be inserted into the crt-list`\n        _socat_cert_add_cmd=\"echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'\"\n        _debug _socat_cert_add_cmd \"${_socat_cert_add_cmd}\"\n        eval \"${_socat_cert_add_cmd}\"\n        _ret=$?\n        if [ \"${_ret}\" != \"0\" ]; then\n          _err \"Can't update '${_pem}' in haproxy\"\n          return \"${_ret}\"\n        fi\n      fi\n    else\n      _err \"'socat' is not available, couldn't update over ${_socketname}\"\n    fi\n  else\n    # Reload HAProxy\n    _debug _reload \"${_reload}\"\n    eval \"${_reload}\"\n    _ret=$?\n    if [ \"${_ret}\" != \"0\" ]; then\n      _err \"Error code ${_ret} during reload\"\n      return ${_ret}\n    else\n      _info \"Reload successful\"\n    fi\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/kemplm.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to a Kemp Loadmaster.\n\n#returns 0 means success, otherwise error.\n\n#DEPLOY_KEMP_TOKEN=\"token\"\n#DEPLOY_KEMP_URL=\"https://kemplm.example.com\"\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nkemplm_deploy() {\n  _domain=\"$1\"\n  _key_file=\"$2\"\n  _cert_file=\"$3\"\n  _ca_file=\"$4\"\n  _fullchain_file=\"$5\"\n\n  _debug _domain \"$_domain\"\n  _debug _key_file \"$_key_file\"\n  _debug _cert_file \"$_cert_file\"\n  _debug _ca_file \"$_ca_file\"\n  _debug _fullchain_file \"$_fullchain_file\"\n\n  if ! _exists jq; then\n    _err \"jq not found\"\n    return 1\n  fi\n\n  # Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename\n  _kemp_domain=$(echo \"${_domain}\" | sed 's/\\*\\.//')\n  _debug _kemp_domain \"$_kemp_domain\"\n\n  # Read config from saved values or env\n  _getdeployconf DEPLOY_KEMP_TOKEN\n  _getdeployconf DEPLOY_KEMP_URL\n\n  _debug DEPLOY_KEMP_URL \"$DEPLOY_KEMP_URL\"\n  _secure_debug DEPLOY_KEMP_TOKEN \"$DEPLOY_KEMP_TOKEN\"\n\n  if [ -z \"$DEPLOY_KEMP_TOKEN\" ]; then\n    _err \"Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN.\"\n    return 1\n  fi\n  if [ -z \"$DEPLOY_KEMP_URL\" ]; then\n    _err \"Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL.\"\n    return 1\n  fi\n\n  # Save current values\n  _savedeployconf DEPLOY_KEMP_TOKEN \"$DEPLOY_KEMP_TOKEN\"\n  _savedeployconf DEPLOY_KEMP_URL \"$DEPLOY_KEMP_URL\"\n\n  # Check if certificate is already installed\n  _info \"Check if certificate is already present\"\n  _list_request=\"{\\\"cmd\\\": \\\"listcert\\\", \\\"apikey\\\": \\\"${DEPLOY_KEMP_TOKEN}\\\"}\"\n  _debug3 _list_request \"${_list_request}\"\n  _kemp_cert_count=$(HTTPS_INSECURE=1 _post \"${_list_request}\" \"${DEPLOY_KEMP_URL}/accessv2\" | jq -r '.cert[] | .name' | grep -c \"^${_kemp_domain}$\")\n  _debug2 _kemp_cert_count \"${_kemp_cert_count}\"\n\n  _kemp_replace_cert=1\n  if [ \"${_kemp_cert_count}\" -eq 0 ]; then\n    _kemp_replace_cert=0\n    _info \"Certificate does not exist on Kemp Loadmaster\"\n  else\n    _info \"Certificate already exists on Kemp Loadmaster\"\n  fi\n  _debug _kemp_replace_cert \"${_kemp_replace_cert}\"\n\n  # Upload new certificate to Kemp Loadmaster\n  _kemp_upload_cert=$(_mktemp)\n  cat \"${_fullchain_file}\" \"${_key_file}\" | base64 | tr -d '\\n' >\"${_kemp_upload_cert}\"\n\n  _info \"Uploading certificate to Kemp Loadmaster\"\n  _add_data=$(cat \"${_kemp_upload_cert}\")\n  _add_request=\"{\\\"cmd\\\": \\\"addcert\\\", \\\"apikey\\\": \\\"${DEPLOY_KEMP_TOKEN}\\\", \\\"replace\\\": ${_kemp_replace_cert}, \\\"cert\\\": \\\"${_kemp_domain}\\\", \\\"data\\\": \\\"${_add_data}\\\"}\"\n  _debug3 _add_request \"${_add_request}\"\n  _kemp_post_result=$(HTTPS_INSECURE=1 _post \"${_add_request}\" \"${DEPLOY_KEMP_URL}/accessv2\")\n  _retval=$?\n  _debug2 _kemp_post_result \"${_kemp_post_result}\"\n  if [ \"${_retval}\" -eq 0 ]; then\n    _kemp_post_status=$(echo \"${_kemp_post_result}\" | jq -r '.status')\n    _kemp_post_message=$(echo \"${_kemp_post_result}\" | jq -r '.message')\n    if [ \"${_kemp_post_status}\" = \"ok\" ]; then\n      _info \"Upload successful\"\n    else\n      _err \"Upload failed: ${_kemp_post_message}\"\n      _retval=1\n    fi\n  else\n    _err \"Upload failed\"\n    _retval=1\n  fi\n\n  rm \"${_kemp_upload_cert}\"\n\n  return $_retval\n}\n"
  },
  {
    "path": "deploy/keychain.sh",
    "content": "#!/usr/bin/env sh\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nkeychain_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  /usr/bin/security import \"$_ckey\" -k \"/Library/Keychains/System.keychain\"\n  /usr/bin/security import \"$_ccert\" -k \"/Library/Keychains/System.keychain\"\n  /usr/bin/security import \"$_cca\" -k \"/Library/Keychains/System.keychain\"\n  /usr/bin/security import \"$_cfullchain\" -k \"/Library/Keychains/System.keychain\"\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/keyhelp.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificate to KeyHelp\n# This deployment required following variables\n# export DEPLOY_KEYHELP_BASEURL=\"https://keyhelp.example.com\"\n# export DEPLOY_KEYHELP_USERNAME=\"Your KeyHelp Username\"\n# export DEPLOY_KEYHELP_PASSWORD=\"Your KeyHelp Password\"\n# export DEPLOY_KEYHELP_DOMAIN_ID=\"Depoly certificate to this Domain ID\"\n\n# Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID.\n# https://DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=xxx\n\n# If have more than one domain name\n# export DEPLOY_KEYHELP_DOMAIN_ID=\"111 222 333\"\n\nkeyhelp_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$DEPLOY_KEYHELP_BASEURL\" ]; then\n    _err \"DEPLOY_KEYHELP_BASEURL is not defined.\"\n    return 1\n  else\n    _savedomainconf DEPLOY_KEYHELP_BASEURL \"$DEPLOY_KEYHELP_BASEURL\"\n  fi\n\n  if [ -z \"$DEPLOY_KEYHELP_USERNAME\" ]; then\n    _err \"DEPLOY_KEYHELP_USERNAME is not defined.\"\n    return 1\n  else\n    _savedomainconf DEPLOY_KEYHELP_USERNAME \"$DEPLOY_KEYHELP_USERNAME\"\n  fi\n\n  if [ -z \"$DEPLOY_KEYHELP_PASSWORD\" ]; then\n    _err \"DEPLOY_KEYHELP_PASSWORD is not defined.\"\n    return 1\n  else\n    _savedomainconf DEPLOY_KEYHELP_PASSWORD \"$DEPLOY_KEYHELP_PASSWORD\"\n  fi\n\n  if [ -z \"$DEPLOY_KEYHELP_DOMAIN_ID\" ]; then\n    _err \"DEPLOY_KEYHELP_DOMAIN_ID is not defined.\"\n    return 1\n  else\n    _savedomainconf DEPLOY_KEYHELP_DOMAIN_ID \"$DEPLOY_KEYHELP_DOMAIN_ID\"\n  fi\n\n  # Optional DEPLOY_KEYHELP_ENFORCE_HTTPS\n  _getdeployconf DEPLOY_KEYHELP_ENFORCE_HTTPS\n  # set default values for DEPLOY_KEYHELP_ENFORCE_HTTPS\n  [ -n \"${DEPLOY_KEYHELP_ENFORCE_HTTPS}\" ] || DEPLOY_KEYHELP_ENFORCE_HTTPS=\"1\"\n\n  _info \"Logging in to keyhelp panel\"\n  username_encoded=\"$(printf \"%s\" \"${DEPLOY_KEYHELP_USERNAME}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${DEPLOY_KEYHELP_PASSWORD}\" | _url_encode)\"\n  _H1=\"Content-Type: application/x-www-form-urlencoded\"\n  _response=$(_get \"$DEPLOY_KEYHELP_BASEURL/index.php?submit=1&username=$username_encoded&password=$password_encoded\" \"TRUE\")\n  _cookie=\"$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _head_n 1 | cut -d \" \" -f 2)\"\n\n  # If cookies is not empty then logon successful\n  if [ -z \"$_cookie\" ]; then\n    _err \"Fail to get cookie.\"\n    return 1\n  fi\n  _debug \"cookie\" \"$_cookie\"\n\n  _info \"Uploading certificate\"\n  _date=$(date +\"%Y%m%d\")\n  encoded_key=\"$(_url_encode <\"$_ckey\")\"\n  encoded_ccert=\"$(_url_encode <\"$_ccert\")\"\n  encoded_cca=\"$(_url_encode <\"$_cca\")\"\n  certificate_name=\"$_cdomain-$_date\"\n\n  _request_body=\"submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca\"\n  _H1=\"Cookie: $_cookie\"\n  _response=$(_post \"$_request_body\" \"$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add\" \"\" \"POST\")\n  _message=$(echo \"$_response\" | grep -A 2 'message-body' | sed -n '/<div class=\"message-body \">/,/<\\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')\n  _info \"_message\" \"$_message\"\n  if [ -z \"$_message\" ]; then\n    _err \"Fail to upload certificate.\"\n    return 1\n  fi\n\n  for DOMAIN_ID in $DEPLOY_KEYHELP_DOMAIN_ID; do\n    _info \"Apply certificate to domain id $DOMAIN_ID\"\n    _response=$(_get \"$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID\")\n    cert_value=$(echo \"$_response\" | grep \"$certificate_name\" | sed -n 's/.*value=\"\\([^\"]*\\).*/\\1/p')\n    target_type=$(echo \"$_response\" | grep 'target_type' | grep 'checked' | sed -n 's/.*value=\"\\([^\"]*\\).*/\\1/p')\n    if [ \"$target_type\" = \"directory\" ]; then\n      path=$(echo \"$_response\" | awk '/name=\"path\"/{getline; print}' | sed -n 's/.*value=\"\\([^\"]*\\).*/\\1/p')\n    fi\n    echo \"$_response\" | grep \"is_prefer_https\" | grep \"checked\" >/dev/null\n    if [ $? -eq 0 ]; then\n      is_prefer_https=1\n    else\n      is_prefer_https=0\n    fi\n    echo \"$_response\" | grep \"hsts_enabled\" | grep \"checked\" >/dev/null\n    if [ $? -eq 0 ]; then\n      hsts_enabled=1\n    else\n      hsts_enabled=0\n    fi\n    _debug \"cert_value\" \"$cert_value\"\n    if [ -z \"$cert_value\" ]; then\n      _err \"Fail to get certificate id.\"\n      return 1\n    fi\n\n    _request_body=\"submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS\"\n    _response=$(_post \"$_request_body\" \"$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit\" \"\" \"POST\")\n    _message=$(echo \"$_response\" | grep -A 2 'message-body' | sed -n '/<div class=\"message-body \">/,/<\\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')\n    _info \"_message\" \"$_message\"\n    if [ -z \"$_message\" ]; then\n      _err \"Fail to apply certificate.\"\n      return 1\n    fi\n  done\n\n  _info \"Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID.\"\n  return 0\n}\n"
  },
  {
    "path": "deploy/keyhelp_api.sh",
    "content": "#!/usr/bin/env sh\n\nkeyhelp_api_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n\n  # Read config from saved values or env\n  _getdeployconf DEPLOY_KEYHELP_HOST\n  _getdeployconf DEPLOY_KEYHELP_API_KEY\n\n  _debug DEPLOY_KEYHELP_HOST \"$DEPLOY_KEYHELP_HOST\"\n  _secure_debug DEPLOY_KEYHELP_API_KEY \"$DEPLOY_KEYHELP_API_KEY\"\n\n  if [ -z \"$DEPLOY_KEYHELP_HOST\" ]; then\n    _err \"KeyHelp host not found, please define DEPLOY_KEYHELP_HOST.\"\n    return 1\n  fi\n  if [ -z \"$DEPLOY_KEYHELP_API_KEY\" ]; then\n    _err \"KeyHelp api key not found, please define DEPLOY_KEYHELP_API_KEY.\"\n    return 1\n  fi\n\n  # Save current values\n  _savedeployconf DEPLOY_KEYHELP_HOST \"$DEPLOY_KEYHELP_HOST\"\n  _savedeployconf DEPLOY_KEYHELP_API_KEY \"$DEPLOY_KEYHELP_API_KEY\"\n\n  _request_key=\"$(tr '\\n' ':' <\"$_ckey\" | sed 's/:/\\\\n/g')\"\n  _request_cert=\"$(tr '\\n' ':' <\"$_ccert\" | sed 's/:/\\\\n/g')\"\n  _request_ca=\"$(tr '\\n' ':' <\"$_cca\" | sed 's/:/\\\\n/g')\"\n\n  _request_body=\"{\n    \\\"name\\\": \\\"$_cdomain\\\",\n    \\\"components\\\": {\n      \\\"private_key\\\": \\\"$_request_key\\\",\n      \\\"certificate\\\": \\\"$_request_cert\\\",\n      \\\"ca_certificate\\\": \\\"$_request_ca\\\"\n    }\n  }\"\n\n  _hosts=\"$(echo \"$DEPLOY_KEYHELP_HOST\" | tr \",\" \" \")\"\n  _keys=\"$(echo \"$DEPLOY_KEYHELP_API_KEY\" | tr \",\" \" \")\"\n  _i=1\n\n  for _host in $_hosts; do\n    _key=\"$(_getfield \"$_keys\" \"$_i\" \" \")\"\n    _i=\"$(_math \"$_i\" + 1)\"\n\n    export _H1=\"X-API-Key: $_key\"\n\n    _put_url=\"$_host/api/v2/certificates/name/$_cdomain\"\n    if _post \"$_request_body\" \"$_put_url\" \"\" \"PUT\" \"application/json\" >/dev/null; then\n      _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n    else\n      _err \"Cannot make PUT request to $_put_url\"\n      return 1\n    fi\n\n    if [ \"$_code\" = \"404\" ]; then\n      _info \"$_cdomain not found, creating new entry at $_host\"\n\n      _post_url=\"$_host/api/v2/certificates\"\n      if _post \"$_request_body\" \"$_post_url\" \"\" \"POST\" \"application/json\" >/dev/null; then\n        _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n      else\n        _err \"Cannot make POST request to $_post_url\"\n        return 1\n      fi\n    fi\n\n    if _startswith \"$_code\" \"2\"; then\n      _info \"$_cdomain set at $_host\"\n    else\n      _err \"HTTP status code is $_code\"\n      return 1\n    fi\n  done\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/kong.sh",
    "content": "#!/usr/bin/env sh\n# If certificate already exists it will update only cert and key, not touching other parameters\n# If certificate doesn't exist it will only upload cert and key, and not set other parameters\n# Note that we deploy full chain\n# Written by Geoffroi Genot <ggenot@voxbone.com>\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nkong_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _info \"Deploying certificate on Kong instance\"\n  if [ -z \"$KONG_URL\" ]; then\n    _debug \"KONG_URL Not set, using default http://localhost:8001\"\n    KONG_URL=\"http://localhost:8001\"\n  fi\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  #Get ssl_uuid linked to the domain\n  ssl_uuid=$(_get \"$KONG_URL/certificates/$_cdomain\" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')\n  if [ -z \"$ssl_uuid\" ]; then\n    _debug \"Unable to get Kong ssl_uuid for domain $_cdomain\"\n    _debug \"Make sure that KONG_URL is correctly configured\"\n    _debug \"Make sure that a Kong certificate match the sni\"\n    _debug \"Kong url: $KONG_URL\"\n    _info \"No existing certificate, creating...\"\n    #return 1\n  fi\n  #Save kong url if it's succesful (First run case)\n  _saveaccountconf KONG_URL \"$KONG_URL\"\n  #Generate DEIM\n  delim=\"-----MultipartDelimiter$(date \"+%s%N\")\"\n  nl=\"\\015\\012\"\n  #Set Header\n  _H1=\"Content-Type: multipart/form-data; boundary=$delim\"\n  #Generate data for request (Multipart/form-data with mixed content)\n  if [ -z \"$ssl_uuid\" ]; then\n    #set sni to domain\n    content=\"--$delim${nl}Content-Disposition: form-data; name=\\\"snis[]\\\"${nl}${nl}$_cdomain\"\n  fi\n  #add key\n  content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"key\\\"; filename=\\\"$(basename \"$_ckey\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_ckey\")\"\n  #Add cert\n  content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"cert\\\"; filename=\\\"$(basename \"$_cfullchain\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_cfullchain\")\"\n  #Close multipart\n  content=\"$content${nl}--$delim--${nl}\"\n  #Convert CRLF\n  content=$(printf %b \"$content\")\n  #DEBUG\n  _debug header \"$_H1\"\n  _debug content \"$content\"\n  #Check if sslcreated (if not => POST else => PATCH)\n\n  if [ -z \"$ssl_uuid\" ]; then\n    #Post certificate to Kong\n    response=$(_post \"$content\" \"$KONG_URL/certificates\" \"\" \"POST\")\n  else\n    #patch\n    response=$(_post \"$content\" \"$KONG_URL/certificates/$ssl_uuid\" \"\" \"PATCH\")\n  fi\n  if ! [ \"$(echo \"$response\" | _egrep_o \"created_at\")\" = \"created_at\" ]; then\n    _err \"An error occurred with cert upload. Check response:\"\n    _err \"$response\"\n    return 1\n  fi\n  _debug response \"$response\"\n  _info \"Certificate successfully deployed\"\n}\n"
  },
  {
    "path": "deploy/lighttpd.sh",
    "content": "#!/usr/bin/env sh\n\n# Script for acme.sh to deploy certificates to lighttpd\n#\n# The following variables can be exported:\n#\n# export DEPLOY_LIGHTTPD_PEM_NAME=\"${domain}.pem\"\n#\n# Defines the name of the PEM file.\n# Defaults to \"<domain>.pem\"\n#\n# export DEPLOY_LIGHTTPD_PEM_PATH=\"/etc/lighttpd\"\n#\n# Defines location of PEM file for Lighttpd.\n# Defaults to /etc/lighttpd\n#\n# export DEPLOY_LIGHTTPD_RELOAD=\"systemctl reload lighttpd\"\n#\n# OPTIONAL: Reload command used post deploy\n# This defaults to be a no-op (ie \"true\").\n# It is strongly recommended to set this something that makes sense\n# for your distro.\n#\n# export DEPLOY_LIGHTTPD_ISSUER=\"yes\"\n#\n# OPTIONAL: Places CA file as \"${DEPLOY_LIGHTTPD_PEM}.issuer\"\n# Note: Required for OCSP stapling to work\n#\n# export DEPLOY_LIGHTTPD_BUNDLE=\"no\"\n#\n# OPTIONAL: Deploy this certificate as part of a multi-cert bundle\n# This adds a suffix to the certificate based on the certificate type\n# eg RSA certificates will have .rsa as a suffix to the file name\n# Lighttpd will load all certificates and provide one or the other\n# depending on client capabilities\n# Note: This functionality requires Lighttpd was compiled against\n# a version of OpenSSL that supports this.\n#\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nlighttpd_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  # Some defaults\n  DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT=\"/etc/lighttpd\"\n  DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT=\"${_cdomain}.pem\"\n  DEPLOY_LIGHTTPD_BUNDLE_DEFAULT=\"no\"\n  DEPLOY_LIGHTTPD_ISSUER_DEFAULT=\"yes\"\n  DEPLOY_LIGHTTPD_RELOAD_DEFAULT=\"true\"\n\n  _debug _cdomain \"${_cdomain}\"\n  _debug _ckey \"${_ckey}\"\n  _debug _ccert \"${_ccert}\"\n  _debug _cca \"${_cca}\"\n  _debug _cfullchain \"${_cfullchain}\"\n\n  # PEM_PATH is optional. If not provided then assume \"${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}\"\n  _getdeployconf DEPLOY_LIGHTTPD_PEM_PATH\n  _debug2 DEPLOY_LIGHTTPD_PEM_PATH \"${DEPLOY_LIGHTTPD_PEM_PATH}\"\n  if [ -n \"${DEPLOY_LIGHTTPD_PEM_PATH}\" ]; then\n    Le_Deploy_lighttpd_pem_path=\"${DEPLOY_LIGHTTPD_PEM_PATH}\"\n    _savedomainconf Le_Deploy_lighttpd_pem_path \"${Le_Deploy_lighttpd_pem_path}\"\n  elif [ -z \"${Le_Deploy_lighttpd_pem_path}\" ]; then\n    Le_Deploy_lighttpd_pem_path=\"${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}\"\n  fi\n\n  # Ensure PEM_PATH exists\n  if [ -d \"${Le_Deploy_lighttpd_pem_path}\" ]; then\n    _debug \"PEM_PATH ${Le_Deploy_lighttpd_pem_path} exists\"\n  else\n    _err \"PEM_PATH ${Le_Deploy_lighttpd_pem_path} does not exist\"\n    return 1\n  fi\n\n  # PEM_NAME is optional. If not provided then assume \"${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}\"\n  _getdeployconf DEPLOY_LIGHTTPD_PEM_NAME\n  _debug2 DEPLOY_LIGHTTPD_PEM_NAME \"${DEPLOY_LIGHTTPD_PEM_NAME}\"\n  if [ -n \"${DEPLOY_LIGHTTPD_PEM_NAME}\" ]; then\n    Le_Deploy_lighttpd_pem_name=\"${DEPLOY_LIGHTTPD_PEM_NAME}\"\n    _savedomainconf Le_Deploy_lighttpd_pem_name \"${Le_Deploy_lighttpd_pem_name}\"\n  elif [ -z \"${Le_Deploy_lighttpd_pem_name}\" ]; then\n    Le_Deploy_lighttpd_pem_name=\"${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}\"\n  fi\n\n  # BUNDLE is optional. If not provided then assume \"${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}\"\n  _getdeployconf DEPLOY_LIGHTTPD_BUNDLE\n  _debug2 DEPLOY_LIGHTTPD_BUNDLE \"${DEPLOY_LIGHTTPD_BUNDLE}\"\n  if [ -n \"${DEPLOY_LIGHTTPD_BUNDLE}\" ]; then\n    Le_Deploy_lighttpd_bundle=\"${DEPLOY_LIGHTTPD_BUNDLE}\"\n    _savedomainconf Le_Deploy_lighttpd_bundle \"${Le_Deploy_lighttpd_bundle}\"\n  elif [ -z \"${Le_Deploy_lighttpd_bundle}\" ]; then\n    Le_Deploy_lighttpd_bundle=\"${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}\"\n  fi\n\n  # ISSUER is optional. If not provided then assume \"${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}\"\n  _getdeployconf DEPLOY_LIGHTTPD_ISSUER\n  _debug2 DEPLOY_LIGHTTPD_ISSUER \"${DEPLOY_LIGHTTPD_ISSUER}\"\n  if [ -n \"${DEPLOY_LIGHTTPD_ISSUER}\" ]; then\n    Le_Deploy_lighttpd_issuer=\"${DEPLOY_LIGHTTPD_ISSUER}\"\n    _savedomainconf Le_Deploy_lighttpd_issuer \"${Le_Deploy_lighttpd_issuer}\"\n  elif [ -z \"${Le_Deploy_lighttpd_issuer}\" ]; then\n    Le_Deploy_lighttpd_issuer=\"${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}\"\n  fi\n\n  # RELOAD is optional. If not provided then assume \"${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}\"\n  _getdeployconf DEPLOY_LIGHTTPD_RELOAD\n  _debug2 DEPLOY_LIGHTTPD_RELOAD \"${DEPLOY_LIGHTTPD_RELOAD}\"\n  if [ -n \"${DEPLOY_LIGHTTPD_RELOAD}\" ]; then\n    Le_Deploy_lighttpd_reload=\"${DEPLOY_LIGHTTPD_RELOAD}\"\n    _savedomainconf Le_Deploy_lighttpd_reload \"${Le_Deploy_lighttpd_reload}\"\n  elif [ -z \"${Le_Deploy_lighttpd_reload}\" ]; then\n    Le_Deploy_lighttpd_reload=\"${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}\"\n  fi\n\n  # Set the suffix depending if we are creating a bundle or not\n  if [ \"${Le_Deploy_lighttpd_bundle}\" = \"yes\" ]; then\n    _info \"Bundle creation requested\"\n    # Initialise $Le_Keylength if its not already set\n    if [ -z \"${Le_Keylength}\" ]; then\n      Le_Keylength=\"\"\n    fi\n    if _isEccKey \"${Le_Keylength}\"; then\n      _info \"ECC key type detected\"\n      _suffix=\".ecdsa\"\n    else\n      _info \"RSA key type detected\"\n      _suffix=\".rsa\"\n    fi\n  else\n    _suffix=\"\"\n  fi\n  _debug _suffix \"${_suffix}\"\n\n  # Set variables for later\n  _pem=\"${Le_Deploy_lighttpd_pem_path}/${Le_Deploy_lighttpd_pem_name}${_suffix}\"\n  _issuer=\"${_pem}.issuer\"\n  _ocsp=\"${_pem}.ocsp\"\n  _reload=\"${Le_Deploy_lighttpd_reload}\"\n\n  _info \"Deploying PEM file\"\n  # Create a temporary PEM file\n  _temppem=\"$(_mktemp)\"\n  _debug _temppem \"${_temppem}\"\n  cat \"${_ckey}\" \"${_ccert}\" \"${_cca}\" >\"${_temppem}\"\n  _ret=\"$?\"\n\n  # Check that we could create the temporary file\n  if [ \"${_ret}\" != \"0\" ]; then\n    _err \"Error code ${_ret} returned during PEM file creation\"\n    [ -f \"${_temppem}\" ] && rm -f \"${_temppem}\"\n    return ${_ret}\n  fi\n\n  # Move PEM file into place\n  _info \"Moving new certificate into place\"\n  _debug _pem \"${_pem}\"\n  cat \"${_temppem}\" >\"${_pem}\"\n  _ret=$?\n\n  # Clean up temp file\n  [ -f \"${_temppem}\" ] && rm -f \"${_temppem}\"\n\n  # Deal with any failure of moving PEM file into place\n  if [ \"${_ret}\" != \"0\" ]; then\n    _err \"Error code ${_ret} returned while moving new certificate into place\"\n    return ${_ret}\n  fi\n\n  # Update .issuer file if requested\n  if [ \"${Le_Deploy_lighttpd_issuer}\" = \"yes\" ]; then\n    _info \"Updating .issuer file\"\n    _debug _issuer \"${_issuer}\"\n    cat \"${_cca}\" >\"${_issuer}\"\n    _ret=\"$?\"\n\n    if [ \"${_ret}\" != \"0\" ]; then\n      _err \"Error code ${_ret} returned while copying issuer/CA certificate into place\"\n      return ${_ret}\n    fi\n  else\n    [ -f \"${_issuer}\" ] && _err \"Issuer file update not requested but .issuer file exists\"\n  fi\n\n  # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option\n  if [ -z \"${Le_OCSP_Staple}\" ]; then\n    Le_OCSP_Staple=\"0\"\n  fi\n  if [ \"${Le_OCSP_Staple}\" = \"1\" ]; then\n    _info \"Updating OCSP stapling info\"\n    _debug _ocsp \"${_ocsp}\"\n    _info \"Extracting OCSP URL\"\n    _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in \"${_pem}\")\n    _debug _ocsp_url \"${_ocsp_url}\"\n\n    # Only process OCSP if URL was present\n    if [ \"${_ocsp_url}\" != \"\" ]; then\n      # Extract the hostname from the OCSP URL\n      _info \"Extracting OCSP URL\"\n      _ocsp_host=$(echo \"${_ocsp_url}\" | cut -d/ -f3)\n      _debug _ocsp_host \"${_ocsp_host}\"\n\n      # Only process the certificate if we have a .issuer file\n      if [ -r \"${_issuer}\" ]; then\n        # Check if issuer cert is also a root CA cert\n        _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in \"${_issuer}\" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)\n        _debug _subjectdn \"${_subjectdn}\"\n        _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in \"${_issuer}\" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)\n        _debug _issuerdn \"${_issuerdn}\"\n        _info \"Requesting OCSP response\"\n        # If the issuer is a CA cert then our command line has \"-CAfile\" added\n        if [ \"${_subjectdn}\" = \"${_issuerdn}\" ]; then\n          _cafile_argument=\"-CAfile \\\"${_issuer}\\\"\"\n        else\n          _cafile_argument=\"\"\n        fi\n        _debug _cafile_argument \"${_cafile_argument}\"\n        # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed\n        _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)\n        _debug _openssl_version \"${_openssl_version}\"\n        _openssl_major=$(echo \"${_openssl_version}\" | cut -d '.' -f1)\n        _openssl_minor=$(echo \"${_openssl_version}\" | cut -d '.' -f2)\n        if [ \"${_openssl_major}\" -eq \"1\" ] && [ \"${_openssl_minor}\" -ge \"1\" ] || [ \"${_openssl_major}\" -ge \"2\" ]; then\n          _header_sep=\"=\"\n        else\n          _header_sep=\" \"\n        fi\n        # Request the OCSP response from the issuer and store it\n        _openssl_ocsp_cmd=\"${ACME_OPENSSL_BIN:-openssl} ocsp \\\n          -issuer \\\"${_issuer}\\\" \\\n          -cert \\\"${_pem}\\\" \\\n          -url \\\"${_ocsp_url}\\\" \\\n          -header Host${_header_sep}\\\"${_ocsp_host}\\\" \\\n          -respout \\\"${_ocsp}\\\" \\\n          -verify_other \\\"${_issuer}\\\" \\\n          ${_cafile_argument} \\\n          | grep -q \\\"${_pem}: good\\\"\"\n        _debug _openssl_ocsp_cmd \"${_openssl_ocsp_cmd}\"\n        eval \"${_openssl_ocsp_cmd}\"\n        _ret=$?\n      else\n        # Non fatal: No issuer file was present so no OCSP stapling file created\n        _err \"OCSP stapling in use but no .issuer file was present\"\n      fi\n    else\n      # Non fatal: No OCSP url was found int the certificate\n      _err \"OCSP update requested but no OCSP URL was found in certificate\"\n    fi\n\n    # Non fatal: Check return code of openssl command\n    if [ \"${_ret}\" != \"0\" ]; then\n      _err \"Updating OCSP stapling failed with return code ${_ret}\"\n    fi\n  else\n    # An OCSP file was already present but certificate did not have OCSP extension\n    if [ -f \"${_ocsp}\" ]; then\n      _err \"OCSP was not requested but .ocsp file exists.\"\n      # Could remove the file at this step, although Lighttpd just ignores it in this case\n      # rm -f \"${_ocsp}\" || _err \"Problem removing stale .ocsp file\"\n    fi\n  fi\n\n  # Reload Lighttpd\n  _debug _reload \"${_reload}\"\n  eval \"${_reload}\"\n  _ret=$?\n  if [ \"${_ret}\" != \"0\" ]; then\n    _err \"Error code ${_ret} during reload\"\n    return ${_ret}\n  else\n    _info \"Reload successful\"\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/localcopy.sh",
    "content": "#!/usr/bin/env sh\n\n# Deploy-hook to very simply copy files to set directories and then\n# execute whatever reloadcmd the admin needs afterwards. This can be\n# useful for configurations where the \"multideploy\" hook (in development)\n# is used or when an admin wants ACME.SH to renew certs but needs to\n# manually configure deployment via an external script\n# (e.g. The deploy-freenas script for TrueNAS Core/Scale\n# https://github.com/danb35/deploy-freenas/ )\n#\n# If the same file is configured for the certificate key\n# and the certificate and/or full chain, a combined PEM file will\n# be output instead.\n#\n# Environment variables to be utilized are as follows:\n#\n# DEPLOY_LOCALCOPY_CERTKEY - /path/to/target/cert.key\n# DEPLOY_LOCALCOPY_CERTIFICATE - /path/to/target/cert.cer\n# DEPLOY_LOCALCOPY_FULLCHAIN - /path/to/target/fullchain.cer\n# DEPLOY_LOCALCOPY_CA - /path/to/target/ca.cer\n# DEPLOY_LOCALCOPY_PFX - /path/to/target/cert.pfx\n# DEPLOY_LOCALCOPY_RELOADCMD - \"echo 'this is my cmd'\"\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nlocalcopy_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _cpfx=\"$6\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n  _debug _cpfx \"$_cpfx\"\n\n  _getdeployconf DEPLOY_LOCALCOPY_CERTIFICATE\n  _getdeployconf DEPLOY_LOCALCOPY_CERTKEY\n  _getdeployconf DEPLOY_LOCALCOPY_FULLCHAIN\n  _getdeployconf DEPLOY_LOCALCOPY_CA\n  _getdeployconf DEPLOY_LOCALCOPY_RELOADCMD\n  _getdeployconf DEPLOY_LOCALCOPY_PFX\n  _combined_target=\"\"\n  _combined_srccert=\"\"\n\n  # Create PEM file\n  if [ \"$DEPLOY_LOCALCOPY_CERTKEY\" ] &&\n    { [ \"$DEPLOY_LOCALCOPY_CERTKEY\" = \"$DEPLOY_LOCALCOPY_FULLCHAIN\" ] ||\n      [ \"$DEPLOY_LOCALCOPY_CERTKEY\" = \"$DEPLOY_LOCALCOPY_CERTIFICATE\" ]; }; then\n\n    _combined_target=\"$DEPLOY_LOCALCOPY_CERTKEY\"\n    _savedeployconf DEPLOY_LOCALCOPY_CERTKEY \"$DEPLOY_LOCALCOPY_CERTKEY\"\n    if [ \"$DEPLOY_LOCALCOPY_CERTKEY\" = \"$DEPLOY_LOCALCOPY_CERTIFICATE\" ]; then\n      _combined_srccert=\"$_ccert\"\n      _savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE \"$DEPLOY_LOCALCOPY_CERTIFICATE\"\n      DEPLOY_LOCALCOPY_CERTIFICATE=\"\"\n    fi\n    if [ \"$DEPLOY_LOCALCOPY_CERTKEY\" = \"$DEPLOY_LOCALCOPY_FULLCHAIN\" ]; then\n      _combined_srccert=\"$_cfullchain\"\n      _savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN \"$DEPLOY_LOCALCOPY_FULLCHAIN\"\n      DEPLOY_LOCALCOPY_FULLCHAIN=\"\"\n    fi\n    DEPLOY_LOCALCOPY_CERTKEY=\"\"\n    _info \"Creating combined PEM\"\n    _debug \"Creating combined PEM at $_combined_target\"\n    if ! [ -f \"$_combined_target\" ]; then\n      touch \"$_combined_target\" || return 1\n      chmod 600 \"$_combined_target\"\n    fi\n    if ! cat \"$_combined_srccert\" \"$_ckey\" >\"$_combined_target\"; then\n      _err \"Failed to create PEM file\"\n      return 1\n    fi\n  fi\n\n  if [ \"$DEPLOY_LOCALCOPY_CERTIFICATE\" ]; then\n    _info \"Copying certificate\"\n    _debug \"Copying $_ccert to $DEPLOY_LOCALCOPY_CERTIFICATE\"\n    if ! cat \"$_ccert\" >\"$DEPLOY_LOCALCOPY_CERTIFICATE\"; then\n      _err \"Failed to copy certificate, aborting.\"\n      return 1\n    fi\n    _savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE \"$DEPLOY_LOCALCOPY_CERTIFICATE\"\n  fi\n\n  if [ \"$DEPLOY_LOCALCOPY_CERTKEY\" ]; then\n    _info \"Copying certificate key\"\n    _debug \"Copying $_ckey to $DEPLOY_LOCALCOPY_CERTKEY\"\n    if ! [ -f \"$DEPLOY_LOCALCOPY_CERTKEY\" ]; then\n      touch \"$DEPLOY_LOCALCOPY_CERTKEY\" || return 1\n      chmod 600 \"$DEPLOY_LOCALCOPY_CERTKEY\"\n    fi\n    if ! cat \"$_ckey\" >\"$DEPLOY_LOCALCOPY_CERTKEY\"; then\n      _err \"Failed to copy certificate key, aborting.\"\n      return 1\n    fi\n    _savedeployconf DEPLOY_LOCALCOPY_CERTKEY \"$DEPLOY_LOCALCOPY_CERTKEY\"\n  fi\n\n  if [ \"$DEPLOY_LOCALCOPY_FULLCHAIN\" ]; then\n    _info \"Copying fullchain\"\n    _debug \"Copying $_cfullchain to $DEPLOY_LOCALCOPY_FULLCHAIN\"\n    if ! cat \"$_cfullchain\" >\"$DEPLOY_LOCALCOPY_FULLCHAIN\"; then\n      _err \"Failed to copy fullchain, aborting.\"\n      return 1\n    fi\n    _savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN \"$DEPLOY_LOCALCOPY_FULLCHAIN\"\n  fi\n\n  if [ \"$DEPLOY_LOCALCOPY_CA\" ]; then\n    _info \"Copying CA\"\n    _debug \"Copying $_cca to $DEPLOY_LOCALCOPY_CA\"\n    if ! cat \"$_cca\" >\"$DEPLOY_LOCALCOPY_CA\"; then\n      _err \"Failed to copy CA, aborting.\"\n      return 1\n    fi\n    _savedeployconf DEPLOY_LOCALCOPY_CA \"$DEPLOY_LOCALCOPY_CA\"\n  fi\n\n  if [ \"$DEPLOY_LOCALCOPY_PFX\" ]; then\n    _info \"Copying PFX\"\n    _debug \"Copying $_cpfx to $DEPLOY_LOCALCOPY_PFX\"\n    if ! [ -f \"$DEPLOY_LOCALCOPY_PFX\" ]; then\n      touch \"$DEPLOY_LOCALCOPY_PFX\" || return 1\n      chmod 600 \"$DEPLOY_LOCALCOPY_PFX\"\n    fi\n    if ! cat \"$_cpfx\" >\"$DEPLOY_LOCALCOPY_PFX\"; then\n      _err \"Failed to copy PFX, aborting.\"\n      return 1\n    fi\n    _savedeployconf DEPLOY_LOCALCOPY_PFX \"$DEPLOY_LOCALCOPY_PFX\"\n  fi\n\n  _reload=$DEPLOY_LOCALCOPY_RELOADCMD\n  _debug \"Running reloadcmd $_reload\"\n\n  if [ -z \"$_reload\" ]; then\n    _info \"Reloadcmd not provided, skipping.\"\n  else\n    _info \"Reloading\"\n    if eval \"$_reload\"; then\n      _info \"Reload successful.\"\n      _savedeployconf DEPLOY_LOCALCOPY_RELOADCMD \"$DEPLOY_LOCALCOPY_RELOADCMD\" \"base64\"\n    else\n      _err \"Reload failed.\"\n      return 1\n    fi\n  fi\n\n  _info \"$(__green \"'localcopy' deploy success\")\"\n  return 0\n}\n"
  },
  {
    "path": "deploy/mailcow.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to mailcow.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nmailcow_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf DEPLOY_MAILCOW_PATH\n  _getdeployconf DEPLOY_MAILCOW_RELOAD\n\n  _debug DEPLOY_MAILCOW_PATH \"$DEPLOY_MAILCOW_PATH\"\n  _debug DEPLOY_MAILCOW_RELOAD \"$DEPLOY_MAILCOW_RELOAD\"\n\n  if [ -z \"$DEPLOY_MAILCOW_PATH\" ]; then\n    _err \"Mailcow path is not found, please define DEPLOY_MAILCOW_PATH.\"\n    return 1\n  fi\n\n  _savedeployconf DEPLOY_MAILCOW_PATH \"$DEPLOY_MAILCOW_PATH\"\n  [ -n \"$DEPLOY_MAILCOW_RELOAD\" ] && _savedeployconf DEPLOY_MAILCOW_RELOAD \"$DEPLOY_MAILCOW_RELOAD\"\n\n  _ssl_path=\"$DEPLOY_MAILCOW_PATH\"\n  if [ -f \"$DEPLOY_MAILCOW_PATH/generate_config.sh\" ]; then\n    _ssl_path=\"$DEPLOY_MAILCOW_PATH/data/assets/ssl/\"\n  fi\n\n  if [ ! -d \"$_ssl_path\" ]; then\n    _err \"Cannot find mailcow ssl path: $_ssl_path\"\n    return 1\n  fi\n\n  _info \"Copying key and cert\"\n  _real_key=\"$_ssl_path/key.pem\"\n  if ! cat \"$_ckey\" >\"$_real_key\"; then\n    _err \"Error: write key file to: $_real_key\"\n    return 1\n  fi\n\n  _real_fullchain=\"$_ssl_path/cert.pem\"\n  if ! cat \"$_cfullchain\" >\"$_real_fullchain\"; then\n    _err \"Error: write cert file to: $_real_fullchain\"\n    return 1\n  fi\n\n  DEFAULT_MAILCOW_RELOAD=\"docker restart \\$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow --filter name=postfix-mailcow)\"\n  _reload=\"${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}\"\n\n  _info \"Run reload: $_reload\"\n  if eval \"$_reload\"; then\n    _info \"Reload success!\"\n  fi\n  return 0\n\n}\n"
  },
  {
    "path": "deploy/multideploy.sh",
    "content": "#!/usr/bin/env sh\n\n################################################################################\n# ACME.sh 3rd party deploy plugin for multiple (same) services\n################################################################################\n# Authors: tomo2403 (creator), https://github.com/tomo2403\n# Updated: 2025-03-01\n# Issues:  https://github.com/acmesh-official/acme.sh/issues and mention @tomo2403\n################################################################################\n# Usage (shown values are the examples):\n# 1. Set optional environment variables\n#   - export MULTIDEPLOY_FILENAME=\"multideploy.yaml\"     - \"multideploy.yml\" will be automatically used if not set\"\n#\n# 2. Run command:\n# acme.sh --deploy --deploy-hook multideploy -d example.com\n################################################################################\n# Dependencies:\n# - yq\n################################################################################\n# Return value:\n# 0 means success, otherwise error.\n################################################################################\n\nMULTIDEPLOY_VERSION=\"1.0\"\n\n# Description: This function handles the deployment of certificates to multiple services.\n#              It processes the provided certificate files and deploys them according to the\n#              configuration specified in the multideploy file.\n#\n# Parameters:\n#   _cdomain     - The domain name for which the certificate is issued.\n#   _ckey        - The private key file for the certificate.\n#   _ccert       - The certificate file.\n#   _cca         - The CA (Certificate Authority) file.\n#   _cfullchain  - The full chain certificate file.\n#   _cpfx        - The PFX (Personal Information Exchange) file.\nmultideploy_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _cpfx=\"$6\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n  _debug _cpfx \"$_cpfx\"\n\n  MULTIDEPLOY_FILENAME=\"${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}\"\n  if [ -z \"$MULTIDEPLOY_FILENAME\" ]; then\n    MULTIDEPLOY_FILENAME=\"multideploy.yml\"\n    _info \"MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'.\"\n  else\n    _savedeployconf \"MULTIDEPLOY_FILENAME\" \"$MULTIDEPLOY_FILENAME\"\n    _debug2 \"MULTIDEPLOY_FILENAME\" \"$MULTIDEPLOY_FILENAME\"\n  fi\n\n  if ! file=$(_preprocess_deployfile \"$MULTIDEPLOY_FILENAME\"); then\n    _err \"Failed to preprocess deploy file.\"\n    return 1\n  fi\n  _debug3 \"File\" \"$file\"\n\n  # Deploy to services\n  _deploy_services \"$file\"\n  _exitCode=\"$?\"\n\n  return \"$_exitCode\"\n}\n\n# Description:\n#   This function preprocesses the deploy file by checking if 'yq' is installed,\n#   verifying the existence of the deploy file, and ensuring only one deploy file is present.\n# Arguments:\n#   $@ - Posible deploy file names.\n# Usage:\n#   _preprocess_deployfile \"<deploy_file1>\" \"<deploy_file2>?\"\n_preprocess_deployfile() {\n  # Check if yq is installed\n  if ! command -v yq >/dev/null 2>&1; then\n    _err \"yq is not installed! Please install yq and try again.\"\n    return 1\n  fi\n  _debug3 \"yq is installed.\"\n\n  # Check if deploy file exists\n  for file in \"$@\"; do\n    _debug3 \"Checking file\" \"$DOMAIN_PATH/$file\"\n    if [ -f \"$DOMAIN_PATH/$file\" ]; then\n      _debug3 \"File found\"\n      if [ -n \"$found_file\" ]; then\n        _err \"Multiple deploy files found. Please keep only one deploy file.\"\n        return 1\n      fi\n      found_file=\"$file\"\n    else\n      _debug3 \"File not found\"\n    fi\n  done\n\n  if [ -z \"$found_file\" ]; then\n    _err \"Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one.\"\n    return 1\n  fi\n  if ! _check_deployfile \"$DOMAIN_PATH/$found_file\"; then\n    _err \"Deploy file is not valid: $DOMAIN_PATH/$found_file\"\n    return 1\n  fi\n\n  echo \"$DOMAIN_PATH/$found_file\"\n}\n\n# Description:\n#   This function checks the deploy file for version compatibility and the existence of the specified configuration and services.\n# Arguments:\n#   $1 - The path to the deploy configuration file.\n#   $2 - The name of the deploy configuration to use.\n# Usage:\n#   _check_deployfile \"<deploy_file_path>\"\n_check_deployfile() {\n  _deploy_file=\"$1\"\n  _debug2 \"check: Deploy file\" \"$_deploy_file\"\n\n  # Check version\n  _deploy_file_version=$(yq -r '.version' \"$_deploy_file\")\n  if [ \"$MULTIDEPLOY_VERSION\" != \"$_deploy_file_version\" ]; then\n    _err \"As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version.\"\n    return 1\n  fi\n  _debug2 \"check: Deploy file version is compatible: $_deploy_file_version\"\n\n  # Extract all services from config\n  _services=$(yq -r '.services[].name' \"$_deploy_file\")\n\n  if [ -z \"$_services\" ]; then\n    _err \"Config does not have any services to deploy to.\"\n    return 1\n  fi\n  _debug2 \"check: Config has services.\"\n  echo \"$_services\" | while read -r _service; do\n    _debug3 \" - $_service\"\n  done\n\n  # Check if extracted services exist in services list\n  echo \"$_services\" | while read -r _service; do\n    _debug2 \"check: Checking service: $_service\"\n    # Check if service exists\n    _service_config=$(yq -r \".services[] | select(.name == \\\"$_service\\\")\" \"$_deploy_file\")\n    if [ -z \"$_service_config\" ] || [ \"$_service_config\" = \"null\" ]; then\n      _err \"Service '$_service' not found.\"\n      return 1\n    fi\n\n    _service_hook=$(echo \"$_service_config\" | yq -r \".hook\" -)\n    if [ -z \"$_service_hook\" ] || [ \"$_service_hook\" = \"null\" ]; then\n      _err \"Service '$_service' does not have a hook.\"\n      return 1\n    fi\n\n    _service_environment=$(echo \"$_service_config\" | yq -r \".environment\" -)\n    if [ -z \"$_service_environment\" ] || [ \"$_service_environment\" = \"null\" ]; then\n      _err \"Service '$_service' does not have an environment.\"\n      return 1\n    fi\n  done\n}\n\n# Description: This function takes a list of environment variables in YAML format,\n#              parses them, and exports each key-value pair as environment variables.\n# Arguments:\n#   $1 - A string containing the list of environment variables in YAML format.\n# Usage:\n#   _export_envs \"$env_list\"\n_export_envs() {\n  _env_list=\"$1\"\n\n  _secure_debug3 \"Exporting envs\" \"$_env_list\"\n\n  echo \"$_env_list\" | yq -r 'to_entries | .[] | .key + \"=\" + .value' | while IFS='=' read -r _key _value; do\n    # Using eval to expand nested variables in the configuration file\n    _value=$(eval 'echo \"'\"$_value\"'\"')\n    _savedeployconf \"$_key\" \"$_value\"\n    _secure_debug3 \"Saved $_key\" \"$_value\"\n  done\n}\n\n# Description:\n#   This function takes a YAML formatted string of environment variables, parses it,\n#   and clears each environment variable. It logs the process of clearing each variable.\n#\n#   Note: Environment variables for a hook may be optional and differ between\n#   services using the same hook.\n#   If one service sets optional environment variables and another does not, the\n#   variables may persist and affect subsequent deployments.\n#   Clearing these variables after each service ensures that only the\n#   environment variables explicitly specified for each service in the deploy\n#   file are used.\n# Arguments:\n#   $1 - A YAML formatted string containing environment variable key-value pairs.\n# Usage:\n#   _clear_envs \"<yaml_string>\"\n_clear_envs() {\n  _env_list=\"$1\"\n\n  _secure_debug3 \"Clearing envs\" \"$_env_list\"\n  env_pairs=$(echo \"$_env_list\" | yq -r 'to_entries | .[] | .key + \"=\" + .value')\n\n  echo \"$env_pairs\" | while IFS='=' read -r _key _value; do\n    _debug3 \"Deleting key\" \"$_key\"\n    _cleardomainconf \"SAVED_$_key\"\n    unset -v \"$_key\"\n  done\n}\n\n# Description:\n#   This function deploys services listed in the deploy configuration file.\n# Arguments:\n#   $1 - The path to the deploy configuration file.\n#   $2 - The list of services to deploy.\n# Usage:\n#   _deploy_services \"<deploy_file_path>\" \"<services_list>\"\n_deploy_services() {\n  _deploy_file=\"$1\"\n  _debug3 \"Deploy file\" \"$_deploy_file\"\n\n  _tempfile=$(mktemp)\n  trap 'rm -f $_tempfile' EXIT\n\n  yq -r '.services[].name' \"$_deploy_file\" >\"$_tempfile\"\n  _debug3 \"Services\" \"$(cat \"$_tempfile\")\"\n\n  _failedServices=\"\"\n  _failedCount=0\n  while read -r _service <&3; do\n    _debug2 \"Service\" \"$_service\"\n    _hook=$(yq -r \".services[] | select(.name == \\\"$_service\\\").hook\" \"$_deploy_file\")\n    _envs=$(yq -r \".services[] | select(.name == \\\"$_service\\\").environment\" \"$_deploy_file\")\n\n    _export_envs \"$_envs\"\n    if ! _deploy_service \"$_service\" \"$_hook\"; then\n      _failedServices=\"$_service, $_failedServices\"\n      _failedCount=$((_failedCount + 1))\n    fi\n    _clear_envs \"$_envs\"\n  done 3<\"$_tempfile\"\n\n  _debug3 \"Failed services\" \"$_failedServices\"\n  _debug2 \"Failed count\" \"$_failedCount\"\n  if [ -n \"$_failedServices\" ]; then\n    _info \"$(__red \"Deployment failed\") for services: $_failedServices\"\n  else\n    _debug \"All services deployed successfully.\"\n  fi\n\n  return \"$_failedCount\"\n}\n\n# Description: Deploys a service using the specified hook.\n# Arguments:\n#   $1 - The name of the service to deploy.\n#   $2 - The hook to use for deployment.\n# Usage:\n#   _deploy_service <service_name> <hook>\n_deploy_service() {\n  _name=\"$1\"\n  _hook=\"$2\"\n\n  _debug2 \"SERVICE\" \"$_name\"\n  _debug2 \"HOOK\" \"$_hook\"\n\n  _info \"$(__green \"Deploying\") to '$_name' using '$_hook'\"\n  _deploy \"$_cdomain\" \"$_hook\"\n}\n"
  },
  {
    "path": "deploy/myapi.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a sample custom api script.\n#This file name is \"myapi.sh\"\n#So, here must be a method   myapi_deploy()\n#Which will be called by acme.sh to deploy the cert\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nmyapi_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/mydevil.sh",
    "content": "#!/usr/bin/env sh\n\n# MyDevil.net API (2019-02-03)\n#\n# MyDevil.net already supports automatic Let's Encrypt certificates,\n# except for wildcard domains.\n#\n# This script depends on `devil` command that MyDevil.net provides,\n# which means that it works only on server side.\n#\n# Author: Marcin Konicki <https://ahwayakchih.neoni.net>\n#\n########  Public functions #####################\n\n# Usage: mydevil_deploy domain keyfile certfile cafile fullchain\nmydevil_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  ip=\"\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if ! _exists \"devil\"; then\n    _err \"Could not find 'devil' command.\"\n    return 1\n  fi\n\n  ip=$(mydevil_get_ip \"$_cdomain\")\n  if [ -z \"$ip\" ]; then\n    _err \"Could not find IP for domain $_cdomain.\"\n    return 1\n  fi\n\n  # Delete old certificate first\n  _info \"Removing old certificate for $_cdomain at $ip\"\n  devil ssl www del \"$ip\" \"$_cdomain\"\n\n  # Add new certificate\n  _info \"Adding new certificate for $_cdomain at $ip\"\n  devil ssl www add \"$ip\" \"$_cfullchain\" \"$_ckey\" \"$_cdomain\" || return 1\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n# Usage: ip=$(mydevil_get_ip domain.com)\n#        echo $ip\nmydevil_get_ip() {\n  devil dns list \"$1\" | cut -w -s -f 3,7 | grep \"^A$(printf '\\t')\" | cut -w -s -f 2 || return 1\n  return 0\n}\n"
  },
  {
    "path": "deploy/mysqld.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to mysqld server.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nmysqld_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"deploy cert to mysqld server, Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/netlify.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificate to Netlify\n# https://docs.netlify.com/api/get-started/#authentication\n# https://open-api.netlify.com/#tag/sniCertificate\n\n# This deployment required following variables\n# export Netlify_ACCESS_TOKEN=\"Your Netlify Access Token\"\n# export Netlify_SITE_ID=\"Your Netlify Site ID\"\n\n# If have more than one SITE ID\n# export Netlify_SITE_ID=\"SITE_ID_1 SITE_ID_2\"\n\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nnetlify_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$Netlify_ACCESS_TOKEN\" ]; then\n    _err \"Netlify_ACCESS_TOKEN is not defined.\"\n    return 1\n  else\n    _savedomainconf Netlify_ACCESS_TOKEN \"$Netlify_ACCESS_TOKEN\"\n  fi\n  if [ -z \"$Netlify_SITE_ID\" ]; then\n    _err \"Netlify_SITE_ID is not defined.\"\n    return 1\n  else\n    _savedomainconf Netlify_SITE_ID \"$Netlify_SITE_ID\"\n  fi\n\n  _info \"Deploying certificate to Netlify...\"\n\n  ## upload certificate\n  string_ccert=$(sed 's/$/\\\\n/' \"$_ccert\" | tr -d '\\n')\n  string_cca=$(sed 's/$/\\\\n/' \"$_cca\" | tr -d '\\n')\n  string_key=$(sed 's/$/\\\\n/' \"$_ckey\" | tr -d '\\n')\n\n  for SITE_ID in $Netlify_SITE_ID; do\n    _request_body=\"{\\\"certificate\\\":\\\"$string_ccert\\\",\\\"key\\\":\\\"$string_key\\\",\\\"ca_certificates\\\":\\\"$string_cca\\\"}\"\n    _debug _request_body \"$_request_body\"\n    _debug Netlify_ACCESS_TOKEN \"$Netlify_ACCESS_TOKEN\"\n    export _H1=\"Authorization: Bearer $Netlify_ACCESS_TOKEN\"\n    _response=$(_post \"$_request_body\" \"https://api.netlify.com/api/v1/sites/$SITE_ID/ssl\" \"\" \"POST\" \"application/json\")\n\n    if _contains \"$_response\" \"\\\"error\\\"\"; then\n      _err \"Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID.\"\n      _err \"$_response\"\n      return 1\n    fi\n    _debug response \"$_response\"\n    _info \"Domain $_cdomain certificate successfully deployed to Netlify SITE_ID $SITE_ID.\"\n  done\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/nginx.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to nginx server.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nnginx_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"deploy cert to nginx server, Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/openmediavault.sh",
    "content": "#!/usr/bin/env sh\n\n# This deploy hook is tested on OpenMediaVault 5.x. It supports both local and remote deployment.\n# The way it works is that if a cert with the matching domain name is not found, it will firstly create a dummy cert to get its uuid, and then replace it with your cert.\n#\n# DEPLOY_OMV_WEBUI_ADMIN - This is OMV web gui admin account. Default value is admin. It's required as the user parameter (-u) for the omv-rpc command.\n# DEPLOY_OMV_HOST and DEPLOY_OMV_SSH_USER are optional. They are used for remote deployment through ssh (support public key authentication only). Per design, OMV web gui admin doesn't have ssh permission, so another account is needed for ssh.\n#\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nopenmediavault_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf DEPLOY_OMV_WEBUI_ADMIN\n\n  if [ -z \"$DEPLOY_OMV_WEBUI_ADMIN\" ]; then\n    DEPLOY_OMV_WEBUI_ADMIN=\"admin\"\n  fi\n\n  _savedeployconf DEPLOY_OMV_WEBUI_ADMIN \"$DEPLOY_OMV_WEBUI_ADMIN\"\n\n  _getdeployconf DEPLOY_OMV_HOST\n  _getdeployconf DEPLOY_OMV_SSH_USER\n\n  if [ -n \"$DEPLOY_OMV_HOST\" ] && [ -n \"$DEPLOY_OMV_SSH_USER\" ]; then\n    _info \"[OMV deploy-hook] Deploy certificate remotely through ssh.\"\n    _savedeployconf DEPLOY_OMV_HOST \"$DEPLOY_OMV_HOST\"\n    _savedeployconf DEPLOY_OMV_SSH_USER \"$DEPLOY_OMV_SSH_USER\"\n  else\n    _info \"[OMV deploy-hook] Deploy certificate locally.\"\n  fi\n\n  if [ -n \"$DEPLOY_OMV_HOST\" ] && [ -n \"$DEPLOY_OMV_SSH_USER\" ]; then\n\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'getList' '{\\\"start\\\": 0, \\\"limit\\\": -1}' | jq -r '.data[] | select(.name==\\\"/CN='$_cdomain'\\\") | .uuid'\"\n    # shellcheck disable=SC2029\n    _uuid=$(ssh \"$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST\" \"$_command\")\n    _debug _command \"$_command\"\n\n    if [ -z \"$_uuid\" ]; then\n      _info \"[OMV deploy-hook] Domain $_cdomain has no certificate in openmediavault, creating it!\"\n      _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'create' '{\\\"cn\\\": \\\"test.example.com\\\", \\\"size\\\": 4096, \\\"days\\\": 3650, \\\"c\\\": \\\"\\\", \\\"st\\\": \\\"\\\", \\\"l\\\": \\\"\\\", \\\"o\\\": \\\"\\\", \\\"ou\\\": \\\"\\\", \\\"email\\\": \\\"\\\"}' | jq -r '.uuid'\"\n      # shellcheck disable=SC2029\n      _uuid=$(ssh \"$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST\" \"$_command\")\n      _debug _command \"$_command\"\n\n      if [ -z \"$_uuid\" ]; then\n        _err \"[OMV deploy-hook] An error occured while creating the certificate\"\n        return 1\n      fi\n    fi\n\n    _info \"[OMV deploy-hook] Domain $_cdomain has uuid: $_uuid\"\n    _fullchain=$(jq <\"$_cfullchain\" -aRs .)\n    _key=$(jq <\"$_ckey\" -aRs .)\n\n    _debug _fullchain \"$_fullchain\"\n    _debug _key \"$_key\"\n\n    _info \"[OMV deploy-hook] Updating key and certificate in openmediavault\"\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'set' '{\\\"uuid\\\":\\\"$_uuid\\\", \\\"certificate\\\":$_fullchain, \\\"privatekey\\\":$_key, \\\"comment\\\":\\\"acme.sh deployed $(date)\\\"}'\"\n    # shellcheck disable=SC2029\n    _result=$(ssh \"$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST\" \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'setSettings' \\$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'getSettings' | jq -c '.sslcertificateref=\\\"$_uuid\\\"')\"\n    # shellcheck disable=SC2029\n    _result=$(ssh \"$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST\" \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n    _info \"[OMV deploy-hook] Asking openmediavault to apply changes... (this could take some time, hang in there)\"\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'Config' 'applyChanges' '{\\\"modules\\\":[], \\\"force\\\": false}'\"\n    # shellcheck disable=SC2029\n    _result=$(ssh \"$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST\" \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n    _info \"[OMV deploy-hook] Asking nginx to reload\"\n    _command=\"nginx -s reload\"\n    # shellcheck disable=SC2029\n    _result=$(ssh \"$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST\" \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n  else\n\n    # shellcheck disable=SC2086\n    _uuid=$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'getList' '{\"start\": 0, \"limit\": -1}' | jq -r '.data[] | select(.name==\"/CN='$_cdomain'\") | .uuid')\n    if [ -z \"$_uuid\" ]; then\n      _info \"[OMV deploy-hook] Domain $_cdomain has no certificate in openmediavault, creating it!\"\n      # shellcheck disable=SC2086\n      _uuid=$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'create' '{\"cn\": \"test.example.com\", \"size\": 4096, \"days\": 3650, \"c\": \"\", \"st\": \"\", \"l\": \"\", \"o\": \"\", \"ou\": \"\", \"email\": \"\"}' | jq -r '.uuid')\n\n      if [ -z \"$_uuid\" ]; then\n        _err \"[OMB deploy-hook] An error occured while creating the certificate\"\n        return 1\n      fi\n    fi\n\n    _info \"[OMV deploy-hook] Domain $_cdomain has uuid: $_uuid\"\n    _fullchain=$(jq <\"$_cfullchain\" -aRs .)\n    _key=$(jq <\"$_ckey\" -aRs .)\n\n    _debug _fullchain \"$_fullchain\"\n    _debug _key \"$_key\"\n\n    _info \"[OMV deploy-hook] Updating key and certificate in openmediavault\"\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'set' '{\\\"uuid\\\":\\\"$_uuid\\\", \\\"certificate\\\":$_fullchain, \\\"privatekey\\\":$_key, \\\"comment\\\":\\\"acme.sh deployed $(date)\\\"}'\"\n    _result=$(eval \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'setSettings' \\$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'getSettings' | jq -c '.sslcertificateref=\\\"$_uuid\\\"')\"\n    _result=$(eval \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n    _info \"[OMV deploy-hook] Asking openmediavault to apply changes... (this could take some time, hang in there)\"\n    _command=\"omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'Config' 'applyChanges' '{\\\"modules\\\":[], \\\"force\\\": false}'\"\n    _result=$(eval \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n    _info \"[OMV deploy-hook] Asking nginx to reload\"\n    _command=\"nginx -s reload\"\n    _result=$(eval \"$_command\")\n\n    _debug _command \"$_command\"\n    _debug _result \"$_result\"\n\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/opensshd.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to opensshd server.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nopensshd_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"deploy cert to opensshd server, Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/openstack.sh",
    "content": "#!/usr/bin/env sh\n\n# OpenStack Barbican deploy hook\n#\n# This requires you to have OpenStackClient and python-barbicanclient\n# installed.\n#\n# You will require Keystone V3 credentials loaded into your environment, which\n# could be either password or v3applicationcredential type.\n#\n# Author: Andy Botting <andy@andybotting.com>\n\nopenstack_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if ! _exists openstack; then\n    _err \"OpenStack client not found\"\n    return 1\n  fi\n\n  _openstack_credentials || return $?\n\n  _info \"Generate import pkcs12\"\n  _import_pkcs12=\"$(_mktemp)\"\n  if ! _openstack_to_pkcs \"$_import_pkcs12\" \"$_ckey\" \"$_ccert\" \"$_cca\"; then\n    _err \"Error creating pkcs12 certificate\"\n    return 1\n  fi\n  _debug _import_pkcs12 \"$_import_pkcs12\"\n  _base64_pkcs12=$(_base64 \"multiline\" <\"$_import_pkcs12\")\n\n  secretHrefs=$(_openstack_get_secrets)\n  _debug secretHrefs \"$secretHrefs\"\n  _openstack_store_secret || return $?\n\n  if [ -n \"$secretHrefs\" ]; then\n    _info \"Cleaning up existing secret\"\n    _openstack_delete_secrets || return $?\n  fi\n\n  _info \"Certificate successfully deployed\"\n  return 0\n}\n\n_openstack_store_secret() {\n  if ! openstack secret store --name \"$_cdomain.\" -t 'application/octet-stream' -e base64 --payload \"$_base64_pkcs12\"; then\n    _err \"Failed to create OpenStack secret\"\n    return 1\n  fi\n  return\n}\n\n_openstack_delete_secrets() {\n  echo \"$secretHrefs\" | while read -r secretHref; do\n    _info \"Deleting old secret $secretHref\"\n    if ! openstack secret delete \"$secretHref\"; then\n      _err \"Failed to delete OpenStack secret\"\n      return 1\n    fi\n  done\n  return\n}\n\n_openstack_get_secrets() {\n  if ! secretHrefs=$(openstack secret list -f value --name \"$_cdomain.\" | cut -d' ' -f1); then\n    _err \"Failed to list secrets\"\n    return 1\n  fi\n  echo \"$secretHrefs\"\n}\n\n_openstack_to_pkcs() {\n  # The existing _toPkcs command can't allow an empty password, due to sh\n  # -z test, so copied here and forcing the empty password.\n  _cpfx=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n\n  ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out \"$_cpfx\" -inkey \"$_ckey\" -in \"$_ccert\" -certfile \"$_cca\" -password \"pass:\"\n}\n\n_openstack_credentials() {\n  _debug \"Check OpenStack credentials\"\n\n  # If we have OS_AUTH_URL already set in the environment, then assume we want\n  # to use those, otherwise use stored credentials\n  if [ -n \"$OS_AUTH_URL\" ]; then\n    _debug \"OS_AUTH_URL env var found, using environment\"\n  else\n    _debug \"OS_AUTH_URL not found, loading stored credentials\"\n    OS_AUTH_URL=\"${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}\"\n    OS_IDENTITY_API_VERSION=\"${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}\"\n    OS_AUTH_TYPE=\"${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}\"\n    OS_APPLICATION_CREDENTIAL_ID=\"${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}\"\n    OS_APPLICATION_CREDENTIAL_SECRET=\"${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}\"\n    OS_USERNAME=\"${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}\"\n    OS_PASSWORD=\"${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}\"\n    OS_PROJECT_NAME=\"${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}\"\n    OS_PROJECT_ID=\"${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}\"\n    OS_USER_DOMAIN_NAME=\"${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}\"\n    OS_USER_DOMAIN_ID=\"${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}\"\n    OS_PROJECT_DOMAIN_NAME=\"${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}\"\n    OS_PROJECT_DOMAIN_ID=\"${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}\"\n  fi\n\n  # Check each var and either save or clear it depending on whether its set.\n  # The helps us clear out old vars in the case where a user may want\n  # to switch between password and app creds\n  _debug \"OS_AUTH_URL\" \"$OS_AUTH_URL\"\n  if [ -n \"$OS_AUTH_URL\" ]; then\n    export OS_AUTH_URL\n    _saveaccountconf_mutable OS_AUTH_URL \"$OS_AUTH_URL\"\n  else\n    unset OS_AUTH_URL\n    _clearaccountconf SAVED_OS_AUTH_URL\n  fi\n\n  _debug \"OS_IDENTITY_API_VERSION\" \"$OS_IDENTITY_API_VERSION\"\n  if [ -n \"$OS_IDENTITY_API_VERSION\" ]; then\n    export OS_IDENTITY_API_VERSION\n    _saveaccountconf_mutable OS_IDENTITY_API_VERSION \"$OS_IDENTITY_API_VERSION\"\n  else\n    unset OS_IDENTITY_API_VERSION\n    _clearaccountconf SAVED_OS_IDENTITY_API_VERSION\n  fi\n\n  _debug \"OS_AUTH_TYPE\" \"$OS_AUTH_TYPE\"\n  if [ -n \"$OS_AUTH_TYPE\" ]; then\n    export OS_AUTH_TYPE\n    _saveaccountconf_mutable OS_AUTH_TYPE \"$OS_AUTH_TYPE\"\n  else\n    unset OS_AUTH_TYPE\n    _clearaccountconf SAVED_OS_AUTH_TYPE\n  fi\n\n  _debug \"OS_APPLICATION_CREDENTIAL_ID\" \"$OS_APPLICATION_CREDENTIAL_ID\"\n  if [ -n \"$OS_APPLICATION_CREDENTIAL_ID\" ]; then\n    export OS_APPLICATION_CREDENTIAL_ID\n    _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID \"$OS_APPLICATION_CREDENTIAL_ID\"\n  else\n    unset OS_APPLICATION_CREDENTIAL_ID\n    _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID\n  fi\n\n  _secure_debug \"OS_APPLICATION_CREDENTIAL_SECRET\" \"$OS_APPLICATION_CREDENTIAL_SECRET\"\n  if [ -n \"$OS_APPLICATION_CREDENTIAL_SECRET\" ]; then\n    export OS_APPLICATION_CREDENTIAL_SECRET\n    _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET \"$OS_APPLICATION_CREDENTIAL_SECRET\"\n  else\n    unset OS_APPLICATION_CREDENTIAL_SECRET\n    _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET\n  fi\n\n  _debug \"OS_USERNAME\" \"$OS_USERNAME\"\n  if [ -n \"$OS_USERNAME\" ]; then\n    export OS_USERNAME\n    _saveaccountconf_mutable OS_USERNAME \"$OS_USERNAME\"\n  else\n    unset OS_USERNAME\n    _clearaccountconf SAVED_OS_USERNAME\n  fi\n\n  _secure_debug \"OS_PASSWORD\" \"$OS_PASSWORD\"\n  if [ -n \"$OS_PASSWORD\" ]; then\n    export OS_PASSWORD\n    _saveaccountconf_mutable OS_PASSWORD \"$OS_PASSWORD\"\n  else\n    unset OS_PASSWORD\n    _clearaccountconf SAVED_OS_PASSWORD\n  fi\n\n  _debug \"OS_PROJECT_NAME\" \"$OS_PROJECT_NAME\"\n  if [ -n \"$OS_PROJECT_NAME\" ]; then\n    export OS_PROJECT_NAME\n    _saveaccountconf_mutable OS_PROJECT_NAME \"$OS_PROJECT_NAME\"\n  else\n    unset OS_PROJECT_NAME\n    _clearaccountconf SAVED_OS_PROJECT_NAME\n  fi\n\n  _debug \"OS_PROJECT_ID\" \"$OS_PROJECT_ID\"\n  if [ -n \"$OS_PROJECT_ID\" ]; then\n    export OS_PROJECT_ID\n    _saveaccountconf_mutable OS_PROJECT_ID \"$OS_PROJECT_ID\"\n  else\n    unset OS_PROJECT_ID\n    _clearaccountconf SAVED_OS_PROJECT_ID\n  fi\n\n  _debug \"OS_USER_DOMAIN_NAME\" \"$OS_USER_DOMAIN_NAME\"\n  if [ -n \"$OS_USER_DOMAIN_NAME\" ]; then\n    export OS_USER_DOMAIN_NAME\n    _saveaccountconf_mutable OS_USER_DOMAIN_NAME \"$OS_USER_DOMAIN_NAME\"\n  else\n    unset OS_USER_DOMAIN_NAME\n    _clearaccountconf SAVED_OS_USER_DOMAIN_NAME\n  fi\n\n  _debug \"OS_USER_DOMAIN_ID\" \"$OS_USER_DOMAIN_ID\"\n  if [ -n \"$OS_USER_DOMAIN_ID\" ]; then\n    export OS_USER_DOMAIN_ID\n    _saveaccountconf_mutable OS_USER_DOMAIN_ID \"$OS_USER_DOMAIN_ID\"\n  else\n    unset OS_USER_DOMAIN_ID\n    _clearaccountconf SAVED_OS_USER_DOMAIN_ID\n  fi\n\n  _debug \"OS_PROJECT_DOMAIN_NAME\" \"$OS_PROJECT_DOMAIN_NAME\"\n  if [ -n \"$OS_PROJECT_DOMAIN_NAME\" ]; then\n    export OS_PROJECT_DOMAIN_NAME\n    _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME \"$OS_PROJECT_DOMAIN_NAME\"\n  else\n    unset OS_PROJECT_DOMAIN_NAME\n    _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME\n  fi\n\n  _debug \"OS_PROJECT_DOMAIN_ID\" \"$OS_PROJECT_DOMAIN_ID\"\n  if [ -n \"$OS_PROJECT_DOMAIN_ID\" ]; then\n    export OS_PROJECT_DOMAIN_ID\n    _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID \"$OS_PROJECT_DOMAIN_ID\"\n  else\n    unset OS_PROJECT_DOMAIN_ID\n    _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID\n  fi\n\n  if [ \"$OS_AUTH_TYPE\" = \"v3applicationcredential\" ]; then\n    # Application Credential auth\n    if [ -z \"$OS_APPLICATION_CREDENTIAL_ID\" ] || [ -z \"$OS_APPLICATION_CREDENTIAL_SECRET\" ]; then\n      _err \"When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID\"\n      _err \"and OS_APPLICATION_CREDENTIAL_SECRET must be set.\"\n      _err \"Please check your credentials and try again.\"\n      return 1\n    fi\n  else\n    # Password auth\n    if [ -z \"$OS_USERNAME\" ] || [ -z \"$OS_PASSWORD\" ]; then\n      _err \"OpenStack username or password not found.\"\n      _err \"Please check your credentials and try again.\"\n      return 1\n    fi\n\n    if [ -z \"$OS_PROJECT_NAME\" ] && [ -z \"$OS_PROJECT_ID\" ]; then\n      _err \"When using password authentication, OS_PROJECT_NAME or\"\n      _err \"OS_PROJECT_ID must be set.\"\n      _err \"Please check your credentials and try again.\"\n      return 1\n    fi\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/panos.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificates to Palo Alto Networks PANOS via API\n# Note PANOS API KEY and IP address needs to be set prior to running.\n# The following variables exported from environment will be used.\n# If not set then values previously saved in domain.conf file are used.\n#\n# Firewall admin with superuser and IP address is required.\n#\n# REQUIRED:\n#     export PANOS_HOST=\"\"\n#     export PANOS_USER=\"\"    #User *MUST* have Commit and Import Permissions in XML API for Admin Role\n#     export PANOS_PASS=\"\"\n#\n# OPTIONAL\n#    export PANOS_TEMPLATE=\"\" # Template Name of panorama managed devices\n#    export PANOS_TEMPLATE_STACK=\"\" # set a Template Stack if certificate should also be pushed automatically\n#    export PANOS_VSYS=\"Shared\"  # name of the vsys to import the certificate\n#    export PANOS_CERTNAME=\"\" # use a custom certificate name to work around Panorama's 31-character limit\n#\n# The script will automatically generate a new API key if\n# no key is found, or if a saved key has expired or is invalid.\n\n_COMMIT_WAIT_INTERVAL=30   # query commit status every 30 seconds\n_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes)\n\n# This function is to parse the XML response from the firewall\nparse_response() {\n  type=$2\n  _debug \"API Response: $1\"\n  if [ \"$type\" = 'keygen' ]; then\n    status=$(echo \"$1\" | sed 's/^.*\\(['\\'']\\)\\([a-z]*\\)'\\''.*/\\2/g')\n    if [ \"$status\" = \"success\" ]; then\n      panos_key=$(echo \"$1\" | sed 's/^.*\\(<key>\\)\\(.*\\)<\\/key>.*/\\2/g')\n      _panos_key=$panos_key\n    else\n      message=\"PAN-OS Key could not be set.\"\n    fi\n  else\n    if [ \"$type\" = 'commit' ]; then\n      job_id=$(echo \"$1\" | sed 's/^.*\\(<job>\\)\\(.*\\)<\\/job>.*/\\2/g')\n      _commit_job_id=$job_id\n    elif [ \"$type\" = 'job_status' ]; then\n      job_status=$(echo \"$1\" | tr -d '\\n' | sed 's/^.*<result>\\([^<]*\\)<\\/result>.*/\\1/g')\n      _commit_job_status=$job_status\n    fi\n    status=$(echo \"$1\" | tr -d '\\n' | sed 's/^.*\"\\([a-z]*\\)\".*/\\1/g')\n    message=$(echo \"$1\" | tr -d '\\n' | sed 's/.*\\(<result>\\|<msg>\\|<line>\\)\\([^<]*\\).*/\\2/g')\n    _debug \"Firewall message:  $message\"\n    if [ \"$type\" = 'keytest' ] && [ \"$status\" != \"success\" ]; then\n      _debug \"****  API Key has EXPIRED or is INVALID ****\"\n      unset _panos_key\n    fi\n  fi\n  return 0\n}\n\n#This function is used to deploy to the firewall\ndeployer() {\n  content=\"\"\n  type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push\n  panos_url=\"https://$_panos_host/api/\"\n  export _H1=\"Content-Type: application/x-www-form-urlencoded\"\n\n  #Test API Key by performing a lookup\n  if [ \"$type\" = 'keytest' ]; then\n    _debug \"**** Testing saved API Key ****\"\n    # Get Version Info to test key\n    content=\"type=version&key=$_panos_key\"\n    ## Exclude all scopes for the empty commit\n    #_exclude_scope=\"<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network><shared-object>exclude</shared-object>\"\n    #content=\"type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>acmekeytest</member></admin></partial></commit>\"\n  fi\n\n  # Generate API Key\n  if [ \"$type\" = 'keygen' ]; then\n    _debug \"**** Generating new API Key ****\"\n    content=\"type=keygen&user=$_panos_user&password=$_panos_pass\"\n    # content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; type=\\\"keygen\\\"; user=\\\"$_panos_user\\\"; password=\\\"$_panos_pass\\\"${nl}Content-Type: application/octet-stream${nl}${nl}\"\n  fi\n\n  # Deploy Cert or Key\n  if [ \"$type\" = 'cert' ] || [ \"$type\" = 'key' ]; then\n    _debug \"**** Deploying $type ****\"\n    #Generate DELIM\n    delim=\"-----MultipartDelimiter$(date \"+%s%N\")\"\n    nl=\"\\015\\012\"\n    #Set Header\n    export _H1=\"Content-Type: multipart/form-data; boundary=$delim\"\n    if [ \"$type\" = 'cert' ]; then\n      panos_url=\"${panos_url}?type=import\"\n      content=\"--$delim${nl}Content-Disposition: form-data; name=\\\"category\\\"\\r\\n\\r\\ncertificate\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"certificate-name\\\"\\r\\n\\r\\n$_panos_certname\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"key\\\"\\r\\n\\r\\n$_panos_key\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"format\\\"\\r\\n\\r\\npem\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"$(basename \"$_cfullchain\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_cfullchain\")\"\n      if [ \"$_panos_template\" ]; then\n        content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"target-tpl\\\"\\r\\n\\r\\n$_panos_template\"\n      fi\n      if [ \"$_panos_vsys\" ]; then\n        content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"target-tpl-vsys\\\"\\r\\n\\r\\n$_panos_vsys\"\n      fi\n    fi\n    if [ \"$type\" = 'key' ]; then\n      panos_url=\"${panos_url}?type=import\"\n      content=\"--$delim${nl}Content-Disposition: form-data; name=\\\"category\\\"\\r\\n\\r\\nprivate-key\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"certificate-name\\\"\\r\\n\\r\\n$_panos_certname\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"key\\\"\\r\\n\\r\\n$_panos_key\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"format\\\"\\r\\n\\r\\npem\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"passphrase\\\"\\r\\n\\r\\n123456\"\n      content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"$(basename \"$_panos_certname.key\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_ckey\")\"\n      if [ \"$_panos_template\" ]; then\n        content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"target-tpl\\\"\\r\\n\\r\\n$_panos_template\"\n      fi\n      if [ \"$_panos_vsys\" ]; then\n        content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"target-tpl-vsys\\\"\\r\\n\\r\\n$_panos_vsys\"\n      fi\n    fi\n    #Close multipart\n    content=\"$content${nl}--$delim--${nl}${nl}\"\n    #Convert CRLF\n    content=$(printf %b \"$content\")\n  fi\n\n  # Commit changes\n  if [ \"$type\" = 'commit' ]; then\n    _debug \"**** Committing changes ****\"\n    #Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution!\n    if [ \"$FORCE\" ]; then\n      _debug \"Force switch detected.  Committing ALL changes to the firewall.\"\n      cmd=$(printf \"%s\" \"<commit><partial><force><admin><member>$_panos_user</member></admin></force></partial></commit>\" | _url_encode)\n    else\n      _exclude_scope=\"<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network>\"\n      cmd=$(printf \"%s\" \"<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>\" | _url_encode)\n    fi\n    content=\"type=commit&action=partial&key=$_panos_key&cmd=$cmd\"\n  fi\n\n  # Query job status\n  if [ \"$type\" = 'job_status' ]; then\n    echo \"**** Querying job $_commit_job_id status ****\"\n    cmd=$(printf \"%s\" \"<show><jobs><id>$_commit_job_id</id></jobs></show>\" | _url_encode)\n    content=\"type=op&key=$_panos_key&cmd=$cmd\"\n  fi\n\n  # Push changes\n  if [ \"$type\" = 'push' ]; then\n    echo \"**** Pushing changes ****\"\n    cmd=$(printf \"%s\" \"<commit-all><template-stack><name>$_panos_template_stack</name><admin><member>$_panos_user</member></admin></template-stack></commit-all>\" | _url_encode)\n    content=\"type=commit&action=all&key=$_panos_key&cmd=$cmd\"\n  fi\n\n  response=$(_post \"$content\" \"$panos_url\" \"\" \"POST\")\n  parse_response \"$response\" \"$type\"\n  # Saving response to variables\n  response_status=$status\n  _debug response_status \"$response_status\"\n  if [ \"$response_status\" = \"success\" ]; then\n    _debug \"Successfully deployed $type\"\n    return 0\n  elif [ \"$_commit_job_status\" ]; then\n    _debug \"Commit Job Status = $_commit_job_status\"\n  else\n    _err \"Deploy of type $type failed. Try deploying with --debug to troubleshoot.\"\n    _debug \"$message\"\n    return 1\n  fi\n}\n\n# This is the main function that will call the other functions to deploy everything.\npanos_deploy() {\n  _cdomain=$(echo \"$1\" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename\n  _ckey=\"$2\"\n  _cfullchain=\"$5\"\n\n  # VALID FILE CHECK\n  if [ ! -f \"$_ckey\" ] || [ ! -f \"$_cfullchain\" ]; then\n    _err \"Unable to find a valid key and/or cert.  If this is an ECDSA/ECC cert, use the --ecc flag when deploying.\"\n    return 1\n  fi\n\n  # PANOS_HOST\n  if [ \"$PANOS_HOST\" ]; then\n    _debug \"Detected ENV variable PANOS_HOST. Saving to file.\"\n    _savedeployconf PANOS_HOST \"$PANOS_HOST\" 1\n  else\n    _debug \"Attempting to load variable PANOS_HOST from file.\"\n    _getdeployconf PANOS_HOST\n  fi\n\n  # PANOS USER\n  if [ \"$PANOS_USER\" ]; then\n    _debug \"Detected ENV variable PANOS_USER. Saving to file.\"\n    _savedeployconf PANOS_USER \"$PANOS_USER\" 1\n  else\n    _debug \"Attempting to load variable PANOS_USER from file.\"\n    _getdeployconf PANOS_USER\n  fi\n\n  # PANOS_PASS\n  if [ \"$PANOS_PASS\" ]; then\n    _debug \"Detected ENV variable PANOS_PASS. Saving to file.\"\n    _savedeployconf PANOS_PASS \"$PANOS_PASS\" 1\n  else\n    _debug \"Attempting to load variable PANOS_PASS from file.\"\n    _getdeployconf PANOS_PASS\n  fi\n\n  # PANOS_KEY\n  if [ \"$PANOS_KEY\" ]; then\n    _debug \"Detected ENV variable PANOS_KEY. Saving to file.\"\n    _savedeployconf PANOS_KEY \"$PANOS_KEY\" 1\n  else\n    _debug \"Attempting to load variable PANOS_KEY from file.\"\n    _getdeployconf PANOS_KEY\n  fi\n\n  # PANOS_TEMPLATE\n  if [ \"$PANOS_TEMPLATE\" ]; then\n    _debug \"Detected ENV variable PANOS_TEMPLATE. Saving to file.\"\n    _savedeployconf PANOS_TEMPLATE \"$PANOS_TEMPLATE\" 1\n  else\n    _debug \"Attempting to load variable PANOS_TEMPLATE from file.\"\n    _getdeployconf PANOS_TEMPLATE\n  fi\n\n  # PANOS_TEMPLATE_STACK\n  if [ \"$PANOS_TEMPLATE_STACK\" ]; then\n    _debug \"Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file.\"\n    _savedeployconf PANOS_TEMPLATE_STACK \"$PANOS_TEMPLATE_STACK\" 1\n  else\n    _debug \"Attempting to load variable PANOS_TEMPLATE_STACK from file.\"\n    _getdeployconf PANOS_TEMPLATE_STACK\n  fi\n\n  # PANOS_TEMPLATE_STACK\n  if [ \"$PANOS_VSYS\" ]; then\n    _debug \"Detected ENV variable PANOS_VSYS. Saving to file.\"\n    _savedeployconf PANOS_VSYS \"$PANOS_VSYS\" 1\n  else\n    _debug \"Attempting to load variable PANOS_VSYS from file.\"\n    _getdeployconf PANOS_VSYS\n  fi\n\n  # PANOS_CERTNAME\n  if [ \"$PANOS_CERTNAME\" ]; then\n    _debug \"Detected ENV variable PANOS_CERTNAME. Saving to file.\"\n    _savedeployconf PANOS_CERTNAME \"$PANOS_CERTNAME\" 1\n  else\n    _debug \"Attempting to load variable PANOS_CERTNAME from file.\"\n    _getdeployconf PANOS_CERTNAME\n  fi\n\n  #Store variables\n  _panos_host=$PANOS_HOST\n  _panos_user=$PANOS_USER\n  _panos_pass=$PANOS_PASS\n  _panos_key=$PANOS_KEY\n  _panos_template=$PANOS_TEMPLATE\n  _panos_template_stack=$PANOS_TEMPLATE_STACK\n  _panos_vsys=$PANOS_VSYS\n  _panos_certname=$PANOS_CERTNAME\n\n  #Test API Key if found.  If the key is invalid, the variable _panos_key will be unset.\n  if [ \"$_panos_host\" ] && [ \"$_panos_key\" ]; then\n    _debug \"**** Testing API KEY ****\"\n    deployer keytest\n  fi\n\n  # Check for valid variables\n  if [ -z \"$_panos_host\" ]; then\n    _err \"No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs.\"\n    return 1\n  else\n    # Use certificate name based on the first domain on the certificate if no custom certificate name is set\n    if [ -z \"$_panos_certname\" ]; then\n      _panos_certname=\"$_cdomain\"\n      _savedeployconf PANOS_CERTNAME \"$_panos_certname\" 1\n    fi\n\n    # Generate a new API key if no valid API key is found\n    if [ -z \"$_panos_key\" ]; then\n      if [ -z \"$_panos_user\" ]; then\n        _err \"No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs.\"\n        return 1\n      elif [ -z \"$_panos_pass\" ]; then\n        _err \"No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs.\"\n        return 1\n      fi\n      _debug \"**** Generating new PANOS API KEY ****\"\n      deployer keygen\n      _savedeployconf PANOS_KEY \"$_panos_key\" 1\n    fi\n\n    # Confirm that a valid key was generated\n    if [ -z \"$_panos_key\" ]; then\n      _err \"Unable to generate an API key.  The user and pass may be invalid or not authorized to generate a new key.  Please check the PANOS_USER and PANOS_PASS credentials and try again\"\n      return 1\n    else\n      deployer cert\n      deployer key\n      deployer commit\n      if [ \"$_panos_template_stack\" ]; then\n        # try to get job status for 20 times in 30 sec interval\n        i=0\n        while [ \"$i\" -lt $_COMMIT_WAIT_ITERATIONS ]; do\n          deployer job_status\n          if [ \"$_commit_job_status\" = \"OK\" ]; then\n            echo \"Commit finished!\"\n            break\n          fi\n          sleep $_COMMIT_WAIT_INTERVAL\n          i=$((i + 1))\n        done\n        deployer push\n      fi\n    fi\n  fi\n}\n"
  },
  {
    "path": "deploy/peplink.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy cert to Peplink Routers\n#\n# The following environment variables must be set:\n#\n# PEPLINK_Hostname - Peplink hostname\n# PEPLINK_Username - Peplink username to login\n# PEPLINK_Password - Peplink password to login\n#\n# The following environmental variables may be set if you don't like their\n# default values:\n#\n# PEPLINK_Certtype - Certificate type to target for replacement\n#                    defaults to \"webadmin\", can be one of:\n#                      * \"chub\" (ContentHub)\n#                      * \"openvpn\" (OpenVPN CA)\n#                      * \"portal\" (Captive Portal SSL)\n#                      * \"webadmin\" (Web Admin SSL)\n#                      * \"webproxy\" (Proxy Root CA)\n#                      * \"wwan_ca\" (Wi-Fi WAN CA)\n#                      * \"wwan_client\" (Wi-Fi WAN Client)\n# PEPLINK_Scheme   - defaults to \"https\"\n# PEPLINK_Port     - defaults to \"443\"\n#\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n_peplink_get_cookie_data() {\n  grep -i \"\\W$1=\" | grep -i \"^Set-Cookie:\" | _tail_n 1 | _egrep_o \"$1=[^;]*;\" | tr -d ';'\n}\n\n#domain keyfile certfile cafile fullchain\npeplink_deploy() {\n\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _cfullchain \"$_cfullchain\"\n  _debug _ckey \"$_ckey\"\n\n  # Get Hostname, Username and Password, but don't save until we successfully authenticate\n  _getdeployconf PEPLINK_Hostname\n  _getdeployconf PEPLINK_Username\n  _getdeployconf PEPLINK_Password\n  if [ -z \"${PEPLINK_Hostname:-}\" ] || [ -z \"${PEPLINK_Username:-}\" ] || [ -z \"${PEPLINK_Password:-}\" ]; then\n    _err \"PEPLINK_Hostname & PEPLINK_Username & PEPLINK_Password must be set\"\n    return 1\n  fi\n  _debug2 PEPLINK_Hostname \"$PEPLINK_Hostname\"\n  _debug2 PEPLINK_Username \"$PEPLINK_Username\"\n  _secure_debug2 PEPLINK_Password \"$PEPLINK_Password\"\n\n  # Optional certificate type, scheme, and port for Peplink\n  _getdeployconf PEPLINK_Certtype\n  _getdeployconf PEPLINK_Scheme\n  _getdeployconf PEPLINK_Port\n\n  # Don't save the certificate type until we verify it exists and is supported\n  _savedeployconf PEPLINK_Scheme \"$PEPLINK_Scheme\"\n  _savedeployconf PEPLINK_Port \"$PEPLINK_Port\"\n\n  # Default vaules for certificate type, scheme, and port\n  [ -n \"${PEPLINK_Certtype}\" ] || PEPLINK_Certtype=\"webadmin\"\n  [ -n \"${PEPLINK_Scheme}\" ] || PEPLINK_Scheme=\"https\"\n  [ -n \"${PEPLINK_Port}\" ] || PEPLINK_Port=\"443\"\n\n  _debug2 PEPLINK_Certtype \"$PEPLINK_Certtype\"\n  _debug2 PEPLINK_Scheme \"$PEPLINK_Scheme\"\n  _debug2 PEPLINK_Port \"$PEPLINK_Port\"\n\n  _base_url=\"$PEPLINK_Scheme://$PEPLINK_Hostname:$PEPLINK_Port\"\n  _debug _base_url \"$_base_url\"\n\n  # Login, get the auth token from the cookie\n  _info \"Logging into $PEPLINK_Hostname:$PEPLINK_Port\"\n  encoded_username=\"$(printf \"%s\" \"$PEPLINK_Username\" | _url_encode)\"\n  encoded_password=\"$(printf \"%s\" \"$PEPLINK_Password\" | _url_encode)\"\n  response=$(_post \"func=login&username=$encoded_username&password=$encoded_password\" \"$_base_url/cgi-bin/MANGA/api.cgi\")\n  auth_token=$(_peplink_get_cookie_data \"bauth\" <\"$HTTP_HEADER\")\n  _debug3 response \"$response\"\n  _debug auth_token \"$auth_token\"\n\n  if [ -z \"$auth_token\" ]; then\n    _err \"Unable to authenticate to $PEPLINK_Hostname:$PEPLINK_Port using $PEPLINK_Scheme.\"\n    _err \"Check your username and password.\"\n    return 1\n  fi\n\n  _H1=\"Cookie: $auth_token\"\n  export _H1\n  _debug2 H1 \"${_H1}\"\n\n  # Now that we know the hostnameusername and password are good, save them\n  _savedeployconf PEPLINK_Hostname \"$PEPLINK_Hostname\"\n  _savedeployconf PEPLINK_Username \"$PEPLINK_Username\"\n  _savedeployconf PEPLINK_Password \"$PEPLINK_Password\"\n\n  _info \"Generate form POST request\"\n\n  encoded_key=\"$(_url_encode <\"$_ckey\")\"\n  encoded_fullchain=\"$(_url_encode <\"$_cfullchain\")\"\n  body=\"cert_type=$PEPLINK_Certtype&cert_uid=&section=CERT_modify&key_pem=$encoded_key&key_pem_passphrase=&key_pem_passphrase_confirm=&cert_pem=$encoded_fullchain\"\n  _debug3 body \"$body\"\n\n  _info \"Upload $PEPLINK_Certtype certificate to the Peplink\"\n\n  response=$(_post \"$body\" \"$_base_url/cgi-bin/MANGA/admin.cgi\")\n  _debug3 response \"$response\"\n\n  if echo \"$response\" | grep 'Success' >/dev/null; then\n    # We've verified this certificate type is valid, so save it\n    _savedeployconf PEPLINK_Certtype \"$PEPLINK_Certtype\"\n    _info \"Certificate was updated\"\n    return 0\n  else\n    _err \"Unable to update certificate, error code $response\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "deploy/proxmoxbs.sh",
    "content": "#!/usr/bin/env sh\n\n# Deploy certificates to a proxmox backup server using the API.\n#\n# Environment variables that can be set are:\n# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to\n#                            _cdomain.\n# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on.\n#                                 Defaults to 8007.\n# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root.\n# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates\n#                                with. Defaults to pam.\n# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the\n#                                    user account. Defaults to acme.\n# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required.\n\nproxmoxbs_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug2 _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # \"Sane\" defaults.\n  _getdeployconf DEPLOY_PROXMOXBS_SERVER\n  if [ -z \"$DEPLOY_PROXMOXBS_SERVER\" ]; then\n    _target_hostname=\"$_cdomain\"\n  else\n    _target_hostname=\"$DEPLOY_PROXMOXBS_SERVER\"\n    _savedeployconf DEPLOY_PROXMOXBS_SERVER \"$DEPLOY_PROXMOXBS_SERVER\"\n  fi\n  _debug2 DEPLOY_PROXMOXBS_SERVER \"$_target_hostname\"\n\n  _getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT\n  if [ -z \"$DEPLOY_PROXMOXBS_SERVER_PORT\" ]; then\n    _target_port=\"8007\"\n  else\n    _target_port=\"$DEPLOY_PROXMOXBS_SERVER_PORT\"\n    _savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT \"$DEPLOY_PROXMOXBS_SERVER_PORT\"\n  fi\n  _debug2 DEPLOY_PROXMOXBS_SERVER_PORT \"$_target_port\"\n\n  # Complete URL.\n  _target_url=\"https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom\"\n  _debug TARGET_URL \"$_target_url\"\n\n  # More \"sane\" defaults.\n  _getdeployconf DEPLOY_PROXMOXBS_USER\n  if [ -z \"$DEPLOY_PROXMOXBS_USER\" ]; then\n    _proxmoxbs_user=\"root\"\n  else\n    _proxmoxbs_user=\"$DEPLOY_PROXMOXBS_USER\"\n    _savedeployconf DEPLOY_PROXMOXBS_USER \"$DEPLOY_PROXMOXBS_USER\"\n  fi\n  _debug2 DEPLOY_PROXMOXBS_USER \"$_proxmoxbs_user\"\n\n  _getdeployconf DEPLOY_PROXMOXBS_USER_REALM\n  if [ -z \"$DEPLOY_PROXMOXBS_USER_REALM\" ]; then\n    _proxmoxbs_user_realm=\"pam\"\n  else\n    _proxmoxbs_user_realm=\"$DEPLOY_PROXMOXBS_USER_REALM\"\n    _savedeployconf DEPLOY_PROXMOXBS_USER_REALM \"$DEPLOY_PROXMOXBS_USER_REALM\"\n  fi\n  _debug2 DEPLOY_PROXMOXBS_USER_REALM \"$_proxmoxbs_user_realm\"\n\n  _getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME\n  if [ -z \"$DEPLOY_PROXMOXBS_API_TOKEN_NAME\" ]; then\n    _proxmoxbs_api_token_name=\"acme\"\n  else\n    _proxmoxbs_api_token_name=\"$DEPLOY_PROXMOXBS_API_TOKEN_NAME\"\n    _savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME \"$DEPLOY_PROXMOXBS_API_TOKEN_NAME\"\n  fi\n  _debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME \"$_proxmoxbs_api_token_name\"\n\n  # This is required.\n  _getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY\n  if [ -z \"$DEPLOY_PROXMOXBS_API_TOKEN_KEY\" ]; then\n    _err \"API key not provided.\"\n    return 1\n  else\n    _proxmoxbs_api_token_key=\"$DEPLOY_PROXMOXBS_API_TOKEN_KEY\"\n    _savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY \"$DEPLOY_PROXMOXBS_API_TOKEN_KEY\"\n  fi\n  _debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY \"$_proxmoxbs_api_token_key\"\n\n  # PBS API Token header value. Used in \"Authorization: PBSAPIToken\".\n  _proxmoxbs_header_api_token=\"${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}\"\n  _debug2 \"Auth Header\" \"$_proxmoxbs_header_api_token\"\n\n  # Ugly. I hate putting heredocs inside functions because heredocs don't\n  # account for whitespace correctly but it _does_ work and is several times\n  # cleaner than anything else I had here.\n  #\n  # This dumps the json payload to a variable that should be passable to the\n  # _psot function.\n  _json_payload=$(\n    cat <<HEREDOC\n{\n  \"certificates\": \"$(tr '\\n' ':' <\"$_cfullchain\" | sed 's/:/\\\\n/g')\",\n  \"key\": \"$(tr '\\n' ':' <\"$_ckey\" | sed 's/:/\\\\n/g')\",\n  \"node\":\"localhost\",\n  \"restart\":true,\n  \"force\":true\n}\nHEREDOC\n  )\n  _debug2 Payload \"$_json_payload\"\n\n  _info \"Push certificates to server\"\n  export HTTPS_INSECURE=1\n  export _H1=\"Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}\"\n  response=$(_post \"$_json_payload\" \"$_target_url\" \"\" POST \"application/json\")\n  response=\"$(echo \"$response\" | _json_decode | _normalizeJson)\"\n  message=$(echo \"$response\" | _egrep_o '\"message\":\"[^\"]*' | cut -d : -f 2 | tr -d '\"')\n  _retval=$?\n  if [ \"${_retval}\" -eq 0 ] && [ -z \"$message\" ]; then\n    _debug3 response \"$response\"\n    _info \"Certificate successfully deployed\"\n    return 0\n  else\n    _err \"Certificate deployment failed: $message\"\n    _debug \"Response\" \"$response\"\n    return 1\n  fi\n\n}\n"
  },
  {
    "path": "deploy/proxmoxve.sh",
    "content": "#!/usr/bin/env sh\n\n# Deploy certificates to a proxmox virtual environment node using the API.\n#\n# Environment variables that can be set are:\n# `DEPLOY_PROXMOXVE_SERVER`: The hostname of the proxmox ve node. Defaults to\n#                            _cdomain.\n# `DEPLOY_PROXMOXVE_SERVER_PORT`: The port number the management interface is on.\n#                                 Defaults to 8006.\n# `DEPLOY_PROXMOXVE_NODE_NAME`: The name of the node we'll be connecting to.\n#                               Defaults to the host portion of the server\n#                               domain name.\n# `DEPLOY_PROXMOXVE_USER`: The user we'll connect as. Defaults to root.\n# `DEPLOY_PROXMOXVE_USER_REALM`: The authentication realm the user authenticates\n#                                with. Defaults to pam.\n# `DEPLOY_PROXMOXVE_API_TOKEN_NAME`: The name of the API token created for the\n#                                    user account. Defaults to acme.\n# `DEPLOY_PROXMOXVE_API_TOKEN_KEY`: The API token. Required.\n\nproxmoxve_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug2 _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # \"Sane\" defaults.\n  _getdeployconf DEPLOY_PROXMOXVE_SERVER\n  if [ -z \"$DEPLOY_PROXMOXVE_SERVER\" ]; then\n    _target_hostname=\"$_cdomain\"\n  else\n    _target_hostname=\"$DEPLOY_PROXMOXVE_SERVER\"\n    _savedeployconf DEPLOY_PROXMOXVE_SERVER \"$DEPLOY_PROXMOXVE_SERVER\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_SERVER \"$_target_hostname\"\n\n  _getdeployconf DEPLOY_PROXMOXVE_SERVER_PORT\n  if [ -z \"$DEPLOY_PROXMOXVE_SERVER_PORT\" ]; then\n    _target_port=\"8006\"\n  else\n    _target_port=\"$DEPLOY_PROXMOXVE_SERVER_PORT\"\n    _savedeployconf DEPLOY_PROXMOXVE_SERVER_PORT \"$DEPLOY_PROXMOXVE_SERVER_PORT\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_SERVER_PORT \"$_target_port\"\n\n  _getdeployconf DEPLOY_PROXMOXVE_NODE_NAME\n  if [ -z \"$DEPLOY_PROXMOXVE_NODE_NAME\" ]; then\n    _node_name=$(echo \"$_target_hostname\" | cut -d. -f1)\n  else\n    _node_name=\"$DEPLOY_PROXMOXVE_NODE_NAME\"\n    _savedeployconf DEPLOY_PROXMOXVE_NODE_NAME \"$DEPLOY_PROXMOXVE_NODE_NAME\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_NODE_NAME \"$_node_name\"\n\n  # Complete URL.\n  _target_url=\"https://${_target_hostname}:${_target_port}/api2/json/nodes/${_node_name}/certificates/custom\"\n  _debug TARGET_URL \"$_target_url\"\n\n  # More \"sane\" defaults.\n  _getdeployconf DEPLOY_PROXMOXVE_USER\n  if [ -z \"$DEPLOY_PROXMOXVE_USER\" ]; then\n    _proxmoxve_user=\"root\"\n  else\n    _proxmoxve_user=\"$DEPLOY_PROXMOXVE_USER\"\n    _savedeployconf DEPLOY_PROXMOXVE_USER \"$DEPLOY_PROXMOXVE_USER\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_USER \"$_proxmoxve_user\"\n\n  _getdeployconf DEPLOY_PROXMOXVE_USER_REALM\n  if [ -z \"$DEPLOY_PROXMOXVE_USER_REALM\" ]; then\n    _proxmoxve_user_realm=\"pam\"\n  else\n    _proxmoxve_user_realm=\"$DEPLOY_PROXMOXVE_USER_REALM\"\n    _savedeployconf DEPLOY_PROXMOXVE_USER_REALM \"$DEPLOY_PROXMOXVE_USER_REALM\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_USER_REALM \"$_proxmoxve_user_realm\"\n\n  _getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME\n  if [ -z \"$DEPLOY_PROXMOXVE_API_TOKEN_NAME\" ]; then\n    _proxmoxve_api_token_name=\"acme\"\n  else\n    _proxmoxve_api_token_name=\"$DEPLOY_PROXMOXVE_API_TOKEN_NAME\"\n    _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME \"$DEPLOY_PROXMOXVE_API_TOKEN_NAME\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_API_TOKEN_NAME \"$_proxmoxve_api_token_name\"\n\n  # This is required.\n  _getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY\n  if [ -z \"$DEPLOY_PROXMOXVE_API_TOKEN_KEY\" ]; then\n    _err \"API key not provided.\"\n    return 1\n  else\n    _proxmoxve_api_token_key=\"$DEPLOY_PROXMOXVE_API_TOKEN_KEY\"\n    _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY \"$DEPLOY_PROXMOXVE_API_TOKEN_KEY\"\n  fi\n  _debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY \"$_proxmoxve_api_token_key\"\n\n  # PVE API Token header value. Used in \"Authorization: PVEAPIToken\".\n  _proxmoxve_header_api_token=\"${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}\"\n  _debug2 \"Auth Header\" \"$_proxmoxve_header_api_token\"\n\n  # Ugly. I hate putting heredocs inside functions because heredocs don't\n  # account for whitespace correctly but it _does_ work and is several times\n  # cleaner than anything else I had here.\n  #\n  # This dumps the json payload to a variable that should be passable to the\n  # _psot function.\n  _json_payload=$(\n    cat <<HEREDOC\n{\n  \"certificates\": \"$(tr '\\n' ':' <\"$_cfullchain\" | sed 's/:/\\\\n/g')\",\n  \"key\": \"$(tr '\\n' ':' <\"$_ckey\" | sed 's/:/\\\\n/g')\",\n  \"node\":\"$_node_name\",\n  \"restart\":\"1\",\n  \"force\":\"1\"\n}\nHEREDOC\n  )\n  _debug2 Payload \"$_json_payload\"\n\n  _info \"Push certificates to server\"\n  export HTTPS_INSECURE=1\n  export _H1=\"Authorization: PVEAPIToken=${_proxmoxve_header_api_token}\"\n  response=$(_post \"$_json_payload\" \"$_target_url\" \"\" POST \"application/json\")\n  response=\"$(echo \"$response\" | _json_decode | _normalizeJson)\"\n  message=$(echo \"$response\" | _egrep_o '\"message\":\"[^\"]*' | cut -d : -f 2 | tr -d '\"')\n  _retval=$?\n  if [ \"${_retval}\" -eq 0 ] && [ -z \"$message\" ]; then\n    _debug3 response \"$response\"\n    _info \"Certificate successfully deployed\"\n    return 0\n  else\n    _err \"Certificate deployment failed: $message\"\n    _debug \"Response\" \"$response\"\n    return 1\n  fi\n\n}\n"
  },
  {
    "path": "deploy/pureftpd.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to pureftpd server.\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\npureftpd_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _err \"deploy cert to pureftpd server, Not implemented yet\"\n  return 1\n\n}\n"
  },
  {
    "path": "deploy/qiniu.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to create certificate to qiniu.com\n#\n# This deployment required following variables\n# export QINIU_AK=\"QINIUACCESSKEY\"\n# export QINIU_SK=\"QINIUSECRETKEY\"\n# export QINIU_CDN_DOMAIN=\"cdn.example.com\"\n# If you have more than one domain, just\n# export QINIU_CDN_DOMAIN=\"cdn1.example.com cdn2.example.com\"\n# Optional: force HTTPS redirect (default: false)\n# export QINIU_FORCE_HTTPS=\"true\"\n\nQINIU_API_BASE=\"https://api.qiniu.com\"\n\nqiniu_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  if [ -z \"$QINIU_AK\" ]; then\n    _err \"QINIU_AK is not defined.\"\n    return 1\n  else\n    _savedomainconf QINIU_AK \"$QINIU_AK\"\n  fi\n\n  if [ -z \"$QINIU_SK\" ]; then\n    _err \"QINIU_SK is not defined.\"\n    return 1\n  else\n    _savedomainconf QINIU_SK \"$QINIU_SK\"\n  fi\n\n  if [ \"$QINIU_CDN_DOMAIN\" ]; then\n    _savedomainconf QINIU_CDN_DOMAIN \"$QINIU_CDN_DOMAIN\"\n  else\n    QINIU_CDN_DOMAIN=\"$_cdomain\"\n  fi\n\n  if [ -z \"$QINIU_FORCE_HTTPS\" ]; then\n    QINIU_FORCE_HTTPS=\"false\"\n  else\n    _savedomainconf QINIU_FORCE_HTTPS \"$QINIU_FORCE_HTTPS\"\n  fi\n\n  ## upload certificate\n  string_fullchain=$(sed 's/$/\\\\n/' \"$_cfullchain\" | tr -d '\\n')\n  string_key=$(sed 's/$/\\\\n/' \"$_ckey\" | tr -d '\\n')\n\n  sslcert_path=\"/sslcert\"\n  sslcerl_body=\"{\\\"name\\\":\\\"$_cdomain\\\",\\\"common_name\\\":\\\"$QINIU_CDN_DOMAIN\\\",\\\"ca\\\":\\\"$string_fullchain\\\",\\\"pri\\\":\\\"$string_key\\\"}\"\n  sslcert_access_token=\"$(_make_access_token \"$sslcert_path\")\"\n  _debug sslcert_access_token \"$sslcert_access_token\"\n  export _H1=\"Authorization: QBox $sslcert_access_token\"\n  sslcert_response=$(_post \"$sslcerl_body\" \"$QINIU_API_BASE$sslcert_path\" 0 \"POST\" \"application/json\" | _dbase64)\n\n  if ! _contains \"$sslcert_response\" \"certID\"; then\n    _err \"Error in creating certificate:\"\n    _err \"$sslcert_response\"\n    return 1\n  fi\n\n  _debug sslcert_response \"$sslcert_response\"\n  _info \"Certificate successfully uploaded, updating domain $_cdomain\"\n\n  ## extract certId\n  _certId=\"$(printf \"%s\" \"$sslcert_response\" | _normalizeJson | _egrep_o \"certID\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2)\"\n  _debug certId \"$_certId\"\n\n  ## update domain ssl config\n  update_body=\"{\\\"certid\\\":$_certId,\\\"forceHttps\\\":$QINIU_FORCE_HTTPS}\"\n  for domain in $QINIU_CDN_DOMAIN; do\n    update_path=\"/domain/$domain/httpsconf\"\n    update_access_token=\"$(_make_access_token \"$update_path\")\"\n    _debug update_access_token \"$update_access_token\"\n    export _H1=\"Authorization: QBox $update_access_token\"\n    update_response=$(_post \"$update_body\" \"$QINIU_API_BASE$update_path\" 0 \"PUT\" \"application/json\" | _dbase64)\n\n    if _contains \"$update_response\" \"error\"; then\n      _err \"Error in updating domain $domain httpsconf:\"\n      _err \"$update_response\"\n      return 1\n    fi\n\n    _debug update_response \"$update_response\"\n    _info \"Domain $domain certificate has been deployed successfully\"\n  done\n\n  return 0\n}\n\n_make_access_token() {\n  _token=\"$(printf \"%s\\n\" \"$1\" | _hmac \"sha1\" \"$(printf \"%s\" \"$QINIU_SK\" | _hex_dump | tr -d \" \")\" | _base64 | tr -- '+/' '-_')\"\n  echo \"$QINIU_AK:$_token\"\n}\n"
  },
  {
    "path": "deploy/routeros.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert to routeros router.\n# Deploy the cert to remote routeros\n#\n# ```sh\n# acme.sh --deploy -d ftp.example.com --deploy-hook routeros\n# ```\n#\n# Before you can deploy the certificate to router os, you need\n# to add the id_rsa.pub key to the routeros and assign a user\n# to that key.\n#\n# The user need to have access to ssh, ftp, read and write.\n#\n# There are no need to enable ftp service for the script to work,\n# as they are transmitted over SCP, however ftp is needed to store\n# the files on the router.\n#\n# Then you need to set the environment variables for the\n# deploy script to work.\n#\n# ```sh\n# export ROUTER_OS_USERNAME=certuser\n# export ROUTER_OS_HOST=router.example.com\n# export ROUTER_OS_PORT=22\n#\n# acme.sh --deploy -d ftp.example.com --deploy-hook routeros\n# ```\n#\n# The deploy script will remove previously deployed certificates,\n# and it does this with an assumption on how RouterOS names imported\n# certificates, adding a \"cer_0\" suffix at the end. This is true for\n# versions 6.32 -> 6.41.3, but it is not guaranteed that it will be\n# true for future versions when upgrading.\n#\n# If the router have other certificates with the same name as the one\n# beeing deployed, then this script will remove those certificates.\n#\n# At the end of the script, the services that use those certificates\n# could be updated. Currently only the www-ssl service is beeing\n# updated, but more services could be added.\n#\n# For instance:\n# ```sh\n# export ROUTER_OS_ADDITIONAL_SERVICES=\"/ip service set api-ssl certificate=$_cdomain.cer_0\"\n# ```\n#\n# One optional thing to do as well is to create a script that updates\n# all the required services and run that script in a single command.\n#\n# To adopt parameters to `scp` and/or `ssh` set the optional\n# `ROUTER_OS_SSH_CMD` and `ROUTER_OS_SCP_CMD` variables accordingly,\n# see ssh(1) and scp(1) for parameters to those commands.\n#\n# Example:\n# ```ssh\n# export ROUTER_OS_SSH_CMD=\"ssh -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts\"\n# export ROUTER_OS_SCP_CMD=\"scp -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts\"\n# ````\n#\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nrouteros_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _err_code=0\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf ROUTER_OS_HOST\n\n  if [ -z \"$ROUTER_OS_HOST\" ]; then\n    _debug \"Using _cdomain as ROUTER_OS_HOST, please set if not correct.\"\n    ROUTER_OS_HOST=\"$_cdomain\"\n  fi\n\n  _getdeployconf ROUTER_OS_USERNAME\n\n  if [ -z \"$ROUTER_OS_USERNAME\" ]; then\n    _err \"Need to set the env variable ROUTER_OS_USERNAME\"\n    return 1\n  fi\n\n  _getdeployconf ROUTER_OS_PORT\n\n  if [ -z \"$ROUTER_OS_PORT\" ]; then\n    _debug \"Using default port 22 as ROUTER_OS_PORT, please set if not correct.\"\n    ROUTER_OS_PORT=22\n  fi\n\n  _getdeployconf ROUTER_OS_SSH_CMD\n\n  if [ -z \"$ROUTER_OS_SSH_CMD\" ]; then\n    _debug \"Use default ssh setup.\"\n    ROUTER_OS_SSH_CMD=\"ssh -p $ROUTER_OS_PORT\"\n  fi\n\n  _getdeployconf ROUTER_OS_SCP_CMD\n\n  if [ -z \"$ROUTER_OS_SCP_CMD\" ]; then\n    _debug \"USe default scp setup.\"\n    ROUTER_OS_SCP_CMD=\"scp -P $ROUTER_OS_PORT\"\n  fi\n\n  _getdeployconf ROUTER_OS_ADDITIONAL_SERVICES\n\n  if [ -z \"$ROUTER_OS_ADDITIONAL_SERVICES\" ]; then\n    _debug \"Not enabling additional services\"\n    ROUTER_OS_ADDITIONAL_SERVICES=\"\"\n  fi\n\n  _savedeployconf ROUTER_OS_HOST \"$ROUTER_OS_HOST\"\n  _savedeployconf ROUTER_OS_USERNAME \"$ROUTER_OS_USERNAME\"\n  _savedeployconf ROUTER_OS_PORT \"$ROUTER_OS_PORT\"\n  _savedeployconf ROUTER_OS_SSH_CMD \"$ROUTER_OS_SSH_CMD\"\n  _savedeployconf ROUTER_OS_SCP_CMD \"$ROUTER_OS_SCP_CMD\"\n  _savedeployconf ROUTER_OS_ADDITIONAL_SERVICES \"$ROUTER_OS_ADDITIONAL_SERVICES\"\n\n  # push key to routeros\n  if ! _scp_certificate \"$_ckey\" \"$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key\"; then\n    return $_err_code\n  fi\n\n  # push certificate chain to routeros\n  if ! _scp_certificate \"$_cfullchain\" \"$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer\"; then\n    return $_err_code\n  fi\n\n  DEPLOY_SCRIPT_CMD=\":do {/system script remove \\\"LECertDeploy-$_cdomain\\\" } on-error={ }; \\\n/system script add name=\\\"LECertDeploy-$_cdomain\\\" owner=$ROUTER_OS_USERNAME \\\ncomment=\\\"generated by routeros deploy script in acme.sh\\\" \\\nsource=\\\"/certificate remove [ find name=$_cdomain.cer_0 ];\\\n\\n/certificate remove [ find name=$_cdomain.cer_1 ];\\\n\\n/certificate remove [ find name=$_cdomain.cer_2 ];\\\n\\ndelay 1;\\\n\\n/certificate import file-name=\\\\\\\"$_cdomain.cer\\\\\\\" passphrase=\\\\\\\"\\\\\\\";\\\n\\n/certificate import file-name=\\\\\\\"$_cdomain.key\\\\\\\" passphrase=\\\\\\\"\\\\\\\";\\\n\\ndelay 1;\\\n\\n:do {/file remove $_cdomain.cer; } on-error={ }\\\n\\n:do {/file remove $_cdomain.key; } on-error={ }\\\n\\ndelay 2;\\\n\\n/ip service set www-ssl certificate=$_cdomain.cer_0;\\\n\\n$ROUTER_OS_ADDITIONAL_SERVICES;\\\n\\n\\\"\n\"\n\n  if ! _ssh_remote_cmd \"$DEPLOY_SCRIPT_CMD\"; then\n    return $_err_code\n  fi\n\n  if ! _ssh_remote_cmd \"/system script run \\\"LECertDeploy-$_cdomain\\\"\"; then\n    return $_err_code\n  fi\n\n  if ! _ssh_remote_cmd \"/system script remove \\\"LECertDeploy-$_cdomain\\\"\"; then\n    return $_err_code\n  fi\n\n  return 0\n}\n\n# inspired by deploy/ssh.sh\n_ssh_remote_cmd() {\n  _cmd=\"$1\"\n  _secure_debug \"Remote commands to execute: $_cmd\"\n  _info \"Submitting sequence of commands to routeros\"\n  # quotations in bash cmd below intended.  Squash travis spellcheck error\n  # shellcheck disable=SC2029\n  $ROUTER_OS_SSH_CMD \"$ROUTER_OS_USERNAME@$ROUTER_OS_HOST\" \"$_cmd\"\n  _err_code=\"$?\"\n\n  if [ \"$_err_code\" != \"0\" ]; then\n    _err \"Error code $_err_code returned from routeros\"\n  fi\n\n  return $_err_code\n}\n\n_scp_certificate() {\n  _src=\"$1\"\n  _dst=\"$2\"\n  _secure_debug \"scp '$_src' to '$_dst'\"\n  _info \"Push key '$_src' to routeros\"\n\n  $ROUTER_OS_SCP_CMD \"$_src\" \"$_dst\"\n  _err_code=\"$?\"\n\n  if [ \"$_err_code\" != \"0\" ]; then\n    _err \"Error code $_err_code returned from scp\"\n  fi\n\n  return $_err_code\n}\n"
  },
  {
    "path": "deploy/ruckus.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert to Ruckus ZoneDirector / Unleashed.\n#\n# Public domain, 2024, Tony Rielly <https://github.com/ms264556>\n#\n# ```sh\n# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus\n# ```\n#\n# Then you need to set the environment variables for the\n# deploy script to work.\n#\n# ```sh\n# export RUCKUS_HOST=myruckus.example.com\n# export RUCKUS_USER=myruckususername\n# export RUCKUS_PASS=myruckuspassword\n#\n# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus\n# ```\n#\n# returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nruckus_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _err_code=0\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf RUCKUS_HOST\n  _getdeployconf RUCKUS_USER\n  _getdeployconf RUCKUS_PASS\n\n  if [ -z \"$RUCKUS_HOST\" ]; then\n    _debug \"Using _cdomain as RUCKUS_HOST, please set if not correct.\"\n    RUCKUS_HOST=\"$_cdomain\"\n  fi\n\n  if [ -z \"$RUCKUS_USER\" ]; then\n    _err \"Need to set the env variable RUCKUS_USER\"\n    return 1\n  fi\n\n  if [ -z \"$RUCKUS_PASS\" ]; then\n    _err \"Need to set the env variable RUCKUS_PASS\"\n    return 1\n  fi\n\n  _savedeployconf RUCKUS_HOST \"$RUCKUS_HOST\"\n  _savedeployconf RUCKUS_USER \"$RUCKUS_USER\"\n  _savedeployconf RUCKUS_PASS \"$RUCKUS_PASS\"\n\n  _debug RUCKUS_HOST \"$RUCKUS_HOST\"\n  _debug RUCKUS_USER \"$RUCKUS_USER\"\n  _secure_debug RUCKUS_PASS \"$RUCKUS_PASS\"\n\n  export ACME_HTTP_NO_REDIRECTS=1\n\n  _info \"Discovering the login URL\"\n  _get \"https://$RUCKUS_HOST\" >/dev/null\n  _login_url=\"$(_response_header 'Location')\"\n  if [ -n \"$_login_url\" ]; then\n    _login_path=$(echo \"$_login_url\" | sed 's|https\\?://[^/]\\+||')\n    if [ -z \"$_login_path\" ]; then\n      # redirect was to a different host\n      _err \"Connection failed: redirected to a different host. Configure Unleashed with a Preferred Master or Management Interface.\"\n      return 1\n    fi\n  fi\n\n  if [ -z \"${_login_url}\" ]; then\n    _err \"Connection failed: couldn't find login page.\"\n    return 1\n  fi\n\n  _base_url=$(dirname \"$_login_url\")\n  _login_page=$(basename \"$_login_url\")\n\n  if [ \"$_login_page\" = \"index.html\" ]; then\n    _err \"Connection temporarily unavailable: Unleashed Rebuilding.\"\n    return 1\n  fi\n\n  if [ \"$_login_page\" = \"wizard.jsp\" ]; then\n    _err \"Connection failed: Setup Wizard not complete.\"\n    return 1\n  fi\n\n  _info \"Login\"\n  _username_encoded=\"$(printf \"%s\" \"$RUCKUS_USER\" | _url_encode)\"\n  _password_encoded=\"$(printf \"%s\" \"$RUCKUS_PASS\" | _url_encode)\"\n  _login_query=\"$(printf \"%s\" \"username=${_username_encoded}&password=${_password_encoded}&ok=Log+In\")\"\n  _post \"$_login_query\" \"$_login_url\" >/dev/null\n\n  _login_code=\"$(_response_code)\"\n  if [ \"$_login_code\" = \"200\" ]; then\n    _err \"Login failed: incorrect credentials.\"\n    return 1\n  fi\n\n  _info \"Collect Session Cookie\"\n  _H1=\"Cookie: $(_response_cookie)\"\n  export _H1\n  _info \"Collect CSRF Token\"\n  _H2=\"X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')\"\n  export _H2\n\n  if _isRSA \"$_ckey\" >/dev/null 2>&1; then\n    _debug \"Using RSA certificate.\"\n  else\n    _info \"Verifying ECC certificate support.\"\n\n    _ul_version=\"$(_get_unleashed_version)\"\n    if [ -z \"$_ul_version\" ]; then\n      _err \"Your controller doesn't support ECC certificates. Please deploy an RSA certificate.\"\n      return 1\n    fi\n\n    _ul_version_major=\"$(echo \"$_ul_version\" | cut -d . -f 1)\"\n    _ul_version_minor=\"$(echo \"$_ul_version\" | cut -d . -f 2)\"\n    if [ \"$_ul_version_major\" -lt \"200\" ]; then\n      _err \"ZoneDirector doesn't support ECC certificates. Please deploy an RSA certificate.\"\n      return 1\n    elif [ \"$_ul_version_minor\" -lt \"13\" ]; then\n      _err \"Unleashed $_ul_version_major.$_ul_version_minor doesn't support ECC certificates. Please deploy an RSA certificate or upgrade to Unleashed 200.13+.\"\n      return 1\n    fi\n\n    _debug \"ECC certificates OK for Unleashed $_ul_version_major.$_ul_version_minor.\"\n  fi\n\n  _info \"Uploading certificate\"\n  _post_upload \"uploadcert\" \"$_cfullchain\"\n\n  _info \"Uploading private key\"\n  _post_upload \"uploadprivatekey\" \"$_ckey\"\n\n  _info \"Replacing certificate\"\n  _replace_cert_ajax='<ajax-request action=\"docmd\" comp=\"system\" updater=\"rid.0.5\" xcmd=\"replace-cert\" checkAbility=\"6\" timeout=\"-1\"><xcmd cmd=\"replace-cert\" cn=\"'$RUCKUS_HOST'\"/></ajax-request>'\n  _post \"$_replace_cert_ajax\" \"$_base_url/_cmdstat.jsp\" >/dev/null\n\n  _info \"Rebooting\"\n  _cert_reboot_ajax='<ajax-request action=\"docmd\" comp=\"worker\" updater=\"rid.0.5\" xcmd=\"cert-reboot\" checkAbility=\"6\"><xcmd cmd=\"cert-reboot\" action=\"undefined\"/></ajax-request>'\n  _post \"$_cert_reboot_ajax\" \"$_base_url/_cmdstat.jsp\" >/dev/null\n\n  return 0\n}\n\n_response_code() {\n  _egrep_o <\"$HTTP_HEADER\" \"^HTTP[^ ]* .*$\" | cut -d \" \" -f 2-100 | tr -d \"\\f\\n\" | _egrep_o \"^[0-9]*\"\n}\n\n_response_header() {\n  grep <\"$HTTP_HEADER\" -i \"^$1:\" | cut -d ':' -f 2- | tr -d \"\\r\\n\\t \"\n}\n\n_response_cookie() {\n  _response_header 'Set-Cookie' | sed 's/;.*//'\n}\n\n_get_unleashed_version() {\n  _post '<ajax-request action=\"getstat\" comp=\"system\"><sysinfo/></ajax-request>' \"$_base_url/_cmdstat.jsp\" | _egrep_o \"version-num=\\\"[^\\\"]*\\\"\" | cut -d '\"' -f 2\n}\n\n_post_upload() {\n  _post_action=\"$1\"\n  _post_file=\"$2\"\n\n  _post_boundary=\"----FormBoundary$(date \"+%s%N\")\"\n\n  _post_data=\"$({\n    printf -- \"--%s\\r\\n\" \"$_post_boundary\"\n    printf -- \"Content-Disposition: form-data; name=\\\"u\\\"; filename=\\\"%s\\\"\\r\\n\" \"$_post_action\"\n    printf -- \"Content-Type: application/octet-stream\\r\\n\\r\\n\"\n    printf -- \"%s\\r\\n\" \"$(cat \"$_post_file\")\"\n\n    printf -- \"--%s\\r\\n\" \"$_post_boundary\"\n    printf -- \"Content-Disposition: form-data; name=\\\"action\\\"\\r\\n\\r\\n\"\n    printf -- \"%s\\r\\n\" \"$_post_action\"\n\n    printf -- \"--%s\\r\\n\" \"$_post_boundary\"\n    printf -- \"Content-Disposition: form-data; name=\\\"callback\\\"\\r\\n\\r\\n\"\n    printf -- \"%s\\r\\n\" \"uploader_$_post_action\"\n\n    printf -- \"--%s--\\r\\n\\r\\n\" \"$_post_boundary\"\n  })\"\n\n  _post \"$_post_data\" \"$_base_url/_upload.jsp?request_type=xhr\" \"\" \"\" \"multipart/form-data; boundary=$_post_boundary\" >/dev/null\n}\n"
  },
  {
    "path": "deploy/ssh.sh",
    "content": "#!/usr/bin/env sh\n\n# Script to deploy certificates to remote server by SSH\n# Note that SSH must be able to login to remote host without a password...\n# SSH Keys must have been exchanged with the remote host.  Validate and\n# test that you can login to USER@SERVER from the host running acme.sh before\n# using this script.\n#\n# The following variables exported from environment will be used.\n# If not set then values previously saved in domain.conf file are used.\n#\n# Only a username is required.  All others are optional.\n#\n# The following examples are for QNAP NAS running QTS 4.2\n# export DEPLOY_SSH_CMD=\"\"  # defaults to \"ssh -T\"\n# export DEPLOY_SSH_USER=\"admin\"  # required\n# export DEPLOY_SSH_SERVER=\"host1 host2:8022 192.168.0.1:9022\"  # defaults to domain name, support multiple servers with optional port\n# export DEPLOY_SSH_KEYFILE=\"/etc/stunnel/stunnel.pem\"\n# export DEPLOY_SSH_CERTFILE=\"/etc/stunnel/stunnel.pem\"\n# export DEPLOY_SSH_CAFILE=\"/etc/stunnel/uca.pem\"\n# export DEPLOY_SSH_FULLCHAIN=\"\"\n# export DEPLOY_SSH_REMOTE_CMD=\"/etc/init.d/stunnel.sh restart\"\n# export DEPLOY_SSH_BACKUP=\"\"  # yes or no, default to yes or previously saved value\n# export DEPLOY_SSH_BACKUP_PATH=\".acme_ssh_deploy\"  # path on remote system. Defaults to .acme_ssh_deploy\n# export DEPLOY_SSH_MULTI_CALL=\"\"  # yes or no, default to no or previously saved value\n# export DEPLOY_SSH_USE_SCP=\"\" yes or no, default to no\n# export DEPLOY_SSH_SCP_CMD=\"\" defaults to \"scp -q\"\n#\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nssh_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n  _deploy_ssh_servers=\"\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # USER is required to login by SSH to remote host.\n  _migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER\n  _getdeployconf DEPLOY_SSH_USER\n  _debug2 DEPLOY_SSH_USER \"$DEPLOY_SSH_USER\"\n  if [ -z \"$DEPLOY_SSH_USER\" ]; then\n    _err \"DEPLOY_SSH_USER not defined.\"\n    return 1\n  fi\n  _savedeployconf DEPLOY_SSH_USER \"$DEPLOY_SSH_USER\"\n\n  # SERVER is optional. If not provided then use _cdomain\n  _migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER\n  _getdeployconf DEPLOY_SSH_SERVER\n  _debug2 DEPLOY_SSH_SERVER \"$DEPLOY_SSH_SERVER\"\n  if [ -z \"$DEPLOY_SSH_SERVER\" ]; then\n    DEPLOY_SSH_SERVER=\"$_cdomain\"\n  fi\n  _savedeployconf DEPLOY_SSH_SERVER \"$DEPLOY_SSH_SERVER\"\n\n  # CMD is optional. If not provided then use ssh\n  _migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD\n  _getdeployconf DEPLOY_SSH_CMD\n  _debug2 DEPLOY_SSH_CMD \"$DEPLOY_SSH_CMD\"\n  if [ -z \"$DEPLOY_SSH_CMD\" ]; then\n    DEPLOY_SSH_CMD=\"ssh -T\"\n  fi\n  _savedeployconf DEPLOY_SSH_CMD \"$DEPLOY_SSH_CMD\"\n\n  # BACKUP is optional. If not provided then default to previously saved value or yes.\n  _migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP\n  _getdeployconf DEPLOY_SSH_BACKUP\n  _debug2 DEPLOY_SSH_BACKUP \"$DEPLOY_SSH_BACKUP\"\n  if [ -z \"$DEPLOY_SSH_BACKUP\" ]; then\n    DEPLOY_SSH_BACKUP=\"yes\"\n  fi\n  _savedeployconf DEPLOY_SSH_BACKUP \"$DEPLOY_SSH_BACKUP\"\n\n  # BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy\n  _migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH\n  _getdeployconf DEPLOY_SSH_BACKUP_PATH\n  _debug2 DEPLOY_SSH_BACKUP_PATH \"$DEPLOY_SSH_BACKUP_PATH\"\n  if [ -z \"$DEPLOY_SSH_BACKUP_PATH\" ]; then\n    DEPLOY_SSH_BACKUP_PATH=\".acme_ssh_deploy\"\n  fi\n  _savedeployconf DEPLOY_SSH_BACKUP_PATH \"$DEPLOY_SSH_BACKUP_PATH\"\n\n  # MULTI_CALL is optional. If not provided then default to previously saved\n  # value (which may be undefined... equivalent to \"no\").\n  _migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL\n  _getdeployconf DEPLOY_SSH_MULTI_CALL\n  _debug2 DEPLOY_SSH_MULTI_CALL \"$DEPLOY_SSH_MULTI_CALL\"\n  if [ -z \"$DEPLOY_SSH_MULTI_CALL\" ]; then\n    DEPLOY_SSH_MULTI_CALL=\"no\"\n  fi\n  _savedeployconf DEPLOY_SSH_MULTI_CALL \"$DEPLOY_SSH_MULTI_CALL\"\n\n  # KEYFILE is optional.\n  # If provided then private key will be copied to provided filename.\n  _migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE\n  _getdeployconf DEPLOY_SSH_KEYFILE\n  _debug2 DEPLOY_SSH_KEYFILE \"$DEPLOY_SSH_KEYFILE\"\n  if [ -n \"$DEPLOY_SSH_KEYFILE\" ]; then\n    _savedeployconf DEPLOY_SSH_KEYFILE \"$DEPLOY_SSH_KEYFILE\"\n  fi\n\n  # CERTFILE is optional.\n  # If provided then certificate will be copied or appended to provided filename.\n  _migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE\n  _getdeployconf DEPLOY_SSH_CERTFILE\n  _debug2 DEPLOY_SSH_CERTFILE \"$DEPLOY_SSH_CERTFILE\"\n  if [ -n \"$DEPLOY_SSH_CERTFILE\" ]; then\n    _savedeployconf DEPLOY_SSH_CERTFILE \"$DEPLOY_SSH_CERTFILE\"\n  fi\n\n  # CAFILE is optional.\n  # If provided then CA intermediate certificate will be copied or appended to provided filename.\n  _migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE\n  _getdeployconf DEPLOY_SSH_CAFILE\n  _debug2 DEPLOY_SSH_CAFILE \"$DEPLOY_SSH_CAFILE\"\n  if [ -n \"$DEPLOY_SSH_CAFILE\" ]; then\n    _savedeployconf DEPLOY_SSH_CAFILE \"$DEPLOY_SSH_CAFILE\"\n  fi\n\n  # FULLCHAIN is optional.\n  # If provided then fullchain certificate will be copied or appended to provided filename.\n  _migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN\n  _getdeployconf DEPLOY_SSH_FULLCHAIN\n  _debug2 DEPLOY_SSH_FULLCHAIN \"$DEPLOY_SSH_FULLCHAIN\"\n  if [ -n \"$DEPLOY_SSH_FULLCHAIN\" ]; then\n    _savedeployconf DEPLOY_SSH_FULLCHAIN \"$DEPLOY_SSH_FULLCHAIN\"\n  fi\n\n  # REMOTE_CMD is optional.\n  # If provided then this command will be executed on remote host.\n  _migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD\n  _getdeployconf DEPLOY_SSH_REMOTE_CMD\n  _debug2 DEPLOY_SSH_REMOTE_CMD \"$DEPLOY_SSH_REMOTE_CMD\"\n  if [ -n \"$DEPLOY_SSH_REMOTE_CMD\" ]; then\n    _savedeployconf DEPLOY_SSH_REMOTE_CMD \"$DEPLOY_SSH_REMOTE_CMD\"\n  fi\n\n  # USE_SCP is optional. If not provided then default to previously saved\n  # value (which may be undefined... equivalent to \"no\").\n  _getdeployconf DEPLOY_SSH_USE_SCP\n  _debug2 DEPLOY_SSH_USE_SCP \"$DEPLOY_SSH_USE_SCP\"\n  if [ -z \"$DEPLOY_SSH_USE_SCP\" ]; then\n    DEPLOY_SSH_USE_SCP=\"no\"\n  fi\n  _savedeployconf DEPLOY_SSH_USE_SCP \"$DEPLOY_SSH_USE_SCP\"\n\n  # SCP_CMD is optional. If not provided then use scp\n  _getdeployconf DEPLOY_SSH_SCP_CMD\n  _debug2 DEPLOY_SSH_SCP_CMD \"$DEPLOY_SSH_SCP_CMD\"\n  if [ -z \"$DEPLOY_SSH_SCP_CMD\" ]; then\n    DEPLOY_SSH_SCP_CMD=\"scp -q\"\n  fi\n  _savedeployconf DEPLOY_SSH_SCP_CMD \"$DEPLOY_SSH_SCP_CMD\"\n\n  if [ \"$DEPLOY_SSH_USE_SCP\" = \"yes\" ]; then\n    DEPLOY_SSH_MULTI_CALL=\"yes\"\n    _info \"Using scp as alternate method for copying files. Multicall Mode is implicit\"\n  elif [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n    _info \"Using MULTI_CALL mode... Required commands sent in multiple calls to remote host\"\n  else\n    _info \"Required commands batched and sent in single call to remote host\"\n  fi\n\n  _deploy_ssh_servers=\"$DEPLOY_SSH_SERVER\"\n  for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do\n    _ssh_deploy\n  done\n}\n\n_ssh_deploy() {\n  _err_code=0\n  _cmdstr=\"\"\n  _backupprefix=\"\"\n  _backupdir=\"\"\n  _local_cert_file=\"\"\n  _local_ca_file=\"\"\n  _local_full_file=\"\"\n\n  case $DEPLOY_SSH_SERVER in\n  *:*)\n    _host=${DEPLOY_SSH_SERVER%:*}\n    _port=${DEPLOY_SSH_SERVER##*:}\n    ;;\n  *)\n    _host=$DEPLOY_SSH_SERVER\n    _port=\n    ;;\n  esac\n\n  _info \"Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port\"\n\n  if [ \"$DEPLOY_SSH_BACKUP\" = \"yes\" ]; then\n    _backupprefix=\"$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup\"\n    _backupdir=\"$_backupprefix-$(_utc_date | tr ' ' '-')\"\n    # run cleanup on the backup directory, erase all older\n    # than 180 days (15552000 seconds).\n    _cmdstr=\"{ now=\\\"\\$(date -u +%s)\\\"; for fn in $_backupprefix*; \\\ndo if [ -d \\\"\\$fn\\\" ] && [ \\\"\\$(expr \\$now - \\$(date -ur \\$fn +%s) )\\\" -ge \\\"15552000\\\" ]; \\\nthen rm -rf \\\"\\$fn\\\"; echo \\\"Backup \\$fn deleted as older than 180 days\\\"; fi; done; }; $_cmdstr\"\n    # Alternate version of above... _cmdstr=\"find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr\"\n    # Create our backup directory for overwritten cert files.\n    _cmdstr=\"mkdir -p $_backupdir; $_cmdstr\"\n    _info \"Backup of old certificate files will be placed in remote directory $_backupdir\"\n    _info \"Backup directories erased after 180 days.\"\n    if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n      if ! _ssh_remote_cmd \"$_cmdstr\"; then\n        return $_err_code\n      fi\n      _cmdstr=\"\"\n    fi\n  fi\n\n  if [ -n \"$DEPLOY_SSH_KEYFILE\" ]; then\n    if [ \"$DEPLOY_SSH_BACKUP\" = \"yes\" ]; then\n      # backup file we are about to overwrite.\n      _cmdstr=\"$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n\n    # copy new key into file.\n    if [ \"$DEPLOY_SSH_USE_SCP\" = \"yes\" ]; then\n      # scp the file\n      if ! _scp_remote_cmd \"$_ckey\" \"$DEPLOY_SSH_KEYFILE\"; then\n        return $_err_code\n      fi\n    else\n      # If file doesn't exist, create it and change its permissions.\n      _cmdstr=\"$_cmdstr test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE;\"\n      # ssh echo to the file\n      _cmdstr=\"$_cmdstr echo \\\"$(cat \"$_ckey\")\\\" > $DEPLOY_SSH_KEYFILE;\"\n      _info \"will copy private key to remote file $DEPLOY_SSH_KEYFILE\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n  fi\n\n  if [ -n \"$DEPLOY_SSH_CERTFILE\" ]; then\n    _pipe=\">\"\n    if [ \"$DEPLOY_SSH_CERTFILE\" = \"$DEPLOY_SSH_KEYFILE\" ]; then\n      # if filename is same as previous file then append.\n      _pipe=\">>\"\n    elif [ \"$DEPLOY_SSH_BACKUP\" = \"yes\" ]; then\n      # backup file we are about to overwrite.\n      _cmdstr=\"$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n\n    # copy new certificate into file.\n    if [ \"$DEPLOY_SSH_USE_SCP\" = \"yes\" ]; then\n      # scp the file\n      _local_cert_file=$(_mktemp)\n      if [ \"$DEPLOY_SSH_CERTFILE\" = \"$DEPLOY_SSH_KEYFILE\" ]; then\n        cat \"$_ckey\" >>\"$_local_cert_file\"\n      fi\n      cat \"$_ccert\" >>\"$_local_cert_file\"\n      if ! _scp_remote_cmd \"$_local_cert_file\" \"$DEPLOY_SSH_CERTFILE\"; then\n        return $_err_code\n      fi\n    else\n      # ssh echo to the file\n      _cmdstr=\"$_cmdstr echo \\\"$(cat \"$_ccert\")\\\" $_pipe $DEPLOY_SSH_CERTFILE;\"\n      _info \"will copy certificate to remote file $DEPLOY_SSH_CERTFILE\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n  fi\n\n  if [ -n \"$DEPLOY_SSH_CAFILE\" ]; then\n    _pipe=\">\"\n    if [ \"$DEPLOY_SSH_CAFILE\" = \"$DEPLOY_SSH_KEYFILE\" ] ||\n      [ \"$DEPLOY_SSH_CAFILE\" = \"$DEPLOY_SSH_CERTFILE\" ]; then\n      # if filename is same as previous file then append.\n      _pipe=\">>\"\n    elif [ \"$DEPLOY_SSH_BACKUP\" = \"yes\" ]; then\n      # backup file we are about to overwrite.\n      _cmdstr=\"$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n\n    # copy new certificate into file.\n    if [ \"$DEPLOY_SSH_USE_SCP\" = \"yes\" ]; then\n      # scp the file\n      _local_ca_file=$(_mktemp)\n      if [ \"$DEPLOY_SSH_CAFILE\" = \"$DEPLOY_SSH_KEYFILE\" ]; then\n        cat \"$_ckey\" >>\"$_local_ca_file\"\n      fi\n      if [ \"$DEPLOY_SSH_CAFILE\" = \"$DEPLOY_SSH_CERTFILE\" ]; then\n        cat \"$_ccert\" >>\"$_local_ca_file\"\n      fi\n      cat \"$_cca\" >>\"$_local_ca_file\"\n      if ! _scp_remote_cmd \"$_local_ca_file\" \"$DEPLOY_SSH_CAFILE\"; then\n        return $_err_code\n      fi\n    else\n      # ssh echo to the file\n      _cmdstr=\"$_cmdstr echo \\\"$(cat \"$_cca\")\\\" $_pipe $DEPLOY_SSH_CAFILE;\"\n      _info \"will copy CA file to remote file $DEPLOY_SSH_CAFILE\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n  fi\n\n  if [ -n \"$DEPLOY_SSH_FULLCHAIN\" ]; then\n    _pipe=\">\"\n    if [ \"$DEPLOY_SSH_FULLCHAIN\" = \"$DEPLOY_SSH_KEYFILE\" ] ||\n      [ \"$DEPLOY_SSH_FULLCHAIN\" = \"$DEPLOY_SSH_CERTFILE\" ] ||\n      [ \"$DEPLOY_SSH_FULLCHAIN\" = \"$DEPLOY_SSH_CAFILE\" ]; then\n      # if filename is same as previous file then append.\n      _pipe=\">>\"\n    elif [ \"$DEPLOY_SSH_BACKUP\" = \"yes\" ]; then\n      # backup file we are about to overwrite.\n      _cmdstr=\"$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;\"\n      if [ \"$DEPLOY_SSH_FULLCHAIN\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n\n    # copy new certificate into file.\n    if [ \"$DEPLOY_SSH_USE_SCP\" = \"yes\" ]; then\n      # scp the file\n      _local_full_file=$(_mktemp)\n      if [ \"$DEPLOY_SSH_FULLCHAIN\" = \"$DEPLOY_SSH_KEYFILE\" ]; then\n        cat \"$_ckey\" >>\"$_local_full_file\"\n      fi\n      if [ \"$DEPLOY_SSH_FULLCHAIN\" = \"$DEPLOY_SSH_CERTFILE\" ]; then\n        cat \"$_ccert\" >>\"$_local_full_file\"\n      fi\n      if [ \"$DEPLOY_SSH_FULLCHAIN\" = \"$DEPLOY_SSH_CAFILE\" ]; then\n        cat \"$_cca\" >>\"$_local_full_file\"\n      fi\n      cat \"$_cfullchain\" >>\"$_local_full_file\"\n      if ! _scp_remote_cmd \"$_local_full_file\" \"$DEPLOY_SSH_FULLCHAIN\"; then\n        return $_err_code\n      fi\n    else\n      # ssh echo to the file\n      _cmdstr=\"$_cmdstr echo \\\"$(cat \"$_cfullchain\")\\\" $_pipe $DEPLOY_SSH_FULLCHAIN;\"\n      _info \"will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN\"\n      if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n        if ! _ssh_remote_cmd \"$_cmdstr\"; then\n          return $_err_code\n        fi\n        _cmdstr=\"\"\n      fi\n    fi\n  fi\n\n  # cleanup local files if any\n  if [ -f \"$_local_cert_file\" ]; then\n    rm -f \"$_local_cert_file\"\n  fi\n  if [ -f \"$_local_ca_file\" ]; then\n    rm -f \"$_local_ca_file\"\n  fi\n  if [ -f \"$_local_full_file\" ]; then\n    rm -f \"$_local_full_file\"\n  fi\n\n  if [ -n \"$DEPLOY_SSH_REMOTE_CMD\" ]; then\n    _cmdstr=\"$_cmdstr $DEPLOY_SSH_REMOTE_CMD;\"\n    _info \"Will execute remote command $DEPLOY_SSH_REMOTE_CMD\"\n    if [ \"$DEPLOY_SSH_MULTI_CALL\" = \"yes\" ]; then\n      if ! _ssh_remote_cmd \"$_cmdstr\"; then\n        return $_err_code\n      fi\n      _cmdstr=\"\"\n    fi\n  fi\n\n  # if commands not all sent in multiple calls then all commands sent in a single SSH call now...\n  if [ -n \"$_cmdstr\" ]; then\n    if ! _ssh_remote_cmd \"$_cmdstr\"; then\n      return $_err_code\n    fi\n  fi\n  # cleanup in case all is ok\n  return 0\n}\n\n#cmd\n_ssh_remote_cmd() {\n  _cmd=\"$1\"\n\n  _ssh_cmd=\"$DEPLOY_SSH_CMD\"\n  if [ -n \"$_port\" ]; then\n    _ssh_cmd=\"$_ssh_cmd -p $_port\"\n  fi\n\n  _secure_debug \"Remote commands to execute: $_cmd\"\n  _info \"Submitting sequence of commands to remote server by $_ssh_cmd\"\n\n  # quotations in bash cmd below intended.  Squash travis spellcheck error\n  # shellcheck disable=SC2029\n  $_ssh_cmd \"$DEPLOY_SSH_USER@$_host\" sh -c \"'$_cmd'\"\n  _err_code=\"$?\"\n\n  if [ \"$_err_code\" != \"0\" ]; then\n    _err \"Error code $_err_code returned from ssh\"\n  fi\n\n  return $_err_code\n}\n\n# cmd scp\n_scp_remote_cmd() {\n  _src=$1\n  _dest=$2\n\n  _scp_cmd=\"$DEPLOY_SSH_SCP_CMD\"\n  if [ -n \"$_port\" ]; then\n    _scp_cmd=\"$_scp_cmd -P $_port\"\n  fi\n\n  _secure_debug \"Remote copy source $_src to destination $_dest\"\n  _info \"Submitting secure copy by $_scp_cmd\"\n\n  $_scp_cmd \"$_src\" \"$DEPLOY_SSH_USER\"@\"$_host\":\"$_dest\"\n  _err_code=\"$?\"\n\n  if [ \"$_err_code\" != \"0\" ]; then\n    _err \"Error code $_err_code returned from scp\"\n  fi\n\n  return $_err_code\n}\n"
  },
  {
    "path": "deploy/strongswan.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a sample custom api script.\n#This file name is \"myapi.sh\"\n#So, here must be a method   myapi_deploy()\n#Which will be called by acme.sh to deploy the cert\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nstrongswan_deploy() {\n  _cdomain=\"${1}\"\n  _ckey=\"${2}\"\n  _ccert=\"${3}\"\n  _cca=\"${4}\"\n  _cfullchain=\"${5}\"\n  _info \"Using strongswan\"\n  if _exists ipsec; then\n    _ipsec=ipsec\n  elif _exists strongswan; then\n    _ipsec=strongswan\n  fi\n  if _exists swanctl; then\n    _swanctl=swanctl\n  fi\n  # For legacy stroke mode\n  if [ -n \"${_ipsec}\" ]; then\n    _info \"${_ipsec} command detected\"\n    _confdir=$(${_ipsec} --confdir)\n    if [ -z \"${_confdir}\" ]; then\n      _err \"no strongswan --confdir is detected\"\n      return 1\n    fi\n    _info _confdir \"${_confdir}\"\n    __deploy_cert \"stroke\" \"${_confdir}\" \"$@\"\n    ${_ipsec} reload\n  fi\n  # For modern vici mode\n  if [ -n \"${_swanctl}\" ]; then\n    _info \"${_swanctl} command detected\"\n    for _dir in /usr/local/etc/swanctl /etc/swanctl /etc/strongswan/swanctl; do\n      if [ -d ${_dir} ]; then\n        _confdir=${_dir}\n        _info _confdir \"${_confdir}\"\n        break\n      fi\n    done\n    if [ -z \"${_confdir}\" ]; then\n      _err \"no swanctl config dir is found\"\n      return 1\n    fi\n    __deploy_cert \"vici\" \"${_confdir}\" \"$@\"\n    ${_swanctl} --load-creds\n  fi\n  if [ -z \"${_swanctl}\" ] && [ -z \"${_ipsec}\" ]; then\n    _err \"no strongswan or ipsec command is detected\"\n    _err \"no swanctl is detected\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n__deploy_cert() {\n  _swan_mode=\"${1}\"\n  _confdir=\"${2}\"\n  _cdomain=\"${3}\"\n  _ckey=\"${4}\"\n  _ccert=\"${5}\"\n  _cca=\"${6}\"\n  _cfullchain=\"${7}\"\n  _debug _cdomain \"${_cdomain}\"\n  _debug _ckey \"${_ckey}\"\n  _debug _ccert \"${_ccert}\"\n  _debug _cca \"${_cca}\"\n  _debug _cfullchain \"${_cfullchain}\"\n  _debug _swan_mode \"${_swan_mode}\"\n  _debug _confdir \"${_confdir}\"\n  if [ \"${_swan_mode}\" = \"vici\" ]; then\n    _dir_private=\"private\"\n    _dir_cert=\"x509\"\n    _dir_ca=\"x509ca\"\n  elif [ \"${_swan_mode}\" = \"stroke\" ]; then\n    _dir_private=\"ipsec.d/private\"\n    _dir_cert=\"ipsec.d/certs\"\n    _dir_ca=\"ipsec.d/cacerts\"\n  else\n    _err \"unknown StrongSwan mode ${_swan_mode}\"\n    return 1\n  fi\n  cat \"${_ckey}\" >\"${_confdir}/${_dir_private}/$(basename \"${_ckey}\")\"\n  cat \"${_ccert}\" >\"${_confdir}/${_dir_cert}/$(basename \"${_ccert}\")\"\n  cat \"${_cca}\" >\"${_confdir}/${_dir_ca}/$(basename \"${_cca}\")\"\n  if [ \"${_swan_mode}\" = \"stroke\" ]; then\n    cat \"${_cfullchain}\" >\"${_confdir}/${_dir_ca}/$(basename \"${_cfullchain}\")\"\n  fi\n}\n"
  },
  {
    "path": "deploy/synology_dsm.sh",
    "content": "#!/bin/bash\n\n################################################################################\n# ACME.sh 3rd party deploy plugin for Synology DSM\n################################################################################\n# Authors: Brian Hartvigsen (creator), https://github.com/tresni\n#          Martin Arndt (contributor), https://troublezone.net/\n# Updated: 2023-07-03\n# Issues:  https://github.com/acmesh-official/acme.sh/issues/2727\n################################################################################\n# Usage (shown values are the examples):\n# 1. Set required environment variables:\n# - use automatically created temp admin user to authenticate\n#   export SYNO_USE_TEMP_ADMIN=1\n# - or provide your own admin user credential to authenticate\n#   1. export SYNO_USERNAME=\"adminUser\"\n#   2. export SYNO_PASSWORD=\"adminPassword\"\n# 2. Set optional environment variables\n# - common optional variables\n#   - export SYNO_SCHEME=\"http\"         - defaults to \"http\"\n#   - export SYNO_HOSTNAME=\"localhost\"  - defaults to \"localhost\"\n#   - export SYNO_PORT=\"5000\"           - defaults to \"5000\"\n#   - export SYNO_CREATE=1 - to allow creating the cert if it doesn't exist\n#   - export SYNO_CERTIFICATE=\"\" - to replace a specific cert by its\n#                                    description\n# - temp admin optional variables\n#   - export SYNO_LOCAL_HOSTNAME=1   - if set to 1, force to treat hostname is\n#                                      targeting current local machine (since\n#                                      this method only locally supported)\n# - exsiting admin 2FA-OTP optional variables\n#   - export SYNO_OTP_CODE=\"XXXXXX\" - if set, script won't require to\n#                                     interactive input the OTP code\n#   - export SYNO_DEVICE_NAME=\"CertRenewal\" - if set, script won't require to\n#                                             interactive input the device name\n#   - export SYNO_DEVICE_ID=\"\"      - (deprecated, auth with OTP code instead)\n#                                     required for omitting 2FA-OTP\n# 3. Run command:\n# acme.sh --deploy --deploy-hook synology_dsm -d example.com\n################################################################################\n# Dependencies:\n# - curl\n# - synouser & synogroup & synosetkeyvalue (Required for SYNO_USE_TEMP_ADMIN=1)\n################################################################################\n# Return value:\n# 0 means success, otherwise error.\n################################################################################\n\n########## Public functions ####################################################\n#domain keyfile certfile cafile fullchain\nsynology_dsm_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n\n  _debug _cdomain \"$_cdomain\"\n\n  # Get username and password, but don't save until we authenticated successfully\n  _migratedeployconf SYNO_Username SYNO_USERNAME\n  _migratedeployconf SYNO_Password SYNO_PASSWORD\n  _migratedeployconf SYNO_Device_ID SYNO_DEVICE_ID\n  _migratedeployconf SYNO_Device_Name SYNO_DEVICE_NAME\n  _getdeployconf SYNO_USERNAME\n  _getdeployconf SYNO_PASSWORD\n  _getdeployconf SYNO_DEVICE_ID\n  _getdeployconf SYNO_DEVICE_NAME\n\n  # Prepare to use temp admin if SYNO_USE_TEMP_ADMIN is set\n  _getdeployconf SYNO_USE_TEMP_ADMIN\n  _check2cleardeployconfexp SYNO_USE_TEMP_ADMIN\n  _debug2 SYNO_USE_TEMP_ADMIN \"$SYNO_USE_TEMP_ADMIN\"\n\n  if [ -n \"$SYNO_USE_TEMP_ADMIN\" ]; then\n    if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then\n      _err \"Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead.\"\n      _err \"Notice: temp admin user authorization method only supports local deployment on DSM.\"\n      return 1\n    fi\n    if synouser --help 2>&1 | grep -q 'Permission denied'; then\n      _err \"For creating temp admin user, the deploy script must be run as root.\"\n      return 1\n    fi\n\n    [ -n \"$SYNO_USERNAME\" ] || _savedeployconf SYNO_USERNAME \"\"\n    [ -n \"$SYNO_PASSWORD\" ] || _savedeployconf SYNO_PASSWORD \"\"\n\n    _debug \"Setting temp admin user credential...\"\n    SYNO_USERNAME=sc-acmesh-tmp\n    SYNO_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)\n    # Set 2FA-OTP settings to empty consider they won't be needed.\n    SYNO_DEVICE_ID=\n    SYNO_DEVICE_NAME=\n    SYNO_OTP_CODE=\n  else\n    _debug2 SYNO_USERNAME \"$SYNO_USERNAME\"\n    _secure_debug2 SYNO_PASSWORD \"$SYNO_PASSWORD\"\n    _debug2 SYNO_DEVICE_NAME \"$SYNO_DEVICE_NAME\"\n    _secure_debug2 SYNO_DEVICE_ID \"$SYNO_DEVICE_ID\"\n  fi\n\n  if [ -z \"$SYNO_USERNAME\" ] || [ -z \"$SYNO_PASSWORD\" ]; then\n    _err \"You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_USERNAME and SYNO_PASSWORD.\"\n    return 1\n  fi\n\n  # Optional scheme, hostname and port for Synology DSM\n  _migratedeployconf SYNO_Scheme SYNO_SCHEME\n  _migratedeployconf SYNO_Hostname SYNO_HOSTNAME\n  _migratedeployconf SYNO_Port SYNO_PORT\n  _getdeployconf SYNO_SCHEME\n  _getdeployconf SYNO_HOSTNAME\n  _getdeployconf SYNO_PORT\n\n  # Default values for scheme, hostname and port\n  # Defaulting to localhost and http, because it's localhost…\n  [ -n \"$SYNO_SCHEME\" ] || SYNO_SCHEME=http\n  [ -n \"$SYNO_HOSTNAME\" ] || SYNO_HOSTNAME=localhost\n  [ -n \"$SYNO_PORT\" ] || SYNO_PORT=5000\n  _savedeployconf SYNO_SCHEME \"$SYNO_SCHEME\"\n  _savedeployconf SYNO_HOSTNAME \"$SYNO_HOSTNAME\"\n  _savedeployconf SYNO_PORT \"$SYNO_PORT\"\n  _debug2 SYNO_SCHEME \"$SYNO_SCHEME\"\n  _debug2 SYNO_HOSTNAME \"$SYNO_HOSTNAME\"\n  _debug2 SYNO_PORT \"$SYNO_PORT\"\n\n  # Get the certificate description, but don't save it until we verify it's real\n  _migratedeployconf SYNO_Certificate SYNO_CERTIFICATE \"base64\"\n  _getdeployconf SYNO_CERTIFICATE\n  _check2cleardeployconfexp SYNO_CERTIFICATE\n  _debug SYNO_CERTIFICATE \"${SYNO_CERTIFICATE:-}\"\n\n  # shellcheck disable=SC1003 # We are not trying to escape a single quote\n  if printf \"%s\" \"$SYNO_CERTIFICATE\" | grep '\\\\'; then\n    _err \"Do not use a backslash (\\) in your certificate description\"\n    return 1\n  fi\n\n  _debug \"Getting API version...\"\n  _base_url=\"$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT\"\n  _debug _base_url \"$_base_url\"\n  response=$(_get \"$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth\")\n  api_path=$(echo \"$response\" | grep \"SYNO.API.Auth\" | sed -n 's/.*\"path\" *: *\"\\([^\"]*\\)\".*/\\1/p')\n  api_version=$(echo \"$response\" | grep \"SYNO.API.Auth\" | sed -n 's/.*\"maxVersion\" *: *\\([0-9]*\\).*/\\1/p')\n  _debug3 response \"$response\"\n  _debug3 api_path \"$api_path\"\n  _debug3 api_version \"$api_version\"\n\n  # Login, get the session ID and SynoToken from JSON\n  _info \"Logging into $SYNO_HOSTNAME:$SYNO_PORT...\"\n  encoded_username=\"$(printf \"%s\" \"$SYNO_USERNAME\" | _url_encode)\"\n  encoded_password=\"$(printf \"%s\" \"$SYNO_PASSWORD\" | _url_encode)\"\n\n  # ## START ## - DEPRECATED, for backward compatibility\n  _getdeployconf SYNO_TOTP_SECRET\n\n  if [ -n \"$SYNO_TOTP_SECRET\" ]; then\n    _info \"WARNING: Usage of SYNO_TOTP_SECRET is deprecated!\"\n    _info \"         See synology_dsm.sh script or ACME.sh Wiki page for details:\"\n    _info \"         https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide\"\n    if ! _exists oathtool; then\n      _err \"oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET\"\n      return 1\n    fi\n    DEPRECATED_otp_code=\"$(oathtool --base32 --totp \"$SYNO_TOTP_SECRET\" 2>/dev/null)\"\n\n    if [ -z \"$SYNO_DEVICE_ID\" ]; then\n      _getdeployconf SYNO_DID\n      [ -n \"$SYNO_DID\" ] || SYNO_DEVICE_ID=\"$SYNO_DID\"\n    fi\n    if [ -n \"$SYNO_DEVICE_ID\" ]; then\n      _H1=\"Cookie: did=$SYNO_DEVICE_ID\"\n      export _H1\n      _debug3 H1 \"${_H1}\"\n    fi\n\n    response=$(_post \"method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DEVICE_ID\" \"$_base_url/webapi/$api_path?enable_syno_token=yes\")\n    _debug3 response \"$response\"\n  # ## END ## - DEPRECATED, for backward compatibility\n  # If SYNO_DEVICE_ID or SYNO_OTP_CODE is set, we treat current account enabled 2FA-OTP.\n  # Notice that if SYNO_USE_TEMP_ADMIN=1, both variables will be unset\n  else\n    if [ -n \"$SYNO_DEVICE_ID\" ] || [ -n \"$SYNO_OTP_CODE\" ]; then\n      response='{\"error\":{\"code\":403}}'\n    # Assume the current account disabled 2FA-OTP, try to log in right away.\n    else\n      if [ -n \"$SYNO_USE_TEMP_ADMIN\" ]; then\n        _getdeployconf SYNO_LOCAL_HOSTNAME\n        _debug SYNO_LOCAL_HOSTNAME \"${SYNO_LOCAL_HOSTNAME:-}\"\n        if [ \"$SYNO_HOSTNAME\" != \"localhost\" ] && [ \"$SYNO_HOSTNAME\" != \"127.0.0.1\" ]; then\n          if [ \"$SYNO_LOCAL_HOSTNAME\" != \"1\" ]; then\n            _err \"SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun.\"\n            return 1\n          fi\n        fi\n        _debug \"Creating temp admin user in Synology DSM...\"\n        if synogroup --help | grep -q '\\-\\-memberadd '; then\n          _temp_admin_create \"$SYNO_USERNAME\" \"$SYNO_PASSWORD\"\n          synogroup --memberadd administrators \"$SYNO_USERNAME\" >/dev/null\n        elif synogroup --help | grep -q '\\-\\-member '; then\n          # For supporting DSM 6.x which only has `--member` parameter.\n          cur_admins=$(synogroup --get administrators | awk -F '[][]' '/Group Members/,0{if(NF>1)printf \"%s \", $2}')\n          if [ -n \"$cur_admins\" ]; then\n            _temp_admin_create \"$SYNO_USERNAME\" \"$SYNO_PASSWORD\"\n            _secure_debug3 admin_users \"$cur_admins$SYNO_USERNAME\"\n            # shellcheck disable=SC2086\n            synogroup --member administrators $cur_admins $SYNO_USERNAME >/dev/null\n          else\n            _err \"The tool synogroup may be broken, please set SYNO_USERNAME and SYNO_PASSWORD instead.\"\n            return 1\n          fi\n        else\n          _err \"Unsupported synogroup tool detected, please set SYNO_USERNAME and SYNO_PASSWORD instead.\"\n          return 1\n        fi\n        # havig a workaround to temporary disable enforce 2FA-OTP, will restore\n        # it soon (after a single request), though if any accident occurs like\n        # unexpected interruption, this setting can be easily reverted manually.\n        otp_enforce_option=$(synogetkeyvalue /etc/synoinfo.conf otp_enforce_option)\n        if [ -n \"$otp_enforce_option\" ] && [ \"${otp_enforce_option:-\"none\"}\" != \"none\" ]; then\n          synosetkeyvalue /etc/synoinfo.conf otp_enforce_option none\n          _info \"Enforcing 2FA-OTP has been disabled to complete temp admin authentication.\"\n          _info \"Notice: it will be restored soon, if not, you can restore it manually via Control Panel.\"\n          _info \"previous_otp_enforce_option\" \"$otp_enforce_option\"\n        else\n          otp_enforce_option=\"\"\n        fi\n      fi\n      response=$(_get \"$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes\")\n      if [ -n \"$SYNO_USE_TEMP_ADMIN\" ] && [ -n \"$otp_enforce_option\" ]; then\n        synosetkeyvalue /etc/synoinfo.conf otp_enforce_option \"$otp_enforce_option\"\n        _info \"Restored previous enforce 2FA-OTP option.\"\n      fi\n      _debug3 response \"$response\"\n    fi\n  fi\n\n  error_code=$(echo \"$response\" | grep '\"error\":' | grep -o '\"code\":[0-9]*' | grep -o '[0-9]*')\n  _debug2 error_code \"$error_code\"\n  # Account has 2FA-OTP enabled, since error 403 reported.\n  # https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf\n  if [ \"$error_code\" == \"403\" ]; then\n    if [ -z \"$SYNO_DEVICE_NAME\" ]; then\n      printf \"Enter device name or leave empty for default (CertRenewal): \"\n      read -r SYNO_DEVICE_NAME\n      [ -n \"$SYNO_DEVICE_NAME\" ] || SYNO_DEVICE_NAME=\"CertRenewal\"\n    fi\n\n    if [ -n \"$SYNO_DEVICE_ID\" ]; then\n      # Omit OTP code with SYNO_DEVICE_ID.\n      response=$(_get \"$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_DEVICE_NAME&device_id=$SYNO_DEVICE_ID\")\n      _secure_debug3 response \"$response\"\n    else\n      # Require the OTP code if still unset.\n      if [ -z \"$SYNO_OTP_CODE\" ]; then\n        printf \"Enter OTP code for user '%s': \" \"$SYNO_USERNAME\"\n        read -r SYNO_OTP_CODE\n      fi\n      _secure_debug SYNO_OTP_CODE \"${SYNO_OTP_CODE:-}\"\n\n      if [ -z \"$SYNO_OTP_CODE\" ]; then\n        response='{\"error\":{\"code\":404}}'\n      else\n        response=$(_get \"$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_DEVICE_NAME&otp_code=$SYNO_OTP_CODE\")\n        _secure_debug3 response \"$response\"\n\n        id_property='device_id'\n        [ \"${api_version}\" -gt '6' ] || id_property='did'\n        SYNO_DEVICE_ID=$(echo \"$response\" | grep \"$id_property\" | sed -n 's/.*\"'$id_property'\" *: *\"\\([^\"]*\\).*/\\1/p')\n        _secure_debug2 SYNO_DEVICE_ID \"$SYNO_DEVICE_ID\"\n      fi\n    fi\n    error_code=$(echo \"$response\" | grep '\"error\":' | grep -o '\"code\":[0-9]*' | grep -o '[0-9]*')\n    _debug2 error_code \"$error_code\"\n  fi\n\n  if [ -n \"$error_code\" ]; then\n    if [ \"$error_code\" == \"403\" ] && [ -n \"$SYNO_DEVICE_ID\" ]; then\n      _cleardeployconf SYNO_DEVICE_ID\n      _err \"Failed to authenticate with SYNO_DEVICE_ID (may expired or invalid), please try again in a new terminal window.\"\n    elif [ \"$error_code\" == \"404\" ]; then\n      _err \"Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window.\"\n    elif [ \"$error_code\" == \"406\" ]; then\n      if [ -n \"$SYNO_USE_TEMP_ADMIN\" ]; then\n        _err \"Failed with unexcepted error, please report this by providing full log with '--debug 3'.\"\n      else\n        _err \"Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue.\"\n      fi\n    elif [ \"$error_code\" == \"400\" ]; then\n      _err \"Failed to authenticate, no such account or incorrect password.\"\n    elif [ \"$error_code\" == \"401\" ]; then\n      _err \"Failed to authenticate with a non-existent account.\"\n    elif [ \"$error_code\" == \"408\" ] || [ \"$error_code\" == \"409\" ] || [ \"$error_code\" == \"410\" ]; then\n      _err \"Failed to authenticate, the account password has expired or must be changed.\"\n    else\n      _err \"Failed to authenticate with error: $error_code.\"\n    fi\n    _temp_admin_cleanup \"$SYNO_USE_TEMP_ADMIN\" \"$SYNO_USERNAME\"\n    return 1\n  fi\n\n  sid=$(echo \"$response\" | grep \"sid\" | sed -n 's/.*\"sid\" *: *\"\\([^\"]*\\).*/\\1/p')\n  token=$(echo \"$response\" | grep \"synotoken\" | sed -n 's/.*\"synotoken\" *: *\"\\([^\"]*\\).*/\\1/p')\n  _debug \"Session ID\" \"$sid\"\n  _debug SynoToken \"$token\"\n  if [ -z \"$sid\" ] || [ -z \"$token\" ]; then\n    # Still can't get necessary info even got no errors, may Synology have API updated?\n    _err \"Unable to authenticate to $_base_url, you may report this by providing full log with '--debug 3'.\"\n    _temp_admin_cleanup \"$SYNO_USE_TEMP_ADMIN\" \"$SYNO_USERNAME\"\n    return 1\n  fi\n\n  _H1=\"X-SYNO-TOKEN: $token\"\n  export _H1\n  _debug2 H1 \"${_H1}\"\n\n  # Now that we know the username and password are good, save them if not in temp admin mode.\n  if [ -n \"$SYNO_USE_TEMP_ADMIN\" ]; then\n    _cleardeployconf SYNO_USERNAME\n    _cleardeployconf SYNO_PASSWORD\n    _cleardeployconf SYNO_DEVICE_ID\n    _cleardeployconf SYNO_DEVICE_NAME\n    _savedeployconf SYNO_USE_TEMP_ADMIN \"$SYNO_USE_TEMP_ADMIN\"\n    _savedeployconf SYNO_LOCAL_HOSTNAME \"$SYNO_LOCAL_HOSTNAME\"\n  else\n    _savedeployconf SYNO_USERNAME \"$SYNO_USERNAME\"\n    _savedeployconf SYNO_PASSWORD \"$SYNO_PASSWORD\"\n    _savedeployconf SYNO_DEVICE_ID \"$SYNO_DEVICE_ID\"\n    _savedeployconf SYNO_DEVICE_NAME \"$SYNO_DEVICE_NAME\"\n  fi\n\n  _info \"Getting certificates in Synology DSM...\"\n  response=$(_post \"api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid\" \"$_base_url/webapi/entry.cgi\")\n  _debug3 response \"$response\"\n  escaped_certificate=\"$(printf \"%s\" \"$SYNO_CERTIFICATE\" | sed 's/\\([].*^$[]\\)/\\\\\\1/g;s/\"/\\\\\\\\\"/g')\"\n  _debug escaped_certificate \"$escaped_certificate\"\n  id=$(echo \"$response\" | sed -n \"s/.*\\\"desc\\\":\\\"$escaped_certificate\\\",\\\"id\\\":\\\"\\([^\\\"]*\\).*/\\1/p\")\n  _debug2 id \"$id\"\n\n  error_code=$(echo \"$response\" | grep '\"error\":' | grep -o '\"code\":[0-9]*' | grep -o '[0-9]*')\n  _debug2 error_code \"$error_code\"\n  if [ -n \"$error_code\" ]; then\n    if [ \"$error_code\" -eq 105 ]; then\n      _err \"Current user is not administrator and does not have sufficient permission for deploying.\"\n    else\n      _err \"Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more.\"\n    fi\n    _temp_admin_cleanup \"$SYNO_USE_TEMP_ADMIN\" \"$SYNO_USERNAME\"\n    return 1\n  fi\n\n  _migratedeployconf SYNO_Create SYNO_CREATE\n  _getdeployconf SYNO_CREATE\n  _debug2 SYNO_CREATE \"$SYNO_CREATE\"\n\n  if [ -z \"$id\" ] && [ -z \"$SYNO_CREATE\" ]; then\n    _err \"Unable to find certificate: $SYNO_CERTIFICATE and $SYNO_CREATE is not set.\"\n    _temp_admin_cleanup \"$SYNO_USE_TEMP_ADMIN\" \"$SYNO_USERNAME\"\n    return 1\n  fi\n\n  # We've verified this certificate description is a thing, so save it\n  _savedeployconf SYNO_CERTIFICATE \"$SYNO_CERTIFICATE\" \"base64\"\n\n  _info \"Generating form POST request...\"\n  nl=\"\\0015\\0012\"\n  delim=\"--------------------------$(_utc_date | tr -d -- '-: ')\"\n  content=\"--$delim${nl}Content-Disposition: form-data; name=\\\"key\\\"; filename=\\\"$(basename \"$_ckey\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_ckey\")\\0012\"\n  content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"cert\\\"; filename=\\\"$(basename \"$_ccert\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_ccert\")\\0012\"\n  content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"inter_cert\\\"; filename=\\\"$(basename \"$_cca\")\\\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat \"$_cca\")\\0012\"\n  content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"id\\\"${nl}${nl}$id\"\n  content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"desc\\\"${nl}${nl}${SYNO_CERTIFICATE}\"\n  if echo \"$response\" | sed -n \"s/.*\\\"desc\\\":\\\"$escaped_certificate\\\",\\([^{]*\\).*/\\1/p\" | grep -- 'is_default\":true' >/dev/null; then\n    _debug2 default \"This is the default certificate\"\n    content=\"$content${nl}--$delim${nl}Content-Disposition: form-data; name=\\\"as_default\\\"${nl}${nl}true\"\n  else\n    _debug2 default \"This is NOT the default certificate\"\n  fi\n  content=\"$content${nl}--$delim--${nl}\"\n  content=\"$(printf \"%b_\" \"$content\")\"\n  content=\"${content%_}\" # protect trailing \\n\n\n  _info \"Upload certificate to the Synology DSM.\"\n  response=$(_post \"$content\" \"$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid\" \"\" \"POST\" \"multipart/form-data; boundary=${delim}\")\n  _debug3 response \"$response\"\n\n  if ! echo \"$response\" | grep '\"error\":' >/dev/null; then\n    if echo \"$response\" | grep '\"restart_httpd\":true' >/dev/null; then\n      _info \"Restart HTTP services succeeded.\"\n    else\n      _info \"Restart HTTP services failed.\"\n    fi\n    _temp_admin_cleanup \"$SYNO_USE_TEMP_ADMIN\" \"$SYNO_USERNAME\"\n    _logout\n    return 0\n  else\n    _temp_admin_cleanup \"$SYNO_USE_TEMP_ADMIN\" \"$SYNO_USERNAME\"\n    _err \"Unable to update certificate, got error response: $response.\"\n    _logout\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n_logout() {\n  # Logout CERT user only to not occupy a permanent session, e.g. in DSM's \"Connected Users\" widget (based on previous variables)\n  response=$(_get \"$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid\")\n  _debug3 response \"$response\"\n}\n\n_temp_admin_create() {\n  _username=\"$1\"\n  _password=\"$2\"\n  synouser --del \"$_username\" >/dev/null 2>/dev/null\n  synouser --add \"$_username\" \"$_password\" \"\" 0 \"\" 0 >/dev/null\n}\n\n_temp_admin_cleanup() {\n  _flag=$1\n  _username=$2\n\n  if [ -n \"${_flag}\" ]; then\n    _debug \"Cleanuping temp admin info...\"\n    synouser --del \"$_username\" >/dev/null\n  fi\n}\n\n#_cleardeployconf   key\n_cleardeployconf() {\n  _cleardomainconf \"SAVED_$1\"\n}\n\n# key\n_check2cleardeployconfexp() {\n  _key=\"$1\"\n  _clear_key=\"CLEAR_$_key\"\n  # Clear saved settings if explicitly requested\n  if [ -n \"$(eval echo \\$\"$_clear_key\")\" ]; then\n    _debug2 \"$_key: value cleared from config, exported value will be ignored.\"\n    _cleardeployconf \"$_key\"\n    eval \"$_key\"=\n    export \"$_key\"=\n    eval SAVED_\"$_key\"=\n    export SAVED_\"$_key\"=\n  fi\n}\n"
  },
  {
    "path": "deploy/truenas.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a scipt to deploy the cert to your TrueNAS using the REST API.\n# https://www.truenas.com/docs/hub/additional-topics/api/rest_api.html\n#\n# Written by Frank Plass github@f-plass.de\n# https://github.com/danb35/deploy-freenas/blob/master/deploy_freenas.py\n# Thanks to danb35 for your template!\n#\n# Following environment variables must be set:\n#\n# export DEPLOY_TRUENAS_APIKEY=\"<API_KEY_GENERATED_IN_THE_WEB_UI>\"\n#\n# The following environmental variables may be set if you don't like their\n# default values:\n#\n# DEPLOY_TRUENAS_HOSTNAME - defaults to localhost\n# DEPLOY_TRUENAS_SCHEME - defaults to http, set alternatively to https\n#\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\ntruenas_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf DEPLOY_TRUENAS_APIKEY\n\n  if [ -z \"$DEPLOY_TRUENAS_APIKEY\" ]; then\n    _err \"TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable.\"\n    return 1\n  fi\n  _secure_debug2 DEPLOY_TRUENAS_APIKEY \"$DEPLOY_TRUENAS_APIKEY\"\n\n  # Optional hostname, scheme for TrueNAS\n  _getdeployconf DEPLOY_TRUENAS_HOSTNAME\n  _getdeployconf DEPLOY_TRUENAS_SCHEME\n\n  # default values for hostname and scheme\n  [ -n \"${DEPLOY_TRUENAS_HOSTNAME}\" ] || DEPLOY_TRUENAS_HOSTNAME=\"localhost\"\n  [ -n \"${DEPLOY_TRUENAS_SCHEME}\" ] || DEPLOY_TRUENAS_SCHEME=\"http\"\n\n  _debug2 DEPLOY_TRUENAS_HOSTNAME \"$DEPLOY_TRUENAS_HOSTNAME\"\n  _debug2 DEPLOY_TRUENAS_SCHEME \"$DEPLOY_TRUENAS_SCHEME\"\n\n  _api_url=\"$DEPLOY_TRUENAS_SCHEME://$DEPLOY_TRUENAS_HOSTNAME/api/v2.0\"\n  _debug _api_url \"$_api_url\"\n\n  _H1=\"Authorization: Bearer $DEPLOY_TRUENAS_APIKEY\"\n  _secure_debug3 _H1 \"$_H1\"\n\n  _info \"Testing Connection TrueNAS\"\n  _response=$(_get \"$_api_url/system/state\")\n  _info \"TrueNAS system state: $_response.\"\n\n  _info \"Getting TrueNAS version\"\n  _response=$(_get \"$_api_url/system/version\")\n\n  if echo \"$_response\" | grep -q \"SCALE\"; then\n    _truenas_os=$(echo \"$_response\" | cut -d '-' -f 2)\n    _truenas_version=$(echo \"$_response\" | cut -d '-' -f 3 | tr -d '\"' | cut -d '.' -f 1,2)\n  else\n    _truenas_os=\"unknown\"\n    _truenas_version=\"unknown\"\n  fi\n\n  _info \"Detected TrueNAS system os: $_truenas_os\"\n  _info \"Detected TrueNAS system version: $_truenas_version\"\n\n  if [ -z \"$_response\" ]; then\n    _err \"Unable to authenticate to $_api_url.\"\n    _err 'Check your connection settings are correct, e.g.'\n    _err 'DEPLOY_TRUENAS_HOSTNAME=\"192.168.x.y\" or DEPLOY_TRUENAS_HOSTNAME=\"truenas.example.com\".'\n    _err 'DEPLOY_TRUENAS_SCHEME=\"https\" or DEPLOY_TRUENAS_SCHEME=\"http\".'\n    _err \"Verify your TrueNAS API key is valid and set correctly, e.g. DEPLOY_TRUENAS_APIKEY=xxxx....\"\n    return 1\n  fi\n\n  _savedeployconf DEPLOY_TRUENAS_APIKEY \"$DEPLOY_TRUENAS_APIKEY\"\n  _savedeployconf DEPLOY_TRUENAS_HOSTNAME \"$DEPLOY_TRUENAS_HOSTNAME\"\n  _savedeployconf DEPLOY_TRUENAS_SCHEME \"$DEPLOY_TRUENAS_SCHEME\"\n\n  _info \"Getting current active certificate from TrueNAS\"\n  _response=$(_get \"$_api_url/system/general\")\n  _active_cert_id=$(echo \"$_response\" | grep -B2 '\"name\":' | grep 'id' | tr -d -- '\"id: ,')\n  _active_cert_name=$(echo \"$_response\" | grep '\"name\":' | sed -n 's/.*: \"\\(.\\{1,\\}\\)\",$/\\1/p')\n  _param_httpsredirect=$(echo \"$_response\" | grep '\"ui_httpsredirect\":' | sed -n 's/.*\": \\(.\\{1,\\}\\),$/\\1/p')\n  _debug Active_UI_Certificate_ID \"$_active_cert_id\"\n  _debug Active_UI_Certificate_Name \"$_active_cert_name\"\n  _debug Active_UI_http_redirect \"$_param_httpsredirect\"\n\n  if [ \"$DEPLOY_TRUENAS_SCHEME\" = \"http\" ] && [ \"$_param_httpsredirect\" = \"true\" ]; then\n    _info \"HTTP->HTTPS redirection is enabled\"\n    _info \"Setting DEPLOY_TRUENAS_SCHEME to 'https'\"\n    DEPLOY_TRUENAS_SCHEME=\"https\"\n    _api_url=\"$DEPLOY_TRUENAS_SCHEME://$DEPLOY_TRUENAS_HOSTNAME/api/v2.0\"\n    _savedeployconf DEPLOY_TRUENAS_SCHEME \"$DEPLOY_TRUENAS_SCHEME\"\n  fi\n\n  _info \"Uploading new certificate to TrueNAS\"\n  _certname=\"Letsencrypt_$(_utc_date | tr ' ' '_' | tr -d -- ':')\"\n  _debug3 _certname \"$_certname\"\n\n  _certData=\"{\\\"create_type\\\": \\\"CERTIFICATE_CREATE_IMPORTED\\\", \\\"name\\\": \\\"${_certname}\\\", \\\"certificate\\\": \\\"$(_json_encode <\"$_cfullchain\")\\\", \\\"privatekey\\\": \\\"$(_json_encode <\"$_ckey\")\\\"}\"\n  _add_cert_result=\"$(_post \"$_certData\" \"$_api_url/certificate\" \"\" \"POST\" \"application/json\")\"\n\n  _debug3 _add_cert_result \"$_add_cert_result\"\n\n  _info \"Fetching list of installed certificates\"\n  _cert_list=$(_get \"$_api_url/system/general/ui_certificate_choices\")\n  _cert_id=$(echo \"$_cert_list\" | grep \"$_certname\" | sed -n 's/.*\"\\([0-9]\\{1,\\}\\)\".*$/\\1/p')\n\n  _debug3 _cert_id \"$_cert_id\"\n\n  _info \"Current activate certificate ID: $_cert_id\"\n  _activateData=\"{\\\"ui_certificate\\\": \\\"${_cert_id}\\\"}\"\n  _activate_result=\"$(_post \"$_activateData\" \"$_api_url/system/general\" \"\" \"PUT\" \"application/json\")\"\n\n  _debug3 _activate_result \"$_activate_result\"\n\n  _truenas_version_23_10=\"23.10\"\n  _truenas_version_24_10=\"24.10\"\n\n  _check_version=$(printf \"%s\\n%s\" \"$_truenas_version_23_10\" \"$_truenas_version\" | sort -V | head -n 1)\n  if [ \"$_truenas_os\" != \"SCALE\" ] || [ \"$_check_version\" != \"$_truenas_version_23_10\" ]; then\n    _info \"Checking if WebDAV certificate is the same as the TrueNAS web UI\"\n    _webdav_list=$(_get \"$_api_url/webdav\")\n    _webdav_cert_id=$(echo \"$_webdav_list\" | grep '\"certssl\":' | tr -d -- '\"certsl: ,')\n\n    if [ \"$_webdav_cert_id\" = \"$_active_cert_id\" ]; then\n      _info \"Updating the WebDAV certificate\"\n      _debug _webdav_cert_id \"$_webdav_cert_id\"\n      _webdav_data=\"{\\\"certssl\\\": \\\"${_cert_id}\\\"}\"\n      _activate_webdav_cert=\"$(_post \"$_webdav_data\" \"$_api_url/webdav\" \"\" \"PUT\" \"application/json\")\"\n      _webdav_new_cert_id=$(echo \"$_activate_webdav_cert\" | _json_decode | grep '\"certssl\":' | sed -n 's/.*: \\([0-9]\\{1,\\}\\),\\{0,1\\}$/\\1/p')\n      if [ \"$_webdav_new_cert_id\" -eq \"$_cert_id\" ]; then\n        _info \"WebDAV certificate updated successfully\"\n      else\n        _err \"Unable to set WebDAV certificate\"\n        _debug3 _activate_webdav_cert \"$_activate_webdav_cert\"\n        _debug3 _webdav_new_cert_id \"$_webdav_new_cert_id\"\n        return 1\n      fi\n      _debug3 _webdav_new_cert_id \"$_webdav_new_cert_id\"\n    else\n      _info \"WebDAV certificate is not configured or is not the same as TrueNAS web UI\"\n    fi\n\n    _info \"Checking if S3 certificate is the same as the TrueNAS web UI\"\n    _s3_list=$(_get \"$_api_url/s3\")\n    _s3_cert_id=$(echo \"$_s3_list\" | grep '\"certificate\":' | tr -d -- '\"certifa:_ ,')\n\n    if [ \"$_s3_cert_id\" = \"$_active_cert_id\" ]; then\n      _info \"Updating the S3 certificate\"\n      _debug _s3_cert_id \"$_s3_cert_id\"\n      _s3_data=\"{\\\"certificate\\\": \\\"${_cert_id}\\\"}\"\n      _activate_s3_cert=\"$(_post \"$_s3_data\" \"$_api_url/s3\" \"\" \"PUT\" \"application/json\")\"\n      _s3_new_cert_id=$(echo \"$_activate_s3_cert\" | _json_decode | grep '\"certificate\":' | sed -n 's/.*: \\([0-9]\\{1,\\}\\),\\{0,1\\}$/\\1/p')\n      if [ \"$_s3_new_cert_id\" -eq \"$_cert_id\" ]; then\n        _info \"S3 certificate updated successfully\"\n      else\n        _err \"Unable to set S3 certificate\"\n        _debug3 _activate_s3_cert \"$_activate_s3_cert\"\n        _debug3 _s3_new_cert_id \"$_s3_new_cert_id\"\n        return 1\n      fi\n      _debug3 _activate_s3_cert \"$_activate_s3_cert\"\n    else\n      _info \"S3 certificate is not configured or is not the same as TrueNAS web UI\"\n    fi\n  fi\n\n  if [ \"$_truenas_os\" = \"SCALE\" ]; then\n    _check_version=$(printf \"%s\\n%s\" \"$_truenas_version_24_10\" \"$_truenas_version\" | sort -V | head -n 1)\n    if [ \"$_check_version\" != \"$_truenas_version_24_10\" ]; then\n      _info \"Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required\"\n      if _exists jq; then\n        _info \"Query all chart release\"\n        _release_list=$(_get \"$_api_url/chart/release\")\n        _related_name_list=$(printf \"%s\" \"$_release_list\" | jq -r \"[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique\")\n        _release_length=$(printf \"%s\" \"$_related_name_list\" | jq -r \"length\")\n        _info \"Found $_release_length related chart release in list: $_related_name_list\"\n        for i in $(seq 0 $((_release_length - 1))); do\n          _release_name=$(echo \"$_related_name_list\" | jq -r \".[$i]\")\n          _info \"Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name\"\n          #Read the chart release configuration\n          _chart_config=$(printf \"%s\" \"$_release_list\" | jq -r \".[] | select(.name==\\\"$_release_name\\\")\")\n          #Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress\n          _updated_chart_config=$(printf \"%s\" \"$_chart_config\" | jq \"(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert  ) |= $_cert_id | .config.ingress \")\n          _update_chart_result=\"$(_post \"{\\\"values\\\" : { \\\"ingress\\\" : $_updated_chart_config } }\" \"$_api_url/chart/release/id/$_release_name\" \"\" \"PUT\" \"application/json\")\"\n          _debug3 _update_chart_result \"$_update_chart_result\"\n        done\n      else\n        _info \"Tool 'jq' does not exists, skip chart release checking\"\n      fi\n    else\n      _info \"Checking if any app is using the same certificate as TrueNAS web UI. Tool 'jq' is required\"\n      if _exists jq; then\n        _info \"Query all apps\"\n        _app_list=$(_get \"$_api_url/app\")\n        _app_id_list=$(printf \"%s\" \"$_app_list\" | jq -r '.[].name')\n        _app_length=$(echo \"$_app_id_list\" | wc -l)\n        _info \"Found $_app_length apps\"\n        _info \"Checking for each app if an update is needed\"\n        for i in $(seq 1 \"$_app_length\"); do\n          _app_id=$(echo \"$_app_id_list\" | sed -n \"${i}p\")\n          _app_config=\"$(_post \"\\\"$_app_id\\\"\" \"$_api_url/app/config\" \"\" \"POST\" \"application/json\")\"\n          # Check if the app use the same certificate TrueNAS web UI\n          _app_active_cert_config=$(echo \"$_app_config\" | tr -d '\\000-\\037' | _json_decode | jq -r \".ix_certificates[\\\"$_active_cert_id\\\"]\")\n          if [ \"$_app_active_cert_config\" != \"null\" ]; then\n            _info \"Updating certificate from $_active_cert_id to $_cert_id for app: $_app_id\"\n            #Replace the old certificate id with the new one in path\n            _update_app_result=\"$(_post \"{\\\"values\\\" : { \\\"network\\\": { \\\"certificate_id\\\": $_cert_id } } }\" \"$_api_url/app/id/$_app_id\" \"\" \"PUT\" \"application/json\")\"\n            _debug3 _update_app_result \"$_update_app_result\"\n          fi\n        done\n      else\n        _info \"Tool 'jq' does not exists, skip app checking\"\n      fi\n    fi\n  fi\n\n  _info \"Checking if FTP certificate is the same as the TrueNAS web UI\"\n  _ftp_list=$(_get \"$_api_url/ftp\")\n  _ftp_cert_id=$(echo \"$_ftp_list\" | grep '\"ssltls_certificate\":' | tr -d -- '\"certislfa:_ ,')\n\n  if [ \"$_ftp_cert_id\" = \"$_active_cert_id\" ]; then\n    _info \"Updating the FTP certificate\"\n    _debug _ftp_cert_id \"$_ftp_cert_id\"\n    _ftp_data=\"{\\\"ssltls_certificate\\\": \\\"${_cert_id}\\\"}\"\n    _activate_ftp_cert=\"$(_post \"$_ftp_data\" \"$_api_url/ftp\" \"\" \"PUT\" \"application/json\")\"\n    _ftp_new_cert_id=$(echo \"$_activate_ftp_cert\" | _json_decode | grep '\"ssltls_certificate\":' | sed -n 's/.*: \\([0-9]\\{1,\\}\\),\\{0,1\\}$/\\1/p')\n    if [ \"$_ftp_new_cert_id\" -eq \"$_cert_id\" ]; then\n      _info \"FTP certificate updated successfully\"\n    else\n      _err \"Unable to set FTP certificate\"\n      _debug3 _activate_ftp_cert \"$_activate_ftp_cert\"\n      _debug3 _ftp_new_cert_id \"$_ftp_new_cert_id\"\n      return 1\n    fi\n    _debug3 _activate_ftp_cert \"$_activate_ftp_cert\"\n  else\n    _info \"FTP certificate is not configured or is not the same as TrueNAS web UI\"\n  fi\n\n  _info \"Deleting old certificate\"\n  _delete_result=\"$(_post \"\" \"$_api_url/certificate/id/$_active_cert_id\" \"\" \"DELETE\" \"application/json\")\"\n\n  _debug3 _delete_result \"$_delete_result\"\n\n  _info \"Reloading TrueNAS web UI\"\n  _restart_UI=$(_get \"$_api_url/system/general/ui_restart\")\n  _debug2 _restart_UI \"$_restart_UI\"\n\n  if [ -n \"$_add_cert_result\" ] && [ -n \"$_activate_result\" ]; then\n    return 0\n  else\n    _err \"Certificate update was not succesful, please try again with --debug\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "deploy/truenas_ws.sh",
    "content": "#!/usr/bin/env sh\n\n# TrueNAS deploy script for SCALE/CORE using websocket\n# It is recommend to use a wildcard certificate\n#\n# Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html\n#\n# Tested with TrueNAS Scale - Electric Eel 24.10\n# Changes certificate in the following services:\n#  - Web UI\n#  - FTP\n#  - iX Apps\n#\n# The following environment variables must be set:\n# ------------------------------------------------\n#\n# # API KEY\n# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys\n# export DEPLOY_TRUENAS_APIKEY=\"<API_KEY_GENERATED_IN_THE_WEB_UI\"\n#\n\n### Private functions\n\n# Call websocket method\n# Usage:\n#   _ws_response=$(_ws_call \"math.dummycalc\" \"'{\"x\": 4, \"y\": 5}'\")\n#   _info \"$_ws_response\"\n#\n# Output:\n#   {\"z\": 9}\n#\n# Arguments:\n#   $@ - midclt arguments for call\n#\n# Returns:\n#   JSON/JOBID\n_ws_call() {\n  _debug \"_ws_call arg1\" \"$1\"\n  _debug \"_ws_call arg2\" \"$2\"\n  _debug \"_ws_call arg3\" \"$3\"\n  if [ $# -eq 3 ]; then\n    _ws_response=$(midclt --uri \"$_ws_uri\" -K \"$DEPLOY_TRUENAS_APIKEY\" call \"$1\" \"$2\" \"$3\")\n  fi\n  if [ $# -eq 2 ]; then\n    _ws_response=$(midclt --uri \"$_ws_uri\" -K \"$DEPLOY_TRUENAS_APIKEY\" call \"$1\" \"$2\")\n  fi\n  if [ $# -eq 1 ]; then\n    _ws_response=$(midclt --uri \"$_ws_uri\" -K \"$DEPLOY_TRUENAS_APIKEY\" call \"$1\")\n  fi\n  _debug \"_ws_response\" \"$_ws_response\"\n  printf \"%s\" \"$_ws_response\"\n  return 0\n}\n\n# Upload certificate with webclient api\n_ws_upload_cert() {\n\n  /usr/bin/env python - <<EOF\n\nimport sys\n\nfrom truenas_api_client import Client\nwith Client(uri=\"$_ws_uri\") as c:\n\n  ### Login with API key\n  print(\"I:Trying to upload new certificate...\")\n  ret = c.call(\"auth.login_with_api_key\", \"${DEPLOY_TRUENAS_APIKEY}\")\n  if ret:\n    ### upload certificate\n    with open('$1', 'r') as file:\n      fullchain = file.read()\n    with open('$2', 'r') as file:\n      privatekey = file.read()\n    ret = c.call(\"certificate.create\", {\"name\": \"$3\", \"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"certificate\": fullchain, \"privatekey\": privatekey}, job=True)\n    print(\"R:\" + str(ret[\"id\"]))\n    sys.exit(0)\n  else:\n    print(\"R:0\")\n    print(\"E:_ws_upload_cert error!\")\n    sys.exit(7)\n\nEOF\n\n  return $?\n\n}\n\n# Check argument is a number\n# Usage:\n#\n# Output:\n#   n/a\n#\n# Arguments:\n#   $1 - Anything\n#\n# Returns:\n#   0: true\n#   1: false\n_ws_check_jobid() {\n  case \"$1\" in\n  [0-9]*)\n    return 0\n    ;;\n  esac\n  return 1\n}\n\n# Wait for job to finish and return result as JSON\n# Usage:\n#   _ws_result=$(_ws_get_job_result \"$_ws_jobid\")\n#   _new_certid=$(printf \"%s\" \"$_ws_result\" | jq -r '.\"id\"')\n#\n# Output:\n#   JSON result of the job\n#\n# Arguments:\n#   $1 - JobID\n#\n# Returns:\n#   n/a\n_ws_get_job_result() {\n  while true; do\n    _sleep 2\n    _ws_response=$(_ws_call \"core.get_jobs\" \"[[\\\"id\\\", \\\"=\\\", $1]]\")\n    if [ \"$(printf \"%s\" \"$_ws_response\" | jq -r '.[].\"state\"')\" != \"RUNNING\" ]; then\n      _ws_result=\"$(printf \"%s\" \"$_ws_response\" | jq '.[].\"result\"')\"\n      _debug \"_ws_result\" \"$_ws_result\"\n      printf \"%s\" \"$_ws_result\"\n      _ws_error=\"$(printf \"%s\" \"$_ws_response\" | jq '.[].\"error\"')\"\n      if [ \"$_ws_error\" != \"null\" ]; then\n        _err \"Job $1 failed:\"\n        _err \"$_ws_error\"\n        return 7\n      fi\n      break\n    fi\n  done\n  return 0\n}\n\n########################\n### Public functions ###\n########################\n\n# truenas_ws_deploy\n#\n# Deploy new certificate to TrueNAS services\n#\n# Arguments\n#  1: Domain\n#  2: Key-File\n#  3: Certificate-File\n#  4: CA-File\n#  5: FullChain-File\n# Returns:\n#  0: Success\n#  1: Missing API Key\n#  2: TrueNAS not ready\n#  3: Not a JobID\n#  4: FTP cert error\n#  5: WebUI cert error\n#  6: Job error\n#  7: WS call error\n#\ntruenas_ws_deploy() {\n  _domain=\"$1\"\n  _file_key=\"$2\"\n  _file_cert=\"$3\"\n  _file_ca=\"$4\"\n  _file_fullchain=\"$5\"\n  _debug _domain \"$_domain\"\n  _debug _file_key \"$_file_key\"\n  _debug _file_cert \"$_file_cert\"\n  _debug _file_ca \"$_file_ca\"\n  _debug _file_fullchain \"$_file_fullchain\"\n\n  ########## Environment check\n\n  _info \"Checking environment variables...\"\n  _getdeployconf DEPLOY_TRUENAS_APIKEY\n  _getdeployconf DEPLOY_TRUENAS_HOSTNAME\n  _getdeployconf DEPLOY_TRUENAS_PROTOCOL\n  # Check API Key\n  if [ -z \"$DEPLOY_TRUENAS_APIKEY\" ]; then\n    _err \"TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable.\"\n    return 1\n  fi\n  # Check Hostname, default to localhost if not set\n  if [ -z \"$DEPLOY_TRUENAS_HOSTNAME\" ]; then\n    _info \"TrueNAS hostname not set. Using 'localhost'.\"\n    DEPLOY_TRUENAS_HOSTNAME=\"localhost\"\n  fi\n  # Check protocol, default to ws if not set\n  if [ -z \"$DEPLOY_TRUENAS_PROTOCOL\" ]; then\n    _info \"TrueNAS protocol not set. Using 'ws'.\"\n    DEPLOY_TRUENAS_PROTOCOL=\"ws\"\n  fi\n  _ws_uri=\"$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket\"\n  _debug2 DEPLOY_TRUENAS_HOSTNAME \"$DEPLOY_TRUENAS_HOSTNAME\"\n  _debug2 DEPLOY_TRUENAS_PROTOCOL \"$DEPLOY_TRUENAS_PROTOCOL\"\n  _debug _ws_uri \"$_ws_uri\"\n  _secure_debug2 DEPLOY_TRUENAS_APIKEY \"$DEPLOY_TRUENAS_APIKEY\"\n  _info \"Environment variables: OK\"\n\n  ########## Health check\n\n  _info \"Checking TrueNAS health...\"\n  _ws_response=$(_ws_call \"system.ready\" | tr '[:lower:]' '[:upper:]')\n  _ws_ret=$?\n  if [ $_ws_ret -gt 0 ]; then\n    _err \"Error calling system.ready:\"\n    _err \"$_ws_response\"\n    return $_ws_ret\n  fi\n\n  if [ \"$_ws_response\" != \"TRUE\" ]; then\n    _err \"TrueNAS is not ready.\"\n    _err \"Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL.\"\n    _err \"Verify API key.\"\n    return 2\n  fi\n  _savedeployconf DEPLOY_TRUENAS_APIKEY \"$DEPLOY_TRUENAS_APIKEY\"\n  _savedeployconf DEPLOY_TRUENAS_HOSTNAME \"$DEPLOY_TRUENAS_HOSTNAME\"\n  _savedeployconf DEPLOY_TRUENAS_PROTOCOL \"$DEPLOY_TRUENAS_PROTOCOL\"\n  _info \"TrueNAS health: OK\"\n\n  ########## System info\n\n  _info \"Gather system info...\"\n  _ws_response=$(_ws_call \"system.info\")\n  _truenas_version=$(printf \"%s\" \"$_ws_response\" | jq -r '.\"version\"')\n  _info \"TrueNAS version: $_truenas_version\"\n\n  ########## Gather current certificate\n\n  _info \"Gather current WebUI certificate...\"\n  _ws_response=\"$(_ws_call \"system.general.config\")\"\n  _ui_certificate_id=$(printf \"%s\" \"$_ws_response\" | jq -r '.\"ui_certificate\".\"id\"')\n  _ui_certificate_name=$(printf \"%s\" \"$_ws_response\" | jq -r '.\"ui_certificate\".\"name\"')\n  _info \"Current WebUI certificate ID: $_ui_certificate_id\"\n  _info \"Current WebUI certificate name: $_ui_certificate_name\"\n\n  ########## Upload new certificate\n\n  _info \"Upload new certificate...\"\n  _certname=\"acme_$(_utc_date | tr -d '\\-\\:' | tr ' ' '_')\"\n  _info \"New WebUI certificate name: $_certname\"\n  _debug _certname \"$_certname\"\n  _ws_out=$(_ws_upload_cert \"$_file_fullchain\" \"$_file_key\" \"$_certname\")\n\n  echo \"$_ws_out\" | while IFS= read -r LINE; do\n    case \"$LINE\" in\n    I:*)\n      _info \"${LINE#I:}\"\n      ;;\n    D:*)\n      _debug \"${LINE#D:}\"\n      ;;\n    E*)\n      _err \"${LINE#E:}\"\n      ;;\n    *) ;;\n\n    esac\n  done\n\n  _new_certid=$(echo \"$_ws_out\" | grep 'R:' | cut -d ':' -f 2)\n\n  _info \"New certificate ID: $_new_certid\"\n\n  ########## FTP\n\n  _info \"Replace FTP certificate...\"\n  _ws_response=$(_ws_call \"ftp.update\" \"{\\\"ssltls_certificate\\\": $_new_certid}\")\n  _ftp_certid=$(printf \"%s\" \"$_ws_response\" | jq -r '.\"ssltls_certificate\"')\n  if [ \"$_ftp_certid\" != \"$_new_certid\" ]; then\n    _err \"Cannot set FTP certificate.\"\n    _debug \"_ws_response\" \"$_ws_response\"\n    return 4\n  fi\n\n  ########## ix Apps (SCALE only)\n\n  _info \"Replace app certificates...\"\n  _ws_response=$(_ws_call \"app.query\")\n  for _app_name in $(printf \"%s\" \"$_ws_response\" | jq -r '.[].\"name\"'); do\n    _info \"Checking app $_app_name...\"\n    _ws_response=$(_ws_call \"app.config\" \"$_app_name\")\n    if [ \"$(printf \"%s\" \"$_ws_response\" | jq -r '.\"network\" | has(\"certificate_id\")')\" = \"true\" ]; then\n      _info \"App has certificate option, setup new certificate...\"\n      _info \"App will be redeployed after updating the certificate.\"\n      _ws_jobid=$(_ws_call \"app.update\" \"$_app_name\" \"{\\\"values\\\": {\\\"network\\\": {\\\"certificate_id\\\": $_new_certid}}}\")\n      _debug \"_ws_jobid\" \"$_ws_jobid\"\n      if ! _ws_check_jobid \"$_ws_jobid\"; then\n        _err \"No JobID returned from websocket method.\"\n        return 3\n      fi\n      _ws_result=$(_ws_get_job_result \"$_ws_jobid\")\n      _ws_ret=$?\n      if [ $_ws_ret -gt 0 ]; then\n        return $_ws_ret\n      fi\n      _debug \"_ws_result\" \"$_ws_result\"\n      _info \"App certificate replaced.\"\n    else\n      _info \"App has no certificate option, skipping...\"\n    fi\n  done\n\n  ########## WebUI\n\n  _info \"Replace WebUI certificate...\"\n  _ws_response=$(_ws_call \"system.general.update\" \"{\\\"ui_certificate\\\": $_new_certid}\")\n  _changed_certid=$(printf \"%s\" \"$_ws_response\" | jq -r '.\"ui_certificate\".\"id\"')\n  if [ \"$_changed_certid\" != \"$_new_certid\" ]; then\n    _err \"WebUI certificate change error..\"\n    return 5\n  else\n    _info \"WebUI certificate replaced.\"\n  fi\n  _info \"Restarting WebUI...\"\n  _ws_response=$(_ws_call \"system.general.ui_restart\")\n  _info \"Waiting for UI restart...\"\n  _sleep 15\n\n  ########## Certificates\n\n  _info \"Deleting old certificate...\"\n  _ws_jobid=$(_ws_call \"certificate.delete\" \"$_ui_certificate_id\")\n  if ! _ws_check_jobid \"$_ws_jobid\"; then\n    _err \"No JobID returned from websocket method.\"\n    return 3\n  fi\n  _ws_result=$(_ws_get_job_result \"$_ws_jobid\")\n  _ws_ret=$?\n  if [ $_ws_ret -gt 0 ]; then\n    return $_ws_ret\n  fi\n\n  _info \"Have a nice day...bye!\"\n\n}\n"
  },
  {
    "path": "deploy/unifi.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert on a Unifi Controller or Cloud Key device.\n# It supports:\n#   - self-hosted Unifi Controller\n#   - Unifi Cloud Key (Gen1/2/2+)\n#   - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only)\n#   - Unifi Dream Machine\n#       This has not been tested on other \"all-in-one\" devices such as\n#       UDM Pro or Unifi Express.\n#\n#       OS Version v2.0.0+\n#       Network Application version 7.0.0+\n#       OS version ~3.1 removed java and keytool from the UnifiOS.\n#       Using PKCS12 format keystore appears to work fine.\n#\n# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359\n\n#returns 0 means success, otherwise error.\n\n# The deploy-hook automatically detects standard Unifi installations\n# for each of the supported environments. Most users should not need\n# to set any of these variables, but if you are running a self-hosted\n# Controller with custom locations, set these as necessary before running\n# the deploy hook. (Defaults shown below.)\n#\n# Settings for Unifi Controller:\n# Location of Java keystore or unifi.keystore.jks file:\n#DEPLOY_UNIFI_KEYSTORE=\"/usr/lib/unifi/data/keystore\"\n# Keystore password (built into Unifi Controller, not a user-set password):\n#DEPLOY_UNIFI_KEYPASS=\"aircontrolenterprise\"\n# Command to restart Unifi Controller:\n# DEPLOY_UNIFI_RELOAD=\"systemctl restart unifi\"\n# System Properties file location for controller\n#DEPLOY_UNIFI_SYSTEM_PROPERTIES=\"/usr/lib/unifi/data/system.properties\"\n#\n# Settings for Unifi Cloud Key Gen1 (nginx admin pages):\n# Directory where cloudkey.crt and cloudkey.key live:\n#DEPLOY_UNIFI_CLOUDKEY_CERTDIR=\"/etc/ssl/private\"\n# Command to restart maintenance pages and Controller\n# (same setting as above, default is updated when running on Cloud Key Gen1):\n#DEPLOY_UNIFI_RELOAD=\"service nginx restart && service unifi restart\"\n#\n# Settings for UnifiOS (Cloud Key Gen2):\n# Directory where unifi-core.crt and unifi-core.key live:\n#DEPLOY_UNIFI_CORE_CONFIG=\"/data/unifi-core/config/\"\n# Command to restart unifi-core:\n# DEPLOY_UNIFI_OS_RELOAD=\"systemctl restart unifi-core\"\n#\n# At least one of DEPLOY_UNIFI_KEYSTORE, DEPLOY_UNIFI_CLOUDKEY_CERTDIR,\n# or DEPLOY_UNIFI_CORE_CONFIG must exist to receive the deployed certs.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nunifi_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf DEPLOY_UNIFI_KEYSTORE\n  _getdeployconf DEPLOY_UNIFI_KEYPASS\n  _getdeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR\n  _getdeployconf DEPLOY_UNIFI_CORE_CONFIG\n  _getdeployconf DEPLOY_UNIFI_RELOAD\n  _getdeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES\n  _getdeployconf DEPLOY_UNIFI_OS_RELOAD\n\n  _debug2 DEPLOY_UNIFI_KEYSTORE \"$DEPLOY_UNIFI_KEYSTORE\"\n  _debug2 DEPLOY_UNIFI_KEYPASS \"$DEPLOY_UNIFI_KEYPASS\"\n  _debug2 DEPLOY_UNIFI_CLOUDKEY_CERTDIR \"$DEPLOY_UNIFI_CLOUDKEY_CERTDIR\"\n  _debug2 DEPLOY_UNIFI_CORE_CONFIG \"$DEPLOY_UNIFI_CORE_CONFIG\"\n  _debug2 DEPLOY_UNIFI_RELOAD \"$DEPLOY_UNIFI_RELOAD\"\n  _debug2 DEPLOY_UNIFI_OS_RELOAD \"$DEPLOY_UNIFI_OS_RELOAD\"\n  _debug2 DEPLOY_UNIFI_SYSTEM_PROPERTIES \"$DEPLOY_UNIFI_SYSTEM_PROPERTIES\"\n\n  # Space-separated list of environments detected and installed:\n  _services_updated=\"\"\n\n  # Default reload commands accumulated as we auto-detect environments:\n  _reload_cmd=\"\"\n\n  # Unifi Controller environment (self hosted or any Cloud Key) --\n  # auto-detect by file /usr/lib/unifi/data/keystore\n  _unifi_keystore=\"${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}\"\n  if [ -f \"$_unifi_keystore\" ]; then\n    _debug _unifi_keystore \"$_unifi_keystore\"\n    if ! _exists keytool; then\n      _do_keytool=0\n      _info \"Installing certificate for Unifi Controller (PKCS12 keystore).\"\n    else\n      _do_keytool=1\n      _info \"Installing certificate for Unifi Controller (Java keystore)\"\n    fi\n    if [ ! -w \"$_unifi_keystore\" ]; then\n      _err \"The file $_unifi_keystore is not writable, please change the permission.\"\n      return 1\n    fi\n\n    _unifi_keypass=\"${DEPLOY_UNIFI_KEYPASS:-aircontrolenterprise}\"\n\n    _debug \"Generate import pkcs12\"\n    _import_pkcs12=\"$(_mktemp)\"\n    _debug \"_toPkcs $_import_pkcs12 $_ckey $_ccert $_cca $_unifi_keypass unifi root\"\n    _toPkcs \"$_import_pkcs12\" \"$_ckey\" \"$_ccert\" \"$_cca\" \"$_unifi_keypass\" unifi root\n    # shellcheck disable=SC2181\n    if [ \"$?\" != \"0\" ]; then\n      _err \"Error generating pkcs12. Please re-run with --debug and report a bug.\"\n      return 1\n    fi\n\n    # Save the existing keystore in case something goes wrong.\n    mv -f \"${_unifi_keystore}\" \"${_unifi_keystore}\"_original\n    _info \"Previous keystore saved to ${_unifi_keystore}_original.\"\n\n    if [ \"$_do_keytool\" -eq 1 ]; then\n      _debug \"Import into keystore: $_unifi_keystore\"\n      if keytool -importkeystore \\\n        -deststorepass \"$_unifi_keypass\" -destkeypass \"$_unifi_keypass\" -destkeystore \"$_unifi_keystore\" \\\n        -srckeystore \"$_import_pkcs12\" -srcstoretype PKCS12 -srcstorepass \"$_unifi_keypass\" \\\n        -alias unifi -noprompt; then\n        _debug \"Import keystore success!\"\n      else\n        _err \"Error importing into Unifi Java keystore.\"\n        _err \"Please re-run with --debug and report a bug.\"\n        _info \"Restoring original keystore.\"\n        mv -f \"${_unifi_keystore}\"_original \"${_unifi_keystore}\"\n        rm \"$_import_pkcs12\"\n        return 1\n      fi\n    else\n      _debug \"Copying new keystore to $_unifi_keystore\"\n      cp -f \"$_import_pkcs12\" \"$_unifi_keystore\"\n    fi\n\n    # correct file ownership according to the directory, the keystore is placed in\n    _unifi_keystore_dir=$(dirname \"${_unifi_keystore}\")\n    # shellcheck disable=SC2012\n    _unifi_keystore_dir_owner=$(ls -ld \"${_unifi_keystore_dir}\" | awk '{print $3}')\n    # shellcheck disable=SC2012\n    _unifi_keystore_owner=$(ls -l \"${_unifi_keystore}\" | awk '{print $3}')\n    if ! [ \"${_unifi_keystore_owner}\" = \"${_unifi_keystore_dir_owner}\" ]; then\n      _debug \"Changing keystore owner to ${_unifi_keystore_dir_owner}\"\n      chown \"$_unifi_keystore_dir_owner\" \"${_unifi_keystore}\" >/dev/null 2>&1 # fail quietly if we're not running as root\n    fi\n\n    # Update unifi service for certificate cipher compatibility\n    _unifi_system_properties=\"${DEPLOY_UNIFI_SYSTEM_PROPERTIES:-/usr/lib/unifi/data/system.properties}\"\n    if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \\\n      -in \"$_import_pkcs12\" \\\n      -password pass:aircontrolenterprise \\\n      -nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \\\n      -noout | grep -i \"signature\" | grep -iq ecdsa >/dev/null 2>&1; then\n      if [ -f \"$(dirname \"${DEPLOY_UNIFI_KEYSTORE}\")/system.properties\" ]; then\n        _unifi_system_properties=\"$(dirname \"${DEPLOY_UNIFI_KEYSTORE}\")/system.properties\"\n      else\n        _unifi_system_properties=\"/usr/lib/unifi/data/system.properties\"\n      fi\n      if [ -f \"${_unifi_system_properties}\" ]; then\n        cp -f \"${_unifi_system_properties}\" \"${_unifi_system_properties}\"_original\n        _info \"Updating system configuration for cipher compatibility.\"\n        _info \"Saved original system config to ${_unifi_system_properties}_original\"\n        sed -i '/unifi\\.https\\.ciphers/d' \"${_unifi_system_properties}\"\n        echo \"unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256\" >>\"${_unifi_system_properties}\"\n        sed -i '/unifi\\.https\\.sslEnabledProtocols/d' \"${_unifi_system_properties}\"\n        echo \"unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2\" >>\"${_unifi_system_properties}\"\n        _info \"System configuration updated.\"\n      fi\n    fi\n\n    rm \"$_import_pkcs12\"\n\n    # Restarting unifi-core will bring up unifi, doing it out of order results in\n    # a certificate error, and breaks wifiman.\n    # Restart if we aren't doing Unifi OS (e.g. unifi-core service), otherwise stop for later restart.\n    _unifi_reload=\"${DEPLOY_UNIFI_RELOAD:-systemctl restart unifi}\"\n    if [ ! -f \"${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key\" ]; then\n      _reload_cmd=\"${_reload_cmd:+$_reload_cmd && }$_unifi_reload\"\n    else\n      _info \"Stopping Unifi Controller for later restart.\"\n      _unifi_stop=$(echo \"${_unifi_reload}\" | sed -e 's/restart/stop/')\n      $_unifi_stop\n      _reload_cmd=\"${_reload_cmd:+$_reload_cmd && }$_unifi_reload\"\n      _info \"Unifi Controller stopped.\"\n    fi\n    _services_updated=\"${_services_updated} unifi\"\n    _info \"Install Unifi Controller certificate success!\"\n  elif [ \"$DEPLOY_UNIFI_KEYSTORE\" ]; then\n    _err \"The specified DEPLOY_UNIFI_KEYSTORE='$DEPLOY_UNIFI_KEYSTORE' is not valid, please check.\"\n    return 1\n  fi\n\n  # Cloud Key environment (non-UnifiOS -- nginx serves admin pages) --\n  # auto-detect by file /etc/ssl/private/cloudkey.key:\n  _cloudkey_certdir=\"${DEPLOY_UNIFI_CLOUDKEY_CERTDIR:-/etc/ssl/private}\"\n  if [ -f \"${_cloudkey_certdir}/cloudkey.key\" ]; then\n    _info \"Installing certificate for Cloud Key Gen1 (nginx admin pages)\"\n    _debug _cloudkey_certdir \"$_cloudkey_certdir\"\n    if [ ! -w \"$_cloudkey_certdir\" ]; then\n      _err \"The directory $_cloudkey_certdir is not writable; please check permissions.\"\n      return 1\n    fi\n    # Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks.\n    # It appears that unifi won't start if this is a symlink, so we'll copy it instead.\n\n    # if ! cmp -s \"$_unifi_keystore\" \"${_cloudkey_certdir}/unifi.keystore.jks\"; then\n    #   _err \"Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'\"\n    #   return 1\n    # fi\n\n    _info \"Updating ${_cloudkey_certdir}/unifi.keystore.jks\"\n    if [ -e \"${_cloudkey_certdir}/unifi.keystore.jks\" ]; then\n      if [ -L \"${_cloudkey_certdir}/unifi.keystore.jks\" ]; then\n        rm -f \"${_cloudkey_certdir}/unifi.keystore.jks\"\n      else\n        mv \"${_cloudkey_certdir}/unifi.keystore.jks\" \"${_cloudkey_certdir}/unifi.keystore.jks_original\"\n      fi\n    fi\n\n    cp \"${_unifi_keystore}\" \"${_cloudkey_certdir}/unifi.keystore.jks\"\n\n    cat \"$_cfullchain\" >\"${_cloudkey_certdir}/cloudkey.crt\"\n    cat \"$_ckey\" >\"${_cloudkey_certdir}/cloudkey.key\"\n    (cd \"$_cloudkey_certdir\" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks)\n\n    if systemctl -q is-active nginx; then\n      _reload_cmd=\"${_reload_cmd:+$_reload_cmd && }service nginx restart\"\n    fi\n    _info \"Install Cloud Key Gen1 certificate success!\"\n    _services_updated=\"${_services_updated} nginx\"\n  elif [ \"$DEPLOY_UNIFI_CLOUDKEY_CERTDIR\" ]; then\n    _err \"The specified DEPLOY_UNIFI_CLOUDKEY_CERTDIR='$DEPLOY_UNIFI_CLOUDKEY_CERTDIR' is not valid, please check.\"\n    return 1\n  fi\n\n  # UnifiOS environment -- auto-detect by /data/unifi-core/config/unifi-core.key:\n  _unifi_core_config=\"${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}\"\n  if [ -f \"${_unifi_core_config}/unifi-core.key\" ]; then\n    _info \"Installing certificate for UnifiOS\"\n    _debug _unifi_core_config \"$_unifi_core_config\"\n    if [ ! -w \"$_unifi_core_config\" ]; then\n      _err \"The directory $_unifi_core_config is not writable; please check permissions.\"\n      return 1\n    fi\n\n    # Save the existing certs in case something goes wrong.\n    cp -f \"${_unifi_core_config}\"/unifi-core.crt \"${_unifi_core_config}\"/unifi-core_original.crt\n    cp -f \"${_unifi_core_config}\"/unifi-core.key \"${_unifi_core_config}\"/unifi-core_original.key\n    _info \"Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt.key.\"\n\n    cat \"$_cfullchain\" >\"${_unifi_core_config}/unifi-core.crt\"\n    cat \"$_ckey\" >\"${_unifi_core_config}/unifi-core.key\"\n\n    _unifi_os_reload=\"${DEPLOY_UNIFI_OS_RELOAD:-systemctl restart unifi-core}\"\n    _reload_cmd=\"${_reload_cmd:+$_reload_cmd && }$_unifi_os_reload\"\n\n    _info \"Install UnifiOS certificate success!\"\n    _services_updated=\"${_services_updated} unifi-core\"\n  elif [ \"$DEPLOY_UNIFI_CORE_CONFIG\" ]; then\n    _err \"The specified DEPLOY_UNIFI_CORE_CONFIG='$DEPLOY_UNIFI_CORE_CONFIG' is not valid, please check.\"\n    return 1\n  fi\n\n  if [ -z \"$_services_updated\" ]; then\n    # None of the Unifi environments were auto-detected, so no deployment has occurred\n    # (and none of DEPLOY_UNIFI_{KEYSTORE,CLOUDKEY_CERTDIR,CORE_CONFIG} were set).\n    _err \"Unable to detect Unifi environment in standard location.\"\n    _err \"(This deploy hook must be run on the Unifi device, not a remote machine.)\"\n    _err \"For non-standard Unifi installations, set DEPLOY_UNIFI_KEYSTORE,\"\n    _err \"DEPLOY_UNIFI_CLOUDKEY_CERTDIR, and/or DEPLOY_UNIFI_CORE_CONFIG as appropriate.\"\n    return 1\n  fi\n\n  _reload_cmd=\"${DEPLOY_UNIFI_RELOAD:-$_reload_cmd}\"\n  if [ -z \"$_reload_cmd\" ]; then\n    _err \"Certificates were installed for services:${_services_updated},\"\n    _err \"but none appear to be active. Please set DEPLOY_UNIFI_RELOAD\"\n    _err \"to a command that will restart the necessary services.\"\n    return 1\n  fi\n  _info \"Reload services (this may take some time): $_reload_cmd\"\n  if eval \"$_reload_cmd\"; then\n    _info \"Reload success!\"\n  else\n    _err \"Reload error\"\n    return 1\n  fi\n\n  # Successful, so save all (non-default) config:\n  _savedeployconf DEPLOY_UNIFI_KEYSTORE \"$DEPLOY_UNIFI_KEYSTORE\"\n  _savedeployconf DEPLOY_UNIFI_KEYPASS \"$DEPLOY_UNIFI_KEYPASS\"\n  _savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR \"$DEPLOY_UNIFI_CLOUDKEY_CERTDIR\"\n  _savedeployconf DEPLOY_UNIFI_CORE_CONFIG \"$DEPLOY_UNIFI_CORE_CONFIG\"\n  _savedeployconf DEPLOY_UNIFI_RELOAD \"$DEPLOY_UNIFI_RELOAD\"\n  _savedeployconf DEPLOY_UNIFI_OS_RELOAD \"$DEPLOY_UNIFI_OS_RELOAD\"\n  _savedeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES \"$DEPLOY_UNIFI_SYSTEM_PROPERTIES\"\n\n  return 0\n}\n"
  },
  {
    "path": "deploy/vault.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert to hashicorp vault using curl\n# (https://www.vaultproject.io/)\n#\n# it requires following environment variables:\n#\n# VAULT_PREFIX - this contains the prefix path in vault\n# VAULT_ADDR - vault requires this to find your vault server\n# VAULT_SAVE_TOKEN - set to anything if you want to save the token\n# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying\n# VAULT_KV_V2 - set to anything if you are using v2 of the kv engine\n#\n# additionally, you need to ensure that VAULT_TOKEN is avialable\n# to access the vault server\n\n#returns 0 means success, otherwise error.\n\n######## Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nvault_deploy() {\n\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # validate required env vars\n  _getdeployconf VAULT_PREFIX\n  if [ -z \"$VAULT_PREFIX\" ]; then\n    _err \"VAULT_PREFIX needs to be defined (contains prefix path in vault)\"\n    return 1\n  fi\n  _savedeployconf VAULT_PREFIX \"$VAULT_PREFIX\"\n\n  _getdeployconf VAULT_ADDR\n  if [ -z \"$VAULT_ADDR\" ]; then\n    _err \"VAULT_ADDR needs to be defined (contains vault connection address)\"\n    return 1\n  fi\n  _savedeployconf VAULT_ADDR \"$VAULT_ADDR\"\n\n  _getdeployconf VAULT_SAVE_TOKEN\n  _savedeployconf VAULT_SAVE_TOKEN \"$VAULT_SAVE_TOKEN\"\n\n  _getdeployconf VAULT_RENEW_TOKEN\n  _savedeployconf VAULT_RENEW_TOKEN \"$VAULT_RENEW_TOKEN\"\n\n  _getdeployconf VAULT_KV_V2\n  _savedeployconf VAULT_KV_V2 \"$VAULT_KV_V2\"\n\n  _getdeployconf VAULT_TOKEN\n  if [ -z \"$VAULT_TOKEN\" ]; then\n    _err \"VAULT_TOKEN needs to be defined\"\n    return 1\n  fi\n  if [ -n \"$VAULT_SAVE_TOKEN\" ]; then\n    _savedeployconf VAULT_TOKEN \"$VAULT_TOKEN\"\n  fi\n\n  _migratedeployconf FABIO VAULT_FABIO_MODE\n\n  # JSON does not allow multiline strings.\n  # So replacing new-lines with \"\\n\" here\n  _ckey=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\\n/\\\\n/g' <\"$2\")\n  _ccert=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\\n/\\\\n/g' <\"$3\")\n  _cca=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\\n/\\\\n/g' <\"$4\")\n  _cfullchain=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\\n/\\\\n/g' <\"$5\")\n\n  export _H1=\"X-Vault-Token: $VAULT_TOKEN\"\n\n  if [ -n \"$VAULT_RENEW_TOKEN\" ]; then\n    URL=\"$VAULT_ADDR/v1/auth/token/renew-self\"\n    _info \"Renew the Vault token to default TTL\"\n    _response=$(_post \"\" \"$URL\")\n    if [ \"$?\" != \"0\" ]; then\n      _err \"Failed to renew the Vault token\"\n      return 1\n    fi\n    if echo \"$_response\" | grep -q '\"errors\":\\['; then\n      _err \"Failed to renew the Vault token: $_response\"\n      return 1\n    fi\n  fi\n\n  URL=\"$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain\"\n\n  if [ -n \"$VAULT_FABIO_MODE\" ]; then\n    _info \"Writing certificate and key to $URL in Fabio mode\"\n    if [ -n \"$VAULT_KV_V2\" ]; then\n      _response=$(_post \"{ \\\"data\\\": {\\\"cert\\\": \\\"$_cfullchain\\\", \\\"key\\\": \\\"$_ckey\\\"} }\" \"$URL\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error: $_response\"\n        return 1\n      fi\n    else\n      _response=$(_post \"{\\\"cert\\\": \\\"$_cfullchain\\\", \\\"key\\\": \\\"$_ckey\\\"}\" \"$URL\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error: $_response\"\n        return 1\n      fi\n    fi\n  else\n    if [ -n \"$VAULT_KV_V2\" ]; then\n      _info \"Writing certificate to $URL/cert.pem\"\n      _response=$(_post \"{\\\"data\\\": {\\\"value\\\": \\\"$_ccert\\\"}}\" \"$URL/cert.pem\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing cert.pem: $_response\"\n        return 1\n      fi\n\n      _info \"Writing key to $URL/cert.key\"\n      _response=$(_post \"{\\\"data\\\": {\\\"value\\\": \\\"$_ckey\\\"}}\" \"$URL/cert.key\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing cert.key: $_response\"\n        return 1\n      fi\n\n      _info \"Writing CA certificate to $URL/ca.pem\"\n      _response=$(_post \"{\\\"data\\\": {\\\"value\\\": \\\"$_cca\\\"}}\" \"$URL/ca.pem\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing ca.pem: $_response\"\n        return 1\n      fi\n\n      _info \"Writing full-chain certificate to $URL/fullchain.pem\"\n      _response=$(_post \"{\\\"data\\\": {\\\"value\\\": \\\"$_cfullchain\\\"}}\" \"$URL/fullchain.pem\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing fullchain.pem: $_response\"\n        return 1\n      fi\n    else\n      _info \"Writing certificate to $URL/cert.pem\"\n      _response=$(_post \"{\\\"value\\\": \\\"$_ccert\\\"}\" \"$URL/cert.pem\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing cert.pem: $_response\"\n        return 1\n      fi\n\n      _info \"Writing key to $URL/cert.key\"\n      _response=$(_post \"{\\\"value\\\": \\\"$_ckey\\\"}\" \"$URL/cert.key\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing cert.key: $_response\"\n        return 1\n      fi\n\n      _info \"Writing CA certificate to $URL/ca.pem\"\n      _response=$(_post \"{\\\"value\\\": \\\"$_cca\\\"}\" \"$URL/ca.pem\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing ca.pem: $_response\"\n        return 1\n      fi\n\n      _info \"Writing full-chain certificate to $URL/fullchain.pem\"\n      _response=$(_post \"{\\\"value\\\": \\\"$_cfullchain\\\"}\" \"$URL/fullchain.pem\")\n      if [ \"$?\" != \"0\" ]; then return 1; fi\n      if echo \"$_response\" | grep -q '\"errors\":\\['; then\n        _err \"Vault error writing fullchain.pem: $_response\"\n        return 1\n      fi\n    fi\n\n    # To make it compatible with the wrong ca path `chain.pem` which was used in former versions\n    if _contains \"$(_get \"$URL/chain.pem\")\" \"-----BEGIN CERTIFICATE-----\"; then\n      _err \"The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning\"\n      _info \"Updating CA certificate to $URL/chain.pem for backward compatibility\"\n      if [ -n \"$VAULT_KV_V2\" ]; then\n        _response=$(_post \"{\\\"data\\\": {\\\"value\\\": \\\"$_cca\\\"}}\" \"$URL/chain.pem\")\n        if [ \"$?\" != \"0\" ]; then return 1; fi\n        if echo \"$_response\" | grep -q '\"errors\":\\['; then\n          _err \"Vault error writing chain.pem: $_response\"\n          return 1\n        fi\n      else\n        _response=$(_post \"{\\\"value\\\": \\\"$_cca\\\"}\" \"$URL/chain.pem\")\n        if [ \"$?\" != \"0\" ]; then return 1; fi\n        if echo \"$_response\" | grep -q '\"errors\":\\['; then\n          _err \"Vault error writing chain.pem: $_response\"\n          return 1\n        fi\n      fi\n    fi\n  fi\n}\n"
  },
  {
    "path": "deploy/vault_cli.sh",
    "content": "#!/usr/bin/env sh\n\n# Here is a script to deploy cert to hashicorp vault\n# (https://www.vaultproject.io/)\n#\n# it requires the vault binary to be available in PATH, and the following\n# environment variables:\n#\n# VAULT_PREFIX - this contains the prefix path in vault\n# VAULT_ADDR - vault requires this to find your vault server\n# VAULT_SAVE_TOKEN - set to anything if you want to save the token\n# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying\n#\n# additionally, you need to ensure that VAULT_TOKEN is avialable or\n# `vault auth` has applied the appropriate authorization for the vault binary\n# to access the vault server\n\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nvault_cli_deploy() {\n\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  # validate required env vars\n  _getdeployconf VAULT_PREFIX\n  if [ -z \"$VAULT_PREFIX\" ]; then\n    _err \"VAULT_PREFIX needs to be defined (contains prefix path in vault)\"\n    return 1\n  fi\n  _savedeployconf VAULT_PREFIX \"$VAULT_PREFIX\"\n\n  _getdeployconf VAULT_ADDR\n  if [ -z \"$VAULT_ADDR\" ]; then\n    _err \"VAULT_ADDR needs to be defined (contains vault connection address)\"\n    return 1\n  fi\n  _savedeployconf VAULT_ADDR \"$VAULT_ADDR\"\n\n  _getdeployconf VAULT_SAVE_TOKEN\n  _savedeployconf VAULT_SAVE_TOKEN \"$VAULT_SAVE_TOKEN\"\n\n  _getdeployconf VAULT_RENEW_TOKEN\n  _savedeployconf VAULT_RENEW_TOKEN \"$VAULT_RENEW_TOKEN\"\n\n  _getdeployconf VAULT_TOKEN\n  if [ -z \"$VAULT_TOKEN\" ]; then\n    _err \"VAULT_TOKEN needs to be defined\"\n    return 1\n  fi\n  if [ -n \"$VAULT_SAVE_TOKEN\" ]; then\n    _savedeployconf VAULT_TOKEN \"$VAULT_TOKEN\"\n  fi\n\n  _migratedeployconf FABIO VAULT_FABIO_MODE\n\n  VAULT_CMD=$(command -v vault)\n  if [ ! $? ]; then\n    _err \"cannot find vault binary!\"\n    return 1\n  fi\n\n  if [ -n \"$VAULT_RENEW_TOKEN\" ]; then\n    _info \"Renew the Vault token to default TTL\"\n    if ! $VAULT_CMD token renew; then\n      _err \"Failed to renew the Vault token\"\n      return 1\n    fi\n  fi\n\n  if [ -n \"$VAULT_FABIO_MODE\" ]; then\n    _info \"Writing certificate and key to ${VAULT_PREFIX}/${_cdomain} in Fabio mode\"\n    $VAULT_CMD kv put \"${VAULT_PREFIX}/${_cdomain}\" cert=@\"$_cfullchain\" key=@\"$_ckey\" || return 1\n  else\n    _info \"Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem\"\n    $VAULT_CMD kv put \"${VAULT_PREFIX}/${_cdomain}/cert.pem\" value=@\"$_ccert\" || return 1\n    _info \"Writing key to ${VAULT_PREFIX}/${_cdomain}/cert.key\"\n    $VAULT_CMD kv put \"${VAULT_PREFIX}/${_cdomain}/cert.key\" value=@\"$_ckey\" || return 1\n    _info \"Writing CA certificate to ${VAULT_PREFIX}/${_cdomain}/ca.pem\"\n    $VAULT_CMD kv put \"${VAULT_PREFIX}/${_cdomain}/ca.pem\" value=@\"$_cca\" || return 1\n    _info \"Writing full-chain certificate to ${VAULT_PREFIX}/${_cdomain}/fullchain.pem\"\n    $VAULT_CMD kv put \"${VAULT_PREFIX}/${_cdomain}/fullchain.pem\" value=@\"$_cfullchain\" || return 1\n\n    # To make it compatible with the wrong ca path `chain.pem` which was used in former versions\n    if $VAULT_CMD kv get \"${VAULT_PREFIX}/${_cdomain}/chain.pem\" >/dev/null; then\n      _err \"The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning\"\n      _info \"Updating CA certificate to ${VAULT_PREFIX}/${_cdomain}/chain.pem for backward compatibility\"\n      $VAULT_CMD kv put \"${VAULT_PREFIX}/${_cdomain}/chain.pem\" value=@\"$_cca\" || return 1\n    fi\n  fi\n\n}\n"
  },
  {
    "path": "deploy/vsftpd.sh",
    "content": "#!/usr/bin/env sh\n\n#Here is a script to deploy cert to vsftpd server.\n\n#returns 0 means success, otherwise error.\n\n#DEPLOY_VSFTPD_CONF=\"/etc/vsftpd.conf\"\n#DEPLOY_VSFTPD_RELOAD=\"service vsftpd restart\"\n\n########  Public functions #####################\n\n#domain keyfile certfile cafile fullchain\nvsftpd_deploy() {\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _ssl_path=\"/etc/acme.sh/vsftpd\"\n  if ! mkdir -p \"$_ssl_path\"; then\n    _err \"Can not create folder:$_ssl_path\"\n    return 1\n  fi\n\n  _info \"Copying key and cert\"\n  _real_key=\"$_ssl_path/vsftpd.key\"\n  if ! cat \"$_ckey\" >\"$_real_key\"; then\n    _err \"Error: write key file to: $_real_key\"\n    return 1\n  fi\n  _real_fullchain=\"$_ssl_path/vsftpd.chain.pem\"\n  if ! cat \"$_cfullchain\" >\"$_real_fullchain\"; then\n    _err \"Error: write key file to: $_real_fullchain\"\n    return 1\n  fi\n\n  DEFAULT_VSFTPD_RELOAD=\"service vsftpd restart\"\n  _reload=\"${DEPLOY_VSFTPD_RELOAD:-$DEFAULT_VSFTPD_RELOAD}\"\n\n  if [ -z \"$IS_RENEW\" ]; then\n    DEFAULT_VSFTPD_CONF=\"/etc/vsftpd.conf\"\n    _vsftpd_conf=\"${DEPLOY_VSFTPD_CONF:-$DEFAULT_VSFTPD_CONF}\"\n    if [ ! -f \"$_vsftpd_conf\" ]; then\n      if [ -z \"$DEPLOY_VSFTPD_CONF\" ]; then\n        _err \"vsftpd conf is not found, please define DEPLOY_VSFTPD_CONF\"\n        return 1\n      else\n        _err \"It seems that the specified vsftpd conf is not valid, please check.\"\n        return 1\n      fi\n    fi\n    if [ ! -w \"$_vsftpd_conf\" ]; then\n      _err \"The file $_vsftpd_conf is not writable, please change the permission.\"\n      return 1\n    fi\n    _backup_conf=\"$DOMAIN_BACKUP_PATH/vsftpd.conf.bak\"\n    _info \"Backup $_vsftpd_conf to $_backup_conf\"\n    cp \"$_vsftpd_conf\" \"$_backup_conf\"\n\n    _info \"Modify vsftpd conf: $_vsftpd_conf\"\n    if _setopt \"$_vsftpd_conf\" \"rsa_cert_file\" \"=\" \"$_real_fullchain\" &&\n      _setopt \"$_vsftpd_conf\" \"rsa_private_key_file\" \"=\" \"$_real_key\" &&\n      _setopt \"$_vsftpd_conf\" \"ssl_enable\" \"=\" \"YES\"; then\n      _info \"Set config success!\"\n    else\n      _err \"Config vsftpd server error, please report bug to us.\"\n      _info \"Restoring vsftpd conf\"\n      if cat \"$_backup_conf\" >\"$_vsftpd_conf\"; then\n        _info \"Restore conf success\"\n        eval \"$_reload\"\n      else\n        _err \"Oops, error restore vsftpd conf, please report bug to us.\"\n      fi\n      return 1\n    fi\n  fi\n\n  _info \"Run reload: $_reload\"\n  if eval \"$_reload\"; then\n    _info \"Reload success!\"\n    if [ \"$DEPLOY_VSFTPD_CONF\" ]; then\n      _savedomainconf DEPLOY_VSFTPD_CONF \"$DEPLOY_VSFTPD_CONF\"\n    else\n      _cleardomainconf DEPLOY_VSFTPD_CONF\n    fi\n    if [ \"$DEPLOY_VSFTPD_RELOAD\" ]; then\n      _savedomainconf DEPLOY_VSFTPD_RELOAD \"$DEPLOY_VSFTPD_RELOAD\"\n    else\n      _cleardomainconf DEPLOY_VSFTPD_RELOAD\n    fi\n    return 0\n  else\n    _err \"Reload error, restoring\"\n    if cat \"$_backup_conf\" >\"$_vsftpd_conf\"; then\n      _info \"Restore conf success\"\n      eval \"$_reload\"\n    else\n      _err \"Oops, error restore vsftpd conf, please report bug to us.\"\n    fi\n    return 1\n  fi\n\n}\n"
  },
  {
    "path": "deploy/zyxel_gs1900.sh",
    "content": "#!/usr/bin/env sh\n\n# Deploy certificates to Zyxel GS1900 series switches\n#\n# This script uses the https web administration interface in order\n# to upload updated certificates to Zyxel GS1900 series switches.\n# Only a few models have been tested but untested switches from the\n# same model line may work as well. If you test and confirm a switch\n# as working please submit a pull request updating this compatibility\n# list!\n#\n# Known Issues:\n#   1. This is a consumer grade switch and is a bit underpowered\n#      the longer the RSA key size the slower your switch web UI\n#      will be. RSA 2048 will work, RSA 4096 will work but you may\n#      experience performance problems.\n#   2. You must use RSA certificates. The switch will reject EC-256\n#      and EC-384 certificates in firmware 2.80\n#      See: https://community.zyxel.com/en/discussion/21506/bug-cannot-import-ssl-cert-on-gs1900-8-and-gs1900-24e-firmware-v2-80/\n#\n# Current GS1900 Switch Compatibility:\n#   GS1900-8    - Working as of firmware V2.80\n#   GS1900-8HP  - Untested\n#   GS1900-10HP - Untested\n#   GS1900-16   - Untested\n#   GS1900-24   - Untested\n#   GS1900-24E  - Working as of firmware V2.80\n#   GS1900-24EP - Untested\n#   GS1900-24HP - Untested\n#   GS1900-48   - Untested\n#   GS1900-48HP - Untested\n#\n# Prerequisite Setup Steps:\n#   1. Install at least firmware V2.80 on your switch\n#   2. Enable HTTPS web management on your switch\n#\n# Usage:\n#   1. Ensure the switch has firmware V2.80 or later.\n#   2. Ensure the switch has HTTPS management enabled.\n#   3. Set the appropriate environment variables for your environment.\n#\n#      DEPLOY_ZYXEL_SWITCH          - The switch hostname. (Default: _cdomain)\n#      DEPLOY_ZYXEL_SWITCH_USER     - The webadmin user. (Default: admin)\n#      DEPLOY_ZYXEL_SWITCH_PASSWORD - The webadmin password for the switch.\n#      DEPLOY_ZYXEL_SWITCH_REBOOT   - If \"1\" reboot after update. (Default: \"0\")\n#\n#   4. Run the deployment plugin:\n#      acme.sh --deploy --deploy-hook zyxel_gs1900 -d example.com\n#\n# returns 0 means success, otherwise error.\n\n#domain keyfile certfile cafile fullchain\nzyxel_gs1900_deploy() {\n  _zyxel_gs1900_minimum_firmware_version=\"v2.80\"\n\n  _cdomain=\"$1\"\n  _ckey=\"$2\"\n  _ccert=\"$3\"\n  _cca=\"$4\"\n  _cfullchain=\"$5\"\n\n  _debug _cdomain \"$_cdomain\"\n  _debug2 _ckey \"$_ckey\"\n  _debug _ccert \"$_ccert\"\n  _debug _cca \"$_cca\"\n  _debug _cfullchain \"$_cfullchain\"\n\n  _getdeployconf DEPLOY_ZYXEL_SWITCH\n  _getdeployconf DEPLOY_ZYXEL_SWITCH_USER\n  _getdeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD\n  _getdeployconf DEPLOY_ZYXEL_SWITCH_REBOOT\n\n  if [ -z \"$DEPLOY_ZYXEL_SWITCH\" ]; then\n    DEPLOY_ZYXEL_SWITCH=\"$_cdomain\"\n  fi\n\n  if [ -z \"$DEPLOY_ZYXEL_SWITCH_USER\" ]; then\n    DEPLOY_ZYXEL_SWITCH_USER=\"admin\"\n  fi\n\n  if [ -z \"$DEPLOY_ZYXEL_SWITCH_PASSWORD\" ]; then\n    DEPLOY_ZYXEL_SWITCH_PASSWORD=\"1234\"\n  fi\n\n  if [ -z \"$DEPLOY_ZYXEL_SWITCH_REBOOT\" ]; then\n    DEPLOY_ZYXEL_SWITCH_REBOOT=\"0\"\n  fi\n\n  _savedeployconf DEPLOY_ZYXEL_SWITCH \"$DEPLOY_ZYXEL_SWITCH\"\n  _savedeployconf DEPLOY_ZYXEL_SWITCH_USER \"$DEPLOY_ZYXEL_SWITCH_USER\"\n  _savedeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD \"$DEPLOY_ZYXEL_SWITCH_PASSWORD\"\n  _savedeployconf DEPLOY_ZYXEL_SWITCH_REBOOT \"$DEPLOY_ZYXEL_SWITCH_REBOOT\"\n\n  _debug DEPLOY_ZYXEL_SWITCH \"$DEPLOY_ZYXEL_SWITCH\"\n  _debug DEPLOY_ZYXEL_SWITCH_USER \"$DEPLOY_ZYXEL_SWITCH_USER\"\n  _secure_debug DEPLOY_ZYXEL_SWITCH_PASSWORD \"$DEPLOY_ZYXEL_SWITCH_PASSWORD\"\n  _debug DEPLOY_ZYXEL_SWITCH_REBOOT \"$DEPLOY_ZYXEL_SWITCH_REBOOT\"\n\n  _zyxel_switch_base_uri=\"https://${DEPLOY_ZYXEL_SWITCH}\"\n\n  _info \"Beginning to deploy to a Zyxel GS1900 series switch at ${_zyxel_switch_base_uri}.\"\n  _zyxel_gs1900_deployment_precheck || return $?\n\n  _zyxel_gs1900_should_update\n  if [ \"$?\" != \"0\" ]; then\n    _info \"The switch already has our certificate installed. No update required.\"\n    return 0\n  else\n    _info \"The switch does not yet have our certificate installed.\"\n  fi\n\n  _info \"Logging into the switch web interface.\"\n  _zyxel_gs1900_login || return $?\n\n  _info \"Validating the switch is compatible with this deployment process.\"\n  _zyxel_gs1900_validate_device_compatibility || return $?\n\n  _info \"Uploading the certificate.\"\n  _zyxel_gs1900_upload_certificate || return $?\n\n  if [ \"$DEPLOY_ZYXEL_SWITCH_REBOOT\" = \"1\" ]; then\n    _info \"Rebooting the switch.\"\n    _zyxel_gs1900_trigger_reboot || return $?\n  fi\n\n  return 0\n}\n\n_zyxel_gs1900_deployment_precheck() {\n  # Initialize the keylength if it isn't already\n  if [ -z \"$Le_Keylength\" ]; then\n    Le_Keylength=\"\"\n  fi\n\n  if _isEccKey \"$Le_Keylength\"; then\n    _info \"Warning: Zyxel GS1900 switches are not currently known to work with ECC keys!\"\n    _info \"You can continue, but your switch may reject your key.\"\n  elif [ -n \"$Le_Keylength\" ] && [ \"$Le_Keylength\" -gt \"2048\" ]; then\n    _info \"Warning: Your RSA key length is greater than 2048!\"\n    _info \"You can continue, but you may experience performance issues in the web administration interface.\"\n  fi\n\n  # Check the server for some common failure modes prior to authentication and certificate upload in order to avoid\n  # sending a certificate when we may not want to.\n  test_login_response=$(_post \"username=test&password=test&login=true;\" \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html\" '' \"POST\" \"application/x-www-form-urlencoded\" 2>&1)\n  test_login_page_exitcode=\"$?\"\n  _debug3 \"Test Login Response: ${test_login_response}\"\n  if [ \"$test_login_page_exitcode\" -ne \"0\" ]; then\n    if { [ \"${ACME_USE_WGET:-0}\" = \"0\" ] && [ \"$test_login_page_exitcode\" = \"60\" ]; } || { [ \"${ACME_USE_WGET:-0}\" = \"1\" ] && [ \"$test_login_page_exitcode\" = \"5\" ]; }; then\n      _err \"The SSL certificate at $_zyxel_switch_base_uri could not be validated.\"\n      _err \"Please double check your hostname, port, and that you are actually connecting to your switch.\"\n      _err \"If the problem persists then please ensure that the certificate is not self-signed, has not\"\n      _err \"expired, and matches the switch hostname. If you expect validation to fail then you can disable\"\n      _err \"certificate validation by running with --insecure.\"\n      return 1\n    elif [ \"${ACME_USE_WGET:-0}\" = \"0\" ] && [ \"$test_login_page_exitcode\" = \"56\" ]; then\n      _debug3 \"Intentionally ignore curl exit code 56 in our precheck\"\n    else\n      _err \"Failed to submit the initial login attempt to $_zyxel_switch_base_uri.\"\n      return 1\n    fi\n  fi\n}\n\n_zyxel_gs1900_login() {\n  # Login to the switch and set the appropriate auth cookie in _H1\n  username_encoded=$(printf \"%s\" \"$DEPLOY_ZYXEL_SWITCH_USER\" | _url_encode)\n  password_encoded=$(_zyxel_gs1900_password_obfuscate \"$DEPLOY_ZYXEL_SWITCH_PASSWORD\" | _url_encode)\n\n  login_response=$(_post \"username=${username_encoded}&password=${password_encoded}&login=true;\" \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html\" '' \"POST\" \"application/x-www-form-urlencoded\" | tr -d '\\n')\n  auth_response=$(_post \"authId=${login_response}&login_chk=true\" \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html\" '' \"POST\" \"application/x-www-form-urlencoded\" | tr -d '\\n')\n  if [ \"$auth_response\" != \"OK\" ]; then\n    _err \"Login failed due to invalid credentials.\"\n    _err \"Please double check the configured username and password and try again.\"\n    return 1\n  fi\n\n  sessionid=$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';')\n  _secure_debug2 \"sessionid\" \"$sessionid\"\n\n  export _H1=\"Cookie: $sessionid\"\n  _secure_debug2 \"_H1\" \"$_H1\"\n\n  return 0\n}\n\n_zyxel_gs1900_validate_device_compatibility() {\n  # Check the switches model and firmware version and throw errors\n  # if this script isn't compatible.\n  device_info_html=$(_get \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12\" | tr -d '\\n')\n\n  model_name=$(_zyxel_gs1900_get_model \"$device_info_html\")\n  _debug2 \"model_name\" \"$model_name\"\n  if [ -z \"$model_name\" ]; then\n    _err \"Could not find the switch model name.\"\n    _err \"Please re-run with --debug and report a bug.\"\n    return $?\n  fi\n\n  if ! expr \"$model_name\" : \"GS1900-\" >/dev/null; then\n    _err \"Switch is an unsupported model: $model_name\"\n    return 1\n  fi\n\n  firmware_version=$(_zyxel_gs1900_get_firmware_version \"$device_info_html\")\n  _debug2 \"firmware_version\" \"$firmware_version\"\n  if [ -z \"$firmware_version\" ]; then\n    _err \"Could not find the switch firmware version.\"\n    _err \"Please re-run with --debug and report a bug.\"\n    return $?\n  fi\n\n  _debug2 \"_zyxel_gs1900_minimum_firmware_version\" \"$_zyxel_gs1900_minimum_firmware_version\"\n  minimum_major_version=$(_zyxel_gs1900_parse_major_version \"$_zyxel_gs1900_minimum_firmware_version\")\n  _debug2 \"minimum_major_version\" \"$minimum_major_version\"\n  minimum_minor_version=$(_zyxel_gs1900_parse_minor_version \"$_zyxel_gs1900_minimum_firmware_version\")\n  _debug2 \"minimum_minor_version\" \"$minimum_minor_version\"\n\n  _debug2 \"firmware_version\" \"$firmware_version\"\n  firmware_major_version=$(_zyxel_gs1900_parse_major_version \"$firmware_version\")\n  _debug2 \"firmware_major_version\" \"$firmware_major_version\"\n  firmware_minor_version=$(_zyxel_gs1900_parse_minor_version \"$firmware_version\")\n  _debug2 \"firmware_minor_version\" \"$firmware_minor_version\"\n\n  _ret=0\n  if [ \"$firmware_major_version\" -lt \"$minimum_major_version\" ]; then\n    _ret=1\n  elif [ \"$firmware_major_version\" -eq \"$minimum_major_version\" ] && [ \"$firmware_minor_version\" -lt \"$minimum_minor_version\" ]; then\n    _ret=1\n  fi\n\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version.\"\n  fi\n\n  return $?\n}\n\n_zyxel_gs1900_should_update() {\n  # Get the remote certificate serial number\n  _remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect \"${DEPLOY_ZYXEL_SWITCH}:443\" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')\n  _debug3 \"_remote_cert\" \"$_remote_cert\"\n\n  _remote_cert_serial=$(printf \"%s\" \"${_remote_cert}\" | ${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial)\n  _debug2 \"_remote_cert_serial\" \"$_remote_cert_serial\"\n\n  # Get our certificate serial number\n  _our_cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial <\"${_ccert}\")\n  _debug2 \"_our_cert_serial\" \"$_our_cert_serial\"\n\n  [ \"${_remote_cert_serial}\" != \"${_our_cert_serial}\" ]\n}\n\n_zyxel_gs1900_upload_certificate() {\n  # Generate a PKCS12 certificate with a temporary password since the web interface\n  # requires a password be present. Then upload that certificate.\n  temp_cert_password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 64)\n  _secure_debug2 \"temp_cert_password\" \"$temp_cert_password\"\n\n  temp_pkcs12=\"$(_mktemp)\"\n  _debug2 \"temp_pkcs12\" \"$temp_pkcs12\"\n  _toPkcs \"$temp_pkcs12\" \"$_ckey\" \"$_ccert\" \"$_cca\" \"$temp_cert_password\"\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Failed to generate a pkcs12 certificate.\"\n    _err \"Please re-run with --debug and report a bug.\"\n\n    # ensure the temporary certificate file is cleaned up\n    [ -f \"${temp_pkcs12}\" ] && rm -f \"${temp_pkcs12}\"\n\n    return $?\n  fi\n\n  # Load the upload page\n  upload_page_html=$(_get \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5914\" | tr -d '\\n')\n\n  # Get the first instance of XSSID from the upload page\n  form_xss_value=$(printf \"%s\" \"$upload_page_html\" | _egrep_o 'name=\"XSSID\"\\s*value=\"[^\"]+\"' | sed 's/^.*=\"\\([^\"]\\{1,\\}\\)\"$/\\1/g' | head -n 1)\n  _secure_debug2 \"form_xss_value\" \"$form_xss_value\"\n\n  _info \"Generating the certificate upload request\"\n  upload_post_request=\"$(_mktemp)\"\n  upload_post_boundary=\"---------------------------$(date +%Y%m%d%H%M%S)\"\n\n  {\n    printf -- \"--%s\\r\\n\" \"${upload_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"XSSID\\\"\\r\\n\\r\\n%s\\r\\n\" \"${form_xss_value}\"\n    printf -- \"--%s\\r\\n\" \"${upload_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"http_file\\\"; filename=\\\"temp_pkcs12.pfx\\\"\\r\\n\"\n    printf \"Content-Type: application/pkcs12\\r\\n\\r\\n\"\n    cat \"${temp_pkcs12}\"\n    printf \"\\r\\n\"\n    printf -- \"--%s\\r\\n\" \"${upload_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"pwd\\\"\\r\\n\\r\\n%s\\r\\n\" \"${temp_cert_password}\"\n    printf -- \"--%s\\r\\n\" \"${upload_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"cmd\\\"\\r\\n\\r\\n%s\\r\\n\" \"31\"\n    printf -- \"--%s\\r\\n\" \"${upload_post_boundary}\"\n    printf \"Content-Disposition: form-data; name=\\\"sysSubmit\\\"\\r\\n\\r\\n%s\\r\\n\" \"Import\"\n    printf -- \"--%s--\\r\\n\" \"${upload_post_boundary}\"\n  } >\"${upload_post_request}\"\n\n  _info \"Upload certificate to the switch\"\n\n  # Unfortunately we cannot rely upon the switch response across switch models\n  # to return a consistent body return - so we cannot inspect the result of this\n  # upload to determine success.\n  upload_response=$(_zyxel_upload_pkcs12 \"${upload_post_request}\" \"${upload_post_boundary}\" 2>&1)\n  _debug3 \"Upload response: ${upload_response}\"\n  rm \"${upload_post_request}\"\n\n  # Pause for a few seconds to give the switch a chance to process the certificate\n  # For some reason I've found this to be necessary on my GS1900-24E\n  _debug2 \"Waiting 4 seconds for the switch to process the newly uploaded certificate.\"\n  sleep \"4\"\n\n  # Check to see whether or not our update was successful\n  _ret=0\n  _zyxel_gs1900_should_update\n  if [ \"$?\" != \"0\" ]; then\n    _info \"The certificate was updated successfully\"\n  else\n    _ret=1\n    _err \"The certificate upload does not appear to have worked.\"\n    _err \"The remote certificate does not match the certificate we tried to upload.\"\n    _err \"Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug.\"\n  fi\n\n  # ensure the temporary files are cleaned up\n  [ -f \"${temp_pkcs12}\" ] && rm -f \"${temp_pkcs12}\"\n\n  return $_ret\n}\n\n# make the certificate upload request using either\n# --data binary with @ for file access in CURL\n# or using --post-file for wget to ensure we upload\n# the pkcs12 without getting tripped up on null bytes\n#\n# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker]\n_zyxel_upload_pkcs12() {\n  bodyfilename=\"$1\"\n  multipartformmarker=\"$2\"\n  _post_url=\"${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi\"\n  httpmethod=\"POST\"\n  _postContentType=\"multipart/form-data; boundary=${multipartformmarker}\"\n\n  if [ -z \"$httpmethod\" ]; then\n    httpmethod=\"POST\"\n  fi\n  _debug $httpmethod\n  _debug \"_post_url\" \"$_post_url\"\n  _debug2 \"bodyfilename\" \"$bodyfilename\"\n  _debug2 \"_postContentType\" \"$_postContentType\"\n\n  _inithttp\n\n  if [ \"$_ACME_CURL\" ] && [ \"${ACME_USE_WGET:-0}\" = \"0\" ]; then\n    _CURL=\"$_ACME_CURL\"\n    if [ \"$HTTPS_INSECURE\" ]; then\n      _CURL=\"$_CURL --insecure  \"\n    fi\n    if [ \"$httpmethod\" = \"HEAD\" ]; then\n      _CURL=\"$_CURL -I  \"\n    fi\n    _debug \"_CURL\" \"$_CURL\"\n\n    response=\"$($_CURL --user-agent \"$USER_AGENT\" -X $httpmethod -H \"$_H1\" -H \"$_H2\" -H \"$_H3\" -H \"$_H4\" -H \"$_H5\" --data-binary \"@${bodyfilename}\" \"$_post_url\")\"\n\n    _ret=\"$?\"\n    if [ \"$_ret\" != \"0\" ]; then\n      _err \"Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret\"\n      if [ \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"2\" ]; then\n        _err \"Here is the curl dump log:\"\n        _err \"$(cat \"$_CURL_DUMP\")\"\n      fi\n    fi\n  elif [ \"$_ACME_WGET\" ]; then\n    _WGET=\"$_ACME_WGET\"\n    if [ \"$HTTPS_INSECURE\" ]; then\n      _WGET=\"$_WGET --no-check-certificate \"\n    fi\n    _debug \"_WGET\" \"$_WGET\"\n\n    response=\"$($_WGET -S -O - --user-agent=\"$USER_AGENT\" --header \"$_H5\" --header \"$_H4\" --header \"$_H3\" --header \"$_H2\" --header \"$_H1\" --post-file=\"${bodyfilename}\" \"$_post_url\" 2>\"$HTTP_HEADER\")\"\n\n    _ret=\"$?\"\n    if [ \"$_ret\" = \"8\" ]; then\n      _ret=0\n      _debug \"wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later.\"\n    fi\n    if [ \"$_ret\" != \"0\" ]; then\n      _err \"Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret\"\n    fi\n    if _contains \"$_WGET\" \" -d \"; then\n      # Demultiplex wget debug output\n      cat \"$HTTP_HEADER\" >&2\n      _sed_i '/^[^ ][^ ]/d; /^ *$/d' \"$HTTP_HEADER\"\n    fi\n    # remove leading whitespaces from header to match curl format\n    _sed_i 's/^  //g' \"$HTTP_HEADER\"\n  else\n    _ret=\"$?\"\n    _err \"Neither curl nor wget have been found, cannot make $httpmethod request.\"\n  fi\n  _debug \"_ret\" \"$_ret\"\n  printf \"%s\" \"$response\"\n  return $_ret\n}\n\n_zyxel_gs1900_trigger_reboot() {\n  # Trigger a reboot via the management reboot page in the web ui\n  reboot_page_html=$(_get \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888\" | tr -d '\\n')\n  reboot_xss_value=$(printf \"%s\" \"$reboot_page_html\" | _egrep_o 'name=\"XSSID\"\\s*value=\"[^\"]+\"' | sed 's/^.*=\"\\([^\"]\\{1,\\}\\)\"$/\\1/g')\n  _secure_debug2 \"reboot_xss_value\" \"$reboot_xss_value\"\n\n  reboot_response_html=$(_post \"XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot\" \"${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi\" '' \"POST\" \"application/x-www-form-urlencoded\")\n  reboot_message=$(printf \"%s\" \"$reboot_response_html\" | tr -d '\\t\\r\\n\\v\\f' | _egrep_o \"Rebooting now...\")\n\n  if [ -z \"$reboot_message\" ]; then\n    _err \"Failed to trigger switch reboot!\"\n    return 1\n  fi\n\n  return 0\n}\n\n# password\n_zyxel_gs1900_password_obfuscate() {\n  # Return the password obfuscated via the same method used by the\n  # switch's web UI login process\n  echo \"$1\" | awk '{\n    encoded = \"\";\n    password = $1;\n    allowed = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n    len = length($1);\n    pwi = length($1);\n\n    for (i=1; i <= (321 - pwi); i++)\n    {\n      if (0 == i % 5 && pwi > 0)\n      {\n        encoded = (encoded)(substr(password, pwi--, 1));\n      }\n      else if (i == 123)\n      {\n        if (len < 10)\n        {\n          encoded = (encoded)(0);\n        }\n        else\n        {\n          encoded = (encoded)(int(len / 10));\n        }\n      }\n      else if (i == 289)\n      {\n        encoded = (encoded)(len % 10)\n      }\n      else\n      {\n        encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1))\n      }\n    }\n    printf(\"%s\", encoded);\n  }'\n}\n\n# html label\n_zyxel_html_table_lookup() {\n  # Look up a value in the html representing the status page of the switch\n  # when provided with the html of the page and the label (i.e. \"Model Name:\")\n  html=\"$1\"\n  label=$(printf \"%s\" \"$2\" | tr -d ' ')\n  lookup_result=$(printf \"%s\" \"$html\" | tr -d \"\\t\\r\\n\\v\\f\" | sed 's/<tr>/\\n<tr>/g' | sed 's/<td[^>]*>/<td>/g' | tr -d ' ' | grep -i \"$label\" | sed \"s/<tr><td>$label<\\/td><td>\\([^<]\\{1,\\}\\)<\\/td><\\/tr>/\\1/i\")\n  printf \"%s\" \"$lookup_result\"\n  return 0\n}\n\n# html\n_zyxel_gs1900_get_model() {\n  html=\"$1\"\n  model_name=$(_zyxel_html_table_lookup \"$html\" \"Model Name:\")\n  printf \"%s\" \"$model_name\"\n}\n\n# html\n_zyxel_gs1900_get_firmware_version() {\n  html=\"$1\"\n  firmware_version=$(_zyxel_html_table_lookup \"$html\" \"Firmware Version:\" | _egrep_o \"V[^.]+.[^(]+\")\n  printf \"%s\" \"$firmware_version\"\n}\n\n# version_number\n_zyxel_gs1900_parse_major_version() {\n  printf \"%s\" \"$1\" | sed 's/^V\\([0-9]\\{1,\\}\\).\\{1,\\}$/\\1/gi'\n}\n\n# version_number\n_zyxel_gs1900_parse_minor_version() {\n  printf \"%s\" \"$1\" | sed 's/^.\\{1,\\}\\.\\([0-9]\\{1,\\}\\)$/\\1/gi'\n}\n"
  },
  {
    "path": "dnsapi/README.md",
    "content": "# How to use DNS API\nDNS api usage:\n\n\nhttps://github.com/acmesh-official/acme.sh/wiki/dnsapi\n\n"
  },
  {
    "path": "dnsapi/dns_1984hosting.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_1984hosting_info='1984.hosting\nDomains: 1984.is\nSite: 1984.hosting\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_1984hosting\nOptions:\n One984HOSTING_Username Username\n One984HOSTING_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2851\nAuthor: Adrian Fedoreanu\n'\n\n######## Public functions #####################\n\n# Usage: dns_1984hosting_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Add a text record.\ndns_1984hosting_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Add TXT record using 1984Hosting.\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _1984hosting_login; then\n    _err \"1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone.\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain '$fulldomain'.\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Add TXT record $fulldomain with value '$txtvalue'.\"\n  value=\"$(printf '%s' \"$txtvalue\" | _url_encode)\"\n  url=\"https://1984.hosting/domains/entry/\"\n\n  postdata=\"entry=new\"\n  postdata=\"$postdata&type=TXT\"\n  postdata=\"$postdata&ttl=900\"\n  postdata=\"$postdata&zone=$_domain\"\n  postdata=\"$postdata&host=$_sub_domain\"\n  postdata=\"$postdata&rdata=%22$value%22\"\n  _debug2 postdata \"$postdata\"\n\n  _authpost \"$postdata\" \"$url\"\n  if _contains \"$_response\" '\"haserrors\": true'; then\n    _err \"1984Hosting failed to add TXT record for $_sub_domain bad RC from _post.\"\n    return 1\n  elif _contains \"$_response\" \"html>\"; then\n    _err \"1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file.\"\n    return 1\n  elif _contains \"$_response\" '\"auth\": false'; then\n    _err \"1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie.\"\n    return 1\n  fi\n\n  _info \"Added acme challenge TXT record for $fulldomain at 1984Hosting.\"\n  return 0\n}\n\n# Usage: fulldomain txtvalue\n# Remove the txt record after validation.\ndns_1984hosting_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Delete TXT record using 1984Hosting.\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _1984hosting_login; then\n    _err \"1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone.\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain '$fulldomain'.\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug \"Delete $fulldomain TXT record.\"\n\n  url=\"https://1984.hosting/domains\"\n  if ! _get_zone_id \"$url\" \"$_domain\"; then\n    _err \"Invalid zone '$_domain'.\"\n    return 1\n  fi\n\n  _htmlget \"$url/$_zone_id\" \"$txtvalue\"\n  entry_id=\"$(echo \"$_response\" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')\"\n  _debug2 entry_id \"$entry_id\"\n  if [ -z \"$entry_id\" ]; then\n    _err \"Error getting TXT entry_id for $1.\"\n    return 1\n  fi\n\n  _authpost \"entry=$entry_id\" \"$url/delentry/\"\n  if ! _contains \"$_response\" '\"ok\": true'; then\n    _err \"1984Hosting failed to delete TXT record for $entry_id bad RC from _post.\"\n    return 1\n  fi\n\n  _info \"Deleted acme challenge TXT record for $fulldomain at 1984Hosting.\"\n  return 0\n}\n\n####################  Private functions below ##################################\n_1984hosting_login() {\n  if ! _check_credentials; then return 1; fi\n\n  if _check_cookies; then\n    _debug \"Already logged in.\"\n    return 0\n  fi\n\n  _debug \"Login to 1984Hosting as user $One984HOSTING_Username.\"\n  username=$(printf '%s' \"$One984HOSTING_Username\" | _url_encode)\n  password=$(printf '%s' \"$One984HOSTING_Password\" | _url_encode)\n  url=\"https://1984.hosting/api/auth/\"\n\n  _get \"https://1984.hosting/accounts/login/\" | grep \"csrfmiddlewaretoken\"\n  csrftoken=\"$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')\"\n  sessionid=\"$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _egrep_o 'cookie1984nammnamm=[^;]*;' | tr -d ';')\"\n\n  if [ -z \"$csrftoken\" ] || [ -z \"$sessionid\" ]; then\n    _err \"One or more cookies are empty: '$csrftoken', '$sessionid'.\"\n    return 1\n  fi\n\n  export _H1=\"Cookie: $csrftoken; $sessionid\"\n  export _H2=\"Referer: https://1984.hosting/accounts/login/\"\n  csrf_header=$(echo \"$csrftoken\" | sed 's/csrftoken=//' | _head_n 1)\n  export _H3=\"X-CSRFToken: $csrf_header\"\n\n  response=\"$(_post \"username=$username&password=$password&otpkey=\" $url)\"\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  _debug2 response \"$response\"\n\n  if _contains \"$response\" '\"loggedin\": true'; then\n    One984HOSTING_SESSIONID_COOKIE=\"$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _egrep_o 'cookie1984nammnamm=[^;]*;' | tr -d ';')\"\n    One984HOSTING_CSRFTOKEN_COOKIE=\"$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')\"\n    export One984HOSTING_SESSIONID_COOKIE\n    export One984HOSTING_CSRFTOKEN_COOKIE\n    _saveaccountconf_mutable One984HOSTING_Username \"$One984HOSTING_Username\"\n    _saveaccountconf_mutable One984HOSTING_Password \"$One984HOSTING_Password\"\n    _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE \"$One984HOSTING_SESSIONID_COOKIE\"\n    _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE \"$One984HOSTING_CSRFTOKEN_COOKIE\"\n    return 0\n  fi\n  return 1\n}\n\n_check_credentials() {\n  One984HOSTING_Username=\"${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}\"\n  One984HOSTING_Password=\"${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}\"\n  if [ -z \"$One984HOSTING_Username\" ] || [ -z \"$One984HOSTING_Password\" ]; then\n    One984HOSTING_Username=\"\"\n    One984HOSTING_Password=\"\"\n    _clearaccountconf_mutable One984HOSTING_Username\n    _clearaccountconf_mutable One984HOSTING_Password\n    _err \"You haven't specified 1984Hosting username or password yet.\"\n    _err \"Please export as One984HOSTING_Username / One984HOSTING_Password and try again.\"\n    return 1\n  fi\n  return 0\n}\n\n_check_cookies() {\n  One984HOSTING_SESSIONID_COOKIE=\"${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}\"\n  One984HOSTING_CSRFTOKEN_COOKIE=\"${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}\"\n  if [ -z \"$One984HOSTING_SESSIONID_COOKIE\" ] || [ -z \"$One984HOSTING_CSRFTOKEN_COOKIE\" ]; then\n    _debug \"No cached cookie(s) found.\"\n    return 1\n  fi\n\n  _authget \"https://1984.hosting/api/auth/\"\n  if _contains \"$_response\" '\"ok\": true'; then\n    _debug \"Cached cookies still valid.\"\n    return 0\n  fi\n\n  _debug \"Cached cookies no longer valid. Clearing cookies.\"\n  One984HOSTING_SESSIONID_COOKIE=\"\"\n  One984HOSTING_CSRFTOKEN_COOKIE=\"\"\n  _clearaccountconf_mutable One984HOSTING_SESSIONID_COOKIE\n  _clearaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE\n  return 1\n}\n\n# _acme-challenge.www.domain.com\n# Returns\n#  _sub_domain=_acme-challenge.www\n#  _domain=domain.com\n_get_root() {\n  domain=\"$1\"\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    # not valid\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    _authget \"https://1984.hosting/domains/zonestatus/$h/?cached=no\"\n    if _contains \"$_response\" '\"ok\": true'; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n# Usage: _get_zone_id url domain.com\n# Returns zone id for domain.com\n_get_zone_id() {\n  url=$1\n  domain=$2\n  _htmlget \"$url\" \"$domain\"\n  _zone_id=\"$(echo \"$_response\" | _egrep_o 'zone\\/[0-9]+' | _head_n 1)\"\n  _debug2 _zone_id \"$_zone_id\"\n  if [ -z \"$_zone_id\" ]; then\n    _err \"Error getting _zone_id for $2.\"\n    return 1\n  fi\n  return 0\n}\n\n# Add extra headers to request\n_authget() {\n  export _H1=\"Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE\"\n  _response=$(_get \"$1\" | _normalizeJson)\n  _debug2 _response \"$_response\"\n}\n\n# Truncate huge HTML response\n_htmlget() {\n  export _H1=\"Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE\"\n  _response=$(_get \"$1\" | grep \"$2\")\n  if _contains \"$_response\" \"@$2\"; then\n    _response=$(echo \"$_response\" | grep -v \"[@]\" | _head_n 1)\n  fi\n  _debug2 _response \"$_response\"\n}\n\n# Add extra headers to request\n_authpost() {\n  url=\"https://1984.hosting/domains\"\n  _get_zone_id \"$url\" \"$_domain\"\n  csrf_header=\"$(echo \"$One984HOSTING_CSRFTOKEN_COOKIE\" | _egrep_o \"=[^=][0-9a-zA-Z]*\" | tr -d \"=\")\"\n  export _H1=\"Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE\"\n  export _H2=\"Referer: https://1984.hosting/domains/$_zone_id\"\n  export _H3=\"X-CSRFToken: $csrf_header\"\n  _response=\"$(_post \"$1\" \"$2\" | _normalizeJson)\"\n  _debug2 _response \"$_response\"\n}\n"
  },
  {
    "path": "dnsapi/dns_acmedns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_acmedns_info='acme-dns Server API\n The acme-dns is a limited DNS server with RESTful API to handle ACME DNS challenges.\nSite: github.com/joohoi/acme-dns\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_acmedns\nOptions:\n ACMEDNS_USERNAME Username. Optional.\n ACMEDNS_PASSWORD Password. Optional.\n ACMEDNS_SUBDOMAIN Subdomain. Optional.\n ACMEDNS_BASE_URL API endpoint. Default: \"https://auth.acme-dns.io\".\nIssues: github.com/dampfklon/acme.sh\nAuthor: Wolfgang Ebner, Sven Neubuaer\n'\n\n########  Public functions #####################\n\n#Usage: dns_acmedns_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_acmedns_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using acme-dns\"\n  _debug \"fulldomain $fulldomain\"\n  _debug \"txtvalue $txtvalue\"\n\n  #for compatiblity from account conf\n  ACMEDNS_USERNAME=\"${ACMEDNS_USERNAME:-$(_readaccountconf_mutable ACMEDNS_USERNAME)}\"\n  _clearaccountconf_mutable ACMEDNS_USERNAME\n  ACMEDNS_PASSWORD=\"${ACMEDNS_PASSWORD:-$(_readaccountconf_mutable ACMEDNS_PASSWORD)}\"\n  _clearaccountconf_mutable ACMEDNS_PASSWORD\n  ACMEDNS_SUBDOMAIN=\"${ACMEDNS_SUBDOMAIN:-$(_readaccountconf_mutable ACMEDNS_SUBDOMAIN)}\"\n  _clearaccountconf_mutable ACMEDNS_SUBDOMAIN\n\n  ACMEDNS_BASE_URL=\"${ACMEDNS_BASE_URL:-$(_readdomainconf ACMEDNS_BASE_URL)}\"\n  ACMEDNS_USERNAME=\"${ACMEDNS_USERNAME:-$(_readdomainconf ACMEDNS_USERNAME)}\"\n  ACMEDNS_PASSWORD=\"${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}\"\n  ACMEDNS_SUBDOMAIN=\"${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}\"\n\n  if [ \"$ACMEDNS_BASE_URL\" = \"\" ]; then\n    ACMEDNS_BASE_URL=\"https://auth.acme-dns.io\"\n  fi\n\n  ACMEDNS_UPDATE_URL=\"$ACMEDNS_BASE_URL/update\"\n  ACMEDNS_REGISTER_URL=\"$ACMEDNS_BASE_URL/register\"\n\n  if [ -z \"$ACMEDNS_USERNAME\" ] || [ -z \"$ACMEDNS_PASSWORD\" ]; then\n    response=\"$(_post \"\" \"$ACMEDNS_REGISTER_URL\" \"\" \"POST\")\"\n    _debug response \"$response\"\n    ACMEDNS_USERNAME=$(echo \"$response\" | sed -n 's/^{.*\\\"username\\\":[ ]*\\\"\\([^\\\"]*\\)\\\".*}/\\1/p')\n    _debug \"received username: $ACMEDNS_USERNAME\"\n    ACMEDNS_PASSWORD=$(echo \"$response\" | sed -n 's/^{.*\\\"password\\\":[ ]*\\\"\\([^\\\"]*\\)\\\".*}/\\1/p')\n    _debug \"received password: $ACMEDNS_PASSWORD\"\n    ACMEDNS_SUBDOMAIN=$(echo \"$response\" | sed -n 's/^{.*\\\"subdomain\\\":[ ]*\\\"\\([^\\\"]*\\)\\\".*}/\\1/p')\n    _debug \"received subdomain: $ACMEDNS_SUBDOMAIN\"\n    ACMEDNS_FULLDOMAIN=$(echo \"$response\" | sed -n 's/^{.*\\\"fulldomain\\\":[ ]*\\\"\\([^\\\"]*\\)\\\".*}/\\1/p')\n    _info \"##########################################################\"\n    _info \"# Create $fulldomain CNAME $ACMEDNS_FULLDOMAIN DNS entry #\"\n    _info \"##########################################################\"\n    _info \"Press enter to continue... \"\n    read -r _\n  fi\n\n  _savedomainconf ACMEDNS_BASE_URL \"$ACMEDNS_BASE_URL\"\n  _savedomainconf ACMEDNS_USERNAME \"$ACMEDNS_USERNAME\"\n  _savedomainconf ACMEDNS_PASSWORD \"$ACMEDNS_PASSWORD\"\n  _savedomainconf ACMEDNS_SUBDOMAIN \"$ACMEDNS_SUBDOMAIN\"\n\n  export _H1=\"X-Api-User: $ACMEDNS_USERNAME\"\n  export _H2=\"X-Api-Key: $ACMEDNS_PASSWORD\"\n  data=\"{\\\"subdomain\\\":\\\"$ACMEDNS_SUBDOMAIN\\\", \\\"txt\\\": \\\"$txtvalue\\\"}\"\n\n  _debug data \"$data\"\n  response=\"$(_post \"$data\" \"$ACMEDNS_UPDATE_URL\" \"\" \"POST\")\"\n  _debug response \"$response\"\n\n  if ! echo \"$response\" | grep \"\\\"$txtvalue\\\"\" >/dev/null; then\n    _err \"invalid response of acme-dns\"\n    return 1\n  fi\n\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_acmedns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using acme-dns\"\n  _debug \"fulldomain $fulldomain\"\n  _debug \"txtvalue $txtvalue\"\n}\n\n####################  Private functions below ##################################\n"
  },
  {
    "path": "dnsapi/dns_acmeproxy.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_acmeproxy_info='AcmeProxy Server API\n AcmeProxy can be used to as a single host in your network to request certificates through a DNS API.\n Clients can connect with the one AcmeProxy host so you do not need to store DNS API credentials on every single host.\nSite: github.com/mdbraber/acmeproxy\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_acmeproxy\nOptions:\n ACMEPROXY_ENDPOINT API Endpoint\n ACMEPROXY_USERNAME Username\n ACMEPROXY_PASSWORD Password\nIssues: github.com/acmesh-official/acme.sh/issues/2251\nAuthor: Maarten den Braber\n'\n\ndns_acmeproxy_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  action=\"present\"\n\n  _debug \"Calling: _acmeproxy_request() '${fulldomain}' '${txtvalue}' '${action}'\"\n  _acmeproxy_request \"$fulldomain\" \"$txtvalue\" \"$action\"\n}\n\ndns_acmeproxy_rm() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  action=\"cleanup\"\n\n  _debug \"Calling: _acmeproxy_request() '${fulldomain}' '${txtvalue}' '${action}'\"\n  _acmeproxy_request \"$fulldomain\" \"$txtvalue\" \"$action\"\n}\n\n_acmeproxy_request() {\n\n  ## Nothing to see here, just some housekeeping\n  fulldomain=$1\n  txtvalue=$2\n  action=$3\n\n  _info \"Using acmeproxy\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ACMEPROXY_ENDPOINT=\"${ACMEPROXY_ENDPOINT:-$(_readaccountconf_mutable ACMEPROXY_ENDPOINT)}\"\n  ACMEPROXY_USERNAME=\"${ACMEPROXY_USERNAME:-$(_readaccountconf_mutable ACMEPROXY_USERNAME)}\"\n  ACMEPROXY_PASSWORD=\"${ACMEPROXY_PASSWORD:-$(_readaccountconf_mutable ACMEPROXY_PASSWORD)}\"\n\n  ## Check for the endpoint\n  if [ -z \"$ACMEPROXY_ENDPOINT\" ]; then\n    ACMEPROXY_ENDPOINT=\"\"\n    _err \"You didn't specify the endpoint\"\n    _err \"Please set them via 'export ACMEPROXY_ENDPOINT=https://ip:port' and try again.\"\n    return 1\n  fi\n\n  ## Save the credentials to the account file\n  _saveaccountconf_mutable ACMEPROXY_ENDPOINT \"$ACMEPROXY_ENDPOINT\"\n  _saveaccountconf_mutable ACMEPROXY_USERNAME \"$ACMEPROXY_USERNAME\"\n  _saveaccountconf_mutable ACMEPROXY_PASSWORD \"$ACMEPROXY_PASSWORD\"\n\n  if [ -z \"$ACMEPROXY_USERNAME\" ] || [ -z \"$ACMEPROXY_PASSWORD\" ]; then\n    _info \"ACMEPROXY_USERNAME and/or ACMEPROXY_PASSWORD not set - using without client authentication! Make sure you're using server authentication (e.g. IP-based)\"\n    export _H1=\"Accept: application/json\"\n    export _H2=\"Content-Type: application/json\"\n  else\n    ## Base64 encode the credentials\n    credentials=$(printf \"%b\" \"$ACMEPROXY_USERNAME:$ACMEPROXY_PASSWORD\" | _base64)\n\n    ## Construct the HTTP Authorization header\n    export _H1=\"Authorization: Basic $credentials\"\n    export _H2=\"Accept: application/json\"\n    export _H3=\"Content-Type: application/json\"\n  fi\n\n  ## Add the challenge record to the acmeproxy grid member\n  response=\"$(_post \"{\\\"fqdn\\\": \\\"$fulldomain.\\\", \\\"value\\\": \\\"$txtvalue\\\"}\" \"$ACMEPROXY_ENDPOINT/$action\" \"\" \"POST\")\"\n\n  ## Let's see if we get something intelligible back from the unit\n  if echo \"$response\" | grep \"\\\"$txtvalue\\\"\" >/dev/null; then\n    _info \"Successfully updated the txt record\"\n    return 0\n  else\n    _err \"Error encountered during record addition\"\n    _err \"$response\"\n    return 1\n  fi\n\n}\n\n####################  Private functions below ##################################\n"
  },
  {
    "path": "dnsapi/dns_active24.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_active24_info='Active24.cz\nSite: Active24.cz\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24\nOptions:\n Active24_ApiKey API Key. Called \"Identifier\" in the Active24 Admin\n Active24_ApiSecret API Secret. Called \"Secret key\" in the Active24 Admin\nIssues: github.com/acmesh-official/acme.sh/issues/2059\n'\n\nActive24_Api=\"https://rest.active24.cz\"\n# export Active24_ApiKey=ak48l3h7-ak5d-qn4t-p8gc-b6fs8c3l\n# export Active24_ApiSecret=ajvkeo3y82ndsu2smvxy3o36496dcascksldncsq\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_active24_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _active24_init\n\n  _info \"Adding txt record\"\n  if _active24_rest POST \"/v2/service/$_service_id/dns/record\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":300}\"; then\n    if _contains \"$response\" \"error\"; then\n      _err \"Add txt record error.\"\n      return 1\n    else\n      _info \"Added, OK\"\n      return 0\n    fi\n  fi\n\n  _err \"Add txt record error.\"\n  return 1\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_active24_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _active24_init\n\n  _debug \"Getting txt records\"\n  # The API needs to send data in body in order the filter to work\n  # TODO: web can also add content $txtvalue to filter and then get the id from response\n  _active24_rest GET \"/v2/service/$_service_id/dns/record\" \"{\\\"page\\\":1,\\\"descending\\\":true,\\\"sortBy\\\":\\\"name\\\",\\\"rowsPerPage\\\":100,\\\"totalRecords\\\":0,\\\"filters\\\":{\\\"type\\\":[\\\"TXT\\\"],\\\"name\\\":\\\"${_sub_domain}\\\"}}\"\n  #_active24_rest GET \"/v2/service/$_service_id/dns/record?rowsPerPage=100\"\n\n  if _contains \"$response\" \"error\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  # Note: it might never be more than one record actually, NEEDS more INVESTIGATION\n  record_ids=$(printf \"%s\" \"$response\" | _egrep_o \"[^{]+${txtvalue}[^}]+\" | _egrep_o '\"id\" *: *[^,]+' | cut -d ':' -f 2)\n  _debug2 record_ids \"$record_ids\"\n\n  for redord_id in $record_ids; do\n    _debug \"Removing record_id\" \"$redord_id\"\n    _debug \"txtvalue\" \"$txtvalue\"\n    if _active24_rest DELETE \"/v2/service/$_service_id/dns/record/$redord_id\" \"\"; then\n      if _contains \"$response\" \"error\"; then\n        _err \"Unable to remove txt record.\"\n        return 1\n      else\n        _info \"Removed txt record.\"\n        return 0\n      fi\n    fi\n  done\n\n  _err \"No txt records found.\"\n  return 1\n}\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  if ! _active24_rest GET \"/v1/user/self/service\"; then\n    return 1\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug \"h\" \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"$h\\\"\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_active24_init() {\n  Active24_ApiKey=\"${Active24_ApiKey:-$(_readaccountconf_mutable Active24_ApiKey)}\"\n  Active24_ApiSecret=\"${Active24_ApiSecret:-$(_readaccountconf_mutable Active24_ApiSecret)}\"\n  #Active24_ServiceId=\"${Active24_ServiceId:-$(_readaccountconf_mutable Active24_ServiceId)}\"\n\n  if [ -z \"$Active24_ApiKey\" ] || [ -z \"$Active24_ApiSecret\" ]; then\n    Active24_ApiKey=\"\"\n    Active24_ApiSecret=\"\"\n    _err \"You don't specify Active24 api key and ApiSecret yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable Active24_ApiKey \"$Active24_ApiKey\"\n  _saveaccountconf_mutable Active24_ApiSecret \"$Active24_ApiSecret\"\n\n  _debug \"A24 API CHECK\"\n  if ! _active24_rest GET \"/v2/check\"; then\n    _err \"A24 API check failed with: $response\"\n    return 1\n  fi\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"verified\\\":true >/dev/null; then\n    _err \"A24 API check failed with: $response\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _active24_get_service_id \"$_domain\"\n  _debug _service_id \"$_service_id\"\n}\n\n_active24_get_service_id() {\n  _d=$1\n  if ! _active24_rest GET \"/v1/user/self/zone/${_d}\"; then\n    return 1\n  else\n    response=$(echo \"$response\" | _json_decode)\n    _service_id=$(echo \"$response\" | _egrep_o '\"id\" *: *[^,]+' | cut -d ':' -f 2)\n  fi\n}\n\n_active24_rest() {\n  m=$1\n  ep_qs=$2 # with query string\n  # ep=$2\n  ep=$(printf \"%s\" \"$ep_qs\" | cut -d '?' -f1) # no query string\n  data=\"$3\"\n\n  _debug \"A24 $ep\"\n  _debug \"A24 $Active24_ApiKey\"\n  _debug \"A24 $Active24_ApiSecret\"\n\n  timestamp=$(_time)\n  datez=$(date -u +\"%Y%m%dT%H%M%SZ\")\n  canonicalRequest=\"${m} ${ep} ${timestamp}\"\n  signature=$(printf \"%s\" \"$canonicalRequest\" | _hmac sha1 \"$(printf \"%s\" \"$Active24_ApiSecret\" | _hex_dump | tr -d \" \")\" hex)\n  authorization64=\"$(printf \"%s:%s\" \"$Active24_ApiKey\" \"$signature\" | _base64)\"\n\n  export _H1=\"Date: ${datez}\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Content-Type: application/json\"\n  export _H4=\"Authorization: Basic ${authorization64}\"\n\n  _debug2 H1 \"$_H1\"\n  _debug2 H2 \"$_H2\"\n  _debug2 H3 \"$_H3\"\n  _debug2 H4 \"$_H4\"\n\n  # _sleep 1\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug2 \"${m} $Active24_Api${ep_qs}\"\n    _debug \"data\" \"$data\"\n    response=\"$(_post \"$data\" \"$Active24_Api${ep_qs}\" \"\" \"$m\" \"application/json\")\"\n  else\n    if [ -z \"$data\" ]; then\n      _debug2 \"GET $Active24_Api${ep_qs}\"\n      response=\"$(_get \"$Active24_Api${ep_qs}\")\"\n    else\n      _debug2 \"GET $Active24_Api${ep_qs} with data: ${data}\"\n      response=\"$(_post \"$data\" \"$Active24_Api${ep_qs}\" \"\" \"$m\" \"application/json\")\"\n    fi\n  fi\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ad.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ad_info='AlwaysData.com\nSite: AlwaysData.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ad\nOptions:\n AD_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/pull/503\nAuthor: Paul Koppen\n'\n\nAD_API_URL=\"https://$AD_API_KEY:@api.alwaysdata.com/v1\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_ad_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$AD_API_KEY\" ]; then\n    AD_API_KEY=\"\"\n    _err \"You didn't specify the AD api key yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf AD_API_KEY \"$AD_API_KEY\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _ad_tmpl_json=\"{\\\"domain\\\":$_domain_id,\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"value\\\":\\\"$txtvalue\\\"}\"\n\n  if _ad_rest POST \"record/\" \"$_ad_tmpl_json\" && [ -z \"$response\" ]; then\n    _info \"txt record updated success.\"\n    return 0\n  fi\n\n  return 1\n}\n\n#fulldomain txtvalue\ndns_ad_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _ad_rest GET \"record/?domain=$_domain_id&name=$_sub_domain\"\n\n  if [ -n \"$response\" ]; then\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":\\s*[0-9]+\" | cut -d : -f 2 | tr -d \" \" | _head_n 1)\n    _debug record_id \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if _ad_rest DELETE \"record/$record_id/\" && [ -z \"$response\" ]; then\n      _info \"txt record deleted success.\"\n      return 0\n    fi\n    _debug response \"$response\"\n    return 1\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=12345\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n\n  if _ad_rest GET \"domain/\"; then\n    response=\"$(echo \"$response\" | tr -d \"\\n\" | sed 's/{/\\n&/g')\"\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        #not valid\n        return 1\n      fi\n\n      hostedzone=\"$(echo \"$response\" | _egrep_o \"{.*\\\"name\\\":\\s*\\\"$h\\\".*}\")\"\n      if [ \"$hostedzone\" ]; then\n        _domain_id=$(printf \"%s\\n\" \"$hostedzone\" | _egrep_o \"\\\"id\\\":\\s*[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n        if [ \"$_domain_id\" ]; then\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n          _domain=$h\n          return 0\n        fi\n        return 1\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n  fi\n  return 1\n}\n\n#method uri qstr data\n_ad_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$mtd\" != \"GET\" ]; then\n    # both POST and DELETE.\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$AD_API_URL/$ep\" \"\" \"$mtd\")\"\n  else\n    response=\"$(_get \"$AD_API_URL/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ali.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ali_info='AlibabaCloud.com\nDomains: Aliyun.com\nSite: AlibabaCloud.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ali\nOptions:\n Ali_Key API Key\n Ali_Secret API Secret\n'\n\n# NOTICE:\n# This file is referenced by Alibaba Cloud Services deploy hooks\n# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276\n# Be careful when modifying this file, especially when making breaking changes for common functions\n\nAli_DNS_API=\"https://alidns.aliyuncs.com/\"\n\n#Usage: dns_ali_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_ali_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _prepare_ali_credentials || return 1\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  _debug \"Add record\"\n  _add_record_query \"$_domain\" \"$_sub_domain\" \"$txtvalue\" && _ali_rest \"Add record\"\n}\n\ndns_ali_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  Ali_Key=\"${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}\"\n  Ali_Secret=\"${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  _clean\n}\n\n####################  Alibaba Cloud common functions below  ####################\n\n_prepare_ali_credentials() {\n  Ali_Key=\"${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}\"\n  Ali_Secret=\"${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}\"\n  if [ -z \"$Ali_Key\" ] || [ -z \"$Ali_Secret\" ]; then\n    Ali_Key=\"\"\n    Ali_Secret=\"\"\n    _err \"You don't specify aliyun api key and secret yet.\"\n    return 1\n  fi\n\n  #save the api key and secret to the account conf file.\n  _saveaccountconf_mutable Ali_Key \"$Ali_Key\"\n  _saveaccountconf_mutable Ali_Secret \"$Ali_Secret\"\n}\n\n# act ign mtd\n_ali_rest() {\n  act=\"$1\"\n  ign=\"$2\"\n  mtd=\"${3:-GET}\"\n\n  signature=$(printf \"%s\" \"$mtd&%2F&$(printf \"%s\" \"$query\" | _url_encode upper-hex)\" | _hmac \"sha1\" \"$(printf \"%s\" \"$Ali_Secret&\" | _hex_dump | tr -d \" \")\" | _base64)\n  signature=$(printf \"%s\" \"$signature\" | _url_encode upper-hex)\n  url=\"$endpoint?Signature=$signature\"\n\n  if [ \"$mtd\" = \"GET\" ]; then\n    url=\"$url&$query\"\n    response=\"$(_get \"$url\")\"\n  else\n    response=\"$(_post \"$query\" \"$url\" \"\" \"$mtd\" \"application/x-www-form-urlencoded\")\"\n  fi\n\n  _ret=\"$?\"\n  _debug2 response \"$response\"\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"Error <$act>\"\n    return 1\n  fi\n\n  if [ -z \"$ign\" ]; then\n    message=\"$(echo \"$response\" | _egrep_o \"\\\"Message\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\"\n    if [ \"$message\" ]; then\n      _err \"$message\"\n      return 1\n    fi\n  fi\n}\n\n_ali_nonce() {\n  if [ \"$ACME_OPENSSL_BIN\" ]; then\n    \"$ACME_OPENSSL_BIN\" rand -hex 16 2>/dev/null && return 0\n  fi\n  printf \"%s\" \"$(date +%s)$$$(date +%N)\" | _digest sha256 hex | cut -c 1-32\n}\n\n_ali_timestamp() {\n  date -u +\"%Y-%m-%dT%H%%3A%M%%3A%SZ\"\n}\n\n####################  Private functions below  ####################\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _describe_records_query \"$h\"\n    if ! _ali_rest \"Get root\" \"ignore\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"PageNumber\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _debug _sub_domain \"$_sub_domain\"\n      _domain=\"$h\"\n      _debug _domain \"$_domain\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_check_exist_query() {\n  _qdomain=\"$1\"\n  _qsubdomain=\"$2\"\n  endpoint=$Ali_DNS_API\n  query=''\n  query=$query'AccessKeyId='$Ali_Key\n  query=$query'&Action=DescribeDomainRecords'\n  query=$query'&DomainName='$_qdomain\n  query=$query'&Format=json'\n  query=$query'&RRKeyWord='$_qsubdomain\n  query=$query'&SignatureMethod=HMAC-SHA1'\n  query=$query\"&SignatureNonce=$(_ali_nonce)\"\n  query=$query'&SignatureVersion=1.0'\n  query=$query'&Timestamp='$(_ali_timestamp)\n  query=$query'&TypeKeyWord=TXT'\n  query=$query'&Version=2015-01-09'\n}\n\n_add_record_query() {\n  endpoint=$Ali_DNS_API\n  query=''\n  query=$query'AccessKeyId='$Ali_Key\n  query=$query'&Action=AddDomainRecord'\n  query=$query'&DomainName='$1\n  query=$query'&Format=json'\n  query=$query'&RR='$2\n  query=$query'&SignatureMethod=HMAC-SHA1'\n  query=$query\"&SignatureNonce=$(_ali_nonce)\"\n  query=$query'&SignatureVersion=1.0'\n  query=$query'&Timestamp='$(_ali_timestamp)\n  query=$query'&Type=TXT'\n  query=$query'&Value='$3\n  query=$query'&Version=2015-01-09'\n}\n\n_delete_record_query() {\n  endpoint=$Ali_DNS_API\n  query=''\n  query=$query'AccessKeyId='$Ali_Key\n  query=$query'&Action=DeleteDomainRecord'\n  query=$query'&Format=json'\n  query=$query'&RecordId='$1\n  query=$query'&SignatureMethod=HMAC-SHA1'\n  query=$query\"&SignatureNonce=$(_ali_nonce)\"\n  query=$query'&SignatureVersion=1.0'\n  query=$query'&Timestamp='$(_ali_timestamp)\n  query=$query'&Version=2015-01-09'\n}\n\n_describe_records_query() {\n  endpoint=$Ali_DNS_API\n  query=''\n  query=$query'AccessKeyId='$Ali_Key\n  query=$query'&Action=DescribeDomainRecords'\n  query=$query'&DomainName='$1\n  query=$query'&Format=json'\n  query=$query'&SignatureMethod=HMAC-SHA1'\n  query=$query\"&SignatureNonce=$(_ali_nonce)\"\n  query=$query'&SignatureVersion=1.0'\n  query=$query'&Timestamp='$(_ali_timestamp)\n  query=$query'&Version=2015-01-09'\n}\n\n_clean() {\n  _check_exist_query \"$_domain\" \"$_sub_domain\"\n  # do not correct grammar here\n  if ! _ali_rest \"Check exist records\" \"ignore\"; then\n    return 1\n  fi\n\n  record_id=\"$(echo \"$response\" | tr '{' \"\\n\" | grep \"$_sub_domain\" | grep -- \"$txtvalue\" | tr \",\" \"\\n\" | grep RecordId | cut -d '\"' -f 4)\"\n  _debug2 record_id \"$record_id\"\n\n  if [ -z \"$record_id\" ]; then\n    _debug \"record not found, skip\"\n  else\n    _delete_record_query \"$record_id\"\n    _ali_rest \"Delete record $record_id\" \"ignore\"\n  fi\n\n}\n"
  },
  {
    "path": "dnsapi/dns_alviy.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_alviy_info='Alviy.com\nSite: Alviy.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_alviy\nOptions:\n Alviy_token API token. Get it from the https://cloud.alviy.com/token\nIssues: github.com/acmesh-official/acme.sh/issues/5115\n'\n\nAlviy_Api=\"https://cloud.alviy.com/api/v1\"\n\n########  Public functions #####################\n\n#Usage: dns_alviy_add  _acme-challenge.www.domain.com   \"content\"\ndns_alviy_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Alviy_token=\"${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}\"\n  if [ -z \"$Alviy_token\" ]; then\n    Alviy_token=\"\"\n    _err \"Please specify Alviy token.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable Alviy_token \"$Alviy_token\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting existing records\"\n  if _alviy_txt_exists \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n    _info \"This record already exists, skipping\"\n    return 0\n  fi\n\n  _add_data=\"{\\\"content\\\":\\\"$txtvalue\\\",\\\"type\\\":\\\"TXT\\\"}\"\n  _debug2 _add_data \"$_add_data\"\n  _info \"Adding record\"\n  if _alviy_rest POST \"zone/$_domain/domain/$fulldomain/\" \"$_add_data\"; then\n    _debug \"Checking updated records of '${fulldomain}'\"\n\n    if ! _alviy_txt_exists \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n      _err \"TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!\"\n      return 1\n    fi\n\n  else\n    _err \"Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set.\"\n    return 1\n  fi\n\n  _sleep 10\n  _info \"Added TXT record '${txtvalue}' for '${fulldomain}'.\"\n  return 0\n}\n\n#fulldomain\ndns_alviy_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Alviy_token=\"${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if ! _alviy_txt_exists \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n    _info \"The record does not exist, skip\"\n    return 0\n  fi\n\n  _add_data=\"\"\n  uuid=$(echo \"$response\" | tr \"{\" \"\\n\" | grep \"$txtvalue\" | tr \",\" \"\\n\" | grep uuid | cut -d \\\" -f4)\n  # delete record\n  _debug \"Delete TXT record for '${fulldomain}'\"\n  if ! _alviy_rest DELETE \"zone/$_domain/record/$uuid\" \"{\\\"confirm\\\":1}\"; then\n    _err \"Cannot delete empty TXT record for '$fulldomain'\"\n    return 1\n  fi\n  _info \"The record '$fulldomain'='$txtvalue' deleted\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=3\n  a=\"init\"\n  while [ -n \"$a\" ]; do\n    a=$(printf \"%s\" \"$domain\" | cut -d . -f $i-)\n    i=$((i + 1))\n  done\n  n=$((i - 3))\n  h=$(printf \"%s\" \"$domain\" | cut -d . -f $n-)\n  if [ -z \"$h\" ]; then\n    #not valid\n    _alviy_rest GET \"zone/$domain/\"\n    _debug \"can't get host from $domain\"\n    return 1\n  fi\n\n  if ! _alviy_rest GET \"zone/$h/\"; then\n    return 1\n  fi\n\n  if _contains \"$response\" '\"code\":\"NOT_FOUND\"'; then\n    _debug \"$h not found\"\n  else\n    s=$((n - 1))\n    _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f -$s)\n    _domain=\"$h\"\n    return 0\n  fi\n  return 1\n}\n\n_alviy_txt_exists() {\n  zone=$1\n  domain=$2\n  content_data=$3\n  _debug \"Getting existing records\"\n\n  if ! _alviy_rest GET \"zone/$zone/domain/$domain/TXT/\"; then\n    _info \"The record does not exist\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$3\"; then\n    _info \"The record has other value\"\n    return 1\n  fi\n  # GOOD code return - TRUE function\n  return 0\n}\n\n_alviy_rest() {\n  method=$1\n  path=\"$2\"\n  content_data=\"$3\"\n  _debug \"$path\"\n\n  export _H1=\"Authorization: Bearer $Alviy_token\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$content_data\" ] || [ \"$method\" = \"DELETE\" ]; then\n    _debug \"data ($method): \" \"$content_data\"\n    response=\"$(_post \"$content_data\" \"$Alviy_Api/$path\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$Alviy_Api/$path\")\"\n  fi\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  if [ \"$_code\" = \"401\" ]; then\n    _err \"It seems that your api key or secret is not correct.\"\n    return 1\n  fi\n\n  if [ \"$_code\" != \"200\" ]; then\n    _err \"API call error ($method): $path Response code $_code\"\n  fi\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error on rest call ($method): $path. Response:\"\n    _err \"$response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_anx.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_anx_info='Anexia.com CloudDNS\nSite: Anexia.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_anx\nOptions:\n ANX_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/3238\n'\n\nANX_API='https://engine.anexia-it.com/api/clouddns/v1'\n\n########  Public functions #####################\n\ndns_anx_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using ANX CDNS API\"\n\n  ANX_Token=\"${ANX_Token:-$(_readaccountconf_mutable ANX_Token)}\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if [ \"$ANX_Token\" ]; then\n    _saveaccountconf_mutable ANX_Token \"$ANX_Token\"\n  else\n    _err \"You didn't specify a ANEXIA Engine API token.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  # Always add records, wildcard need two records with the same name\n  _anx_rest POST \"zone.json/${_domain}/records\" \"{\\\"name\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"rdata\\\":\\\"$txtvalue\\\"}\"\n  if _contains \"$response\" \"$txtvalue\"; then\n    return 0\n  else\n    return 1\n  fi\n}\n\ndns_anx_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using ANX CDNS API\"\n\n  ANX_Token=\"${ANX_Token:-$(_readaccountconf_mutable ANX_Token)}\"\n\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _get_record_id\n\n  if _is_uuid \"$_record_id\"; then\n    if ! _anx_rest DELETE \"zone.json/${_domain}/records/$_record_id\"; then\n      _err \"Delete record\"\n      return 1\n    fi\n  else\n    _info \"No record found.\"\n  fi\n  echo \"$response\" | tr -d \" \" | grep \\\"status\\\":\\\"OK\\\" >/dev/null\n}\n\n####################  Private functions below ##################################\n\n_is_uuid() {\n  pattern='^\\{?[A-Z0-9a-z]{8}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{12}\\}?$'\n  if echo \"$1\" | _egrep_o \"$pattern\" >/dev/null; then\n    return 0\n  fi\n  return 1\n}\n\n_get_record_id() {\n  _debug subdomain \"$_sub_domain\"\n  _debug domain \"$_domain\"\n\n  if _anx_rest GET \"zone.json/${_domain}/records?name=$_sub_domain&type=TXT\"; then\n    _debug response \"$response\"\n    if _contains \"$response\" \"\\\"name\\\":\\\"$_sub_domain\\\"\" >/dev/null; then\n      _record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\[.\\\"identifier\\\":\\\"[^\\\"]*\\\"\" | head -n 1 | cut -d : -f 2 | tr -d \\\")\n    else\n      _record_id=''\n    fi\n  else\n    _err \"Search existing record\"\n  fi\n}\n\n_anx_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Token $ANX_Token\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"${ANX_API}/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"${ANX_API}/$ep\")\"\n  fi\n\n  # shellcheck disable=SC2181\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug response \"$response\"\n  return 0\n}\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _anx_rest GET \"zone.json/${h}\"\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_artfiles.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_artfiles_info='ArtFiles.de\nSite: ArtFiles.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_artfiles\nOptions:\n AF_API_USERNAME API Username\n AF_API_PASSWORD API Password\nIssues: github.com/acmesh-official/acme.sh/issues/4718\nAuthor: Martin Arndt <https://troublezone.net/>\n'\n\n########## API configuration ###################################################\n\nAF_API_SUCCESS='status\":\"OK'\nAF_URL_DCP='https://dcp.c.artfiles.de/api/'\nAF_URL_DNS=${AF_URL_DCP}'dns/{*}_dns.html?domain='\nAF_URL_DOMAINS=${AF_URL_DCP}'domain/get_domains.html'\n\n########## Public functions ####################################################\n\n# Adds a new TXT record for given ACME challenge value & domain.\n# Usage: dns_artfiles_add _acme-challenge.www.example.com \"ACME challenge value\"\ndns_artfiles_add() {\n  domain=\"$1\"\n  txtValue=\"$2\"\n  _info 'Using ArtFiles.de DNS addition API…'\n  _debug 'Domain' \"$domain\"\n  _debug 'txtValue' \"$txtValue\"\n\n  _set_credentials\n  _saveaccountconf_mutable 'AF_API_USERNAME' \"$AF_API_USERNAME\"\n  _saveaccountconf_mutable 'AF_API_PASSWORD' \"$AF_API_PASSWORD\"\n\n  _set_headers\n  _get_zone \"$domain\"\n  _dns 'GET'\n  if ! _contains \"$response\" 'TXT'; then\n    _err 'Retrieving TXT records failed.'\n\n    return 1\n  fi\n\n  _clean_records\n  _dns 'SET' \"$(printf -- '%s\\n_acme-challenge \"%s\"' \"$response\" \"$txtValue\")\"\n  if ! _contains \"$response\" \"$AF_API_SUCCESS\"; then\n    _err 'Adding ACME challenge value failed.'\n\n    return 1\n  fi\n}\n\n# Removes the existing TXT record for given ACME challenge value & domain.\n# Usage: dns_artfiles_rm _acme-challenge.www.example.com \"ACME challenge value\"\ndns_artfiles_rm() {\n  domain=\"$1\"\n  txtValue=\"$2\"\n  _info 'Using ArtFiles.de DNS removal API…'\n  _debug 'Domain' \"$domain\"\n  _debug 'txtValue' \"$txtValue\"\n\n  _set_credentials\n  _set_headers\n  _get_zone \"$domain\"\n  if ! _dns 'GET'; then\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$txtValue\"; then\n    _err 'Retrieved TXT records are missing given ACME challenge value.'\n\n    return 1\n  fi\n\n  _clean_records\n  response=\"$(printf -- '%s' \"$response\" | sed '/_acme-challenge \"'\"$txtValue\"'\"/d')\"\n  _dns 'SET' \"$response\"\n  if ! _contains \"$response\" \"$AF_API_SUCCESS\"; then\n    _err 'Removing ACME challenge value failed.'\n\n    return 1\n  fi\n}\n\n########## Private functions ###################################################\n\n# Cleans awful TXT records response of ArtFiles's API & pretty prints it.\n# Usage: _clean_records\n_clean_records() {\n  _info 'Cleaning TXT records…'\n  # Extract TXT part, strip trailing quote sign (ACME.sh API guidelines forbid\n  # usage of SED's GNU extensions, hence couldn't omit it via regex), strip '\\'\n  # from '\\\"' & turn '\\n' into real LF characters.\n  # Yup, awful API to use - but that's all we got to get this working, so… ;)\n  _debug2 'Raw  ' \"$response\"\n  response=\"$(printf -- '%s' \"$response\" | sed 's/^.*TXT\":\"\\([^}]*\\).*$/\\1/;s/,\".*$//;s/.$//;s/\\\\\"/\"/g;s/\\\\n/\\n/g')\"\n  _debug2 'Clean' \"$response\"\n}\n\n# Executes an HTTP GET or POST request for getting or setting DNS records,\n# containing given payload upon POST.\n# Usage: _dns [GET | SET] [payload]\n_dns() {\n  _info 'Executing HTTP request…'\n  action=\"$1\"\n  payload=\"$(printf -- '%s' \"$2\" | _url_encode)\"\n  url=\"$(printf -- '%s%s' \"$AF_URL_DNS\" \"$domain\" | sed 's/{\\*}/'\"$(printf -- '%s' \"$action\" | _lower_case)\"'/')\"\n\n  if [ \"$action\" = 'SET' ]; then\n    _debug2 'Payload' \"$payload\"\n    response=\"$(_post '' \"$url&TXT=$payload\" '' 'POST' 'application/x-www-form-urlencoded')\"\n  else\n    response=\"$(_get \"$url\" '' 10)\"\n  fi\n\n  if ! _contains \"$response\" \"$AF_API_SUCCESS\"; then\n    _err \"DNS API error: $response\"\n\n    return 1\n  fi\n\n  _debug 'Response' \"$response\"\n\n  return 0\n}\n\n# Gets the root domain zone for given domain.\n# Usage: _get_zone _acme-challenge.www.example.com\n_get_zone() {\n  fqdn=\"$1\"\n  domains=\"$(_get \"$AF_URL_DOMAINS\" '' 10)\"\n  _info 'Getting domain zone…'\n  _debug2 'FQDN' \"$fqdn\"\n  _debug2 'Domains' \"$domains\"\n\n  while _contains \"$fqdn\" \".\"; do\n    if _contains \"$domains\" \"$fqdn\"; then\n      domain=\"$fqdn\"\n      _info \"Found root domain zone: $domain\"\n      break\n    else\n      fqdn=\"${fqdn#*.}\"\n      _debug2 'FQDN' \"$fqdn\"\n    fi\n  done\n\n  if [ \"$domain\" = \"$fqdn\" ]; then\n    return 0\n  fi\n\n  _err 'Couldn'\\''t find root domain zone.'\n\n  return 1\n}\n\n# Sets the credentials for accessing ArtFiles's API\n# Usage: _set_credentials\n_set_credentials() {\n  _info 'Setting credentials…'\n  AF_API_USERNAME=\"${AF_API_USERNAME:-$(_readaccountconf_mutable AF_API_USERNAME)}\"\n  AF_API_PASSWORD=\"${AF_API_PASSWORD:-$(_readaccountconf_mutable AF_API_PASSWORD)}\"\n  if [ -z \"$AF_API_USERNAME\" ] || [ -z \"$AF_API_PASSWORD\" ]; then\n    _err 'Missing ArtFiles.de username and/or password.'\n    _err 'Please ensure both are set via export command & try again.'\n\n    return 1\n  fi\n}\n\n# Adds the HTTP Authorization & Content-Type headers to a follow-up request.\n# Usage: _set_headers\n_set_headers() {\n  _info 'Setting headers…'\n  encoded=\"$(printf -- '%s:%s' \"$AF_API_USERNAME\" \"$AF_API_PASSWORD\" | _base64)\"\n  export _H1=\"Authorization: Basic $encoded\"\n  export _H2='Content-Type: application/json'\n}\n"
  },
  {
    "path": "dnsapi/dns_arvan.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_arvan_info='ArvanCloud.ir\nSite: ArvanCloud.ir\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_arvan\nOptions:\n Arvan_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/2796\nAuthor: Vahid Fardi\n'\n\nARVAN_API_URL=\"https://napi.arvancloud.ir/cdn/4.0/domains\"\n\n########  Public functions #####################\n\n#Usage: dns_arvan_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_arvan_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using Arvan\"\n\n  Arvan_Token=\"${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}\"\n\n  if [ -z \"$Arvan_Token\" ]; then\n    _err \"You didn't specify \\\"Arvan_Token\\\" token yet.\"\n    _err \"You can get yours from here https://npanel.arvancloud.ir/profile/api-keys\"\n    return 1\n  fi\n  #save the api token to the account conf file.\n  _saveaccountconf_mutable Arvan_Token \"$Arvan_Token\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _arvan_rest POST \"$_domain/dns-records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"value\\\":{\\\"text\\\":\\\"$txtvalue\\\"},\\\"ttl\\\":120}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"response id is $response\"\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"Record Data is duplicate\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_arvan_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using Arvan\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  Arvan_Token=\"${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _arvan_rest GET \"${_domain}/dns-records\"\n  if ! printf \"%s\" \"$response\" | grep \\\"current_page\\\":1 >/dev/null; then\n    _err \"Error on Arvan Api\"\n    _err \"Please create a github issue with debbug log\"\n    return 1\n  fi\n\n  _record_id=$(echo \"$response\" | _egrep_o \".\\\"id\\\":\\\"[^\\\"]*\\\",\\\"type\\\":\\\"txt\\\",\\\"name\\\":\\\"_acme-challenge\\\",\\\"value\\\":{\\\"text\\\":\\\"$txtvalue\\\"}\" | cut -d : -f 2 | cut -d , -f 1 | tr -d \\\")\n  if ! _arvan_rest \"DELETE\" \"${_domain}/dns-records/${_record_id}\"; then\n    _err \"Error on Arvan Api\"\n    return 1\n  fi\n  _debug \"$response\"\n  _contains \"$response\" 'dns record deleted'\n  return 0\n}\n\n####################  Private functions below ##################################\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _arvan_rest GET \"$h\"; then\n      return 1\n    fi\n    if _contains \"$response\" \"\\\"domain\\\":\\\"$h\\\"\"; then\n      _domain_id=$(echo \"$response\" | cut -d : -f 3 | cut -d , -f 1 | tr -d \\\")\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_arvan_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  token_trimmed=$(echo \"$Arvan_Token\" | tr -d '\"')\n  export _H1=\"Authorization: $token_trimmed\"\n\n  if [ \"$mtd\" = \"DELETE\" ]; then\n    #DELETE Request shouldn't have Content-Type\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$ARVAN_API_URL/$ep\" \"\" \"$mtd\")\"\n  elif [ \"$mtd\" = \"POST\" ]; then\n    export _H2=\"Content-Type: application/json\"\n    export _H3=\"Accept: application/json\"\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$ARVAN_API_URL/$ep\" \"\" \"$mtd\")\"\n  else\n    response=\"$(_get \"$ARVAN_API_URL/$ep$data\")\"\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_aurora.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_aurora_info='versio.nl AuroraDNS\nDomains: pcextreme.nl\nSite: versio.nl\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_aurora\nOptions:\n AURORA_Key API Key\n AURORA_Secret API Secret\nIssues: github.com/acmesh-official/acme.sh/issues/3459\nAuthor: Jasper Zonneveld\n'\n\nAURORA_Api=\"https://api.auroradns.eu\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_aurora_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  AURORA_Key=\"${AURORA_Key:-$(_readaccountconf_mutable AURORA_Key)}\"\n  AURORA_Secret=\"${AURORA_Secret:-$(_readaccountconf_mutable AURORA_Secret)}\"\n\n  if [ -z \"$AURORA_Key\" ] || [ -z \"$AURORA_Secret\" ]; then\n    AURORA_Key=\"\"\n    AURORA_Secret=\"\"\n    _err \"You didn't specify an Aurora api key and secret yet.\"\n    _err \"You can get yours from here https://cp.pcextreme.nl/auroradns/users.\"\n    return 1\n  fi\n\n  #save the api key and secret to the account conf file.\n  _saveaccountconf_mutable AURORA_Key \"$AURORA_Key\"\n  _saveaccountconf_mutable AURORA_Secret \"$AURORA_Secret\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _aurora_rest POST \"zones/$_domain_id/records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":300}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"RecordExistsError\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_aurora_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  AURORA_Key=\"${AURORA_Key:-$(_readaccountconf_mutable AURORA_Key)}\"\n  AURORA_Secret=\"${AURORA_Secret:-$(_readaccountconf_mutable AURORA_Secret)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting records\"\n  _aurora_rest GET \"zones/${_domain_id}/records\"\n\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _info \"Don't need to remove.\"\n  else\n    records=$(echo \"$response\" | _normalizeJson | tr -d \"[]\" | sed \"s/},{/}|{/g\" | tr \"|\" \"\\n\")\n    if [ \"$(echo \"$records\" | wc -l)\" -le 2 ]; then\n      _err \"Can not parse records.\"\n      return 1\n    fi\n    record_id=$(echo \"$records\" | grep \"\\\"type\\\": *\\\"TXT\\\"\" | grep \"\\\"name\\\": *\\\"$_sub_domain\\\"\" | grep \"\\\"content\\\": *\\\"$txtvalue\\\"\" | _egrep_o \"\\\"id\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | _head_n 1 | tr -d \" \")\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _aurora_rest DELETE \"zones/$_domain_id/records/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n  fi\n  return 0\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _aurora_rest GET \"zones/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\": \\\"$h\\\"\"; then\n      _domain_id=$(echo \"$response\" | _normalizeJson | tr -d \"{}\" | tr \",\" \"\\n\" | grep \"\\\"id\\\": *\\\"\" | cut -d : -f 2 | tr -d \\\" | _head_n 1 | tr -d \" \")\n      _debug _domain_id \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_aurora_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  key_trimmed=$(echo \"$AURORA_Key\" | tr -d '\"')\n  secret_trimmed=$(echo \"$AURORA_Secret\" | tr -d '\"')\n\n  timestamp=$(date -u +\"%Y%m%dT%H%M%SZ\")\n  signature=$(printf \"%s/%s%s\" \"$m\" \"$ep\" \"$timestamp\" | _hmac sha256 \"$(printf \"%s\" \"$secret_trimmed\" | _hex_dump | tr -d \" \")\" | _base64)\n  authorization=$(printf \"AuroraDNSv1 %s\" \"$(printf \"%s:%s\" \"$key_trimmed\" \"$signature\" | _base64)\")\n\n  export _H1=\"Content-Type: application/json; charset=UTF-8\"\n  export _H2=\"X-AuroraDNS-Date: $timestamp\"\n  export _H3=\"Authorization: $authorization\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$AURORA_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$AURORA_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_autodns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_autodns_info='InternetX autoDNS\n InternetX autoDNS XML API\nSite: InternetX.com/autodns/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_autodns\nOptions:\n AUTODNS_USER Username\n AUTODNS_PASSWORD Password\n AUTODNS_CONTEXT Context\nAuthor: <auerswald@gmail.com>\n'\n\nAUTODNS_API=\"https://gateway.autodns.com\"\n\n# Arguments:\n#   txtdomain\n#   txt\ndns_autodns_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  AUTODNS_USER=\"${AUTODNS_USER:-$(_readaccountconf_mutable AUTODNS_USER)}\"\n  AUTODNS_PASSWORD=\"${AUTODNS_PASSWORD:-$(_readaccountconf_mutable AUTODNS_PASSWORD)}\"\n  AUTODNS_CONTEXT=\"${AUTODNS_CONTEXT:-$(_readaccountconf_mutable AUTODNS_CONTEXT)}\"\n\n  if [ -z \"$AUTODNS_USER\" ] || [ -z \"$AUTODNS_CONTEXT\" ] || [ -z \"$AUTODNS_PASSWORD\" ]; then\n    _err \"You don't specify autodns user, password and context.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable AUTODNS_USER \"$AUTODNS_USER\"\n  _saveaccountconf_mutable AUTODNS_PASSWORD \"$AUTODNS_PASSWORD\"\n  _saveaccountconf_mutable AUTODNS_CONTEXT \"$AUTODNS_CONTEXT\"\n\n  _debug \"First detect the root zone\"\n\n  if ! _get_autodns_zone \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _zone \"$_zone\"\n  _debug _system_ns \"$_system_ns\"\n\n  _info \"Adding TXT record\"\n\n  autodns_response=\"$(_autodns_zone_update \"$_zone\" \"$_sub_domain\" \"$txtvalue\" \"$_system_ns\")\"\n\n  if [ \"$?\" -eq \"0\" ]; then\n    _info \"Added, OK\"\n    return 0\n  fi\n\n  return 1\n}\n\n# Arguments:\n#   txtdomain\n#   txt\ndns_autodns_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  AUTODNS_USER=\"${AUTODNS_USER:-$(_readaccountconf_mutable AUTODNS_USER)}\"\n  AUTODNS_PASSWORD=\"${AUTODNS_PASSWORD:-$(_readaccountconf_mutable AUTODNS_PASSWORD)}\"\n  AUTODNS_CONTEXT=\"${AUTODNS_CONTEXT:-$(_readaccountconf_mutable AUTODNS_CONTEXT)}\"\n\n  if [ -z \"$AUTODNS_USER\" ] || [ -z \"$AUTODNS_CONTEXT\" ] || [ -z \"$AUTODNS_PASSWORD\" ]; then\n    _err \"You don't specify autodns user, password and context.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n\n  if ! _get_autodns_zone \"$fulldomain\"; then\n    _err \"zone not found\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _zone \"$_zone\"\n  _debug _system_ns \"$_system_ns\"\n\n  _info \"Delete TXT record\"\n\n  autodns_response=\"$(_autodns_zone_cleanup \"$_zone\" \"$_sub_domain\" \"$txtvalue\" \"$_system_ns\")\"\n\n  if [ \"$?\" -eq \"0\" ]; then\n    _info \"Deleted, OK\"\n    return 0\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n\n# Arguments:\n#   fulldomain\n# Returns:\n#   _sub_domain=_acme-challenge.www\n#   _zone=domain.com\n#   _system_ns\n_get_autodns_zone() {\n  domain=\"$1\"\n\n  i=2\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n\n    if [ -z \"$h\" ]; then\n      # not valid\n      return 1\n    fi\n\n    autodns_response=\"$(_autodns_zone_inquire \"$h\")\"\n\n    if [ \"$?\" -ne \"0\" ]; then\n      _err \"invalid domain\"\n      return 1\n    fi\n\n    if _contains \"$autodns_response\" \"<summary>1</summary>\" >/dev/null; then\n      _zone=\"$(echo \"$autodns_response\" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)\"\n      _system_ns=\"$(echo \"$autodns_response\" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)\"\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_build_request_auth_xml() {\n  printf \"<auth>\n    <user>%s</user>\n    <password>%s</password>\n    <context>%s</context>\n  </auth>\" \"$AUTODNS_USER\" \"$AUTODNS_PASSWORD\" \"$AUTODNS_CONTEXT\"\n}\n\n# Arguments:\n#   zone\n_build_zone_inquire_xml() {\n  printf \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\n  <request>\n    %s\n    <task>\n      <code>0205</code>\n      <view>\n        <children>1</children>\n        <limit>1</limit>\n      </view>\n      <where>\n        <key>name</key>\n        <operator>eq</operator>\n        <value>%s</value>\n      </where>\n    </task>\n  </request>\" \"$(_build_request_auth_xml)\" \"$1\"\n}\n\n# Arguments:\n#   zone\n#   subdomain\n#   txtvalue\n#   system_ns\n_build_zone_update_xml() {\n  printf \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\n  <request>\n    %s\n    <task>\n      <code>0202001</code>\n      <default>\n        <rr_add>\n          <name>%s</name>\n          <ttl>600</ttl>\n          <type>TXT</type>\n          <value>%s</value>\n        </rr_add>\n      </default>\n      <zone>\n        <name>%s</name>\n        <system_ns>%s</system_ns>\n      </zone>\n    </task>\n  </request>\" \"$(_build_request_auth_xml)\" \"$2\" \"$3\" \"$1\" \"$4\"\n}\n\n# Arguments:\n#   zone\n_autodns_zone_inquire() {\n  request_data=\"$(_build_zone_inquire_xml \"$1\")\"\n  autodns_response=\"$(_autodns_api_call \"$request_data\")\"\n  ret=\"$?\"\n\n  printf \"%s\" \"$autodns_response\"\n  return \"$ret\"\n}\n\n# Arguments:\n#   zone\n#   subdomain\n#   txtvalue\n#   system_ns\n_autodns_zone_update() {\n  request_data=\"$(_build_zone_update_xml \"$1\" \"$2\" \"$3\" \"$4\")\"\n  autodns_response=\"$(_autodns_api_call \"$request_data\")\"\n  ret=\"$?\"\n\n  printf \"%s\" \"$autodns_response\"\n  return \"$ret\"\n}\n\n# Arguments:\n#   zone\n#   subdomain\n#   txtvalue\n#   system_ns\n_autodns_zone_cleanup() {\n  request_data=\"$(_build_zone_update_xml \"$1\" \"$2\" \"$3\" \"$4\")\"\n  # replace 'rr_add>' with 'rr_rem>' in request_data\n  request_data=\"$(printf -- \"%s\" \"$request_data\" | sed 's/rr_add>/rr_rem>/g')\"\n  autodns_response=\"$(_autodns_api_call \"$request_data\")\"\n  ret=\"$?\"\n\n  printf \"%s\" \"$autodns_response\"\n  return \"$ret\"\n}\n\n# Arguments:\n#   request_data\n_autodns_api_call() {\n  request_data=\"$1\"\n\n  _debug request_data \"$request_data\"\n\n  autodns_response=\"$(_post \"$request_data\" \"$AUTODNS_API\")\"\n  ret=\"$?\"\n\n  _debug autodns_response \"$autodns_response\"\n\n  if [ \"$ret\" -ne \"0\" ]; then\n    _err \"error\"\n    return 1\n  fi\n\n  if _contains \"$autodns_response\" \"<type>success</type>\" >/dev/null; then\n    _info \"success\"\n    printf \"%s\" \"$autodns_response\"\n    return 0\n  fi\n\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_aws.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_aws_info='Amazon AWS Route53 domain API\nSite: docs.aws.amazon.com/route53/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_aws\nOptions:\n AWS_ACCESS_KEY_ID API Key ID\n AWS_SECRET_ACCESS_KEY API Secret\n'\n\n# All `_sleep` commands are included to avoid Route53 throttling, see\n# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests\n\nAWS_HOST=\"route53.amazonaws.com\"\nAWS_URL=\"https://$AWS_HOST\"\n\nAWS_WIKI=\"https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_aws_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  AWS_ACCESS_KEY_ID=\"${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}\"\n  AWS_SECRET_ACCESS_KEY=\"${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}\"\n  AWS_DNS_SLOWRATE=\"${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}\"\n\n  if [ -z \"$AWS_ACCESS_KEY_ID\" ] || [ -z \"$AWS_SECRET_ACCESS_KEY\" ]; then\n    _use_container_role || _use_instance_role\n  fi\n\n  if [ -z \"$AWS_ACCESS_KEY_ID\" ] || [ -z \"$AWS_SECRET_ACCESS_KEY\" ]; then\n    AWS_ACCESS_KEY_ID=\"\"\n    AWS_SECRET_ACCESS_KEY=\"\"\n    _err \"You haven't specified the aws route53 api key id and and api key secret yet.\"\n    _err \"Please create your key and try again. see $(__green $AWS_WIKI)\"\n    return 1\n  fi\n\n  #save for future use, unless using a role which will be fetched as needed\n  if [ -z \"$_using_role\" ]; then\n    _saveaccountconf_mutable AWS_ACCESS_KEY_ID \"$AWS_ACCESS_KEY_ID\"\n    _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY \"$AWS_SECRET_ACCESS_KEY\"\n    _saveaccountconf_mutable AWS_DNS_SLOWRATE \"$AWS_DNS_SLOWRATE\"\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    _sleep 1\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Getting existing records for $fulldomain\"\n  if ! aws_rest GET \"2013-04-01$_domain_id/rrset\" \"name=$fulldomain&type=TXT\"; then\n    _sleep 1\n    return 1\n  fi\n\n  if _contains \"$response\" \"<Name>$fulldomain.</Name>\"; then\n    _resource_record=\"$(echo \"$response\" | sed 's/<ResourceRecordSet>/\"/g' | tr '\"' \"\\n\" | grep \"<Name>$fulldomain.</Name>\" | _egrep_o \"<ResourceRecords.*</ResourceRecords>\" | sed \"s/<ResourceRecords>//\" | sed \"s#</ResourceRecords>##\")\"\n    _debug \"_resource_record\" \"$_resource_record\"\n  else\n    _debug \"single new add\"\n  fi\n\n  if [ \"$_resource_record\" ] && _contains \"$response\" \"$txtvalue\"; then\n    _info \"The TXT record already exists. Skipping.\"\n    _sleep 1\n    return 0\n  fi\n\n  _debug \"Adding records\"\n\n  _aws_tmpl_xml=\"<ChangeResourceRecordSetsRequest xmlns=\\\"https://route53.amazonaws.com/doc/2013-04-01/\\\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\\\"$txtvalue\\\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>\"\n\n  if aws_rest POST \"2013-04-01$_domain_id/rrset/\" \"\" \"$_aws_tmpl_xml\" && _contains \"$response\" \"ChangeResourceRecordSetsResponse\"; then\n    _info \"TXT record updated successfully.\"\n    if [ -n \"$AWS_DNS_SLOWRATE\" ]; then\n      _info \"Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds\"\n      _sleep \"$AWS_DNS_SLOWRATE\"\n    else\n      _sleep 1\n    fi\n\n    return 0\n  fi\n  _sleep 1\n  return 1\n}\n\n#fulldomain txtvalue\ndns_aws_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  AWS_ACCESS_KEY_ID=\"${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}\"\n  AWS_SECRET_ACCESS_KEY=\"${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}\"\n  AWS_DNS_SLOWRATE=\"${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}\"\n\n  if [ -z \"$AWS_ACCESS_KEY_ID\" ] || [ -z \"$AWS_SECRET_ACCESS_KEY\" ]; then\n    _use_container_role || _use_instance_role\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    _sleep 1\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Getting existing records for $fulldomain\"\n  if ! aws_rest GET \"2013-04-01$_domain_id/rrset\" \"name=$fulldomain&type=TXT\"; then\n    _sleep 1\n    return 1\n  fi\n\n  if _contains \"$response\" \"<Name>$fulldomain.</Name>\"; then\n    _resource_record=\"$(echo \"$response\" | sed 's/<ResourceRecordSet>/\"/g' | tr '\"' \"\\n\" | grep \"<Name>$fulldomain.</Name>\" | _egrep_o \"<ResourceRecords.*</ResourceRecords>\" | sed \"s/<ResourceRecords>//\" | sed \"s#</ResourceRecords>##\")\"\n    _debug \"_resource_record\" \"$_resource_record\"\n  else\n    _debug \"no records exist, skip\"\n    _sleep 1\n    return 0\n  fi\n\n  _aws_tmpl_xml=\"<ChangeResourceRecordSetsRequest xmlns=\\\"https://route53.amazonaws.com/doc/2013-04-01/\\\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>\"\n\n  if aws_rest POST \"2013-04-01$_domain_id/rrset/\" \"\" \"$_aws_tmpl_xml\" && _contains \"$response\" \"ChangeResourceRecordSetsResponse\"; then\n    _info \"TXT record deleted successfully.\"\n    if [ -n \"$AWS_DNS_SLOWRATE\" ]; then\n      _info \"Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds\"\n      _sleep \"$AWS_DNS_SLOWRATE\"\n    else\n      _sleep 1\n    fi\n\n    return 0\n  fi\n  _sleep 1\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  # iterate over names (a.b.c.d -> b.c.d -> c.d -> d)\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100 | sed 's/\\./\\\\./g')\n    _debug \"Checking domain: $h\"\n    if [ -z \"$h\" ]; then\n      _err \"invalid domain\"\n      return 1\n    fi\n\n    # iterate over paginated result for list_hosted_zones\n    aws_rest GET \"2013-04-01/hostedzone\"\n    while true; do\n      if _contains \"$response\" \"<Name>$h.</Name>\"; then\n        hostedzone=\"$(echo \"$response\" | tr -d '\\n' | sed 's/<HostedZone>/#&/g' | tr '#' '\\n' | _egrep_o \"<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>\")\"\n        _debug hostedzone \"$hostedzone\"\n        if [ \"$hostedzone\" ]; then\n          _domain_id=$(printf \"%s\\n\" \"$hostedzone\" | _egrep_o \"<Id>.*<.Id>\" | head -n 1 | _egrep_o \">.*<\" | tr -d \"<>\")\n          if [ \"$_domain_id\" ]; then\n            _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n            _domain=$h\n            return 0\n          fi\n          _err \"Can't find domain with id: $h\"\n          return 1\n        fi\n      fi\n      if _contains \"$response\" \"<IsTruncated>true</IsTruncated>\" && _contains \"$response\" \"<NextMarker>\"; then\n        _debug \"IsTruncated\"\n        _nextMarker=\"$(echo \"$response\" | _egrep_o \"<NextMarker>.*</NextMarker>\" | cut -d '>' -f 2 | cut -d '<' -f 1)\"\n        _debug \"NextMarker\" \"$_nextMarker\"\n      else\n        break\n      fi\n      _debug \"Checking domain: $h - Next Page \"\n      aws_rest GET \"2013-04-01/hostedzone\" \"marker=$_nextMarker\"\n    done\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_use_container_role() {\n  # automatically set if running inside ECS\n  if [ -z \"$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\" ]; then\n    _debug \"No ECS environment variable detected\"\n    return 1\n  fi\n  _use_metadata \"169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\"\n}\n\n_use_instance_role() {\n  _instance_role_name_url=\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\"\n\n  if _get \"$_instance_role_name_url\" true 1 | _head_n 1 | grep -Fq 401; then\n    _debug \"Using IMDSv2\"\n    _token_url=\"http://169.254.169.254/latest/api/token\"\n    export _H1=\"X-aws-ec2-metadata-token-ttl-seconds: 21600\"\n    _token=\"$(_post \"\" \"$_token_url\" \"\" \"PUT\")\"\n    _secure_debug3 \"_token\" \"$_token\"\n    if [ -z \"$_token\" ]; then\n      _debug \"Unable to fetch IMDSv2 token from instance metadata\"\n      return 1\n    fi\n    export _H1=\"X-aws-ec2-metadata-token: $_token\"\n  fi\n\n  if ! _get \"$_instance_role_name_url\" true 1 | _head_n 1 | grep -Fq 200; then\n    _debug \"Unable to fetch IAM role from instance metadata\"\n    return 1\n  fi\n\n  _instance_role_name=$(_get \"$_instance_role_name_url\" \"\" 1)\n  _debug \"_instance_role_name\" \"$_instance_role_name\"\n  _use_metadata \"$_instance_role_name_url$_instance_role_name\" \"$_token\"\n\n}\n\n_use_metadata() {\n  export _H1=\"X-aws-ec2-metadata-token: $2\"\n  _aws_creds=\"$(\n    _get \"$1\" \"\" 1 |\n      _normalizeJson |\n      tr '{,}' '\\n' |\n      while read -r _line; do\n        _key=\"$(echo \"${_line%%:*}\" | tr -d '\\\"')\"\n        _value=\"${_line#*:}\"\n        _debug3 \"_key\" \"$_key\"\n        _secure_debug3 \"_value\" \"$_value\"\n        case \"$_key\" in\n        AccessKeyId) echo \"AWS_ACCESS_KEY_ID=$_value\" ;;\n        SecretAccessKey) echo \"AWS_SECRET_ACCESS_KEY=$_value\" ;;\n        Token) echo \"AWS_SESSION_TOKEN=$_value\" ;;\n        esac\n      done |\n      paste -sd' ' -\n  )\"\n  _secure_debug \"_aws_creds\" \"$_aws_creds\"\n\n  if [ -z \"$_aws_creds\" ]; then\n    return 1\n  fi\n\n  eval \"$_aws_creds\"\n  _using_role=true\n}\n\n#method uri qstr data\naws_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  qsr=\"$3\"\n  data=\"$4\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n  _debug qsr \"$qsr\"\n  _debug data \"$data\"\n\n  CanonicalURI=\"/$ep\"\n  _debug2 CanonicalURI \"$CanonicalURI\"\n\n  CanonicalQueryString=\"$qsr\"\n  _debug2 CanonicalQueryString \"$CanonicalQueryString\"\n\n  RequestDate=\"$(date -u +\"%Y%m%dT%H%M%SZ\")\"\n  _debug2 RequestDate \"$RequestDate\"\n\n  #RequestDate=\"20161120T141056Z\" ##############\n\n  export _H1=\"x-amz-date: $RequestDate\"\n\n  aws_host=\"$AWS_HOST\"\n  CanonicalHeaders=\"host:$aws_host\\nx-amz-date:$RequestDate\\n\"\n  SignedHeaders=\"host;x-amz-date\"\n  if [ -n \"$AWS_SESSION_TOKEN\" ]; then\n    export _H3=\"x-amz-security-token: $AWS_SESSION_TOKEN\"\n    CanonicalHeaders=\"${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\\n\"\n    SignedHeaders=\"${SignedHeaders};x-amz-security-token\"\n  fi\n  _debug2 CanonicalHeaders \"$CanonicalHeaders\"\n  _debug2 SignedHeaders \"$SignedHeaders\"\n\n  RequestPayload=\"$data\"\n  _debug2 RequestPayload \"$RequestPayload\"\n\n  Hash=\"sha256\"\n\n  CanonicalRequest=\"$mtd\\n$CanonicalURI\\n$CanonicalQueryString\\n$CanonicalHeaders\\n$SignedHeaders\\n$(printf \"%s\" \"$RequestPayload\" | _digest \"$Hash\" hex)\"\n  _debug2 CanonicalRequest \"$CanonicalRequest\"\n\n  HashedCanonicalRequest=\"$(printf \"$CanonicalRequest%s\" | _digest \"$Hash\" hex)\"\n  _debug2 HashedCanonicalRequest \"$HashedCanonicalRequest\"\n\n  Algorithm=\"AWS4-HMAC-SHA256\"\n  _debug2 Algorithm \"$Algorithm\"\n\n  RequestDateOnly=\"$(echo \"$RequestDate\" | cut -c 1-8)\"\n  _debug2 RequestDateOnly \"$RequestDateOnly\"\n\n  Region=\"us-east-1\"\n  Service=\"route53\"\n\n  CredentialScope=\"$RequestDateOnly/$Region/$Service/aws4_request\"\n  _debug2 CredentialScope \"$CredentialScope\"\n\n  StringToSign=\"$Algorithm\\n$RequestDate\\n$CredentialScope\\n$HashedCanonicalRequest\"\n\n  _debug2 StringToSign \"$StringToSign\"\n\n  kSecret=\"AWS4$AWS_SECRET_ACCESS_KEY\"\n\n  #kSecret=\"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY\" ############################\n\n  _secure_debug2 kSecret \"$kSecret\"\n\n  kSecretH=\"$(printf \"%s\" \"$kSecret\" | _hex_dump | tr -d \" \")\"\n  _secure_debug2 kSecretH \"$kSecretH\"\n\n  kDateH=\"$(printf \"$RequestDateOnly%s\" | _hmac \"$Hash\" \"$kSecretH\" hex)\"\n  _debug2 kDateH \"$kDateH\"\n\n  kRegionH=\"$(printf \"$Region%s\" | _hmac \"$Hash\" \"$kDateH\" hex)\"\n  _debug2 kRegionH \"$kRegionH\"\n\n  kServiceH=\"$(printf \"$Service%s\" | _hmac \"$Hash\" \"$kRegionH\" hex)\"\n  _debug2 kServiceH \"$kServiceH\"\n\n  kSigningH=\"$(printf \"%s\" \"aws4_request\" | _hmac \"$Hash\" \"$kServiceH\" hex)\"\n  _debug2 kSigningH \"$kSigningH\"\n\n  signature=\"$(printf \"$StringToSign%s\" | _hmac \"$Hash\" \"$kSigningH\" hex)\"\n  _debug2 signature \"$signature\"\n\n  Authorization=\"$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature\"\n  _debug2 Authorization \"$Authorization\"\n\n  _H2=\"Authorization: $Authorization\"\n  _debug _H2 \"$_H2\"\n\n  url=\"$AWS_URL/$ep\"\n  if [ \"$qsr\" ]; then\n    url=\"$AWS_URL/$ep?$qsr\"\n  fi\n\n  if [ \"$mtd\" = \"GET\" ]; then\n    response=\"$(_get \"$url\")\"\n  else\n    response=\"$(_post \"$data\" \"$url\")\"\n  fi\n\n  _ret=\"$?\"\n  _debug2 response \"$response\"\n  if [ \"$_ret\" = \"0\" ]; then\n    if _contains \"$response\" \"<ErrorResponse\"; then\n      _err \"Response error:$response\"\n      return 1\n    fi\n  fi\n\n  return \"$_ret\"\n}\n"
  },
  {
    "path": "dnsapi/dns_azion.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_azion_info='Azion.om\nSite: Azion.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_azion\nOptions:\n AZION_Email Email\n AZION_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/3555\n'\n\nAZION_Api=\"https://api.azionapi.net\"\n\n########  Public functions ########\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_azion_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"Detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain not found\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _domain_id \"$_domain_id\"\n\n  _info \"Add or update record\"\n  _get_record \"$_domain_id\" \"$_sub_domain\"\n  if [ \"$record_id\" ]; then\n    _payload=\"{\\\"record_type\\\": \\\"TXT\\\", \\\"entry\\\": \\\"$_sub_domain\\\", \\\"answers_list\\\": [$answers_list, \\\"$txtvalue\\\"], \\\"ttl\\\": 20}\"\n    if _azion_rest PUT \"intelligent_dns/$_domain_id/records/$record_id\" \"$_payload\"; then\n      if _contains \"$response\" \"$txtvalue\"; then\n        _info \"Record updated.\"\n        return 0\n      fi\n    fi\n  else\n    _payload=\"{\\\"record_type\\\": \\\"TXT\\\", \\\"entry\\\": \\\"$_sub_domain\\\", \\\"answers_list\\\": [\\\"$txtvalue\\\"], \\\"ttl\\\": 20}\"\n    if _azion_rest POST \"intelligent_dns/$_domain_id/records\" \"$_payload\"; then\n      if _contains \"$response\" \"$txtvalue\"; then\n        _info \"Record added.\"\n        return 0\n      fi\n    fi\n  fi\n  _err \"Failed to add or update record.\"\n  return 1\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_azion_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"Detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain not found\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _domain_id \"$_domain_id\"\n\n  _info \"Removing record\"\n  _get_record \"$_domain_id\" \"$_sub_domain\"\n  if [ \"$record_id\" ]; then\n    if _azion_rest DELETE \"intelligent_dns/$_domain_id/records/$record_id\"; then\n      _info \"Record removed.\"\n      return 0\n    else\n      _err \"Failed to remove record.\"\n      return 1\n    fi\n  else\n    _info \"Record not found or already removed.\"\n    return 0\n  fi\n}\n\n####################  Private functions below ##################################\n# Usage: _acme-challenge.www.domain.com\n# returns\n#  _sub_domain=_acme-challenge.www\n#  _domain=domain.com\n#  _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  if ! _azion_rest GET \"intelligent_dns\"; then\n    return 1\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      # not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"domain\\\":\\\"$h\\\"\"; then\n      _domain_id=$(echo \"$response\" | tr '{' \"\\n\" | grep \"\\\"domain\\\":\\\"$h\\\"\" | _egrep_o \"\\\"id\\\":[0-9]*\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n      _debug _domain_id \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_get_record() {\n  _domain_id=$1\n  _record=$2\n\n  if ! _azion_rest GET \"intelligent_dns/$_domain_id/records\"; then\n    return 1\n  fi\n\n  if _contains \"$response\" \"\\\"entry\\\":\\\"$_record\\\"\"; then\n    _json_record=$(echo \"$response\" | tr '{' \"\\n\" | grep \"\\\"entry\\\":\\\"$_record\\\"\")\n    if [ \"$_json_record\" ]; then\n      record_id=$(echo \"$_json_record\" | _egrep_o \"\\\"record_id\\\":[0-9]*\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n      answers_list=$(echo \"$_json_record\" | _egrep_o \"\\\"answers_list\\\":\\[.*\\]\" | _head_n 1 | cut -d : -f 2 | tr -d \\[\\])\n      return 0\n    fi\n    return 1\n  fi\n  return 1\n}\n\n_get_token() {\n  AZION_Email=\"${AZION_Email:-$(_readaccountconf_mutable AZION_Email)}\"\n  AZION_Password=\"${AZION_Password:-$(_readaccountconf_mutable AZION_Password)}\"\n\n  if ! _contains \"$AZION_Email\" \"@\"; then\n    _err \"It seems that the AZION_Email is not a valid email address. Revalidate your environments.\"\n    return 1\n  fi\n\n  if [ -z \"$AZION_Email\" ] || [ -z \"$AZION_Password\" ]; then\n    _err \"You didn't specified a AZION_Email/AZION_Password to generate Azion token.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable AZION_Email \"$AZION_Email\"\n  _saveaccountconf_mutable AZION_Password \"$AZION_Password\"\n\n  _basic_auth=$(printf \"%s:%s\" \"$AZION_Email\" \"$AZION_Password\" | _base64)\n  _debug _basic_auth \"$_basic_auth\"\n\n  export _H1=\"Accept: application/json; version=3\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Authorization: Basic $_basic_auth\"\n\n  response=\"$(_post \"\" \"$AZION_Api/tokens\" \"\" \"POST\")\"\n  if _contains \"$response\" \"\\\"token\\\":\\\"\" >/dev/null; then\n    _azion_token=$(echo \"$response\" | _egrep_o \"\\\"token\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\n    export AZION_Token=\"$_azion_token\"\n  else\n    _err \"Failed to generate Azion token\"\n    return 1\n  fi\n}\n\n_azion_rest() {\n  _method=$1\n  _uri=\"$2\"\n  _data=\"$3\"\n\n  if [ -z \"$AZION_Token\" ]; then\n    _get_token\n  fi\n  _debug2 token \"$AZION_Token\"\n\n  export _H1=\"Accept: application/json; version=3\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Authorization: token $AZION_Token\"\n\n  if [ \"$_method\" != \"GET\" ]; then\n    _debug _data \"$_data\"\n    response=\"$(_post \"$_data\" \"$AZION_Api/$_uri\" \"\" \"$_method\")\"\n  else\n    response=\"$(_get \"$AZION_Api/$_uri\")\"\n  fi\n\n  _debug2 response \"$response\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $_method $_uri $_data\"\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_azure.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_azure_info='Azure\nSite: Azure.microsoft.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_azure\nOptions:\n AZUREDNS_SUBSCRIPTIONID Subscription ID\n AZUREDNS_TENANTID Tenant ID\n AZUREDNS_APPID App ID. App ID of the service principal\n AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal\n AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. \"true\"/\"false\"\n AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Optional.\n'\n\nwiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\n#\n# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/create-or-update?view=rest-dns-2018-05-01&tabs=HTTP\n#\n\ndns_azure_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  AZUREDNS_SUBSCRIPTIONID=\"${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}\"\n  if [ -z \"$AZUREDNS_SUBSCRIPTIONID\" ]; then\n    AZUREDNS_SUBSCRIPTIONID=\"\"\n    AZUREDNS_TENANTID=\"\"\n    AZUREDNS_APPID=\"\"\n    AZUREDNS_CLIENTSECRET=\"\"\n    AZUREDNS_BEARERTOKEN=\"\"\n    _err \"You didn't specify the Azure Subscription ID\"\n    return 1\n  fi\n  #save subscription id to account conf file.\n  _saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID \"$AZUREDNS_SUBSCRIPTIONID\"\n\n  AZUREDNS_MANAGEDIDENTITY=\"${AZUREDNS_MANAGEDIDENTITY:-$(_readaccountconf_mutable AZUREDNS_MANAGEDIDENTITY)}\"\n  if [ \"$AZUREDNS_MANAGEDIDENTITY\" = true ]; then\n    _info \"Using Azure managed identity\"\n    #save managed identity as preferred authentication method, clear service principal credentials from conf file.\n    _saveaccountconf_mutable AZUREDNS_MANAGEDIDENTITY \"$AZUREDNS_MANAGEDIDENTITY\"\n    _saveaccountconf_mutable AZUREDNS_TENANTID \"\"\n    _saveaccountconf_mutable AZUREDNS_APPID \"\"\n    _saveaccountconf_mutable AZUREDNS_CLIENTSECRET \"\"\n    _saveaccountconf_mutable AZUREDNS_BEARERTOKEN \"\"\n  else\n    _info \"You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token\"\n    AZUREDNS_TENANTID=\"${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}\"\n    AZUREDNS_APPID=\"${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}\"\n    AZUREDNS_CLIENTSECRET=\"${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}\"\n    AZUREDNS_BEARERTOKEN=\"${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}\"\n    if [ -z \"$AZUREDNS_BEARERTOKEN\" ]; then\n      if [ -z \"$AZUREDNS_TENANTID\" ]; then\n        AZUREDNS_SUBSCRIPTIONID=\"\"\n        AZUREDNS_TENANTID=\"\"\n        AZUREDNS_APPID=\"\"\n        AZUREDNS_CLIENTSECRET=\"\"\n        AZUREDNS_BEARERTOKEN=\"\"\n        _err \"You didn't specify the Azure Tenant ID \"\n        return 1\n      fi\n\n      if [ -z \"$AZUREDNS_APPID\" ]; then\n        AZUREDNS_SUBSCRIPTIONID=\"\"\n        AZUREDNS_TENANTID=\"\"\n        AZUREDNS_APPID=\"\"\n        AZUREDNS_CLIENTSECRET=\"\"\n        AZUREDNS_BEARERTOKEN=\"\"\n        _err \"You didn't specify the Azure App ID\"\n        return 1\n      fi\n\n      if [ -z \"$AZUREDNS_CLIENTSECRET\" ]; then\n        AZUREDNS_SUBSCRIPTIONID=\"\"\n        AZUREDNS_TENANTID=\"\"\n        AZUREDNS_APPID=\"\"\n        AZUREDNS_CLIENTSECRET=\"\"\n        AZUREDNS_BEARERTOKEN=\"\"\n        _err \"You didn't specify the Azure Client Secret\"\n        return 1\n      fi\n    else\n      _info \"Using provided bearer token\"\n    fi\n\n    #save account details to account conf file, don't opt in for azure manages identity check.\n    _saveaccountconf_mutable AZUREDNS_MANAGEDIDENTITY \"false\"\n    _saveaccountconf_mutable AZUREDNS_TENANTID \"$AZUREDNS_TENANTID\"\n    _saveaccountconf_mutable AZUREDNS_APPID \"$AZUREDNS_APPID\"\n    _saveaccountconf_mutable AZUREDNS_CLIENTSECRET \"$AZUREDNS_CLIENTSECRET\"\n    _saveaccountconf_mutable AZUREDNS_BEARERTOKEN \"$AZUREDNS_BEARERTOKEN\"\n  fi\n\n  if [ -z \"$AZUREDNS_BEARERTOKEN\" ]; then\n    accesstoken=$(_azure_getaccess_token \"$AZUREDNS_MANAGEDIDENTITY\" \"$AZUREDNS_TENANTID\" \"$AZUREDNS_APPID\" \"$AZUREDNS_CLIENTSECRET\")\n  else\n    accesstoken=$(echo \"$AZUREDNS_BEARERTOKEN\" | sed \"s/Bearer //g\")\n  fi\n\n  if ! _get_root \"$fulldomain\" \"$AZUREDNS_SUBSCRIPTIONID\" \"$accesstoken\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  acmeRecordURI=\"https://management.azure.com$(printf '%s' \"$_domain_id\" | sed 's/\\\\//g')/TXT/$_sub_domain?api-version=2017-09-01\"\n  _debug \"$acmeRecordURI\"\n  # Get existing TXT record\n  _azure_rest GET \"$acmeRecordURI\" \"\" \"$accesstoken\"\n  values=\"{\\\"value\\\":[\\\"$txtvalue\\\"]}\"\n  timestamp=\"$(_time)\"\n  if [ \"$_code\" = \"200\" ]; then\n    vlist=\"$(echo \"$response\" | _egrep_o \"\\\"value\\\"\\\\s*:\\\\s*\\\\[\\\\s*\\\"[^\\\"]*\\\"\\\\s*]\" | cut -d : -f 2 | tr -d \"[]\\\"\")\"\n    _debug \"existing TXT found\"\n    _debug \"$vlist\"\n    existingts=\"$(echo \"$response\" | _egrep_o \"\\\"acmetscheck\\\"\\\\s*:\\\\s*\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \"\\\"\")\"\n    if [ -z \"$existingts\" ]; then\n      # the record was not created by acme.sh. Copy the exisiting entires\n      existingts=$timestamp\n    fi\n    _diff=\"$(_math \"$timestamp - $existingts\")\"\n    _debug \"existing txt age: $_diff\"\n    # only use recently added records and discard if older than 2 hours because they are probably orphaned\n    if [ \"$_diff\" -lt 7200 ]; then\n      _debug \"existing txt value: $vlist\"\n      for v in $vlist; do\n        values=\"$values ,{\\\"value\\\":[\\\"$v\\\"]}\"\n      done\n    fi\n  fi\n  # Add the txtvalue TXT Record\n  body=\"{\\\"properties\\\":{\\\"metadata\\\":{\\\"acmetscheck\\\":\\\"$timestamp\\\"},\\\"TTL\\\":10, \\\"TXTRecords\\\":[$values]}}\"\n  _azure_rest PUT \"$acmeRecordURI\" \"$body\" \"$accesstoken\"\n  if [ \"$_code\" = \"200\" ] || [ \"$_code\" = '201' ]; then\n    _info \"validation value added\"\n    return 0\n  else\n    _err \"error adding validation value ($_code)\"\n    return 1\n  fi\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\n#\n# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/delete?view=rest-dns-2018-05-01&tabs=HTTP\n#\ndns_azure_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  AZUREDNS_SUBSCRIPTIONID=\"${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}\"\n  if [ -z \"$AZUREDNS_SUBSCRIPTIONID\" ]; then\n    AZUREDNS_SUBSCRIPTIONID=\"\"\n    AZUREDNS_TENANTID=\"\"\n    AZUREDNS_APPID=\"\"\n    AZUREDNS_CLIENTSECRET=\"\"\n    AZUREDNS_BEARERTOKEN=\"\"\n    _err \"You didn't specify the Azure Subscription ID \"\n    return 1\n  fi\n\n  AZUREDNS_MANAGEDIDENTITY=\"${AZUREDNS_MANAGEDIDENTITY:-$(_readaccountconf_mutable AZUREDNS_MANAGEDIDENTITY)}\"\n  if [ \"$AZUREDNS_MANAGEDIDENTITY\" = true ]; then\n    _info \"Using Azure managed identity\"\n  else\n    _info \"You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token\"\n    AZUREDNS_TENANTID=\"${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}\"\n    AZUREDNS_APPID=\"${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}\"\n    AZUREDNS_CLIENTSECRET=\"${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}\"\n    AZUREDNS_BEARERTOKEN=\"${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}\"\n    if [ -z \"$AZUREDNS_BEARERTOKEN\" ]; then\n      if [ -z \"$AZUREDNS_TENANTID\" ]; then\n        AZUREDNS_SUBSCRIPTIONID=\"\"\n        AZUREDNS_TENANTID=\"\"\n        AZUREDNS_APPID=\"\"\n        AZUREDNS_CLIENTSECRET=\"\"\n        AZUREDNS_BEARERTOKEN=\"\"\n        _err \"You didn't specify the Azure Tenant ID \"\n        return 1\n      fi\n\n      if [ -z \"$AZUREDNS_APPID\" ]; then\n        AZUREDNS_SUBSCRIPTIONID=\"\"\n        AZUREDNS_TENANTID=\"\"\n        AZUREDNS_APPID=\"\"\n        AZUREDNS_CLIENTSECRET=\"\"\n        AZUREDNS_BEARERTOKEN=\"\"\n        _err \"You didn't specify the Azure App ID\"\n        return 1\n      fi\n\n      if [ -z \"$AZUREDNS_CLIENTSECRET\" ]; then\n        AZUREDNS_SUBSCRIPTIONID=\"\"\n        AZUREDNS_TENANTID=\"\"\n        AZUREDNS_APPID=\"\"\n        AZUREDNS_CLIENTSECRET=\"\"\n        AZUREDNS_BEARERTOKEN=\"\"\n        _err \"You didn't specify the Azure Client Secret\"\n        return 1\n      fi\n    else\n      _info \"Using provided bearer token\"\n    fi\n  fi\n\n  if [ -z \"$AZUREDNS_BEARERTOKEN\" ]; then\n    accesstoken=$(_azure_getaccess_token \"$AZUREDNS_MANAGEDIDENTITY\" \"$AZUREDNS_TENANTID\" \"$AZUREDNS_APPID\" \"$AZUREDNS_CLIENTSECRET\")\n  else\n    accesstoken=$(echo \"$AZUREDNS_BEARERTOKEN\" | sed \"s/Bearer //g\")\n  fi\n\n  if ! _get_root \"$fulldomain\" \"$AZUREDNS_SUBSCRIPTIONID\" \"$accesstoken\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  acmeRecordURI=\"https://management.azure.com$(printf '%s' \"$_domain_id\" | sed 's/\\\\//g')/TXT/$_sub_domain?api-version=2017-09-01\"\n  _debug \"$acmeRecordURI\"\n  # Get existing TXT record\n  _azure_rest GET \"$acmeRecordURI\" \"\" \"$accesstoken\"\n  timestamp=\"$(_time)\"\n  if [ \"$_code\" = \"200\" ]; then\n    vlist=\"$(echo \"$response\" | _egrep_o \"\\\"value\\\"\\\\s*:\\\\s*\\\\[\\\\s*\\\"[^\\\"]*\\\"\\\\s*]\" | cut -d : -f 2 | tr -d \"[]\\\"\" | grep -v -- \"$txtvalue\")\"\n    values=\"\"\n    comma=\"\"\n    for v in $vlist; do\n      values=\"$values$comma{\\\"value\\\":[\\\"$v\\\"]}\"\n      comma=\",\"\n    done\n    if [ -z \"$values\" ]; then\n      # No values left remove record\n      _debug \"removing validation record completely $acmeRecordURI\"\n      _azure_rest DELETE \"$acmeRecordURI\" \"\" \"$accesstoken\"\n      if [ \"$_code\" = \"200\" ] || [ \"$_code\" = '204' ]; then\n        _info \"validation record removed\"\n      else\n        _err \"error removing validation record ($_code)\"\n        return 1\n      fi\n    else\n      # Remove only txtvalue from the TXT Record\n      body=\"{\\\"properties\\\":{\\\"metadata\\\":{\\\"acmetscheck\\\":\\\"$timestamp\\\"},\\\"TTL\\\":10, \\\"TXTRecords\\\":[$values]}}\"\n      _azure_rest PUT \"$acmeRecordURI\" \"$body\" \"$accesstoken\"\n      if [ \"$_code\" = \"200\" ] || [ \"$_code\" = '201' ]; then\n        _info \"validation value removed\"\n        return 0\n      else\n        _err \"error removing validation value ($_code)\"\n        return 1\n      fi\n    fi\n  fi\n}\n\n###################  Private functions below ##################################\n\n_azure_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  accesstoken=\"$4\"\n\n  MAX_REQUEST_RETRY_TIMES=5\n  _request_retry_times=0\n  while [ \"${_request_retry_times}\" -lt \"$MAX_REQUEST_RETRY_TIMES\" ]; do\n    _debug3 _request_retry_times \"$_request_retry_times\"\n    export _H1=\"authorization: Bearer $accesstoken\"\n    export _H2=\"accept: application/json\"\n    export _H3=\"Content-Type: application/json\"\n    # clear headers from previous request to avoid getting wrong http code on timeouts\n    : >\"$HTTP_HEADER\"\n    _debug \"$ep\"\n    if [ \"$m\" != \"GET\" ]; then\n      _secure_debug2 \"data $data\"\n      response=\"$(_post \"$data\" \"$ep\" \"\" \"$m\")\"\n    else\n      response=\"$(_get \"$ep\")\"\n    fi\n    _ret=\"$?\"\n    _secure_debug2 \"response $response\"\n    _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n    _debug \"http response code $_code\"\n    if [ \"$_code\" = \"401\" ]; then\n      # we have an invalid access token set to expired\n      _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO \"0\"\n      _err \"Access denied. Invalid access token. Make sure your Azure settings are correct. See: $wiki\"\n      return 1\n    fi\n    # See https://learn.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes\n    if [ \"$_ret\" != \"0\" ] || [ -z \"$_code\" ] || [ \"$_code\" = \"408\" ] || [ \"$_code\" = \"500\" ] || [ \"$_code\" = \"503\" ] || [ \"$_code\" = \"504\" ]; then\n      _request_retry_times=\"$(_math \"$_request_retry_times\" + 1)\"\n      _info \"REST call error $_code retrying $ep in $_request_retry_times s\"\n      _sleep \"$_request_retry_times\"\n      continue\n    fi\n    break\n  done\n  if [ \"$_request_retry_times\" = \"$MAX_REQUEST_RETRY_TIMES\" ]; then\n    _err \"Error Azure REST called was retried $MAX_REQUEST_RETRY_TIMES times.\"\n    _err \"Calling $ep failed.\"\n    return 1\n  fi\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  return 0\n}\n\n## Ref: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#request-an-access-token\n_azure_getaccess_token() {\n  managedIdentity=$1\n  tenantID=$2\n  clientID=$3\n  clientSecret=$4\n\n  accesstoken=\"${AZUREDNS_ACCESSTOKEN:-$(_readaccountconf_mutable AZUREDNS_ACCESSTOKEN)}\"\n  expires_on=\"${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}\"\n\n  # can we reuse the bearer token?\n  if [ -n \"$accesstoken\" ] && [ -n \"$expires_on\" ]; then\n    if [ \"$(_time)\" -lt \"$expires_on\" ]; then\n      # brearer token is still valid - reuse it\n      _debug \"reusing bearer token\"\n      printf \"%s\" \"$accesstoken\"\n      return 0\n    else\n      _debug \"bearer token expired\"\n    fi\n  fi\n  _debug \"getting new bearer token\"\n\n  if [ \"$managedIdentity\" = true ]; then\n    # https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http\n    if [ -n \"$IDENTITY_ENDPOINT\" ]; then\n      # Some Azure environments may set IDENTITY_ENDPOINT (formerly MSI_ENDPOINT) to have an alternative metadata endpoint\n      url=\"$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/\"\n      headers=\"X-IDENTITY-HEADER: $IDENTITY_HEADER\"\n    else\n      url=\"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/\"\n      headers=\"Metadata: true\"\n    fi\n\n    export _H1=\"$headers\"\n    response=\"$(_get \"$url\")\"\n    response=\"$(echo \"$response\" | _normalizeJson)\"\n    accesstoken=$(echo \"$response\" | _egrep_o \"\\\"access_token\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n    expires_on=$(echo \"$response\" | _egrep_o \"\\\"expires_on\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n  else\n    export _H1=\"accept: application/json\"\n    export _H2=\"Content-Type: application/x-www-form-urlencoded\"\n    body=\"resource=$(printf \"%s\" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf \"%s\" \"$clientID\" | _url_encode)&client_secret=$(printf \"%s\" \"$clientSecret\" | _url_encode)&grant_type=client_credentials\"\n    _secure_debug2 \"data $body\"\n    response=\"$(_post \"$body\" \"https://login.microsoftonline.com/$tenantID/oauth2/token\" \"\" \"POST\")\"\n    _ret=\"$?\"\n    _secure_debug2 \"response $response\"\n    response=\"$(echo \"$response\" | _normalizeJson)\"\n    accesstoken=$(echo \"$response\" | _egrep_o \"\\\"access_token\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n    expires_on=$(echo \"$response\" | _egrep_o \"\\\"expires_on\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n  fi\n\n  if [ -z \"$accesstoken\" ]; then\n    _err \"No acccess token received. Check your Azure settings. See: $wiki\"\n    return 1\n  fi\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"error $response\"\n    return 1\n  fi\n  _saveaccountconf_mutable AZUREDNS_ACCESSTOKEN \"$accesstoken\"\n  _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO \"$expires_on\"\n  printf \"%s\" \"$accesstoken\"\n  return 0\n}\n\n_get_root() {\n  domain=$1\n  subscriptionId=$2\n  accesstoken=$3\n  i=1\n  p=1\n\n  ## Ref: https://learn.microsoft.com/en-us/rest/api/dns/zones/list?view=rest-dns-2018-05-01&tabs=HTTP\n  ## returns up to 100 zones in one response. Handling more results is not implemented\n  ## (ZoneListResult with continuation token for the next page of results)\n  ##\n  ## TODO: handle more than 100 results, as per:\n  ## https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-dns-limits\n  ## The new limit is 250 Public DNS zones per subscription, while the old limit was only 100\n  ##\n  _azure_rest GET \"https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\\$top=500&api-version=2017-09-01\" \"\" \"$accesstoken\"\n  # Find matching domain name in Json response\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug2 \"Checking domain: $h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      _err \"Invalid domain\"\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"\\\\{\\\"id\\\":\\\"[^\\\"]*\\\\/$h\\\"\" | head -n 1 | cut -d : -f 2 | tr -d \\\")\n      if [ \"$_domain_id\" ]; then\n        if [ \"$i\" = 1 ]; then\n          #create the record at the domain apex (@) if only the domain name was provided as --domain-alias\n          _sub_domain=\"@\"\n        else\n          _sub_domain=$(echo \"$domain\" | cut -d . -f 1-\"$p\")\n        fi\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_beget.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_beget_info='Beget.com\nSite: Beget.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_beget\nOptions:\n BEGET_User API user\n BEGET_Password API password\nIssues: github.com/acmesh-official/acme.sh/issues/6200\nAuthor: ARNik <arnik@arnik.ru>\n'\n\nBeget_Api=\"https://api.beget.com/api\"\n\n####################  Public functions ####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_beget_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"dns_beget_add() $fulldomain $txtvalue\"\n  fulldomain=$(echo \"$fulldomain\" | _lower_case)\n\n  Beget_Username=\"${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}\"\n  Beget_Password=\"${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}\"\n\n  if [ -z \"$Beget_Username\" ] || [ -z \"$Beget_Password\" ]; then\n    Beget_Username=\"\"\n    Beget_Password=\"\"\n    _err \"You must export variables: Beget_Username, and Beget_Password\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable Beget_Username \"$Beget_Username\"\n  _saveaccountconf_mutable Beget_Password \"$Beget_Password\"\n\n  _info \"Prepare subdomain.\"\n  if ! _prepare_subdomain \"$fulldomain\"; then\n    _err \"Can't prepare subdomain.\"\n    return 1\n  fi\n\n  _info \"Get domain records\"\n  data=\"{\\\"fqdn\\\":\\\"$fulldomain\\\"}\"\n  res=$(_api_call \"$Beget_Api/dns/getData\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't get domain records.\"\n    return 1\n  fi\n\n  _info \"Add new TXT record\"\n  data=\"{\\\"fqdn\\\":\\\"$fulldomain\\\",\\\"records\\\":{\"\n  data=${data}$(_parce_records \"$res\" \"A\")\n  data=${data}$(_parce_records \"$res\" \"AAAA\")\n  data=${data}$(_parce_records \"$res\" \"CAA\")\n  data=${data}$(_parce_records \"$res\" \"MX\")\n  data=${data}$(_parce_records \"$res\" \"SRV\")\n  data=${data}$(_parce_records \"$res\" \"TXT\")\n  data=$(echo \"$data\" | sed 's/,$//')\n  data=${data}'}}'\n\n  str=$(_txt_to_dns_json \"$txtvalue\")\n  data=$(_add_record \"$data\" \"TXT\" \"$str\")\n\n  res=$(_api_call \"$Beget_Api/dns/changeRecords\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't change domain records.\"\n    return 1\n  fi\n\n  return 0\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_beget_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"dns_beget_rm() $fulldomain $txtvalue\"\n  fulldomain=$(echo \"$fulldomain\" | _lower_case)\n\n  Beget_Username=\"${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}\"\n  Beget_Password=\"${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}\"\n\n  _info \"Get current domain records\"\n  data=\"{\\\"fqdn\\\":\\\"$fulldomain\\\"}\"\n  res=$(_api_call \"$Beget_Api/dns/getData\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't get domain records.\"\n    return 1\n  fi\n\n  _info \"Remove TXT record\"\n  data=\"{\\\"fqdn\\\":\\\"$fulldomain\\\",\\\"records\\\":{\"\n  data=${data}$(_parce_records \"$res\" \"A\")\n  data=${data}$(_parce_records \"$res\" \"AAAA\")\n  data=${data}$(_parce_records \"$res\" \"CAA\")\n  data=${data}$(_parce_records \"$res\" \"MX\")\n  data=${data}$(_parce_records \"$res\" \"SRV\")\n  data=${data}$(_parce_records \"$res\" \"TXT\")\n  data=$(echo \"$data\" | sed 's/,$//')\n  data=${data}'}}'\n\n  str=$(_txt_to_dns_json \"$txtvalue\")\n  data=$(_rm_record \"$data\" \"$str\")\n\n  res=$(_api_call \"$Beget_Api/dns/changeRecords\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't change domain records.\"\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ####################\n\n# Create subdomain if needed\n# Usage: _prepare_subdomain [fulldomain]\n_prepare_subdomain() {\n  fulldomain=$1\n\n  _info \"Detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if [ -z \"$_sub_domain\" ]; then\n    _debug \"$fulldomain is a root domain.\"\n    return 0\n  fi\n\n  _info \"Get subdomain list\"\n  res=$(_api_call \"$Beget_Api/domain/getSubdomainList\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't get subdomain list.\"\n    return 1\n  fi\n\n  if _contains \"$res\" \"\\\"fqdn\\\":\\\"$fulldomain\\\"\"; then\n    _debug \"Subdomain $fulldomain already exist.\"\n    return 0\n  fi\n\n  _info \"Subdomain $fulldomain does not exist. Let's create one.\"\n  data=\"{\\\"subdomain\\\":\\\"$_sub_domain\\\",\\\"domain_id\\\":$_domain_id}\"\n  res=$(_api_call \"$Beget_Api/domain/addSubdomainVirtual\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't create subdomain.\"\n    return 1\n  fi\n\n  _debug \"Cleanup subdomen records\"\n  data=\"{\\\"fqdn\\\":\\\"$fulldomain\\\",\\\"records\\\":{}}\"\n  res=$(_api_call \"$Beget_Api/dns/changeRecords\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _debug \"Can't cleanup $fulldomain records.\"\n  fi\n\n  data=\"{\\\"fqdn\\\":\\\"www.$fulldomain\\\",\\\"records\\\":{}}\"\n  res=$(_api_call \"$Beget_Api/dns/changeRecords\" \"$data\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _debug \"Can't cleanup www.$fulldomain records.\"\n  fi\n\n  return 0\n}\n\n# Usage: _get_root _acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=32436365\n_get_root() {\n  fulldomain=$1\n  i=1\n  p=1\n\n  _debug \"Get domain list\"\n  res=$(_api_call \"$Beget_Api/domain/getList\")\n  if ! _is_api_reply_ok \"$res\"; then\n    _err \"Can't get domain list.\"\n    return 1\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    if _contains \"$res\" \"$h\"; then\n      _domain_id=$(echo \"$res\" | _egrep_o \"\\\"id\\\":[0-9]*,\\\"fqdn\\\":\\\"$h\\\"\" | cut -d , -f1 | cut -d : -f2)\n      if [ \"$_domain_id\" ]; then\n        if [ \"$h\" != \"$fulldomain\" ]; then\n          _sub_domain=$(echo \"$fulldomain\" | cut -d . -f 1-\"$p\")\n        else\n          _sub_domain=\"\"\n        fi\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n# Parce DNS records from json string\n# Usage: _parce_records [j_str] [record_name]\n_parce_records() {\n  j_str=$1\n  record_name=$2\n  res=\"\\\"$record_name\\\":[\"\n  res=${res}$(echo \"$j_str\" | _egrep_o \"\\\"$record_name\\\":\\[.*\" | cut -d '[' -f2 | cut -d ']' -f1)\n  res=${res}\"],\"\n  echo \"$res\"\n}\n\n# Usage: _add_record [data] [record_name] [record_data]\n_add_record() {\n  data=$1\n  record_name=$2\n  record_data=$3\n  echo \"$data\" | sed \"s/\\\"$record_name\\\":\\[/\\\"$record_name\\\":\\[$record_data,/\" | sed \"s/,\\]/\\]/\"\n}\n\n# Usage: _rm_record [data] [record_data]\n_rm_record() {\n  data=$1\n  record_data=$2\n  echo \"$data\" | sed \"s/$record_data//g\" | sed \"s/,\\+/,/g\" |\n    sed \"s/{,/{/g\" | sed \"s/,}/}/g\" |\n    sed \"s/\\[,/\\[/g\" | sed \"s/,\\]/\\]/g\"\n}\n\n_txt_to_dns_json() {\n  echo \"{\\\"ttl\\\":600,\\\"txtdata\\\":\\\"$1\\\"}\"\n}\n\n# Usage: _api_call [api_url] [input_data]\n_api_call() {\n  api_url=\"$1\"\n  input_data=\"$2\"\n\n  _debug \"_api_call $api_url\"\n  _debug \"Request: $input_data\"\n\n  # res=$(curl -s -L -D ./http.header \\\n  # \"$api_url\" \\\n  # --data-urlencode login=$Beget_Username \\\n  # --data-urlencode passwd=$Beget_Password \\\n  # --data-urlencode input_format=json \\\n  # --data-urlencode output_format=json \\\n  # --data-urlencode \"input_data=$input_data\")\n\n  url=\"$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json\"\n  if [ -n \"$input_data\" ]; then\n    url=${url}\"&input_data=\"\n    url=${url}$(echo \"$input_data\" | _url_encode)\n  fi\n  res=$(_get \"$url\")\n\n  _debug \"Reply: $res\"\n  echo \"$res\"\n}\n\n# Usage: _is_api_reply_ok [api_reply]\n_is_api_reply_ok() {\n  _contains \"$1\" '^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":.*}}$'\n}\n"
  },
  {
    "path": "dnsapi/dns_bookmyname.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_bookmyname_info='BookMyName.com\nSite: BookMyName.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bookmyname\nOptions:\n BOOKMYNAME_USERNAME Username\n BOOKMYNAME_PASSWORD Password\nIssues: github.com/acmesh-official/acme.sh/issues/3209\nAuthor: @Neilpang\n'\n\n########  Public functions #####################\n\n# BookMyName urls:\n# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=add&value=\"XXXXXXXX\"'\n# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=remove&value=\"XXXXXXXX\"'\n\n# Output:\n#good: update done, cid 123456, domain id 456789, type txt, ip XXXXXXXX\n#good: remove done 1, cid 123456, domain id 456789, ttl 300, type txt, ip XXXXXXXX\n\n# Be careful, BMN DNS servers can be slow to pick up changes; using dnssleep is thus advised.\n\n# Usage:\n# export BOOKMYNAME_USERNAME=\"ABCDE-FREE\"\n# export BOOKMYNAME_PASSWORD=\"MyPassword\"\n# /usr/local/ssl/acme.sh/acme.sh --dns dns_bookmyname --dnssleep 600 --issue -d domain.tld\n\n#Usage: dns_bookmyname_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_bookmyname_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using bookmyname\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  BOOKMYNAME_USERNAME=\"${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}\"\n  BOOKMYNAME_PASSWORD=\"${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}\"\n\n  if [ -z \"$BOOKMYNAME_USERNAME\" ] || [ -z \"$BOOKMYNAME_PASSWORD\" ]; then\n    BOOKMYNAME_USERNAME=\"\"\n    BOOKMYNAME_PASSWORD=\"\"\n    _err \"You didn't specify BookMyName username and password yet.\"\n    _err \"Please specify them and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable BOOKMYNAME_USERNAME \"$BOOKMYNAME_USERNAME\"\n  _saveaccountconf_mutable BOOKMYNAME_PASSWORD \"$BOOKMYNAME_PASSWORD\"\n\n  uri=\"https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/\"\n  data=\"?hostname=${fulldomain}&type=TXT&ttl=300&do=add&value=${txtvalue}\"\n  result=\"$(_get \"${uri}${data}\")\"\n  _debug \"Result: $result\"\n\n  if ! _startswith \"$result\" 'good: update done, cid '; then\n    _err \"Can't add $fulldomain\"\n    return 1\n  fi\n\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_bookmyname_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using bookmyname\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  BOOKMYNAME_USERNAME=\"${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}\"\n  BOOKMYNAME_PASSWORD=\"${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}\"\n\n  uri=\"https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/\"\n  data=\"?hostname=${fulldomain}&type=TXT&ttl=300&do=remove&value=${txtvalue}\"\n  result=\"$(_get \"${uri}${data}\")\"\n  _debug \"Result: $result\"\n\n  if ! _startswith \"$result\" 'good: remove done 1, cid '; then\n    _info \"Can't remove $fulldomain\"\n  fi\n\n}\n\n####################  Private functions below ##################################\n"
  },
  {
    "path": "dnsapi/dns_bunny.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_bunny_info='Bunny.net\nSite: Bunny.net/dns/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bunny\nOptions:\n BUNNY_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/4296\nAuthor: <nosilver4u@ewww.io>\n'\n\n#####################  Public functions  #####################\n\n## Create the text record for validation.\n## Usage: fulldomain txtvalue\n## EG: \"_acme-challenge.www.other.domain.com\" \"XKrxpRBosdq0HG9i01zxXp5CPBs\"\ndns_bunny_add() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=$2\n\n  BUNNY_API_KEY=\"${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}\"\n  # Check if API Key is set\n  if [ -z \"$BUNNY_API_KEY\" ]; then\n    BUNNY_API_KEY=\"\"\n    _err \"You did not specify Bunny.net API key.\"\n    _err \"Please export BUNNY_API_KEY and try again.\"\n    return 1\n  fi\n\n  _info \"Using Bunny.net dns validation - add record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ## save the env vars (key and domain split location) for later automated use\n  _saveaccountconf_mutable BUNNY_API_KEY \"$BUNNY_API_KEY\"\n\n  ## split the domain for Bunny API\n  if ! _get_base_domain \"$fulldomain\"; then\n    _err \"domain not found in your account for addition\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _domain_id \"$_domain_id\"\n\n  ## Set the header with our post type and auth key\n  export _H1=\"Accept: application/json\"\n  export _H2=\"AccessKey: $BUNNY_API_KEY\"\n  export _H3=\"Content-Type: application/json\"\n  PURL=\"https://api.bunny.net/dnszone/$_domain_id/records\"\n  PBODY='{\"Id\":'$_domain_id',\"Type\":3,\"Name\":\"'$_sub_domain'\",\"Value\":\"'$txtvalue'\",\"ttl\":120}'\n\n  _debug PURL \"$PURL\"\n  _debug PBODY \"$PBODY\"\n\n  ## the create request - POST\n  ## args: BODY, URL, [need64, httpmethod]\n  response=\"$(_post \"$PBODY\" \"$PURL\" \"\" \"PUT\")\"\n\n  ## check response\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error in response: $response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n\n  ## finished correctly\n  return 0\n}\n\n## Remove the txt record after validation.\n## Usage: fulldomain txtvalue\n## EG: \"_acme-challenge.www.other.domain.com\" \"XKrxpRBosdq0HG9i01zxXp5CPBs\"\ndns_bunny_rm() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=$2\n\n  BUNNY_API_KEY=\"${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}\"\n  # Check if API Key Exists\n  if [ -z \"$BUNNY_API_KEY\" ]; then\n    BUNNY_API_KEY=\"\"\n    _err \"You did not specify Bunny.net API key.\"\n    _err \"Please export BUNNY_API_KEY and try again.\"\n    return 1\n  fi\n\n  _info \"Using Bunny.net dns validation - remove record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ## split the domain for Bunny API\n  if ! _get_base_domain \"$fulldomain\"; then\n    _err \"Domain not found in your account for TXT record removal\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _domain_id \"$_domain_id\"\n\n  ## Set the header with our post type and key auth key\n  export _H1=\"Accept: application/json\"\n  export _H2=\"AccessKey: $BUNNY_API_KEY\"\n  ## get URL for the list of DNS records\n  GURL=\"https://api.bunny.net/dnszone/$_domain_id\"\n\n  ## 1) Get the domain/zone records\n  ## the fetch request - GET\n  ## args: URL, [onlyheader, timeout]\n  domain_list=\"$(_get \"$GURL\")\"\n\n  ## check response\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error in domain_list response: $domain_list\"\n    return 1\n  fi\n  _debug2 domain_list \"$domain_list\"\n\n  ## 2) search through records\n  ## check for what we are looking for: \"Type\":3,\"Value\":\"$txtvalue\",\"Name\":\"$_sub_domain\"\n  record=\"$(echo \"$domain_list\" | _egrep_o \"\\\"Id\\\"\\s*\\:\\s*\\\"*[0-9]+\\\"*,\\s*\\\"Type\\\"[^}]*\\\"Value\\\"\\s*\\:\\s*\\\"$txtvalue\\\"[^}]*\\\"Name\\\"\\s*\\:\\s*\\\"$_sub_domain\\\"\")\"\n\n  if [ -n \"$record\" ]; then\n\n    ## We found records\n    rec_ids=\"$(echo \"$record\" | _egrep_o \"Id\\\"\\s*\\:\\s*\\\"*[0-9]+\" | _egrep_o \"[0-9]+\")\"\n    _debug rec_ids \"$rec_ids\"\n    if [ -n \"$rec_ids\" ]; then\n      echo \"$rec_ids\" | while IFS= read -r rec_id; do\n        ## delete the record\n        ## delete URL for removing the one we dont want\n        DURL=\"https://api.bunny.net/dnszone/$_domain_id/records/$rec_id\"\n\n        ## the removal request - DELETE\n        ## args: BODY, URL, [need64, httpmethod]\n        response=\"$(_post \"\" \"$DURL\" \"\" \"DELETE\")\"\n\n        ## check response (sort of)\n        if [ \"$?\" != \"0\" ]; then\n          _err \"error in remove response: $response\"\n          return 1\n        fi\n        _debug2 response \"$response\"\n\n      done\n    fi\n  fi\n\n  ## finished correctly\n  return 0\n}\n\n#####################  Private functions below  #####################\n\n## Split the domain provided into the \"base domain\" and the \"start prefix\".\n## This function searches for the longest subdomain in your account\n## for the full domain given and splits it into the base domain (zone)\n## and the prefix/record to be added/removed\n## USAGE: fulldomain\n## EG: \"_acme-challenge.two.three.four.domain.com\"\n## returns\n## _sub_domain=\"_acme-challenge.two\"\n## _domain=\"three.four.domain.com\" *IF* zone \"three.four.domain.com\" exists\n## _domain_id=234\n## if only \"domain.com\" exists it will return\n## _sub_domain=\"_acme-challenge.two.three.four\"\n## _domain=\"domain.com\"\n## _domain_id=234\n_get_base_domain() {\n  # args\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  _debug fulldomain \"$fulldomain\"\n\n  # domain max legal length = 253\n  MAX_DOM=255\n  page=1\n\n  ## get a list of domains for the account to check thru\n  ## Set the headers\n  export _H1=\"Accept: application/json\"\n  export _H2=\"AccessKey: $BUNNY_API_KEY\"\n  _debug BUNNY_API_KEY \"$BUNNY_API_KEY\"\n  ## get URL for the list of domains\n  ## may get: \"links\":{\"pages\":{\"last\":\".../v2/domains/DOM/records?page=2\",\"next\":\".../v2/domains/DOM/records?page=2\"}}\n  DOMURL=\"https://api.bunny.net/dnszone\"\n\n  ## while we dont have a matching domain we keep going\n  while [ -z \"$found\" ]; do\n    ## get the domain list (current page)\n    domain_list=\"$(_get \"$DOMURL\")\"\n\n    ## check response\n    if [ \"$?\" != \"0\" ]; then\n      _err \"error in domain_list response: $domain_list\"\n      return 1\n    fi\n    _debug2 domain_list \"$domain_list\"\n\n    i=1\n    while [ \"$i\" -gt 0 ]; do\n      ## get next longest domain\n      _domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i\"-\"$MAX_DOM\")\n      ## check we got something back from our cut (or are we at the end)\n      if [ -z \"$_domain\" ]; then\n        break\n      fi\n      ## we got part of a domain back - grep it out\n      found=\"$(echo \"$domain_list\" | _egrep_o \"\\\"Id\\\"\\s*:\\s*\\\"*[0-9]+\\\"*,\\s*\\\"Domain\\\"\\s*\\:\\s*\\\"$_domain\\\"\")\"\n      ## check if it exists\n      if [ -n \"$found\" ]; then\n        ## exists - exit loop returning the parts\n        sub_point=$(_math \"$i\" - 1)\n        _sub_domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 1-\"$sub_point\")\n        _domain_id=\"$(echo \"$found\" | _egrep_o \"Id\\\"\\s*\\:\\s*\\\"*[0-9]+\" | _egrep_o \"[0-9]+\")\"\n        _debug _domain_id \"$_domain_id\"\n        _debug _domain \"$_domain\"\n        _debug _sub_domain \"$_sub_domain\"\n        found=\"\"\n        return 0\n      fi\n      ## increment cut point $i\n      i=$(_math \"$i\" + 1)\n    done\n\n    if [ -z \"$found\" ]; then\n      page=$(_math \"$page\" + 1)\n      nextpage=\"https://api.bunny.net/dnszone?page=$page\"\n      ## Find the next page if we don't have a match.\n      hasnextpage=\"$(echo \"$domain_list\" | _egrep_o \"\\\"HasMoreItems\\\"\\s*:\\s*true\")\"\n      if [ -z \"$hasnextpage\" ]; then\n        _err \"No record and no nextpage in Bunny.net domain search.\"\n        found=\"\"\n        return 1\n      fi\n      _debug2 nextpage \"$nextpage\"\n      DOMURL=\"$nextpage\"\n    fi\n\n  done\n\n  ## We went through the entire domain zone list and didn't find one that matched.\n  ## If we ever get here, something is broken in the code...\n  _err \"Domain not found in Bunny.net account, but we should never get here!\"\n  found=\"\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_cf.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_cf_info='CloudFlare\nSite: CloudFlare.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf\nOptions:\n CF_Key API Key\n CF_Email Your account email\nOptionsAlt:\n CF_Token API Token\n CF_Account_ID Account ID\n CF_Zone_ID Zone ID. Optional.\n'\n\nCF_Api=\"https://api.cloudflare.com/client/v4\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_cf_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  CF_Token=\"${CF_Token:-$(_readaccountconf_mutable CF_Token)}\"\n  CF_Account_ID=\"${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}\"\n  CF_Zone_ID=\"${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}\"\n  CF_Key=\"${CF_Key:-$(_readaccountconf_mutable CF_Key)}\"\n  CF_Email=\"${CF_Email:-$(_readaccountconf_mutable CF_Email)}\"\n\n  if [ \"$CF_Token\" ]; then\n    if [ \"$CF_Zone_ID\" ]; then\n      _savedomainconf CF_Token \"$CF_Token\"\n      _savedomainconf CF_Account_ID \"$CF_Account_ID\"\n      _savedomainconf CF_Zone_ID \"$CF_Zone_ID\"\n    else\n      _saveaccountconf_mutable CF_Token \"$CF_Token\"\n      _saveaccountconf_mutable CF_Account_ID \"$CF_Account_ID\"\n      _clearaccountconf_mutable CF_Zone_ID\n      _clearaccountconf CF_Zone_ID\n    fi\n  else\n    if [ -z \"$CF_Key\" ] || [ -z \"$CF_Email\" ]; then\n      CF_Key=\"\"\n      CF_Email=\"\"\n      _err \"You didn't specify a Cloudflare api key and email yet.\"\n      _err \"You can get yours from here https://dash.cloudflare.com/profile.\"\n      return 1\n    fi\n\n    if ! _contains \"$CF_Email\" \"@\"; then\n      _err \"It seems that the CF_Email=$CF_Email is not a valid email address.\"\n      _err \"Please check and retry.\"\n      return 1\n    fi\n    #save the api key and email to the account conf file.\n    _saveaccountconf_mutable CF_Key \"$CF_Key\"\n    _saveaccountconf_mutable CF_Email \"$CF_Email\"\n\n    _clearaccountconf_mutable CF_Token\n    _clearaccountconf_mutable CF_Account_ID\n    _clearaccountconf_mutable CF_Zone_ID\n    _clearaccountconf CF_Token\n    _clearaccountconf CF_Account_ID\n    _clearaccountconf CF_Zone_ID\n\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _cf_rest GET \"zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain\"\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"success\\\":true >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n\n  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so\n  # we can not use updating anymore.\n  #  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  #  _debug count \"$count\"\n  #  if [ \"$count\" = \"0\" ]; then\n  _info \"Adding record\"\n  if _cf_rest POST \"zones/$_domain_id/dns_records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"The record already exists\" ||\n      _contains \"$response\" \"An identical record already exists.\" ||\n      _contains \"$response\" '\"code\":81058'; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_cf_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  CF_Token=\"${CF_Token:-$(_readaccountconf_mutable CF_Token)}\"\n  CF_Account_ID=\"${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}\"\n  CF_Zone_ID=\"${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}\"\n  CF_Key=\"${CF_Key:-$(_readaccountconf_mutable CF_Key)}\"\n  CF_Email=\"${CF_Email:-$(_readaccountconf_mutable CF_Email)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _cf_rest GET \"zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue\"\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"success\\\":true >/dev/null; then\n    _err \"Error: $response\"\n    return 1\n  fi\n\n  count=$(echo \"$response\" | _egrep_o \"\\\"count\\\": *[^,]*\" | cut -d : -f 2 | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(echo \"$response\" | _egrep_o \"\\\"id\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | _head_n 1 | tr -d \" \")\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _cf_rest DELETE \"zones/$_domain_id/dns_records/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    echo \"$response\" | tr -d \" \" | grep \\\"success\\\":true >/dev/null\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  # Use Zone ID directly if provided\n  if [ \"$CF_Zone_ID\" ]; then\n    if ! _cf_rest GET \"zones/$CF_Zone_ID\"; then\n      return 1\n    else\n      if echo \"$response\" | tr -d \" \" | grep \\\"success\\\":true >/dev/null; then\n        _domain=$(echo \"$response\" | _egrep_o \"\\\"name\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | _head_n 1 | tr -d \" \")\n        if [ \"$_domain\" ]; then\n          _cutlength=$((${#domain} - ${#_domain} - 1))\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -c \"1-$_cutlength\")\n          _domain_id=$CF_Zone_ID\n          return 0\n        else\n          return 1\n        fi\n      else\n        return 1\n      fi\n    fi\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if [ \"$CF_Account_ID\" ]; then\n      if ! _cf_rest GET \"zones?name=$h&account.id=$CF_Account_ID\"; then\n        return 1\n      fi\n    else\n      if ! _cf_rest GET \"zones?name=$h\"; then\n        return 1\n      fi\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" || _contains \"$response\" '\"total_count\":1'; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"\\[.\\\"id\\\": *\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_cf_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  email_trimmed=$(echo \"$CF_Email\" | tr -d '\"')\n  key_trimmed=$(echo \"$CF_Key\" | tr -d '\"')\n  token_trimmed=$(echo \"$CF_Token\" | tr -d '\"')\n\n  export _H1=\"Content-Type: application/json\"\n  if [ \"$token_trimmed\" ]; then\n    export _H2=\"Authorization: Bearer $token_trimmed\"\n  else\n    export _H2=\"X-Auth-Email: $email_trimmed\"\n    export _H3=\"X-Auth-Key: $key_trimmed\"\n  fi\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$CF_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$CF_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_clouddns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_clouddns_info='vshosting.cz CloudDNS\nSite: github.com/vshosting/clouddns\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_clouddns\nOptions:\n CLOUDDNS_EMAIL Email\n CLOUDDNS_PASSWORD Password\n CLOUDDNS_CLIENT_ID Client ID\nIssues: github.com/acmesh-official/acme.sh/issues/2699\nAuthor: Radek Sprta <sprta@vshosting.cz>\n'\n\nCLOUDDNS_API='https://admin.vshosting.cloud/clouddns'\nCLOUDDNS_LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login'\n\n########  Public functions #####################\n\n# Usage: add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_clouddns_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"fulldomain\" \"$fulldomain\"\n\n  CLOUDDNS_CLIENT_ID=\"${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}\"\n  CLOUDDNS_EMAIL=\"${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}\"\n  CLOUDDNS_PASSWORD=\"${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}\"\n\n  if [ -z \"$CLOUDDNS_PASSWORD\" ] || [ -z \"$CLOUDDNS_EMAIL\" ] || [ -z \"$CLOUDDNS_CLIENT_ID\" ]; then\n    CLOUDDNS_CLIENT_ID=\"\"\n    CLOUDDNS_EMAIL=\"\"\n    CLOUDDNS_PASSWORD=\"\"\n    _err \"You didn't specify a CloudDNS password, email and client ID yet.\"\n    return 1\n  fi\n  if ! _contains \"$CLOUDDNS_EMAIL\" \"@\"; then\n    _err \"It seems that the CLOUDDNS_EMAIL=$CLOUDDNS_EMAIL is not a valid email address.\"\n    _err \"Please check and retry.\"\n    return 1\n  fi\n  # Save CloudDNS client id, email and password to config file\n  _saveaccountconf_mutable CLOUDDNS_CLIENT_ID \"$CLOUDDNS_CLIENT_ID\"\n  _saveaccountconf_mutable CLOUDDNS_EMAIL \"$CLOUDDNS_EMAIL\"\n  _saveaccountconf_mutable CLOUDDNS_PASSWORD \"$CLOUDDNS_PASSWORD\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # Add TXT record\n  data=\"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain.\\\",\\\"value\\\":\\\"$txtvalue\\\",\\\"domainId\\\":\\\"$_domain_id\\\"}\"\n  if _clouddns_api POST \"record-txt\" \"$data\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n    elif _contains \"$response\" '\"code\":4136'; then\n      _info \"Already exists, OK\"\n    else\n      _err \"Add TXT record error.\"\n      return 1\n    fi\n  fi\n\n  _debug \"Publishing record changes\"\n  _clouddns_api PUT \"domain/$_domain_id/publish\" \"{\\\"soaTtl\\\":300}\"\n}\n\n# Usage: rm _acme-challenge.www.domain.com\ndns_clouddns_rm() {\n  fulldomain=$1\n  _debug \"fulldomain\" \"$fulldomain\"\n\n  CLOUDDNS_CLIENT_ID=\"${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}\"\n  CLOUDDNS_EMAIL=\"${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}\"\n  CLOUDDNS_PASSWORD=\"${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # Get record ID\n  _clouddns_api GET \"domain/$_domain_id\"\n  if _contains \"$response\" \"lastDomainRecordList\"; then\n    re=\"\\\"lastDomainRecordList\\\".*\\\"id\\\":\\\"([^\\\"}]*)\\\"[^}]*\\\"name\\\":\\\"$fulldomain.\\\",\"\n    _last_domains=$(echo \"$response\" | _egrep_o \"$re\")\n    re2=\"\\\"id\\\":\\\"([^\\\"}]*)\\\"[^}]*\\\"name\\\":\\\"$fulldomain.\\\",\"\n    _record_id=$(echo \"$_last_domains\" | _egrep_o \"$re2\" | _head_n 1 | cut -d : -f 2 | cut -d , -f 1 | tr -d \"\\\"\")\n    _debug _record_id \"$_record_id\"\n  else\n    _err \"Could not retrieve record ID\"\n    return 1\n  fi\n\n  _info \"Removing record\"\n  if _clouddns_api DELETE \"record/$_record_id\"; then\n    if _contains \"$response\" \"\\\"error\\\":\"; then\n      _err \"Could not remove record\"\n      return 1\n    fi\n  fi\n\n  _debug \"Publishing record changes\"\n  _clouddns_api PUT \"domain/$_domain_id/publish\" \"{\\\"soaTtl\\\":300}\"\n}\n\n####################  Private functions below ##################################\n\n# Usage: _get_root _acme-challenge.www.domain.com\n# Returns:\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n\n  # Get domain root\n  data=\"{\\\"search\\\": [{\\\"name\\\": \\\"clientId\\\", \\\"operator\\\": \\\"eq\\\", \\\"value\\\": \\\"$CLOUDDNS_CLIENT_ID\\\"}]}\"\n  _clouddns_api \"POST\" \"domain/search\" \"$data\"\n  domain_slice=\"$domain\"\n  while [ -z \"$domain_root\" ]; do\n    if _contains \"$response\" \"\\\"domainName\\\":\\\"$domain_slice\\.\\\"\"; then\n      domain_root=\"$domain_slice\"\n      _debug domain_root \"$domain_root\"\n    fi\n    domain_slice=\"$(echo \"$domain_slice\" | cut -d . -f 2-)\"\n  done\n\n  # Get domain id\n  data=\"{\\\"search\\\": [{\\\"name\\\": \\\"clientId\\\", \\\"operator\\\": \\\"eq\\\", \\\"value\\\": \\\"$CLOUDDNS_CLIENT_ID\\\"}, \\\n      {\\\"name\\\": \\\"domainName\\\", \\\"operator\\\": \\\"eq\\\", \\\"value\\\": \\\"$domain_root.\\\"}]}\"\n  _clouddns_api \"POST\" \"domain/search\" \"$data\"\n  if _contains \"$response\" \"\\\"id\\\":\\\"\"; then\n    re='domainType\\\":\\\"[^\\\"]*\\\",\\\"id\\\":\\\"([^\\\"]*)\\\",' # Match domain id\n    _domain_id=$(echo \"$response\" | _egrep_o \"$re\" | _head_n 1 | cut -d : -f 3 | tr -d \"\\\",\")\n    if [ \"$_domain_id\" ]; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | sed \"s/.$domain_root//\")\n      _domain=\"$domain_root\"\n      return 0\n    fi\n    _err 'Domain name not found on your CloudDNS account'\n    return 1\n  fi\n  return 1\n}\n\n# Usage: _clouddns_api GET domain/search '{\"data\": \"value\"}'\n# Returns:\n#  response='{\"message\": \"api response\"}'\n_clouddns_api() {\n  method=$1\n  endpoint=\"$2\"\n  data=\"$3\"\n  _debug endpoint \"$endpoint\"\n\n  if [ -z \"$CLOUDDNS_TOKEN\" ]; then\n    _clouddns_login\n  fi\n  _debug CLOUDDNS_TOKEN \"$CLOUDDNS_TOKEN\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $CLOUDDNS_TOKEN\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$CLOUDDNS_API/$endpoint\" \"\" \"$method\" | tr -d '\\t\\r\\n ')\"\n  else\n    response=\"$(_get \"$CLOUDDNS_API/$endpoint\" | tr -d '\\t\\r\\n ')\"\n  fi\n\n  # shellcheck disable=SC2181\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $endpoint\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n# Returns:\n#  CLOUDDNS_TOKEN=dslfje2rj23l\n_clouddns_login() {\n  login_data=\"{\\\"email\\\": \\\"$CLOUDDNS_EMAIL\\\", \\\"password\\\": \\\"$CLOUDDNS_PASSWORD\\\"}\"\n  response=\"$(_post \"$login_data\" \"$CLOUDDNS_LOGIN_API\" \"\" \"POST\" \"Content-Type: application/json\")\"\n\n  if _contains \"$response\" \"\\\"accessToken\\\":\\\"\"; then\n    CLOUDDNS_TOKEN=$(echo \"$response\" | _egrep_o \"\\\"accessToken\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\n    export CLOUDDNS_TOKEN\n  else\n    echo 'Could not get CloudDNS access token; check your credentials'\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_cloudns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_cloudns_info='ClouDNS.net\nSite: ClouDNS.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cloudns\nOptions:\n CLOUDNS_AUTH_ID Regular auth ID\n CLOUDNS_SUB_AUTH_ID Sub auth ID\n CLOUDNS_AUTH_PASSWORD Auth Password\nAuthor: Boyan Peychev <boyan@cloudns.net>\n'\n\nCLOUDNS_API=\"https://api.cloudns.net\"\nDOMAIN_TYPE=\nDOMAIN_MASTER=\n\n########  Public functions #####################\n\n#Usage: dns_cloudns_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_cloudns_add() {\n  _info \"Using cloudns\"\n\n  if ! _dns_cloudns_init_check; then\n    return 1\n  fi\n\n  zone=\"$(_dns_cloudns_get_zone_name \"$1\")\"\n  if [ -z \"$zone\" ]; then\n    _err \"Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup.\"\n    return 1\n  fi\n\n  host=\"$(echo \"$1\" | sed \"s/\\.$zone\\$//\")\"\n  record=$2\n\n  _debug zone \"$zone\"\n  _debug host \"$host\"\n  _debug record \"$record\"\n\n  _info \"Adding the TXT record for $1\"\n  _dns_cloudns_http_api_call \"dns/add-record.json\" \"domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60\"\n  if ! _contains \"$response\" \"\\\"status\\\":\\\"Success\\\"\"; then\n    _err \"Record cannot be added.\"\n    return 1\n  fi\n  _info \"Added.\"\n\n  return 0\n}\n\n#Usage: dns_cloudns_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_cloudns_rm() {\n  _info \"Using cloudns\"\n\n  if ! _dns_cloudns_init_check; then\n    return 1\n  fi\n\n  if [ -z \"$zone\" ]; then\n    zone=\"$(_dns_cloudns_get_zone_name \"$1\")\"\n    if [ -z \"$zone\" ]; then\n      _err \"Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup.\"\n      return 1\n    fi\n  fi\n\n  host=\"$(echo \"$1\" | sed \"s/\\.$zone\\$//\")\"\n  record=$2\n\n  _dns_cloudns_get_zone_info \"$zone\"\n\n  _debug \"Type\" \"$DOMAIN_TYPE\"\n  _debug \"Cloud Master\" \"$DOMAIN_MASTER\"\n  if _contains \"$DOMAIN_TYPE\" \"cloud\"; then\n    zone=$DOMAIN_MASTER\n  fi\n  _debug \"ZONE\" \"$zone\"\n\n  _dns_cloudns_http_api_call \"dns/records.json\" \"domain-name=$zone&host=$host&type=TXT\"\n  if ! _contains \"$response\" \"\\\"id\\\":\"; then\n    return 1\n  fi\n\n  for i in $(echo \"$response\" | tr '{' \"\\n\" | grep -- \"$record\"); do\n    record_id=$(echo \"$i\" | tr ',' \"\\n\" | grep -E '^\"id\"' | sed -re 's/^\\\"id\\\"\\:\\\"([0-9]+)\\\"$/\\1/g')\n\n    if [ -n \"$record_id\" ]; then\n      _debug zone \"$zone\"\n      _debug host \"$host\"\n      _debug record \"$record\"\n      _debug record_id \"$record_id\"\n\n      _info \"Deleting the TXT record for $1\"\n      _dns_cloudns_http_api_call \"dns/delete-record.json\" \"domain-name=$zone&record-id=$record_id\"\n\n      if ! _contains \"$response\" \"\\\"status\\\":\\\"Success\\\"\"; then\n        _err \"The TXT record for $1 cannot be deleted.\"\n      else\n        _info \"Deleted.\"\n      fi\n    fi\n  done\n\n  return 0\n}\n\n####################  Private functions below ##################################\n_dns_cloudns_init_check() {\n  if [ -n \"$CLOUDNS_INIT_CHECK_COMPLETED\" ]; then\n    return 0\n  fi\n\n  CLOUDNS_AUTH_ID=\"${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}\"\n  CLOUDNS_SUB_AUTH_ID=\"${CLOUDNS_SUB_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_SUB_AUTH_ID)}\"\n  CLOUDNS_AUTH_PASSWORD=\"${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}\"\n  if [ -z \"$CLOUDNS_AUTH_ID$CLOUDNS_SUB_AUTH_ID\" ] || [ -z \"$CLOUDNS_AUTH_PASSWORD\" ]; then\n    CLOUDNS_AUTH_ID=\"\"\n    CLOUDNS_SUB_AUTH_ID=\"\"\n    CLOUDNS_AUTH_PASSWORD=\"\"\n    _err \"You don't specify cloudns api id and password yet.\"\n    _err \"Please create you id and password and try again.\"\n    return 1\n  fi\n\n  if [ -z \"$CLOUDNS_AUTH_ID\" ] && [ -z \"$CLOUDNS_SUB_AUTH_ID\" ]; then\n    _err \"CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID is not configured\"\n    return 1\n  fi\n\n  if [ -z \"$CLOUDNS_AUTH_PASSWORD\" ]; then\n    _err \"CLOUDNS_AUTH_PASSWORD is not configured\"\n    return 1\n  fi\n\n  _dns_cloudns_http_api_call \"dns/login.json\" \"\"\n\n  if ! _contains \"$response\" \"\\\"status\\\":\\\"Success\\\"\"; then\n    _err \"Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials.\"\n    return 1\n  fi\n\n  # save the api id and password to the account conf file.\n  _saveaccountconf_mutable CLOUDNS_AUTH_ID \"$CLOUDNS_AUTH_ID\"\n  _saveaccountconf_mutable CLOUDNS_SUB_AUTH_ID \"$CLOUDNS_SUB_AUTH_ID\"\n  _saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD \"$CLOUDNS_AUTH_PASSWORD\"\n\n  CLOUDNS_INIT_CHECK_COMPLETED=1\n\n  return 0\n}\n\n_dns_cloudns_get_zone_info() {\n  zone=$1\n  _dns_cloudns_http_api_call \"dns/get-zone-info.json\" \"domain-name=$zone\"\n  if ! _contains \"$response\" \"\\\"status\\\":\\\"Failed\\\"\"; then\n    DOMAIN_TYPE=$(echo \"$response\" | _egrep_o '\"type\":\"[^\"]*\"' | cut -d : -f 2 | tr -d '\"')\n    if _contains \"$DOMAIN_TYPE\" \"cloud\"; then\n      DOMAIN_MASTER=$(echo \"$response\" | _egrep_o '\"cloud-master\":\"[^\"]*\"' | cut -d : -f 2 | tr -d '\"')\n    fi\n  fi\n  return 0\n}\n\n_dns_cloudns_get_zone_name() {\n  i=2\n  while true; do\n    zoneForCheck=$(printf \"%s\" \"$1\" | cut -d . -f \"$i\"-100)\n\n    if [ -z \"$zoneForCheck\" ]; then\n      return 1\n    fi\n\n    _debug zoneForCheck \"$zoneForCheck\"\n\n    _dns_cloudns_http_api_call \"dns/get-zone-info.json\" \"domain-name=$zoneForCheck\"\n\n    if ! _contains \"$response\" \"\\\"status\\\":\\\"Failed\\\"\"; then\n      echo \"$zoneForCheck\"\n      return 0\n    fi\n\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_dns_cloudns_http_api_call() {\n  method=$1\n\n  _debug CLOUDNS_AUTH_ID \"$CLOUDNS_AUTH_ID\"\n  _debug CLOUDNS_SUB_AUTH_ID \"$CLOUDNS_SUB_AUTH_ID\"\n  _debug CLOUDNS_AUTH_PASSWORD \"$CLOUDNS_AUTH_PASSWORD\"\n\n  if [ -n \"$CLOUDNS_SUB_AUTH_ID\" ]; then\n    auth_user=\"sub-auth-id=$CLOUDNS_SUB_AUTH_ID\"\n  else\n    auth_user=\"auth-id=$CLOUDNS_AUTH_ID\"\n  fi\n\n  encoded_password=$(echo \"$CLOUDNS_AUTH_PASSWORD\" | tr -d \"\\n\\r\" | _url_encode)\n  if [ -z \"$2\" ]; then\n    data=\"$auth_user&auth-password=$encoded_password\"\n  else\n    data=\"$auth_user&auth-password=$encoded_password&$2\"\n  fi\n\n  response=\"$(_get \"$CLOUDNS_API/$method?$data\")\"\n\n  _debug response \"$response\"\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_cn.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_cn_info='Core-Networks.de\nSite: beta.api.Core-Networks.de/doc/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cn\nOptions:\n CN_User User\n CN_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2142\nAuthor: 5ll, francis\n'\n\nCN_API=\"https://beta.api.core-networks.de\"\n\n########  Public functions  #####################\n\ndns_cn_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _cn_login; then\n    _err \"login failed\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _cn_get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug \"_sub_domain $_sub_domain\"\n  _debug \"_domain $_domain\"\n\n  _info \"Adding record\"\n  curData=\"{\\\"name\\\":\\\"$_sub_domain\\\",\\\"ttl\\\":120,\\\"type\\\":\\\"TXT\\\",\\\"data\\\":\\\"$txtvalue\\\"}\"\n  curResult=\"$(_post \"${curData}\" \"${CN_API}/dnszones/${_domain}/records/\")\"\n\n  _debug \"curData $curData\"\n  _debug \"curResult $curResult\"\n\n  if _contains \"$curResult\" \"\"; then\n    _info \"Added, OK\"\n\n    if ! _cn_commit; then\n      _err \"commiting changes failed\"\n      return 1\n    fi\n    return 0\n\n  else\n    _err \"Add txt record error.\"\n    _debug \"curData is $curData\"\n    _debug \"curResult is $curResult\"\n    _err \"error adding text record, response was $curResult\"\n    return 1\n  fi\n}\n\ndns_cn_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _cn_login; then\n    _err \"login failed\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _cn_get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _info \"Deleting record\"\n  curData=\"{\\\"name\\\":\\\"$_sub_domain\\\",\\\"data\\\":\\\"$txtvalue\\\"}\"\n  curResult=\"$(_post \"${curData}\" \"${CN_API}/dnszones/${_domain}/records/delete\")\"\n  _debug curData is \"$curData\"\n\n  _info \"commiting changes\"\n  if ! _cn_commit; then\n    _err \"commiting changes failed\"\n    return 1\n  fi\n\n  _info \"Deletet txt record\"\n  return 0\n}\n\n###################  Private functions below  ##################################\n_cn_login() {\n  CN_User=\"${CN_User:-$(_readaccountconf_mutable CN_User)}\"\n  CN_Password=\"${CN_Password:-$(_readaccountconf_mutable CN_Password)}\"\n  if [ -z \"$CN_User\" ] || [ -z \"$CN_Password\" ]; then\n    CN_User=\"\"\n    CN_Password=\"\"\n    _err \"You must export variables: CN_User and CN_Password\"\n    return 1\n  fi\n\n  #save the config variables to the account conf file.\n  _saveaccountconf_mutable CN_User \"$CN_User\"\n  _saveaccountconf_mutable CN_Password \"$CN_Password\"\n\n  _info \"Getting an AUTH-Token\"\n  curData=\"{\\\"login\\\":\\\"${CN_User}\\\",\\\"password\\\":\\\"${CN_Password}\\\"}\"\n  curResult=\"$(_post \"${curData}\" \"${CN_API}/auth/token\")\"\n  _debug \"Calling _CN_login: '${curData}' '${CN_API}/auth/token'\"\n\n  if _contains \"${curResult}\" '\"token\":\"'; then\n    authToken=$(echo \"${curResult}\" | cut -d \":\" -f2 | cut -d \",\" -f1 | sed 's/^.\\(.*\\).$/\\1/')\n    export _H1=\"Authorization: Bearer $authToken\"\n    _info \"Successfully acquired AUTH-Token\"\n    _debug \"AUTH-Token: '${authToken}'\"\n    _debug \"_H1 '${_H1}'\"\n  else\n    _err \"Couldn't acquire an AUTH-Token\"\n    return 1\n  fi\n}\n\n# Commit changes\n_cn_commit() {\n  _info \"Commiting changes\"\n  _post \"\" \"${CN_API}/dnszones/$h/records/commit\"\n}\n\n_cn_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    _debug _H1 \"${_H1}\"\n\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _cn_zonelist=\"$(_get ${CN_API}/dnszones/)\"\n    _debug _cn_zonelist \"${_cn_zonelist}\"\n\n    if [ \"$?\" != \"0\" ]; then\n      _err \"something went wrong while getting the zone list\"\n      return 1\n    fi\n\n    if _contains \"$_cn_zonelist\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    else\n      _debug \"Zonelist does not contain domain - iterating \"\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n\n  done\n  _err \"Zonelist does not contain domain - exiting\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_conoha.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_conoha_info='ConoHa.jp\nDomains: ConoHa.io\nSite: ConoHa.jp\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_conoha\nOptions:\n CONOHA_Username Username\n CONOHA_Password Password\n CONOHA_TenantId TenantId\n CONOHA_IdentityServiceApi Identity Service API. E.g. \"https://identity.xxxx.conoha.io/v2.0\"\n'\n\nCONOHA_DNS_EP_PREFIX_REGEXP=\"https://dns-service\\.\"\n\n########  Public functions #####################\n\n#Usage: dns_conoha_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_conoha_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using conoha\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _debug \"Check uesrname and password\"\n  CONOHA_Username=\"${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}\"\n  CONOHA_Password=\"${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}\"\n  CONOHA_TenantId=\"${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}\"\n  CONOHA_IdentityServiceApi=\"${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}\"\n  if [ -z \"$CONOHA_Username\" ] || [ -z \"$CONOHA_Password\" ] || [ -z \"$CONOHA_TenantId\" ] || [ -z \"$CONOHA_IdentityServiceApi\" ]; then\n    CONOHA_Username=\"\"\n    CONOHA_Password=\"\"\n    CONOHA_TenantId=\"\"\n    CONOHA_IdentityServiceApi=\"\"\n    _err \"You didn't specify a conoha api username and password yet.\"\n    _err \"Please create the user and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable CONOHA_Username \"$CONOHA_Username\"\n  _saveaccountconf_mutable CONOHA_Password \"$CONOHA_Password\"\n  _saveaccountconf_mutable CONOHA_TenantId \"$CONOHA_TenantId\"\n  _saveaccountconf_mutable CONOHA_IdentityServiceApi \"$CONOHA_IdentityServiceApi\"\n\n  if token=\"$(_conoha_get_accesstoken \"$CONOHA_IdentityServiceApi/tokens\" \"$CONOHA_Username\" \"$CONOHA_Password\" \"$CONOHA_TenantId\")\"; then\n    accesstoken=\"$(printf \"%s\" \"$token\" | sed -n 1p)\"\n    CONOHA_Api=\"$(printf \"%s\" \"$token\" | sed -n 2p)\"\n  else\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\" \"$CONOHA_Api\" \"$accesstoken\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  body=\"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain.\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":60}\"\n  if _conoha_rest POST \"$CONOHA_Api/v1/domains/$_domain_id/records\" \"$body\" \"$accesstoken\"; then\n    if _contains \"$response\" '\"data\":\"'\"$txtvalue\"'\"'; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_conoha_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using conoha\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _debug \"Check uesrname and password\"\n  CONOHA_Username=\"${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}\"\n  CONOHA_Password=\"${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}\"\n  CONOHA_TenantId=\"${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}\"\n  CONOHA_IdentityServiceApi=\"${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}\"\n  if [ -z \"$CONOHA_Username\" ] || [ -z \"$CONOHA_Password\" ] || [ -z \"$CONOHA_TenantId\" ] || [ -z \"$CONOHA_IdentityServiceApi\" ]; then\n    CONOHA_Username=\"\"\n    CONOHA_Password=\"\"\n    CONOHA_TenantId=\"\"\n    CONOHA_IdentityServiceApi=\"\"\n    _err \"You didn't specify a conoha api username and password yet.\"\n    _err \"Please create the user and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable CONOHA_Username \"$CONOHA_Username\"\n  _saveaccountconf_mutable CONOHA_Password \"$CONOHA_Password\"\n  _saveaccountconf_mutable CONOHA_TenantId \"$CONOHA_TenantId\"\n  _saveaccountconf_mutable CONOHA_IdentityServiceApi \"$CONOHA_IdentityServiceApi\"\n\n  if token=\"$(_conoha_get_accesstoken \"$CONOHA_IdentityServiceApi/tokens\" \"$CONOHA_Username\" \"$CONOHA_Password\" \"$CONOHA_TenantId\")\"; then\n    accesstoken=\"$(printf \"%s\" \"$token\" | sed -n 1p)\"\n    CONOHA_Api=\"$(printf \"%s\" \"$token\" | sed -n 2p)\"\n  else\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\" \"$CONOHA_Api\" \"$accesstoken\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  if ! _conoha_rest GET \"$CONOHA_Api/v1/domains/$_domain_id/records\" \"\" \"$accesstoken\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  record_id=$(printf \"%s\" \"$response\" | _egrep_o '{[^}]*}' |\n    grep '\"type\":\"TXT\"' | grep \"\\\"data\\\":\\\"$txtvalue\\\"\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\"\" |\n    _head_n 1 | cut -d : -f 2 | tr -d \\\")\n  if [ -z \"$record_id\" ]; then\n    _err \"Can not get record id to remove.\"\n    return 1\n  fi\n  _debug record_id \"$record_id\"\n\n  _info \"Removing the txt record\"\n  if ! _conoha_rest DELETE \"$CONOHA_Api/v1/domains/$_domain_id/records/$record_id\" \"\" \"$accesstoken\"; then\n    _err \"Delete record error.\"\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_conoha_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  accesstoken=\"$4\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n  if [ -n \"$accesstoken\" ]; then\n    export _H3=\"X-Auth-Token: $accesstoken\"\n  fi\n\n  _debug \"$ep\"\n  if [ \"$m\" != \"GET\" ]; then\n    _secure_debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$ep\")\"\n  fi\n  _ret=\"$?\"\n  _secure_debug2 response \"$response\"\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n\n  response=\"$(printf \"%s\" \"$response\" | _normalizeJson)\"\n  return 0\n}\n\n_conoha_get_accesstoken() {\n  ep=\"$1\"\n  username=\"$2\"\n  password=\"$3\"\n  tenantId=\"$4\"\n\n  accesstoken=\"$(_readaccountconf_mutable conoha_accesstoken)\"\n  expires=\"$(_readaccountconf_mutable conoha_tokenvalidto)\"\n  CONOHA_Api=\"$(_readaccountconf_mutable conoha_dns_ep)\"\n\n  # can we reuse the access token?\n  if [ -n \"$accesstoken\" ] && [ -n \"$expires\" ] && [ -n \"$CONOHA_Api\" ]; then\n    utc_date=\"$(_utc_date | sed \"s/ /T/\")\"\n    if expr \"$utc_date\" \"<\" \"$expires\" >/dev/null; then\n      # access token is still valid - reuse it\n      _debug \"reusing access token\"\n      printf \"%s\\n%s\\n\" \"$accesstoken\" \"$CONOHA_Api\"\n      return 0\n    else\n      _debug \"access token expired\"\n    fi\n  fi\n  _debug \"getting new access token\"\n\n  body=\"$(printf '{\"auth\":{\"passwordCredentials\":{\"username\":\"%s\",\"password\":\"%s\"},\"tenantId\":\"%s\"}}' \"$username\" \"$password\" \"$tenantId\")\"\n  if ! _conoha_rest POST \"$ep\" \"$body\" \"\"; then\n    _err error \"$response\"\n    return 1\n  fi\n  accesstoken=$(printf \"%s\" \"$response\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n  expires=$(printf \"%s\" \"$response\" | _egrep_o \"\\\"expires\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2-4 | tr -d \\\" | tr -d Z) #expect UTC\n  if [ -z \"$accesstoken\" ] || [ -z \"$expires\" ]; then\n    _err \"no acccess token received. Check your Conoha settings see $WIKI\"\n    return 1\n  fi\n  _saveaccountconf_mutable conoha_accesstoken \"$accesstoken\"\n  _saveaccountconf_mutable conoha_tokenvalidto \"$expires\"\n\n  CONOHA_Api=$(printf \"%s\" \"$response\" | _egrep_o 'publicURL\":\"'\"$CONOHA_DNS_EP_PREFIX_REGEXP\"'[^\"]*\"' | _head_n 1 | cut -d : -f 2-3 | tr -d \\\")\n  if [ -z \"$CONOHA_Api\" ]; then\n    _err \"failed to get conoha dns endpoint url\"\n    return 1\n  fi\n  _saveaccountconf_mutable conoha_dns_ep \"$CONOHA_Api\"\n\n  printf \"%s\\n%s\\n\" \"$accesstoken\" \"$CONOHA_Api\"\n  return 0\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=\"$1\"\n  ep=\"$2\"\n  accesstoken=\"$3\"\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100).\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _conoha_rest GET \"$ep/v1/domains?name=$h\" \"\" \"$accesstoken\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\"\" | head -n 1 | cut -d : -f 2 | tr -d \\\")\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_constellix.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_constellix_info='Constellix.com\nSite: Constellix.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_constellix\nOptions:\n CONSTELLIX_Key API Key\n CONSTELLIX_Secret API Secret\nIssues: github.com/acmesh-official/acme.sh/issues/2724\nAuthor: Wout Decre <wout@canodus.be>\n'\n\nCONSTELLIX_Api=\"https://api.dns.constellix.com/v1\"\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_constellix_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  CONSTELLIX_Key=\"${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}\"\n  CONSTELLIX_Secret=\"${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}\"\n\n  if [ -z \"$CONSTELLIX_Key\" ] || [ -z \"$CONSTELLIX_Secret\" ]; then\n    _err \"You did not specify the Contellix API key and secret yet.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable CONSTELLIX_Key \"$CONSTELLIX_Key\"\n  _saveaccountconf_mutable CONSTELLIX_Secret \"$CONSTELLIX_Secret\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  # The TXT record might already exist when working with wildcard certificates. In that case, update the record by adding the new value.\n  _debug \"Search TXT record\"\n  if _constellix_rest GET \"domains/${_domain_id}/records/TXT/search?exact=${_sub_domain}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"{\\\"errors\\\":\\[\\\"Requested record was not found\\\"\\]}\" >/dev/null; then\n      _info \"Adding TXT record\"\n      if _constellix_rest POST \"domains/${_domain_id}/records\" \"[{\\\"type\\\":\\\"txt\\\",\\\"add\\\":true,\\\"set\\\":{\\\"name\\\":\\\"${_sub_domain}\\\",\\\"ttl\\\":60,\\\"roundRobin\\\":[{\\\"value\\\":\\\"${txtvalue}\\\"}]}}]\"; then\n        if printf -- \"%s\" \"$response\" | grep \"{\\\"success\\\":\\\"1 record(s) added, 0 record(s) updated, 0 record(s) deleted\\\"}\" >/dev/null; then\n          _info \"Added\"\n          return 0\n        else\n          _err \"Error adding TXT record\"\n        fi\n      fi\n    else\n      _record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":[0-9]*\" | cut -d ':' -f 2)\n      if _constellix_rest GET \"domains/${_domain_id}/records/TXT/${_record_id}\"; then\n        _new_rr_values=$(printf \"%s\\n\" \"$response\" | _egrep_o '\"roundRobin\":\\[[^]]*\\]' | sed \"s/\\]$/,{\\\"value\\\":\\\"${txtvalue}\\\"}]/\")\n        _debug _new_rr_values \"$_new_rr_values\"\n        _info \"Updating TXT record\"\n        if _constellix_rest PUT \"domains/${_domain_id}/records/TXT/${_record_id}\" \"{\\\"name\\\":\\\"${_sub_domain}\\\",\\\"ttl\\\":60,${_new_rr_values}}\"; then\n          if printf -- \"%s\" \"$response\" | grep \"{\\\"success\\\":\\\"Record.*updated successfully\\\"}\" >/dev/null; then\n            _info \"Updated\"\n            return 0\n          elif printf -- \"%s\" \"$response\" | grep \"{\\\"errors\\\":\\[\\\"Contents are identical\\\"\\]}\" >/dev/null; then\n            _info \"Already exists, no need to update\"\n            return 0\n          else\n            _err \"Error updating TXT record\"\n          fi\n        fi\n      fi\n    fi\n  fi\n\n  return 1\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_constellix_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  CONSTELLIX_Key=\"${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}\"\n  CONSTELLIX_Secret=\"${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}\"\n\n  if [ -z \"$CONSTELLIX_Key\" ] || [ -z \"$CONSTELLIX_Secret\" ]; then\n    _err \"You did not specify the Contellix API key and secret yet.\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  # The TXT record might have been removed already when working with some wildcard certificates.\n  _debug \"Search TXT record\"\n  if _constellix_rest GET \"domains/${_domain_id}/records/TXT/search?exact=${_sub_domain}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"{\\\"errors\\\":\\[\\\"Requested record was not found\\\"\\]}\" >/dev/null; then\n      _info \"Removed\"\n      return 0\n    else\n      _info \"Removing TXT record\"\n      if _constellix_rest POST \"domains/${_domain_id}/records\" \"[{\\\"type\\\":\\\"txt\\\",\\\"delete\\\":true,\\\"filter\\\":{\\\"field\\\":\\\"name\\\",\\\"op\\\":\\\"eq\\\",\\\"value\\\":\\\"${_sub_domain}\\\"}}]\"; then\n        if printf -- \"%s\" \"$response\" | grep \"{\\\"success\\\":\\\"0 record(s) added, 0 record(s) updated, 1 record(s) deleted\\\"}\" >/dev/null; then\n          _info \"Removed\"\n          return 0\n        else\n          _err \"Error removing TXT record\"\n        fi\n      fi\n    fi\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$(echo \"$1\" | _lower_case)\n  i=2\n  p=1\n  _debug \"Detecting root zone\"\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    if ! _constellix_rest GET \"domains/search?exact=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":[0-9]*\" | cut -d ':' -f 2)\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d '.' -f 1-\"$p\")\n        _domain=\"$h\"\n\n        _debug _domain_id \"$_domain_id\"\n        _debug _sub_domain \"$_sub_domain\"\n        _debug _domain \"$_domain\"\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_constellix_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  # Prevent rate limit\n  _sleep 2\n\n  rdate=$(date +\"%s\")\"000\"\n  hmac=$(printf \"%s\" \"$rdate\" | _hmac sha1 \"$(printf \"%s\" \"$CONSTELLIX_Secret\" | _hex_dump | tr -d ' ')\" | _base64)\n\n  export _H1=\"x-cnsdns-apiKey: $CONSTELLIX_Key\"\n  export _H2=\"x-cnsdns-requestDate: $rdate\"\n  export _H3=\"x-cnsdns-hmac: $hmac\"\n  export _H4=\"Accept: application/json\"\n  export _H5=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$CONSTELLIX_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$CONSTELLIX_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $ep\"\n    return 1\n  fi\n\n  _debug response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_cpanel.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_cpanel_info='cPanel Server API\n Manage DNS via cPanel Dashboard.\nSite: cPanel.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_cpanel\nOptions:\n cPanel_Username Username\n cPanel_Apitoken API Token\n cPanel_Hostname Server URL. E.g. \"https://hostname:port\"\nIssues: github.com/acmesh-official/acme.sh/issues/3732\nAuthor: Bjarne Saltbaek\n'\n\n########  Public functions #####################\n\n# Used to add txt record\ndns_cpanel_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Adding TXT record to cPanel based system\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _debug cPanel_Username \"$cPanel_Username\"\n  _debug cPanel_Apitoken \"$cPanel_Apitoken\"\n  _debug cPanel_Hostname \"$cPanel_Hostname\"\n\n  if ! _cpanel_login; then\n    _err \"cPanel Login failed for user $cPanel_Username. Check $HTTP_HEADER file\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"No matching root domain for $fulldomain found\"\n    return 1\n  fi\n  # adding entry\n  _info \"Adding the entry\"\n  stripped_fulldomain=$(echo \"$fulldomain\" | sed \"s/.$_domain//\")\n  _debug \"Adding $stripped_fulldomain to $_domain zone\"\n  _myget \"json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1\"\n  if _successful_update; then return 0; fi\n  _err \"Couldn't create entry!\"\n  return 1\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_cpanel_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using cPanel based system\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _cpanel_login; then\n    _err \"cPanel Login failed for user $cPanel_Username. Check $HTTP_HEADER file\"\n    return 1\n  fi\n\n  if ! _get_root; then\n    _err \"No matching root domain for $fulldomain found\"\n    return 1\n  fi\n\n  _findentry \"$fulldomain\" \"$txtvalue\"\n  if [ -z \"$_id\" ]; then\n    _info \"Entry doesn't exist, nothing to delete\"\n    return 0\n  fi\n  _debug \"Deleting record...\"\n  _myget \"json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=remove_zone_record&domain=$_domain&line=$_id\"\n  # removing entry\n  _debug \"_result is: $_result\"\n\n  if _successful_update; then return 0; fi\n  _err \"Couldn't delete entry!\"\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_checkcredentials() {\n  cPanel_Username=\"${cPanel_Username:-$(_readaccountconf_mutable cPanel_Username)}\"\n  cPanel_Apitoken=\"${cPanel_Apitoken:-$(_readaccountconf_mutable cPanel_Apitoken)}\"\n  cPanel_Hostname=\"${cPanel_Hostname:-$(_readaccountconf_mutable cPanel_Hostname)}\"\n\n  if [ -z \"$cPanel_Username\" ] || [ -z \"$cPanel_Apitoken\" ] || [ -z \"$cPanel_Hostname\" ]; then\n    cPanel_Username=\"\"\n    cPanel_Apitoken=\"\"\n    cPanel_Hostname=\"\"\n    _err \"You haven't specified cPanel username, apitoken and hostname yet.\"\n    _err \"Please add credentials and try again.\"\n    return 1\n  fi\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable cPanel_Username \"$cPanel_Username\"\n  _saveaccountconf_mutable cPanel_Apitoken \"$cPanel_Apitoken\"\n  _saveaccountconf_mutable cPanel_Hostname \"$cPanel_Hostname\"\n  return 0\n}\n\n_cpanel_login() {\n  if ! _checkcredentials; then return 1; fi\n\n  if ! _myget \"json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=CustInfo&cpanel_jsonapi_func=displaycontactinfo\"; then\n    _err \"cPanel login failed for user $cPanel_Username.\"\n    return 1\n  fi\n  return 0\n}\n\n_myget() {\n  #Adds auth header to request\n  export _H1=\"Authorization: cpanel $cPanel_Username:$cPanel_Apitoken\"\n  _result=$(_get \"$cPanel_Hostname/$1\")\n}\n\n_get_root() {\n  _myget 'json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzones'\n  _domains=$(echo \"$_result\" | _egrep_o '\"[a-z0-9\\.\\-]*\":\\[\"; cPanel first' | cut -d':' -f1 | sed 's/\"//g' | sed 's/{//g')\n  _debug \"_result is: $_result\"\n  _debug \"_domains is: $_domains\"\n  if [ -z \"$_domains\" ]; then\n    _err \"Primary domain list not found!\"\n    return 1\n  fi\n  for _domain in $_domains; do\n    _debug \"Checking if $fulldomain ends with $_domain\"\n    if (_endswith \"$fulldomain\" \"$_domain\"); then\n      _debug \"Root domain: $_domain\"\n      return 0\n    fi\n  done\n  return 1\n}\n\n_successful_update() {\n  if (echo \"$_result\" | _egrep_o 'data\":\\[[^]]*]' | grep -q '\"newserial\":null'); then return 1; fi\n  return 0\n}\n\n_findentry() {\n  _debug \"In _findentry\"\n  #returns id of dns entry, if it exists\n  _myget \"json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&domain=$_domain\"\n  _id=$(echo \"$_result\" | sed -e \"s/},{/},\\n{/g\" | grep \"$fulldomain\" | grep \"$txtvalue\" | _egrep_o 'line\":[0-9]+' | cut -d ':' -f 2)\n  _debug \"_result is: $_result\"\n  _debug \"fulldomain. is $fulldomain.\"\n  _debug \"txtvalue is $txtvalue\"\n  _debug \"_id is: $_id\"\n  if [ -n \"$_id\" ]; then\n    _debug \"Entry found with _id=$_id\"\n    return 0\n  fi\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_curanet.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_curanet_info='Curanet.dk\nDomains: scannet.dk wannafind.dk dandomain.dk\nSite: Curanet.dk\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_curanet\nOptions:\n CURANET_AUTHCLIENTID Auth ClientID. Requires scope dns\n CURANET_AUTHSECRET Auth Secret\nIssues: github.com/acmesh-official/acme.sh/issues/3933\nAuthor: Peter L. Hansen <peter@r12.dk>\n'\n\nCURANET_REST_URL=\"https://api.curanet.dk/dns/v1/Domains\"\nCURANET_AUTH_URL=\"https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token\"\nCURANET_ACCESS_TOKEN=\"\"\n\n########  Public functions ####################\n\n#Usage: dns_curanet_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_curanet_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using curanet\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  CURANET_AUTHCLIENTID=\"${CURANET_AUTHCLIENTID:-$(_readaccountconf_mutable CURANET_AUTHCLIENTID)}\"\n  CURANET_AUTHSECRET=\"${CURANET_AUTHSECRET:-$(_readaccountconf_mutable CURANET_AUTHSECRET)}\"\n  if [ -z \"$CURANET_AUTHCLIENTID\" ] || [ -z \"$CURANET_AUTHSECRET\" ]; then\n    CURANET_AUTHCLIENTID=\"\"\n    CURANET_AUTHSECRET=\"\"\n    _err \"You don't specify curanet api client and secret.\"\n    _err \"Please create your auth info and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable CURANET_AUTHCLIENTID \"$CURANET_AUTHCLIENTID\"\n  _saveaccountconf_mutable CURANET_AUTHSECRET \"$CURANET_AUTHSECRET\"\n\n  if ! _get_token; then\n    _err \"Unable to get token\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  export _H1=\"Content-Type: application/json-patch+json\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Authorization: Bearer $CURANET_ACCESS_TOKEN\"\n  data=\"{\\\"name\\\": \\\"$fulldomain\\\",\\\"type\\\": \\\"TXT\\\",\\\"ttl\\\": 60,\\\"priority\\\": 0,\\\"data\\\": \\\"$txtvalue\\\"}\"\n  response=\"$(_post \"$data\" \"$CURANET_REST_URL/${_domain}/Records\" \"\" \"\")\"\n\n  if _contains \"$response\" \"$txtvalue\"; then\n    _debug \"TXT record added OK\"\n  else\n    _err \"Unable to add TXT record\"\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_curanet_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using curanet\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  CURANET_AUTHCLIENTID=\"${CURANET_AUTHCLIENTID:-$(_readaccountconf_mutable CURANET_AUTHCLIENTID)}\"\n  CURANET_AUTHSECRET=\"${CURANET_AUTHSECRET:-$(_readaccountconf_mutable CURANET_AUTHSECRET)}\"\n\n  if ! _get_token; then\n    _err \"Unable to get token\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug \"Getting current record list to identify TXT to delete\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Authorization: Bearer $CURANET_ACCESS_TOKEN\"\n\n  response=\"$(_get \"$CURANET_REST_URL/${_domain}/Records\" \"\" \"\")\"\n\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _err \"Unable to delete record (does not contain $txtvalue )\"\n    return 1\n  fi\n\n  recordid=$(echo \"$response\" | _egrep_o \"{\\\"id\\\":[0-9]+,\\\"name\\\":\\\"$fulldomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":60,\\\"priority\\\":0,\\\"data\\\":\\\"..$txtvalue\" | _egrep_o \"id\\\":[0-9]+\" | cut -c 5-)\n\n  if [ -z \"$recordid\" ]; then\n    _err \"Unable to get recordid\"\n    _debug \"regex {\\\"id\\\":[0-9]+,\\\"name\\\":\\\"$fulldomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":60,\\\"priority\\\":0,\\\"data\\\":\\\"..$txtvalue\"\n    _debug \"response $response\"\n    return 1\n  fi\n\n  _debug \"Deleting recordID $recordid\"\n  response=\"$(_post \"\" \"$CURANET_REST_URL/${_domain}/Records/$recordid\" \"\" \"DELETE\")\"\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_get_token() {\n  response=\"$(_post \"grant_type=client_credentials&client_id=$CURANET_AUTHCLIENTID&client_secret=$CURANET_AUTHSECRET&scope=dns\" \"$CURANET_AUTH_URL\" \"\" \"\")\"\n  if ! _contains \"$response\" \"access_token\"; then\n    _err \"Unable get access token\"\n    return 1\n  fi\n  CURANET_ACCESS_TOKEN=$(echo \"$response\" | _egrep_o \"\\\"access_token\\\":\\\"[^\\\"]+\" | cut -c 17-)\n\n  if [ -z \"$CURANET_ACCESS_TOKEN\" ]; then\n    _err \"Unable to get token\"\n    return 1\n  fi\n\n  return 0\n\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    export _H1=\"Content-Type: application/json\"\n    export _H2=\"Accept: application/json\"\n    export _H3=\"Authorization: Bearer $CURANET_ACCESS_TOKEN\"\n    response=\"$(_get \"$CURANET_REST_URL/$h/Records\" \"\" \"\")\"\n\n    if [ ! \"$(echo \"$response\" | _egrep_o \"Entity not found|Bad Request\")\" ]; then\n      _domain=$h\n      return 0\n    fi\n\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_cyon.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_cyon_info='cyon.ch\nSite: cyon.ch\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cyon\nOptions:\n CY_Username Username\n CY_Password API Token\n CY_OTP_Secret OTP token. Only required if using 2FA\nIssues: github.com/noplanman/cyon-api/issues\nAuthor: Armando Lüscher <armando@noplanman.ch>\n'\n\ndns_cyon_add() {\n  _cyon_load_credentials &&\n    _cyon_load_parameters \"$@\" &&\n    _cyon_print_header \"add\" &&\n    _cyon_login &&\n    _cyon_change_domain_env &&\n    _cyon_add_txt &&\n    _cyon_logout\n}\n\ndns_cyon_rm() {\n  _cyon_load_credentials &&\n    _cyon_load_parameters \"$@\" &&\n    _cyon_print_header \"delete\" &&\n    _cyon_login &&\n    _cyon_change_domain_env &&\n    _cyon_delete_txt &&\n    _cyon_logout\n}\n\n#########################\n### PRIVATE FUNCTIONS ###\n#########################\n\n_cyon_load_credentials() {\n  # Convert loaded password to/from base64 as needed.\n  if [ \"${CY_Password_B64}\" ]; then\n    CY_Password=\"$(printf \"%s\" \"${CY_Password_B64}\" | _dbase64)\"\n  elif [ \"${CY_Password}\" ]; then\n    CY_Password_B64=\"$(printf \"%s\" \"${CY_Password}\" | _base64)\"\n  fi\n\n  if [ -z \"${CY_Username}\" ] || [ -z \"${CY_Password}\" ]; then\n    # Dummy entries to satisfy script checker.\n    CY_Username=\"\"\n    CY_Password=\"\"\n    CY_OTP_Secret=\"\"\n\n    _err \"\"\n    _err \"You haven't set your cyon.ch login credentials yet.\"\n    _err \"Please set the required cyon environment variables.\"\n    _err \"\"\n    return 1\n  fi\n\n  # Save the login credentials to the account.conf file.\n  _debug \"Save credentials to account.conf\"\n  _saveaccountconf CY_Username \"${CY_Username}\"\n  _saveaccountconf CY_Password_B64 \"$CY_Password_B64\"\n  if [ -n \"${CY_OTP_Secret}\" ]; then\n    _saveaccountconf CY_OTP_Secret \"$CY_OTP_Secret\"\n  else\n    _clearaccountconf CY_OTP_Secret\n  fi\n}\n\n_cyon_is_idn() {\n  _idn_temp=\"$(printf \"%s\" \"${1}\" | tr -d \"0-9a-zA-Z.,-_\")\"\n  _idn_temp2=\"$(printf \"%s\" \"${1}\" | grep -o \"xn--\")\"\n  [ \"$_idn_temp\" ] || [ \"$_idn_temp2\" ]\n}\n\n_cyon_load_parameters() {\n  # Read the required parameters to add the TXT entry.\n  # shellcheck disable=SC2018,SC2019\n  fulldomain=\"$(printf \"%s\" \"${1}\" | tr \"A-Z\" \"a-z\")\"\n  fulldomain_idn=\"${fulldomain}\"\n\n  # Special case for IDNs, as cyon needs a domain environment change,\n  # which uses the \"pretty\" instead of the punycode version.\n  if _cyon_is_idn \"${fulldomain}\"; then\n    if ! _exists idn; then\n      _err \"Please install idn to process IDN names.\"\n      _err \"\"\n      return 1\n    fi\n\n    fulldomain=\"$(idn -u \"${fulldomain}\")\"\n    fulldomain_idn=\"$(idn -a \"${fulldomain}\")\"\n  fi\n\n  _debug fulldomain \"${fulldomain}\"\n  _debug fulldomain_idn \"${fulldomain_idn}\"\n\n  txtvalue=\"${2}\"\n  _debug txtvalue \"${txtvalue}\"\n\n  # This header is required for curl calls.\n  _H1=\"X-Requested-With: XMLHttpRequest\"\n  export _H1\n  _H3=\"User-Agent: cyon-dns-acmesh/1.0\"\n  export _H3\n}\n\n_cyon_print_header() {\n  if [ \"${1}\" = \"add\" ]; then\n    _info \"\"\n    _info \"+---------------------------------------------+\"\n    _info \"| Adding DNS TXT entry to your cyon.ch domain |\"\n    _info \"+---------------------------------------------+\"\n    _info \"\"\n    _info \"  * Full Domain: ${fulldomain}\"\n    _info \"  * TXT Value:   ${txtvalue}\"\n    _info \"\"\n  elif [ \"${1}\" = \"delete\" ]; then\n    _info \"\"\n    _info \"+-------------------------------------------------+\"\n    _info \"| Deleting DNS TXT entry from your cyon.ch domain |\"\n    _info \"+-------------------------------------------------+\"\n    _info \"\"\n    _info \"  * Full Domain: ${fulldomain}\"\n    _info \"\"\n  fi\n}\n\n_cyon_get_cookie_header() {\n  # Extract all cookies from the response headers (case-insensitive)\n  _cookies=\"$(grep -i \"^set-cookie:\" \"$HTTP_HEADER\" | sed 's/^[Ss]et-[Cc]ookie: //' | sed 's/;.*//' | tr '\\n' '; ' | sed 's/; $//')\"\n  if [ -n \"$_cookies\" ]; then\n    printf \"Cookie: %s\" \"$_cookies\"\n  fi\n}\n\n_cyon_login() {\n  _info \"  - Logging in...\"\n\n  username_encoded=\"$(printf \"%s\" \"${CY_Username}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${CY_Password}\" | _url_encode)\"\n\n  login_url=\"https://my.cyon.ch/auth/index/dologin-async\"\n  login_data=\"$(printf \"%s\" \"username=${username_encoded}&password=${password_encoded}&pathname=%2F\")\"\n\n  login_response=\"$(_post \"$login_data\" \"$login_url\")\"\n  _debug login_response \"${login_response}\"\n\n  # Bail if login fails.\n  if [ \"$(printf \"%s\" \"${login_response}\" | _cyon_get_response_success)\" != \"success\" ]; then\n    _err \"    $(printf \"%s\" \"${login_response}\" | _cyon_get_response_message)\"\n    _err \"\"\n    return 1\n  fi\n\n  _info \"    success\"\n\n  # NECESSARY!! Load the main page after login, to get the new cookie.\n  _H2=\"$(_cyon_get_cookie_header)\"\n  export _H2\n\n  _get \"https://my.cyon.ch/\" >/dev/null\n\n  # Update cookie after loading main page (only if new cookies are set)\n  _new_cookies=\"$(_cyon_get_cookie_header)\"\n  if [ -n \"$_new_cookies\" ]; then\n    _H2=\"$_new_cookies\"\n    export _H2\n  fi\n\n  # 2FA authentication with OTP?\n  if [ -n \"${CY_OTP_Secret}\" ]; then\n    _info \"  - Authorising with OTP code...\"\n\n    if ! _exists oathtool; then\n      _err \"Please install oathtool to use 2 Factor Authentication.\"\n      _err \"\"\n      return 1\n    fi\n\n    # Get OTP code with the defined secret.\n    otp_code=\"$(oathtool --base32 --totp \"${CY_OTP_Secret}\" 2>/dev/null)\"\n\n    login_otp_url=\"https://my.cyon.ch/auth/multi-factor/domultifactorauth-async\"\n    login_otp_data=\"totpcode=${otp_code}&pathname=%2F&rememberme=0\"\n\n    login_otp_response=\"$(_post \"$login_otp_data\" \"$login_otp_url\")\"\n    _debug login_otp_response \"${login_otp_response}\"\n\n    # Bail if OTP authentication fails.\n    if [ \"$(printf \"%s\" \"${login_otp_response}\" | _cyon_get_response_success)\" != \"success\" ]; then\n      _err \"    $(printf \"%s\" \"${login_otp_response}\" | _cyon_get_response_message)\"\n      _err \"\"\n      return 1\n    fi\n\n    _info \"    success\"\n\n    # Update cookie after 2FA (only if new cookies are set)\n    _new_cookies=\"$(_cyon_get_cookie_header)\"\n    if [ -n \"$_new_cookies\" ]; then\n      _H2=\"$_new_cookies\"\n      export _H2\n    fi\n  fi\n\n  _info \"\"\n}\n\n_cyon_logout() {\n  _info \"  - Logging out...\"\n\n  _get \"https://my.cyon.ch/auth/index/dologout\" >/dev/null\n\n  _info \"    success\"\n  _info \"\"\n}\n\n_cyon_change_domain_env() {\n  _info \"  - Changing domain environment...\"\n\n  # Get the \"example.com\" part of the full domain name.\n  domain_env=\"$(printf \"%s\" \"${fulldomain}\" | sed -E -e 's/.*\\.(.*\\..*)$/\\1/')\"\n  _debug \"Changing domain environment to ${domain_env}\"\n\n  domain_page_response=\"$(_get \"https://my.cyon.ch/domain/\")\"\n  _debug domain_page_response \"${domain_page_response}\"\n\n  # Check if we got an error response (JSON) instead of HTML\n  if printf \"%s\" \"${domain_page_response}\" | grep -q '\"iserror\":true'; then\n    _err \"    $(printf \"%s\" \"${domain_page_response}\" | _cyon_get_response_message)\"\n    _err \"\"\n    return 1\n  fi\n\n  gloo_item_key=\"$(printf \"%s\" \"${domain_page_response}\" | tr '\\n' ' ' | sed -E -e \"s/.*data-domain=\\\"${domain_env}\\\"[^<]*data-itemkey=\\\"([^\\\"]*).*/\\1/\")\"\n  _debug gloo_item_key \"${gloo_item_key}\"\n\n  domain_env_url=\"https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}\"\n\n  domain_env_response=\"$(_get \"${domain_env_url}\")\"\n  _debug domain_env_response \"${domain_env_response}\"\n\n  if ! _cyon_check_if_2fa_missed \"${domain_env_response}\"; then return 1; fi\n\n  # Bail if domain environment change fails.\n  if [ \"$(printf \"%s\" \"${domain_env_response}\" | _cyon_get_environment_change_status)\" != \"true\" ]; then\n    _err \"    $(printf \"%s\" \"${domain_env_response}\" | _cyon_get_response_message)\"\n    _err \"\"\n    return 1\n  fi\n\n  _info \"    success\"\n  _info \"\"\n}\n\n_cyon_add_txt() {\n  _info \"  - Adding DNS TXT entry...\"\n\n  add_txt_url=\"https://my.cyon.ch/domain/dnseditor/add-record-async\"\n  add_txt_data=\"name=${fulldomain_idn}.&ttl=900&type=TXT&dnscontent=${txtvalue}\"\n\n  add_txt_response=\"$(_post \"$add_txt_data\" \"$add_txt_url\")\"\n  _debug add_txt_response \"${add_txt_response}\"\n\n  if ! _cyon_check_if_2fa_missed \"${add_txt_response}\"; then return 1; fi\n\n  add_txt_message=\"$(printf \"%s\" \"${add_txt_response}\" | _cyon_get_response_message)\"\n  add_txt_status=\"$(printf \"%s\" \"${add_txt_response}\" | _cyon_get_response_status)\"\n  add_txt_validation=\"$(printf \"%s\" \"${add_txt_response}\" | _cyon_get_validation_status)\"\n\n  # Bail if adding TXT entry fails.\n  if [ \"${add_txt_status}\" != \"true\" ] || [ \"${add_txt_validation}\" != \"true\" ]; then\n    _err \"    ${add_txt_message}\"\n    _err \"\"\n    return 1\n  fi\n\n  _info \"    success (TXT|${fulldomain_idn}.|${txtvalue})\"\n  _info \"\"\n}\n\n_cyon_delete_txt() {\n  _info \"  - Deleting DNS TXT entry...\"\n\n  list_txt_url=\"https://my.cyon.ch/domain/dnseditor/list-async\"\n\n  list_txt_response=\"$(_get \"${list_txt_url}\" | sed -e 's/data-hash/\\\\ndata-hash/g')\"\n  _debug list_txt_response \"${list_txt_response}\"\n\n  if ! _cyon_check_if_2fa_missed \"${list_txt_response}\"; then return 1; fi\n\n  # Find and delete all acme challenge entries for the $fulldomain.\n  _dns_entries=\"$(printf \"%b\\n\" \"${list_txt_response}\" | sed -n 's/data-hash=\\\\\"\\([^\"]*\\)\\\\\" data-identifier=\\\\\"\\([^\"]*\\)\\\\\".*/\\1 \\2/p')\"\n\n  printf \"%s\" \"${_dns_entries}\" | while read -r _hash _identifier; do\n    dns_type=\"$(printf \"%s\" \"$_identifier\" | cut -d'|' -f1)\"\n    dns_domain=\"$(printf \"%s\" \"$_identifier\" | cut -d'|' -f2)\"\n\n    if [ \"${dns_type}\" != \"TXT\" ] || [ \"${dns_domain}\" != \"${fulldomain_idn}.\" ]; then\n      continue\n    fi\n\n    hash_encoded=\"$(printf \"%s\" \"${_hash}\" | _url_encode)\"\n    identifier_encoded=\"$(printf \"%s\" \"${_identifier}\" | _url_encode)\"\n\n    delete_txt_url=\"https://my.cyon.ch/domain/dnseditor/delete-record-async\"\n    delete_txt_data=\"$(printf \"%s\" \"hash=${hash_encoded}&identifier=${identifier_encoded}\")\"\n\n    delete_txt_response=\"$(_post \"$delete_txt_data\" \"$delete_txt_url\")\"\n    _debug delete_txt_response \"${delete_txt_response}\"\n\n    if ! _cyon_check_if_2fa_missed \"${delete_txt_response}\"; then return 1; fi\n\n    delete_txt_message=\"$(printf \"%s\" \"${delete_txt_response}\" | _cyon_get_response_message)\"\n    delete_txt_status=\"$(printf \"%s\" \"${delete_txt_response}\" | _cyon_get_response_status)\"\n\n    # Skip if deleting TXT entry fails.\n    if [ \"${delete_txt_status}\" != \"true\" ]; then\n      _err \"    ${delete_txt_message} (${_identifier})\"\n    else\n      _info \"    success (${_identifier})\"\n    fi\n  done\n\n  _info \"    done\"\n  _info \"\"\n}\n\n_cyon_get_response_message() {\n  _egrep_o '\"message\":\"[^\"]*\"' | cut -d : -f 2 | tr -d '\"'\n}\n\n_cyon_get_response_status() {\n  _egrep_o '\"status\":[a-zA-Z0-9]*' | cut -d : -f 2\n}\n\n_cyon_get_validation_status() {\n  _egrep_o '\"valid\":[a-zA-Z0-9]*' | cut -d : -f 2\n}\n\n_cyon_get_response_success() {\n  _egrep_o '\"onSuccess\":\"[^\"]*\"' | cut -d : -f 2 | tr -d '\"'\n}\n\n_cyon_get_environment_change_status() {\n  _egrep_o '\"authenticated\":[a-zA-Z0-9]*' | cut -d : -f 2\n}\n\n_cyon_check_if_2fa_missed() {\n  # Did we miss the 2FA?\n  if test \"${1#*multi_factor_form}\" != \"${1}\"; then\n    _err \"    Missed OTP authentication!\"\n    _err \"\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_da.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_da_info='DirectAdmin Server API\nSite: DirectAdmin.com/api.php\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da\nOptions:\n DA_Api API Server URL. E.g. \"https://remoteUser:remotePassword@da.domain.tld:8443\"\n DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept\nIssues: github.com/TigerP/acme.sh/issues\n'\n\n########  Public functions #####################\n\n# Usage: dns_myapi_add  _acme-challenge.www.example.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_da_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  _debug \"Calling: dns_da_add() '${fulldomain}' '${txtvalue}'\"\n  _DA_credentials && _DA_getDomainInfo && _DA_addTxt\n}\n\n# Usage: dns_da_rm  _acme-challenge.www.example.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to remove the txt record after validation\ndns_da_rm() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  _debug \"Calling: dns_da_rm() '${fulldomain}' '${txtvalue}'\"\n  _DA_credentials && _DA_getDomainInfo && _DA_rmTxt\n}\n\n####################  Private functions below ##################################\n# Usage: _DA_credentials\n# It will check if the needed settings are available\n_DA_credentials() {\n  DA_Api=\"${DA_Api:-$(_readaccountconf_mutable DA_Api)}\"\n  DA_Api_Insecure=\"${DA_Api_Insecure:-$(_readaccountconf_mutable DA_Api_Insecure)}\"\n  if [ -z \"${DA_Api}\" ] || [ -z \"${DA_Api_Insecure}\" ]; then\n    DA_Api=\"\"\n    DA_Api_Insecure=\"\"\n    _err \"You haven't specified the DirectAdmin Login data, URL and whether you want check the DirectAdmin SSL cert. Please try again.\"\n    return 1\n  else\n    _saveaccountconf_mutable DA_Api \"${DA_Api}\"\n    _saveaccountconf_mutable DA_Api_Insecure \"${DA_Api_Insecure}\"\n    # Set whether curl should use secure or insecure mode\n    export HTTPS_INSECURE=\"${DA_Api_Insecure}\"\n  fi\n}\n\n# Usage: _get_root _acme-challenge.www.example.com\n# Split the full domain to a domain and subdomain\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=example.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  # Get a list of all the domains\n  # response will contain \"list[]=example.com&list[]=example.org\"\n  _da_api CMD_API_SHOW_DOMAINS \"\" \"${domain}\"\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      # not valid\n      _debug \"The given domain $h is not valid\"\n      return 1\n    fi\n    if _contains \"$response\" \"$h\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  _debug \"Stop on 100\"\n  return 1\n}\n\n# Usage: _da_api CMD_API_* data example.com\n# Use the DirectAdmin API and check the result\n# returns\n#  response=\"error=0&text=Result text&details=\"\n_da_api() {\n  cmd=$1\n  data=$2\n  domain=$3\n  _debug \"$domain; $data\"\n  response=\"$(_post \"$data\" \"$DA_Api/$cmd\" \"\" \"POST\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $cmd\"\n    return 1\n  fi\n  _debug response \"$response\"\n\n  case \"${cmd}\" in\n  CMD_API_DNS_CONTROL)\n    # Parse the result in general\n    # error=0&text=Records Deleted&details=\n    # error=1&text=Cannot View Dns Record&details=No domain provided\n    err_field=\"$(_getfield \"$response\" 1 '&')\"\n    txt_field=\"$(_getfield \"$response\" 2 '&')\"\n    details_field=\"$(_getfield \"$response\" 3 '&')\"\n    error=\"$(_getfield \"$err_field\" 2 '=')\"\n    text=\"$(_getfield \"$txt_field\" 2 '=')\"\n    details=\"$(_getfield \"$details_field\" 2 '=')\"\n    _debug \"error: ${error}, text: ${text}, details: ${details}\"\n    if [ \"$error\" != \"0\" ]; then\n      _err \"error $response\"\n      return 1\n    fi\n    ;;\n  CMD_API_SHOW_DOMAINS) ;;\n  esac\n  return 0\n}\n\n# Usage: _DA_getDomainInfo\n# Get the root zone if possible\n_DA_getDomainInfo() {\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  else\n    _debug \"The root domain: $_domain\"\n    _debug \"The sub domain: $_sub_domain\"\n  fi\n  return 0\n}\n\n# Usage: _DA_addTxt\n# Use the API to add a record\n_DA_addTxt() {\n  curData=\"domain=${_domain}&action=add&type=TXT&name=${_sub_domain}&value=\\\"${txtvalue}\\\"\"\n  _debug \"Calling _DA_addTxt: '${curData}' '${DA_Api}/CMD_API_DNS_CONTROL'\"\n  _da_api CMD_API_DNS_CONTROL \"${curData}\" \"${_domain}\"\n  _debug \"Result of _DA_addTxt: '$response'\"\n  if _contains \"${response}\" 'error=0'; then\n    _debug \"Add TXT succeeded\"\n    return 0\n  fi\n  _debug \"Add TXT failed\"\n  return 1\n}\n\n# Usage: _DA_rmTxt\n# Use the API to remove a record\n_DA_rmTxt() {\n  curData=\"domain=${_domain}&action=select&txtrecs0=name=${_sub_domain}&amp;value=\\\"${txtvalue}\\\"\"\n  _debug \"Calling _DA_rmTxt: '${curData}' '${DA_Api}/CMD_API_DNS_CONTROL'\"\n  if _da_api CMD_API_DNS_CONTROL \"${curData}\" \"${_domain}\"; then\n    _debug \"Result of _DA_rmTxt: '$response'\"\n  else\n    _err \"Result of _DA_rmTxt: '$response'\"\n  fi\n  if _contains \"${response}\" 'error=0'; then\n    _debug \"RM TXT succeeded\"\n    return 0\n  fi\n  _debug \"RM TXT failed\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_ddnss.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ddnss_info='DDNSS.de\nSite: DDNSS.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss\nOptions:\n DDNSS_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/2230\nAuthor: @helbgd, @mod242\n'\n\nDDNSS_DNS_API=\"https://ddnss.de/upd.php\"\n\n########  Public functions #####################\n\n#Usage: dns_ddnss_add _acme-challenge.domain.ddnss.de \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_ddnss_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DDNSS_Token=\"${DDNSS_Token:-$(_readaccountconf_mutable DDNSS_Token)}\"\n  if [ -z \"$DDNSS_Token\" ]; then\n    _err \"You must export variable: DDNSS_Token\"\n    _err \"The token for your DDNSS account is necessary.\"\n    _err \"You can look it up in your DDNSS account.\"\n    return 1\n  fi\n\n  # Now save the credentials.\n  _saveaccountconf_mutable DDNSS_Token \"$DDNSS_Token\"\n\n  # Unfortunately, DDNSS does not seems to support lookup domain through API\n  # So I assume your credentials (which are your domain and token) are correct\n  # If something goes wrong, we will get a KO response from DDNSS\n\n  if ! _ddnss_get_domain; then\n    return 1\n  fi\n\n  # Now add the TXT record to DDNSS DNS\n  _info \"Trying to add TXT record\"\n  if _ddnss_rest GET \"key=$DDNSS_Token&host=$_ddnss_domain&txtm=1&txt=$txtvalue\"; then\n    if [ \"$response\" = \"Updated 1 hostname.\" ]; then\n      _info \"TXT record has been successfully added to your DDNSS domain.\"\n      _info \"Note that all subdomains under this domain uses the same TXT record.\"\n      return 0\n    else\n      _err \"Errors happened during adding the TXT record, response=$response\"\n      return 1\n    fi\n  else\n    _err \"Errors happened during adding the TXT record.\"\n    return 1\n  fi\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_ddnss_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DDNSS_Token=\"${DDNSS_Token:-$(_readaccountconf_mutable DDNSS_Token)}\"\n  if [ -z \"$DDNSS_Token\" ]; then\n    _err \"You must export variable: DDNSS_Token\"\n    _err \"The token for your DDNSS account is necessary.\"\n    _err \"You can look it up in your DDNSS account.\"\n    return 1\n  fi\n\n  if ! _ddnss_get_domain; then\n    return 1\n  fi\n\n  # Now remove the TXT record from DDNS DNS\n  _info \"Trying to remove TXT record\"\n  if _ddnss_rest GET \"key=$DDNSS_Token&host=$_ddnss_domain&txtm=2\"; then\n    if [ \"$response\" = \"Updated 1 hostname.\" ]; then\n      _info \"TXT record has been successfully removed from your DDNSS domain.\"\n      return 0\n    else\n      _err \"Errors happened during removing the TXT record, response=$response\"\n      return 1\n    fi\n  else\n    _err \"Errors happened during removing the TXT record.\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n#fulldomain=_acme-challenge.domain.ddnss.de\n#returns\n# _ddnss_domain=domain\n_ddnss_get_domain() {\n\n  # We'll extract the domain/username from full domain\n  _ddnss_domain=\"$(echo \"$fulldomain\" | _lower_case | _egrep_o '[.][^.][^.]*[.](ddnss|dyn-ip24|dyndns|dyn|dyndns1|home-webserver|myhome-server|dynip)\\..*' | cut -d . -f 2-)\"\n\n  if [ -z \"$_ddnss_domain\" ]; then\n    _err \"Error extracting the domain.\"\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: method URI\n_ddnss_rest() {\n  method=$1\n  param=\"$2\"\n  _debug param \"$param\"\n  url=\"$DDNSS_DNS_API?$param\"\n  _debug url \"$url\"\n\n  # DDNSS uses GET to update domain info\n  if [ \"$method\" = \"GET\" ]; then\n    response=\"$(_get \"$url\" | sed 's/<[a-zA-Z\\/][^>]*>//g' | tr -s \"\\n\" | _tail_n 1)\"\n  else\n    _err \"Unsupported method\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_desec.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_desec_info='deSEC.io\nSite: desec.readthedocs.io/en/latest/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec\nOptions:\n DDNSS_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/2180\nAuthor: Zheng Qian\n'\n\nREST_API=\"https://desec.io/api/v1/domains\"\n\n########  Public functions #####################\n\n#Usage: dns_desec_add   _acme-challenge.foobar.dedyn.io   \"d41d8cd98f00b204e9800998ecf8427e\"\ndns_desec_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using desec.io api\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  DEDYN_TOKEN=\"${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}\"\n\n  if [ -z \"$DEDYN_TOKEN\" ]; then\n    DEDYN_TOKEN=\"\"\n    _err \"You did not specify DEDYN_TOKEN yet.\"\n    _err \"Please create your key and try again.\"\n    _err \"e.g.\"\n    _err \"export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e\"\n    return 1\n  fi\n  #save the api token to the account conf file.\n  _saveaccountconf_mutable DEDYN_TOKEN \"$DEDYN_TOKEN\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\" \"$REST_API/\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # Get existing TXT record\n  _debug \"Getting txt records\"\n  txtvalues=\"\\\"\\\\\\\"$txtvalue\\\\\\\"\\\"\"\n  _desec_rest GET \"$REST_API/$_domain/rrsets/$_sub_domain/TXT/\"\n\n  if [ \"$_code\" = \"200\" ]; then\n    oldtxtvalues=\"$(echo \"$response\" | _egrep_o \"\\\"records\\\":\\\\[\\\"\\\\S*\\\"\\\\]\" | cut -d : -f 2 | tr -d \"[]\\\\\\\\\\\"\" | sed \"s/,/ /g\")\"\n    _debug \"existing TXT found\"\n    _debug oldtxtvalues \"$oldtxtvalues\"\n    if [ -n \"$oldtxtvalues\" ]; then\n      for oldtxtvalue in $oldtxtvalues; do\n        txtvalues=\"$txtvalues, \\\"\\\\\\\"$oldtxtvalue\\\\\\\"\\\"\"\n      done\n    fi\n  fi\n  _debug txtvalues \"$txtvalues\"\n  _info \"Adding record\"\n  body=\"[{\\\"subname\\\":\\\"$_sub_domain\\\", \\\"type\\\":\\\"TXT\\\", \\\"records\\\":[$txtvalues], \\\"ttl\\\":3600}]\"\n\n  if _desec_rest PUT \"$REST_API/$_domain/rrsets/\" \"$body\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_desec_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using desec.io api\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  DEDYN_TOKEN=\"${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}\"\n\n  if [ -z \"$DEDYN_TOKEN\" ]; then\n    DEDYN_TOKEN=\"\"\n    _err \"You did not specify DEDYN_TOKEN yet.\"\n    _err \"Please create your key and try again.\"\n    _err \"e.g.\"\n    _err \"export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\" \"$REST_API/\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # Get existing TXT record\n  _debug \"Getting txt records\"\n  txtvalues=\"\"\n  _desec_rest GET \"$REST_API/$_domain/rrsets/$_sub_domain/TXT/\"\n\n  if [ \"$_code\" = \"200\" ]; then\n    oldtxtvalues=\"$(echo \"$response\" | _egrep_o \"\\\"records\\\":\\\\[\\\"\\\\S*\\\"\\\\]\" | cut -d : -f 2 | tr -d \"[]\\\\\\\\\\\"\" | sed \"s/,/ /g\")\"\n    _debug \"existing TXT found\"\n    _debug oldtxtvalues \"$oldtxtvalues\"\n    if [ -n \"$oldtxtvalues\" ]; then\n      for oldtxtvalue in $oldtxtvalues; do\n        if [ \"$txtvalue\" != \"$oldtxtvalue\" ]; then\n          txtvalues=\"$txtvalues, \\\"\\\\\\\"$oldtxtvalue\\\\\\\"\\\"\"\n        fi\n      done\n    fi\n  fi\n  txtvalues=\"$(echo \"$txtvalues\" | cut -c3-)\"\n  _debug txtvalues \"$txtvalues\"\n\n  _info \"Deleting record\"\n  body=\"[{\\\"subname\\\":\\\"$_sub_domain\\\", \\\"type\\\":\\\"TXT\\\", \\\"records\\\":[$txtvalues], \\\"ttl\\\":3600}]\"\n  _desec_rest PUT \"$REST_API/$_domain/rrsets/\" \"$body\"\n  if [ \"$_code\" = \"200\" ]; then\n    _info \"Deleted, OK\"\n    return 0\n  fi\n\n  _err \"Delete txt record error.\"\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_desec_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  export _H1=\"Authorization: Token $DEDYN_TOKEN\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _secure_debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$ep\")\"\n  fi\n  _ret=\"$?\"\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  _debug \"http response code $_code\"\n  _secure_debug2 response \"$response\"\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n\n  response=\"$(printf \"%s\" \"$response\" | _normalizeJson)\"\n  return 0\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=\"$1\"\n  ep=\"$2\"\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _desec_rest GET \"$ep\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_df.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_df_info='DynDnsFree.de\nDomains: dynup.de\nSite: DynDnsFree.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_df\nOptions:\n DF_user Username\n DF_password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2897\nAuthor: Thilo Gass <thilo.gass@gmail.com>\n'\n\ndyndnsfree_api=\"https://dynup.de/acme.php\"\n\ndns_df_add() {\n  fulldomain=$1\n  txt_value=$2\n  _info \"Using DNS-01 dyndnsfree.de hook\"\n\n  DF_user=\"${DF_user:-$(_readaccountconf_mutable DF_user)}\"\n  DF_password=\"${DF_password:-$(_readaccountconf_mutable DF_password)}\"\n  if [ -z \"$DF_user\" ] || [ -z \"$DF_password\" ]; then\n    DF_user=\"\"\n    DF_password=\"\"\n    _err \"No auth details provided. Please set user credentials using the \\$DF_user and \\$DF_password environment variables.\"\n    return 1\n  fi\n  #save the api user and password to the account conf file.\n  _debug \"Save user and password\"\n  _saveaccountconf_mutable DF_user \"$DF_user\"\n  _saveaccountconf_mutable DF_password \"$DF_password\"\n\n  domain=\"$(printf \"%s\" \"$fulldomain\" | cut -d\".\" -f2-)\"\n\n  get=\"$dyndnsfree_api?username=$DF_user&password=$DF_password&hostname=$domain&add_hostname=$fulldomain&txt=$txt_value\"\n\n  if ! erg=\"$(_get \"$get\")\"; then\n    _err \"error Adding $fulldomain TXT: $txt_value\"\n    return 1\n  fi\n\n  if _contains \"$erg\" \"success\"; then\n    _info \"Success, TXT Added, OK\"\n  else\n    _err \"error Adding $fulldomain TXT: $txt_value erg: $erg\"\n    return 1\n  fi\n\n  _debug \"ok Auto $fulldomain TXT: $txt_value erg: $erg\"\n  return 0\n}\n\ndns_df_rm() {\n\n  fulldomain=$1\n  txtvalue=$2\n  _info \"TXT enrty in $fulldomain is deleted automatically\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n}\n"
  },
  {
    "path": "dnsapi/dns_dgon.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dgon_info='DigitalOcean.com\nSite: DigitalOcean.com/help/api/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dgon\nOptions:\n DO_API_KEY API Key\nAuthor: <github@thewer.com>\n'\n\n#####################  Public functions  #####################\n\n## Create the text record for validation.\n## Usage: fulldomain txtvalue\n## EG: \"_acme-challenge.www.other.domain.com\" \"XKrxpRBosdq0HG9i01zxXp5CPBs\"\ndns_dgon_add() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=$2\n\n  DO_API_KEY=\"${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}\"\n  # Check if API Key Exists\n  if [ -z \"$DO_API_KEY\" ]; then\n    DO_API_KEY=\"\"\n    _err \"You did not specify DigitalOcean API key.\"\n    _err \"Please export DO_API_KEY and try again.\"\n    return 1\n  fi\n\n  _info \"Using digitalocean dns validation - add record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ## save the env vars (key and domain split location) for later automated use\n  _saveaccountconf_mutable DO_API_KEY \"$DO_API_KEY\"\n\n  ## split the domain for DO API\n  if ! _get_base_domain \"$fulldomain\"; then\n    _err \"domain not found in your account for addition\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  ## Set the header with our post type and key auth key\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $DO_API_KEY\"\n  PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records'\n  PBODY='{\"type\":\"TXT\",\"name\":\"'$_sub_domain'\",\"data\":\"'$txtvalue'\",\"ttl\":120}'\n\n  _debug PURL \"$PURL\"\n  _debug PBODY \"$PBODY\"\n\n  ## the create request - post\n  ## args: BODY, URL, [need64, httpmethod]\n  response=\"$(_post \"$PBODY\" \"$PURL\")\"\n\n  ## check response\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error in response: $response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n\n  ## finished correctly\n  return 0\n}\n\n## Remove the txt record after validation.\n## Usage: fulldomain txtvalue\n## EG: \"_acme-challenge.www.other.domain.com\" \"XKrxpRBosdq0HG9i01zxXp5CPBs\"\ndns_dgon_rm() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=$2\n\n  DO_API_KEY=\"${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}\"\n  # Check if API Key Exists\n  if [ -z \"$DO_API_KEY\" ]; then\n    DO_API_KEY=\"\"\n    _err \"You did not specify DigitalOcean API key.\"\n    _err \"Please export DO_API_KEY and try again.\"\n    return 1\n  fi\n\n  _info \"Using digitalocean dns validation - remove record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ## split the domain for DO API\n  if ! _get_base_domain \"$fulldomain\"; then\n    _err \"domain not found in your account for removal\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  ## Set the header with our post type and key auth key\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $DO_API_KEY\"\n  ## get URL for the list of domains\n  ## may get: \"links\":{\"pages\":{\"last\":\".../v2/domains/DOM/records?page=2\",\"next\":\".../v2/domains/DOM/records?page=2\"}}\n  GURL=\"https://api.digitalocean.com/v2/domains/$_domain/records\"\n\n  ## Get all the matching records\n  while true; do\n    ## 1) get the URL\n    ## the create request - get\n    ## args: URL, [onlyheader, timeout]\n    domain_list=\"$(_get \"$GURL\")\"\n\n    ## check response\n    if [ \"$?\" != \"0\" ]; then\n      _err \"error in domain_list response: $domain_list\"\n      return 1\n    fi\n    _debug2 domain_list \"$domain_list\"\n\n    ## 2) find records\n    ## check for what we are looking for: \"type\":\"A\",\"name\":\"$_sub_domain\"\n    record=\"$(echo \"$domain_list\" | _egrep_o \"\\\"id\\\"\\s*\\:\\s*\\\"*[0-9]+\\\"*[^}]*\\\"name\\\"\\s*\\:\\s*\\\"$_sub_domain\\\"[^}]*\\\"data\\\"\\s*\\:\\s*\\\"$txtvalue\\\"\")\"\n\n    if [ -n \"$record\" ]; then\n\n      ## we found records\n      rec_ids=\"$(echo \"$record\" | _egrep_o \"id\\\"\\s*\\:\\s*\\\"*[0-9]+\" | _egrep_o \"[0-9]+\")\"\n      _debug rec_ids \"$rec_ids\"\n      if [ -n \"$rec_ids\" ]; then\n        echo \"$rec_ids\" | while IFS= read -r rec_id; do\n          ## delete the record\n          ## delete URL for removing the one we dont want\n          DURL=\"https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id\"\n\n          ## the create request - delete\n          ## args: BODY, URL, [need64, httpmethod]\n          response=\"$(_post \"\" \"$DURL\" \"\" \"DELETE\")\"\n\n          ## check response (sort of)\n          if [ \"$?\" != \"0\" ]; then\n            _err \"error in remove response: $response\"\n            return 1\n          fi\n          _debug2 response \"$response\"\n\n        done\n      fi\n    fi\n\n    ## 3) find the next page\n    nextpage=\"$(echo \"$domain_list\" | _egrep_o \"\\\"links\\\".*\" | _egrep_o \"\\\"next\\\".*\" | _egrep_o \"http.*page\\=[0-9]+\")\"\n    if [ -z \"$nextpage\" ]; then\n      break\n    fi\n    _debug2 nextpage \"$nextpage\"\n    GURL=\"$nextpage\"\n\n  done\n\n  ## finished correctly\n  return 0\n}\n\n#####################  Private functions below  #####################\n\n## Split the domain provided into the \"bade domain\" and the \"start prefix\".\n## This function searches for the longest subdomain in your account\n## for the full domain given and splits it into the base domain (zone)\n## and the prefix/record to be added/removed\n## USAGE: fulldomain\n## EG: \"_acme-challenge.two.three.four.domain.com\"\n## returns\n## _sub_domain=\"_acme-challenge.two\"\n## _domain=\"three.four.domain.com\" *IF* zone \"three.four.domain.com\" exists\n## if only \"domain.com\" exists it will return\n## _sub_domain=\"_acme-challenge.two.three.four\"\n## _domain=\"domain.com\"\n_get_base_domain() {\n  # args\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  _debug fulldomain \"$fulldomain\"\n\n  # domain max legal length = 253\n  MAX_DOM=255\n\n  ## get a list of domains for the account to check thru\n  ## Set the headers\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $DO_API_KEY\"\n  _debug DO_API_KEY \"$DO_API_KEY\"\n  ## get URL for the list of domains\n  ## may get: \"links\":{\"pages\":{\"last\":\".../v2/domains/DOM/records?page=2\",\"next\":\".../v2/domains/DOM/records?page=2\"}}\n  DOMURL=\"https://api.digitalocean.com/v2/domains\"\n  found=\"\"\n\n  ## while we dont have a matching domain we keep going\n  while [ -z \"$found\" ]; do\n    ## get the domain list (current page)\n    domain_list=\"$(_get \"$DOMURL\")\"\n\n    ## check response\n    if [ \"$?\" != \"0\" ]; then\n      _err \"error in domain_list response: $domain_list\"\n      return 1\n    fi\n    _debug2 domain_list \"$domain_list\"\n\n    i=1\n    while [ \"$i\" -gt 0 ]; do\n      ## get next longest domain\n      _domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i\"-\"$MAX_DOM\")\n      ## check we got something back from our cut (or are we at the end)\n      if [ -z \"$_domain\" ]; then\n        break\n      fi\n      ## we got part of a domain back - grep it out\n      found=\"$(echo \"$domain_list\" | _egrep_o \"\\\"name\\\"\\s*\\:\\s*\\\"$_domain\\\"\")\"\n      ## check if it exists\n      if [ -n \"$found\" ]; then\n        ## exists - exit loop returning the parts\n        sub_point=$(_math \"$i\" - 1)\n        _sub_domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 1-\"$sub_point\")\n        _debug _domain \"$_domain\"\n        _debug _sub_domain \"$_sub_domain\"\n        return 0\n      fi\n      ## increment cut point $i\n      i=$(_math \"$i\" + 1)\n    done\n\n    if [ -z \"$found\" ]; then\n      ## find the next page if we dont have a match\n      nextpage=\"$(echo \"$domain_list\" | _egrep_o \"\\\"links\\\".*\" | _egrep_o \"\\\"next\\\".*\" | _egrep_o \"http.*page\\=[0-9]+\")\"\n      if [ -z \"$nextpage\" ]; then\n        _err \"no record and no nextpage in digital ocean DNS removal\"\n        return 1\n      fi\n      _debug2 nextpage \"$nextpage\"\n      DOMURL=\"$nextpage\"\n    fi\n\n  done\n\n  ## we went through the entire domain zone list and dint find one that matched\n  ## doesnt look like we can add in the record\n  _err \"domain not found in DigitalOcean account, but we should never get here\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_dnsexit.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dnsexit_info='DNSExit.com\nSite: DNSExit.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit\nOptions:\n DNSEXIT_API_KEY API Key\n DNSEXIT_AUTH_USER Username\n DNSEXIT_AUTH_PASS Password\nIssues: github.com/acmesh-official/acme.sh/issues/4719\nAuthor: Samuel Jimenez\n'\n\nDNSEXIT_API_URL=\"https://api.dnsexit.com/dns/\"\nDNSEXIT_HOSTS_URL=\"https://update.dnsexit.com/ipupdate/hosts.jsp\"\n\n########  Public functions #####################\n#Usage: dns_dnsexit_add   _acme-challenge.*.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dnsexit_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using DNSExit.com\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _debug 'Load account auth'\n  if ! get_account_info; then\n    return 1\n  fi\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if ! _dnsexit_rest \"{\\\"domain\\\":\\\"$_domain\\\",\\\"add\\\":{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":0,\\\"overwrite\\\":false}}\"; then\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 _response \"$response\"\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_dnsexit_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using DNSExit.com\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _debug 'Load account auth'\n  if ! get_account_info; then\n    return 1\n  fi\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    _err \"$response\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if ! _dnsexit_rest \"{\\\"domain\\\":\\\"$_domain\\\",\\\"delete\\\":{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\"}}\"; then\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 _response \"$response\"\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  while true; do\n    _domain=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$_domain\"\n    if [ -z \"$_domain\" ]; then\n      return 1\n    fi\n\n    _debug login \"$DNSEXIT_AUTH_USER\"\n    _debug password \"$DNSEXIT_AUTH_PASS\"\n    _debug domain \"$_domain\"\n\n    _dnsexit_http \"login=$DNSEXIT_AUTH_USER&password=$DNSEXIT_AUTH_PASS&domain=$_domain\"\n\n    if _contains \"$response\" \"0=$_domain\"; then\n      _sub_domain=\"$(echo \"$fulldomain\" | sed \"s/\\\\.$_domain\\$//\")\"\n      return 0\n    else\n      _debug \"Go to next level of $_domain\"\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_dnsexit_rest() {\n  m=POST\n  ep=\"\"\n  data=\"$1\"\n  _debug _dnsexit_rest \"$ep\"\n  _debug data \"$data\"\n\n  api_key_trimmed=$(echo \"$DNSEXIT_API_KEY\" | tr -d '\"')\n\n  export _H1=\"apikey: $api_key_trimmed\"\n  export _H2='Content-Type: application/json'\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$DNSEXIT_API_URL/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$DNSEXIT_API_URL/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $ep\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n\n_dnsexit_http() {\n  m=GET\n  param=\"$1\"\n  _debug param \"$param\"\n  _debug get \"$DNSEXIT_HOSTS_URL?$param\"\n\n  response=\"$(_get \"$DNSEXIT_HOSTS_URL?$param\")\"\n\n  _debug response \"$response\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $param\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n\nget_account_info() {\n\n  DNSEXIT_API_KEY=\"${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}\"\n  if test -z \"$DNSEXIT_API_KEY\"; then\n    DNSEXIT_API_KEY=''\n    _err 'DNSEXIT_API_KEY was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable DNSEXIT_API_KEY \"$DNSEXIT_API_KEY\"\n\n  DNSEXIT_AUTH_USER=\"${DNSEXIT_AUTH_USER:-$(_readaccountconf_mutable DNSEXIT_AUTH_USER)}\"\n  if test -z \"$DNSEXIT_AUTH_USER\"; then\n    DNSEXIT_AUTH_USER=\"\"\n    _err 'DNSEXIT_AUTH_USER was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable DNSEXIT_AUTH_USER \"$DNSEXIT_AUTH_USER\"\n\n  DNSEXIT_AUTH_PASS=\"${DNSEXIT_AUTH_PASS:-$(_readaccountconf_mutable DNSEXIT_AUTH_PASS)}\"\n  if test -z \"$DNSEXIT_AUTH_PASS\"; then\n    DNSEXIT_AUTH_PASS=\"\"\n    _err 'DNSEXIT_AUTH_PASS was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable DNSEXIT_AUTH_PASS \"$DNSEXIT_AUTH_PASS\"\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dnshome.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dnshome_info='dnsHome.de\nSite: dnsHome.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnshome\nOptions:\n DNSHOME_Subdomain Subdomain\n DNSHOME_SubdomainPassword Subdomain Password\nIssues: github.com/acmesh-official/acme.sh/issues/3819\nAuthor: @dnsHome-de\n'\n\n# Usage: add subdomain.ddnsdomain.tld \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_dnshome_add() {\n  txtvalue=$2\n\n  DNSHOME_Subdomain=\"${DNSHOME_Subdomain:-$(_readdomainconf DNSHOME_Subdomain)}\"\n  DNSHOME_SubdomainPassword=\"${DNSHOME_SubdomainPassword:-$(_readdomainconf DNSHOME_SubdomainPassword)}\"\n\n  if [ -z \"$DNSHOME_Subdomain\" ] || [ -z \"$DNSHOME_SubdomainPassword\" ]; then\n    DNSHOME_Subdomain=\"\"\n    DNSHOME_SubdomainPassword=\"\"\n    _err \"Please specify/export your dnsHome.de Subdomain and Password\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _savedomainconf DNSHOME_Subdomain \"$DNSHOME_Subdomain\"\n  _savedomainconf DNSHOME_SubdomainPassword \"$DNSHOME_SubdomainPassword\"\n\n  DNSHOME_Api=\"https://$DNSHOME_Subdomain:$DNSHOME_SubdomainPassword@www.dnshome.de/dyndns.php\"\n\n  _DNSHOME_rest POST \"acme=add&txt=$txtvalue\"\n  if ! echo \"$response\" | grep 'successfully' >/dev/null; then\n    _err \"Error\"\n    _err \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n\n# Usage: txtvalue\n# Used to remove the txt record after validation\ndns_dnshome_rm() {\n  txtvalue=$2\n\n  DNSHOME_Subdomain=\"${DNSHOME_Subdomain:-$(_readdomainconf DNSHOME_Subdomain)}\"\n  DNSHOME_SubdomainPassword=\"${DNSHOME_SubdomainPassword:-$(_readdomainconf DNSHOME_SubdomainPassword)}\"\n\n  DNSHOME_Api=\"https://$DNSHOME_Subdomain:$DNSHOME_SubdomainPassword@www.dnshome.de/dyndns.php\"\n\n  if [ -z \"$DNSHOME_Subdomain\" ] || [ -z \"$DNSHOME_SubdomainPassword\" ]; then\n    DNSHOME_Subdomain=\"\"\n    DNSHOME_SubdomainPassword=\"\"\n    _err \"Please specify/export your dnsHome.de Subdomain and Password\"\n    return 1\n  fi\n\n  _DNSHOME_rest POST \"acme=rm&txt=$txtvalue\"\n  if ! echo \"$response\" | grep 'successfully' >/dev/null; then\n    _err \"Error\"\n    _err \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n_DNSHOME_rest() {\n  method=$1\n  data=\"$2\"\n  _debug \"$data\"\n\n  _debug data \"$data\"\n  response=\"$(_post \"$data\" \"$DNSHOME_Api\" \"\" \"$method\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $data\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dnsimple.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dnsimple_info='DNSimple.com\nSite: DNSimple.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple\nOptions:\n DNSimple_OAUTH_TOKEN OAuth Token\nIssues: github.com/pho3nixf1re/acme.sh/issues\n'\n\nDNSimple_API=\"https://api.dnsimple.com/v2\"\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dnsimple_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$DNSimple_OAUTH_TOKEN\" ]; then\n    DNSimple_OAUTH_TOKEN=\"\"\n    _err \"You have not set the dnsimple oauth token yet.\"\n    _err \"Please visit https://dnsimple.com/user to generate it.\"\n    return 1\n  fi\n\n  # save the oauth token for later\n  _saveaccountconf DNSimple_OAUTH_TOKEN \"$DNSimple_OAUTH_TOKEN\"\n\n  if ! _get_account_id; then\n    _err \"failed to retrive account id\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _get_records \"$_account_id\" \"$_domain\" \"$_sub_domain\"\n\n  _info \"Adding record\"\n  if _dnsimple_rest POST \"$_account_id/zones/$_domain/records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"\\\"name\\\":\\\"$_sub_domain\\\"\" >/dev/null; then\n      _info \"Added\"\n      return 0\n    else\n      _err \"Unexpected response while adding text record.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n}\n\n# fulldomain\ndns_dnsimple_rm() {\n  fulldomain=$1\n\n  if ! _get_account_id; then\n    _err \"failed to retrive account id\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _get_records \"$_account_id\" \"$_domain\" \"$_sub_domain\"\n\n  _extract_record_id \"$_records\" \"$_sub_domain\"\n  if [ \"$_record_id\" ]; then\n    echo \"$_record_id\" | while read -r item; do\n      if _dnsimple_rest DELETE \"$_account_id/zones/$_domain/records/$item\"; then\n        _info \"removed record\" \"$item\"\n        return 0\n      else\n        _err \"failed to remove record\" \"$item\"\n        return 1\n      fi\n    done\n  fi\n}\n\n####################  Private functions bellow ##################################\n# _acme-challenge.www.domain.com\n# returns\n#   _sub_domain=_acme-challenge.www\n#   _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  previous=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      # not valid\n      return 1\n    fi\n\n    if ! _dnsimple_rest GET \"$_account_id/zones/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" 'not found'; then\n      _debug \"$h not found\"\n    else\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$previous\")\n      _domain=\"$h\"\n\n      _debug _domain \"$_domain\"\n      _debug _sub_domain \"$_sub_domain\"\n\n      return 0\n    fi\n\n    previous=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n# returns _account_id\n_get_account_id() {\n  _debug \"retrive account id\"\n  if ! _dnsimple_rest GET \"whoami\"; then\n    return 1\n  fi\n\n  if _contains \"$response\" \"\\\"account\\\":null\"; then\n    _err \"no account associated with this token\"\n    return 1\n  fi\n\n  if _contains \"$response\" \"timeout\"; then\n    _err \"timeout retrieving account id\"\n    return 1\n  fi\n\n  _account_id=$(printf \"%s\" \"$response\" | _egrep_o \"\\\"id\\\":[^,]*,\\\"email\\\":\" | cut -d: -f2 | cut -d, -f1)\n  _debug _account_id \"$_account_id\"\n\n  return 0\n}\n\n# returns\n#   _records\n#   _records_count\n_get_records() {\n  account_id=$1\n  domain=$2\n  sub_domain=$3\n\n  _debug \"fetching txt records\"\n  _dnsimple_rest GET \"$account_id/zones/$domain/records?per_page=5000&sort=id:desc\"\n\n  if ! _contains \"$response\" \"\\\"id\\\":\"; then\n    _err \"failed to retrieve records\"\n    return 1\n  fi\n\n  _records_count=$(printf \"%s\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$sub_domain\\\"\" | wc -l | _egrep_o \"[0-9]+\")\n  _records=$response\n  _debug _records_count \"$_records_count\"\n}\n\n# returns _record_id\n_extract_record_id() {\n  _record_id=$(printf \"%s\" \"$_records\" | _egrep_o \"\\\"id\\\":[^,]*,\\\"zone_id\\\":\\\"[^,]*\\\",\\\"parent_id\\\":null,\\\"name\\\":\\\"$_sub_domain\\\"\" | cut -d: -f2 | cut -d, -f1)\n  _debug \"_record_id\" \"$_record_id\"\n}\n\n# returns response\n_dnsimple_rest() {\n  method=$1\n  path=\"$2\"\n  data=\"$3\"\n  request_url=\"$DNSimple_API/$path\"\n  _debug \"$path\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Authorization: Bearer $DNSimple_OAUTH_TOKEN\"\n\n  if [ \"$data\" ] || [ \"$method\" = \"DELETE\" ]; then\n    _H1=\"Content-Type: application/json\"\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$request_url\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$request_url\" \"\" \"\" \"$method\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $request_url\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dnsservices.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dnsservices_info='DNS.Services\nSite: DNS.Services\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsservices\nOptions:\n DnsServices_Username Username\n DnsServices_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/4152\nAuthor: Bjarke Bruun <bbruun@gmail.com>\n'\n\nDNSServices_API=https://dns.services/api\n\n########  Public functions #####################\n\n#Usage: dns_dnsservices_add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dnsservices_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Using dns.services to create ACME DNS challenge\"\n  _debug2 add_fulldomain \"$fulldomain\"\n  _debug2 add_txtvalue \"$txtvalue\"\n\n  # Read username/password from environment or .acme.sh/accounts.conf\n  DnsServices_Username=\"${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}\"\n  DnsServices_Password=\"${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}\"\n  if [ -z \"$DnsServices_Username\" ] || [ -z \"$DnsServices_Password\" ]; then\n    DnsServices_Username=\"\"\n    DnsServices_Password=\"\"\n    _err \"You didn't specify dns.services api username and password yet.\"\n    _err \"Set environment variables DnsServices_Username and DnsServices_Password\"\n    return 1\n  fi\n\n  # Setup GET/POST/DELETE headers\n  _setup_headers\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable DnsServices_Username \"$DnsServices_Username\"\n  _saveaccountconf_mutable DnsServices_Password \"$DnsServices_Password\"\n\n  if ! _contains \"$DnsServices_Username\" \"@\"; then\n    _err \"It seems that the username variable DnsServices_Username has not been set/left blank\"\n    _err \"or is not a valid email. Please correct and try again.\"\n    return 1\n  fi\n\n  if ! _get_root \"${fulldomain}\"; then\n    _err \"Invalid domain ${fulldomain}\"\n    return 1\n  fi\n\n  if ! createRecord \"$fulldomain\" \"${txtvalue}\"; then\n    _err \"Error creating TXT record in domain $fulldomain in $rootZoneName\"\n    return 1\n  fi\n\n  _debug2 challenge-created \"Created $fulldomain\"\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Description: Remove the txt record after validation.\ndns_dnsservices_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Using dns.services to remove DNS record $fulldomain TXT $txtvalue\"\n  _debug rm_fulldomain \"$fulldomain\"\n  _debug rm_txtvalue \"$txtvalue\"\n\n  # Read username/password from environment or .acme.sh/accounts.conf\n  DnsServices_Username=\"${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}\"\n  DnsServices_Password=\"${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}\"\n  if [ -z \"$DnsServices_Username\" ] || [ -z \"$DnsServices_Password\" ]; then\n    DnsServices_Username=\"\"\n    DnsServices_Password=\"\"\n    _err \"You didn't specify dns.services api username and password yet.\"\n    _err \"Set environment variables DnsServices_Username and DnsServices_Password\"\n    return 1\n  fi\n\n  # Setup GET/POST/DELETE headers\n  _setup_headers\n\n  if ! _get_root \"${fulldomain}\"; then\n    _err \"Invalid domain ${fulldomain}\"\n    return 1\n  fi\n\n  _debug2 rm_rootDomainInfo \"found root domain $rootZoneName for $fulldomain\"\n\n  if ! deleteRecord \"${fulldomain}\" \"${txtvalue}\"; then\n    _err \"Error removing record: $fulldomain TXT ${txtvalue}\"\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_setup_headers() {\n  # Set up API Headers for _get() and _post()\n  # The <function>_add or <function>_rm must have been called before to work\n\n  if [ -z \"$DnsServices_Username\" ] || [ -z \"$DnsServices_Password\" ]; then\n    _err \"Could not setup BASIC authentication headers, they are missing\"\n    return 1\n  fi\n\n  DnsServiceCredentials=\"$(printf \"%s\" \"$DnsServices_Username:$DnsServices_Password\" | _base64)\"\n  export _H1=\"Authorization: Basic $DnsServiceCredentials\"\n  export _H2=\"Content-Type: application/json\"\n\n  # Just return if headers are set\n  return 0\n}\n\n_get_root() {\n  domain=\"$1\"\n  _debug2 _get_root \"Get the root domain of ${domain} for DNS API\"\n\n  # Setup _get() and _post() headers\n  #_setup_headers\n\n  result=$(_H1=\"$_H1\" _H2=\"$_H2\" _get \"$DNSServices_API/dns\")\n  result2=\"$(printf \"%s\\n\" \"$result\" | tr '[' '\\n' | grep '\"name\"')\"\n  result3=\"$(printf \"%s\\n\" \"$result2\" | tr '}' '\\n' | grep '\"name\"' | sed \"s,^\\,,,g\" | sed \"s,$,},g\")\"\n  useResult=\"\"\n  _debug2 _get_root \"Got the following root domain(s) $result\"\n  _debug2 _get_root \"- JSON: $result\"\n\n  if [ \"$(printf \"%s\\n\" \"$result\" | tr '}' '\\n' | grep -c '\"name\"')\" -gt \"1\" ]; then\n    checkMultiZones=\"true\"\n    _debug2 _get_root \"- multiple zones found\"\n  else\n    checkMultiZones=\"false\"\n    _debug2 _get_root \"- single zone found\"\n  fi\n\n  # Find/isolate the root zone to work with in createRecord() and deleteRecord()\n  rootZone=\"\"\n  if [ \"$checkMultiZones\" = \"true\" ]; then\n    #rootZone=$(for x in $(printf \"%s\" \"${result3}\" | tr ',' '\\n' | sed -n 's/.*\"name\":\"\\(.*\\)\",.*/\\1/p'); do if [ \"$(echo \"$domain\" | grep \"$x\")\" != \"\" ]; then echo \"$x\"; fi; done)\n    rootZone=$(for x in $(printf \"%s\\n\" \"${result3}\" | tr ',' '\\n' | grep name | cut -d'\"' -f4); do if [ \"$(echo \"$domain\" | grep \"$x\")\" != \"\" ]; then echo \"$x\"; fi; done)\n    if [ \"$rootZone\" != \"\" ]; then\n      _debug2 _rootZone \"- root zone for $domain is $rootZone\"\n    else\n      _err \"Could not find root zone for $domain, is it correctly typed?\"\n      return 1\n    fi\n  else\n    rootZone=$(echo \"$result\" | tr '}' '\\n' | _egrep_o '\"name\":\"[^\"]*' | cut -d'\"' -f4)\n    _debug2 _get_root \"- only found 1 domain in API: $rootZone\"\n  fi\n\n  if [ -z \"$rootZone\" ]; then\n    _err \"Could not find root domain for $domain - is it correctly typed?\"\n    return 1\n  fi\n\n  # Make sure we use the correct API zone data\n  useResult=\"$(printf \"%s\\n\" \"${result3}\" tr ',' '\\n' | grep \"$rootZone\")\"\n  _debug2 _useResult \"useResult=$useResult\"\n\n  # Setup variables used by other functions to communicate with DNS.Services API\n  #zoneInfo=$(printf \"%s\\n\" \"$useResult\" | sed -E 's,.*(zones)(.*),\\1\\2,g' | sed -E 's,^(.*\"name\":\")([^\"]*)\"(.*)$,\\2,g')\n  zoneInfo=$(printf \"%s\\n\" \"$useResult\" | tr ',' '\\n' | grep '\"name\"' | cut -d'\"' -f4)\n  rootZoneName=\"$rootZone\"\n  subDomainName=\"$(printf \"%s\\n\" \"$domain\" | sed \"s,\\.$rootZone,,g\")\"\n  subDomainNameClean=\"$(printf \"%s\\n\" \"$domain\" | sed \"s,_acme-challenge.,,g\")\"\n  rootZoneDomainID=$(printf \"%s\\n\" \"$useResult\" | tr ',' '\\n' | grep domain_id | cut -d'\"' -f4)\n  rootZoneServiceID=$(printf \"%s\\n\" \"$useResult\" | tr ',' '\\n' | grep service_id | cut -d'\"' -f4)\n\n  _debug2 _zoneInfo \"Zone info from API  : $zoneInfo\"\n  _debug2 _get_root \"Root zone name      : $rootZoneName\"\n  _debug2 _get_root \"Root zone domain ID : $rootZoneDomainID\"\n  _debug2 _get_root \"Root zone service ID: $rootZoneServiceID\"\n  _debug2 _get_root \"Sub domain          : $subDomainName\"\n\n  _debug _get_root \"Found valid root domain $rootZone for $subDomainNameClean\"\n  return 0\n}\n\ncreateRecord() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  # Get root domain information - needed for DNS.Services API communication\n  if [ -z \"$rootZoneName\" ] || [ -z \"$rootZoneDomainID\" ] || [ -z \"$rootZoneServiceID\" ]; then\n    _get_root \"$fulldomain\"\n  fi\n  if [ -z \"$rootZoneName\" ] || [ -z \"$rootZoneDomainID\" ] || [ -z \"$rootZoneServiceID\" ]; then\n    _err \"Something happend - could not get the API zone information\"\n    return 1\n  fi\n\n  _debug2 createRecord \"CNAME TXT value is: $txtvalue\"\n\n  # Prepare data to send to API\n  data=\"{\\\"name\\\":\\\"${fulldomain}\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"${txtvalue}\\\", \\\"ttl\\\":\\\"10\\\"}\"\n\n  _debug2 createRecord \"data to API: $data\"\n  result=$(_post \"$data\" \"$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records\" \"\" \"POST\")\n  _debug2 createRecord \"result from API: $result\"\n\n  if [ \"$(echo \"$result\" | _egrep_o \"\\\"success\\\":true\")\" = \"\" ]; then\n    _err \"Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName\"\n    _err \"$result\"\n    return 1\n  fi\n\n  _info \"Record \\\"$fulldomain TXT $txtvalue\\\" has been created\"\n  return 0\n}\n\ndeleteRecord() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _log deleteRecord \"Deleting $fulldomain TXT $txtvalue record\"\n\n  if [ -z \"$rootZoneName\" ] || [ -z \"$rootZoneDomainID\" ] || [ -z \"$rootZoneServiceID\" ]; then\n    _get_root \"$fulldomain\"\n  fi\n\n  result=\"$(_H1=\"$_H1\" _H2=\"$_H2\" _get \"$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID\")\"\n  #recordInfo=\"$(echo \"$result\" | sed -e 's/:{/:{\\n/g' -e 's/},/\\n},\\n/g' | grep \"${txtvalue}\")\"\n  #recordID=\"$(echo \"$recordInfo\" | sed -e 's/:{/:{\\n/g' -e 's/},/\\n},\\n/g' | grep \"${txtvalue}\" | sed -E 's,.*(zones)(.*),\\1\\2,g' | sed -E 's,^(.*\"id\":\")([^\"]*)\"(.*)$,\\2,g')\"\n  recordID=\"$(printf \"%s\\n\" \"$result\" | tr '}' '\\n' | grep -- \"$txtvalue\" | tr ',' '\\n' | grep '\"id\"' | cut -d'\"' -f4)\"\n  _debug2 _recordID \"recordID used for deletion of record: $recordID\"\n\n  if [ -z \"$recordID\" ]; then\n    _info \"Record $fulldomain TXT $txtvalue not found or already deleted\"\n    return 0\n  else\n    _debug2 deleteRecord \"Found recordID=$recordID\"\n  fi\n\n  _debug2 deleteRecord \"DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID\"\n  _log \"curl DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID\"\n  result=\"$(_H1=\"$_H1\" _H2=\"$_H2\" _post \"\" \"$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID\" \"\" \"DELETE\")\"\n  _debug2 deleteRecord \"API Delete result \\\"$result\\\"\"\n  _log \"curl API Delete result \\\"$result\\\"\"\n\n  # Return OK regardless\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_doapi.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_doapi_info='Domain-Offensive do.de\n Official LetsEncrypt API for do.de / Domain-Offensive.\n This API is also available to private customers/individuals.\nSite: do.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_doapi\nOptions:\n DO_LETOKEN LetsEncrypt Token\nIssues: github.com/acmesh-official/acme.sh/issues/2057\n'\n\nDO_API=\"https://my.do.de/api/letsencrypt\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_doapi_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DO_LETOKEN=\"${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}\"\n  if [ -z \"$DO_LETOKEN\" ]; then\n    DO_LETOKEN=\"\"\n    _err \"You didn't configure a do.de API token yet.\"\n    _err \"Please set DO_LETOKEN and try again.\"\n    return 1\n  fi\n  _saveaccountconf_mutable DO_LETOKEN \"$DO_LETOKEN\"\n\n  _info \"Adding TXT record to ${fulldomain}\"\n  response=\"$(_get \"$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&value=${txtvalue}\")\"\n  if _contains \"${response}\" 'success'; then\n    return 0\n  fi\n  _err \"Could not create resource record, check logs\"\n  _err \"${response}\"\n  return 1\n}\n\ndns_doapi_rm() {\n  fulldomain=$1\n\n  DO_LETOKEN=\"${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}\"\n  if [ -z \"$DO_LETOKEN\" ]; then\n    DO_LETOKEN=\"\"\n    _err \"You didn't configure a do.de API token yet.\"\n    _err \"Please set DO_LETOKEN and try again.\"\n    return 1\n  fi\n  _saveaccountconf_mutable DO_LETOKEN \"$DO_LETOKEN\"\n\n  _info \"Deleting resource record $fulldomain\"\n  response=\"$(_get \"$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&action=delete\")\"\n  if _contains \"${response}\" 'success'; then\n    return 0\n  fi\n  _err \"Could not delete resource record, check logs\"\n  _err \"${response}\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_domeneshop.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_domeneshop_info='DomeneShop.no\nSite: DomeneShop.no\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_domeneshop\nOptions:\n DOMENESHOP_Token Token\n DOMENESHOP_Secret Secret\nIssues: github.com/acmesh-official/acme.sh/issues/2457\n'\n\nDOMENESHOP_Api_Endpoint=\"https://api.domeneshop.no/v0\"\n\n#####################  Public functions #####################\n\n# Usage: dns_domeneshop_add <full domain> <txt record>\n# Example: dns_domeneshop_add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_domeneshop_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  # Get token and secret\n  DOMENESHOP_Token=\"${DOMENESHOP_Token:-$(_readaccountconf_mutable DOMENESHOP_Token)}\"\n  DOMENESHOP_Secret=\"${DOMENESHOP_Secret:-$(_readaccountconf_mutable DOMENESHOP_Secret)}\"\n\n  if [ -z \"$DOMENESHOP_Token\" ] || [ -z \"$DOMENESHOP_Secret\" ]; then\n    DOMENESHOP_Token=\"\"\n    DOMENESHOP_Secret=\"\"\n    _err \"You need to spesify a Domeneshop/Domainnameshop API Token and Secret.\"\n    return 1\n  fi\n\n  # Save the api token and secret.\n  _saveaccountconf_mutable DOMENESHOP_Token \"$DOMENESHOP_Token\"\n  _saveaccountconf_mutable DOMENESHOP_Secret \"$DOMENESHOP_Secret\"\n\n  # Get the domain name id\n  if ! _get_domainid \"$fulldomain\"; then\n    _err \"Did not find domainname\"\n    return 1\n  fi\n\n  # Create record\n  _domeneshop_rest POST \"domains/$_domainid/dns\" \"{\\\"type\\\":\\\"TXT\\\",\\\"host\\\":\\\"$_sub_domain\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"\n}\n\n# Usage: dns_domeneshop_rm <full domain> <txt record>\n# Example: dns_domeneshop_rm _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_domeneshop_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  # Get token and secret\n  DOMENESHOP_Token=\"${DOMENESHOP_Token:-$(_readaccountconf_mutable DOMENESHOP_Token)}\"\n  DOMENESHOP_Secret=\"${DOMENESHOP_Secret:-$(_readaccountconf_mutable DOMENESHOP_Secret)}\"\n\n  if [ -z \"$DOMENESHOP_Token\" ] || [ -z \"$DOMENESHOP_Secret\" ]; then\n    DOMENESHOP_Token=\"\"\n    DOMENESHOP_Secret=\"\"\n    _err \"You need to spesify a Domeneshop/Domainnameshop API Token and Secret.\"\n    return 1\n  fi\n\n  # Get the domain name id\n  if ! _get_domainid \"$fulldomain\"; then\n    _err \"Did not find domainname\"\n    return 1\n  fi\n\n  # Find record\n  if ! _get_recordid \"$_domainid\" \"$_sub_domain\" \"$txtvalue\"; then\n    _err \"Did not find dns record\"\n    return 1\n  fi\n\n  # Remove record\n  _domeneshop_rest DELETE \"domains/$_domainid/dns/$_recordid\"\n}\n\n#####################  Private functions #####################\n\n_get_domainid() {\n  domain=$1\n\n  # Get domains\n  _domeneshop_rest GET \"domains\"\n\n  if ! _contains \"$response\" \"\\\"id\\\":\"; then\n    _err \"failed to get domain names\"\n    return 1\n  fi\n\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug \"h\" \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"$h\\\"\" >/dev/null; then\n      # We have found the domain name.\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      _domainid=$(printf \"%s\" \"$response\" | _egrep_o \"[^{]*\\\"domain\\\":\\\"$_domain\\\"[^}]*\" | _egrep_o \"\\\"id\\\":[0-9]+\" | cut -d : -f 2)\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_get_recordid() {\n  domainid=$1\n  subdomain=$2\n  txtvalue=$3\n\n  # Get all dns records for the domainname\n  _domeneshop_rest GET \"domains/$domainid/dns\"\n\n  if ! _contains \"$response\" \"\\\"id\\\":\"; then\n    _debug \"No records in dns\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"\\\"host\\\":\\\"$subdomain\\\"\"; then\n    _debug \"Record does not exist\"\n    return 1\n  fi\n\n  # Get the id of the record in question\n  _recordid=$(printf \"%s\" \"$response\" | _egrep_o \"[^{]*\\\"host\\\":\\\"$subdomain\\\"[^}]*\" | _egrep_o \"[^{]*\\\"data\\\":\\\"$txtvalue\\\"[^}]*\" | _egrep_o \"\\\"id\\\":[0-9]+\" | cut -d : -f 2)\n  if [ -z \"$_recordid\" ]; then\n    return 1\n  fi\n  return 0\n}\n\n_domeneshop_rest() {\n  method=$1\n  endpoint=$2\n  data=$3\n\n  credentials=$(printf \"%b\" \"$DOMENESHOP_Token:$DOMENESHOP_Secret\" | _base64)\n\n  export _H1=\"Authorization: Basic $credentials\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    response=\"$(_post \"$data\" \"$DOMENESHOP_Api_Endpoint/$endpoint\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$DOMENESHOP_Api_Endpoint/$endpoint\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $endpoint\"\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dp.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dp_info='DNSPod.cn\nSite: DNSPod.cn\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dp\nOptions:\n DP_Id Id\n DP_Key Key\n'\n\nREST_API=\"https://dnsapi.cn\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dp_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DP_Id=\"${DP_Id:-$(_readaccountconf_mutable DP_Id)}\"\n  DP_Key=\"${DP_Key:-$(_readaccountconf_mutable DP_Key)}\"\n  if [ -z \"$DP_Id\" ] || [ -z \"$DP_Key\" ]; then\n    DP_Id=\"\"\n    DP_Key=\"\"\n    _err \"You don't specify dnspod api key and key id yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable DP_Id \"$DP_Id\"\n  _saveaccountconf_mutable DP_Key \"$DP_Key\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  add_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n\n}\n\n#fulldomain txtvalue\ndns_dp_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DP_Id=\"${DP_Id:-$(_readaccountconf_mutable DP_Id)}\"\n  DP_Key=\"${DP_Key:-$(_readaccountconf_mutable DP_Key)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  if ! _rest POST \"Record.List\" \"login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain\"; then\n    _err \"Record.Lis error.\"\n    return 1\n  fi\n\n  if _contains \"$response\" 'No records'; then\n    _info \"Don't need to remove.\"\n    return 0\n  fi\n\n  record_id=$(echo \"$response\" | tr \"{\" \"\\n\" | grep -- \"$txtvalue\" | grep '^\"id\"' | cut -d : -f 2 | cut -d '\"' -f 2)\n  _debug record_id \"$record_id\"\n  if [ -z \"$record_id\" ]; then\n    _err \"Can not get record id.\"\n    return 1\n  fi\n\n  if ! _rest POST \"Record.Remove\" \"login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&record_id=$record_id\"; then\n    _err \"Record.Remove error.\"\n    return 1\n  fi\n\n  _contains \"$response\" \"successful\"\n\n}\n\n#add the txt record.\n#usage: root  sub  txtvalue\nadd_record() {\n  root=$1\n  sub=$2\n  txtvalue=$3\n  fulldomain=\"$sub.$root\"\n\n  _info \"Adding record\"\n\n  if ! _rest POST \"Record.Create\" \"login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=%E9%BB%98%E8%AE%A4\"; then\n    return 1\n  fi\n\n  _contains \"$response\" \"successful\" || _contains \"$response\" \"Domain record already exists\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _rest POST \"Domain.Info\" \"login_token=$DP_Id,$DP_Key&format=json&lang=en&domain=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"successful\"; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\n      _debug _domain_id \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _debug _sub_domain \"$_sub_domain\"\n        _domain=\"$h\"\n        _debug _domain \"$_domain\"\n        return 0\n      fi\n      return 1\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n#Usage: method  URI  data\n_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n  url=\"$REST_API/$ep\"\n\n  _debug url \"$url\"\n\n  if [ \"$m\" = \"GET\" ]; then\n    response=\"$(_get \"$url\" | tr -d '\\r')\"\n  else\n    _debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"$url\" | tr -d '\\r')\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dpi.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dpi_info='DNSPod.com\nSite: DNSPod.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dpi\nOptions:\n DPI_Id Id\n DPI_Key Key\n'\n\nREST_API=\"https://api.dnspod.com\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dpi_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DPI_Id=\"${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}\"\n  DPI_Key=\"${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}\"\n  if [ -z \"$DPI_Id\" ] || [ -z \"$DPI_Key\" ]; then\n    DPI_Id=\"\"\n    DPI_Key=\"\"\n    _err \"You don't specify dnspod api key and key id yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable DPI_Id \"$DPI_Id\"\n  _saveaccountconf_mutable DPI_Key \"$DPI_Key\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  add_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n\n}\n\n#fulldomain txtvalue\ndns_dpi_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DPI_Id=\"${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}\"\n  DPI_Key=\"${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  if ! _rest POST \"Record.List\" \"login_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain\"; then\n    _err \"Record.Lis error.\"\n    return 1\n  fi\n\n  if _contains \"$response\" 'No records'; then\n    _info \"Don't need to remove.\"\n    return 0\n  fi\n\n  record_id=$(echo \"$response\" | tr \"{\" \"\\n\" | grep -- \"$txtvalue\" | grep '^\"id\"' | cut -d : -f 2 | cut -d '\"' -f 2)\n  _debug record_id \"$record_id\"\n  if [ -z \"$record_id\" ]; then\n    _err \"Can not get record id.\"\n    return 1\n  fi\n\n  if ! _rest POST \"Record.Remove\" \"login_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&record_id=$record_id\"; then\n    _err \"Record.Remove error.\"\n    return 1\n  fi\n\n  _contains \"$response\" \"Operation successful\"\n\n}\n\n#add the txt record.\n#usage: root  sub  txtvalue\nadd_record() {\n  root=$1\n  sub=$2\n  txtvalue=$3\n  fulldomain=\"$sub.$root\"\n\n  _info \"Adding record\"\n\n  if ! _rest POST \"Record.Create\" \"login_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=default\"; then\n    return 1\n  fi\n\n  _contains \"$response\" \"Operation successful\" || _contains \"$response\" \"Domain record already exists\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _rest POST \"Domain.Info\" \"login_token=$DPI_Id,$DPI_Key&format=json&domain=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"Operation successful\"; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\n      _debug _domain_id \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _debug _sub_domain \"$_sub_domain\"\n        _domain=\"$h\"\n        _debug _domain \"$_domain\"\n        return 0\n      fi\n      return 1\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n#Usage: method  URI  data\n_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n  url=\"$REST_API/$ep\"\n\n  _debug url \"$url\"\n\n  if [ \"$m\" = \"GET\" ]; then\n    response=\"$(_get \"$url\" | tr -d '\\r')\"\n  else\n    _debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"$url\" | tr -d '\\r')\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dreamhost.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dreamhost_info='DreamHost.com\nSite: DreamHost.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dreamhost\nOptions:\n DH_API_KEY API Key\nIssues: github.com/RhinoLance/acme.sh\nAuthor: RhinoLance\n'\n\nDH_API_ENDPOINT=\"https://api.dreamhost.com/\"\nquerystring=\"\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dreamhost_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! validate \"$fulldomain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n  querystring=\"key=$DH_API_KEY&cmd=dns-add_record&record=$fulldomain&type=TXT&value=$txtvalue\"\n  if ! submit \"$querystring\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_dreamhost_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! validate \"$fulldomain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n  querystring=\"key=$DH_API_KEY&cmd=dns-remove_record&record=$fulldomain&type=TXT&value=$txtvalue\"\n  if ! submit \"$querystring\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n#send the command to the api endpoint.\nsubmit() {\n  querystring=$1\n\n  url=\"$DH_API_ENDPOINT?$querystring\"\n\n  _debug url \"$url\"\n\n  if ! response=\"$(_get \"$url\")\"; then\n    _err \"Error <$1>\"\n    return 1\n  fi\n\n  if [ -z \"$2\" ]; then\n    message=\"$(echo \"$response\" | _egrep_o \"\\\"Message\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\"\n    if [ -n \"$message\" ]; then\n      _err \"$message\"\n      return 1\n    fi\n  fi\n\n  _debug response \"$response\"\n\n  return 0\n}\n\n#check that we have a valid API Key\nvalidate() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using dreamhost\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  #retrieve the API key from the environment variable if it exists, otherwise look for a saved key.\n  DH_API_KEY=\"${DH_API_KEY:-$(_readaccountconf_mutable DH_API_KEY)}\"\n\n  if [ -z \"$DH_API_KEY\" ]; then\n    DH_API_KEY=\"\"\n    _err \"You didn't specify the DreamHost api key yet (export DH_API_KEY=\\\"<api key>\\\")\"\n    _err \"Please login to your control panel, create a key and try again.\"\n    return 1\n  fi\n\n  #save the api key to the account conf file.\n  _saveaccountconf_mutable DH_API_KEY \"$DH_API_KEY\"\n}\n"
  },
  {
    "path": "dnsapi/dns_duckdns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_duckdns_info='DuckDNS.org\nSite: www.DuckDNS.org\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns\nOptions:\n DuckDNS_Token API Token\nAuthor: @RaidenII\n'\n\nDuckDNS_API=\"https://www.duckdns.org/update\"\n\n########  Public functions ######################\n\n#Usage: dns_duckdns_add _acme-challenge.domain.duckdns.org \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_duckdns_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DuckDNS_Token=\"${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}\"\n  if [ -z \"$DuckDNS_Token\" ]; then\n    _err \"You must export variable: DuckDNS_Token\"\n    _err \"The token for your DuckDNS account is necessary.\"\n    _err \"You can look it up in your DuckDNS account.\"\n    return 1\n  fi\n\n  # Now save the credentials.\n  _saveaccountconf_mutable DuckDNS_Token \"$DuckDNS_Token\"\n\n  # Unfortunately, DuckDNS does not seems to support lookup domain through API\n  # So I assume your credentials (which are your domain and token) are correct\n  # If something goes wrong, we will get a KO response from DuckDNS\n\n  if ! _duckdns_get_domain; then\n    return 1\n  fi\n\n  # Now add the TXT record to DuckDNS\n  _info \"Trying to add TXT record\"\n  if _duckdns_rest GET \"domains=$_duckdns_domain&token=$DuckDNS_Token&txt=$txtvalue\"; then\n    if [ \"$response\" = \"OK\" ]; then\n      _info \"TXT record has been successfully added to your DuckDNS domain.\"\n      _info \"Note that all subdomains under this domain uses the same TXT record.\"\n      return 0\n    else\n      _err \"Errors happened during adding the TXT record, response=$response\"\n      return 1\n    fi\n  else\n    _err \"Errors happened during adding the TXT record.\"\n    return 1\n  fi\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_duckdns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DuckDNS_Token=\"${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}\"\n  if [ -z \"$DuckDNS_Token\" ]; then\n    _err \"You must export variable: DuckDNS_Token\"\n    _err \"The token for your DuckDNS account is necessary.\"\n    _err \"You can look it up in your DuckDNS account.\"\n    return 1\n  fi\n\n  if ! _duckdns_get_domain; then\n    return 1\n  fi\n\n  # Now remove the TXT record from DuckDNS\n  _info \"Trying to remove TXT record\"\n  if _duckdns_rest GET \"domains=$_duckdns_domain&token=$DuckDNS_Token&txt=&clear=true\"; then\n    if [ \"$response\" = \"OK\" ]; then\n      _info \"TXT record has been successfully removed from your DuckDNS domain.\"\n      return 0\n    else\n      _err \"Errors happened during removing the TXT record, response=$response\"\n      return 1\n    fi\n  else\n    _err \"Errors happened during removing the TXT record.\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n# fulldomain may be 'domain.duckdns.org' (if using --domain-alias) or '_acme-challenge.domain.duckdns.org'\n# either way, return 'domain'. (duckdns does not allow further subdomains and restricts domains to [a-z0-9-].)\n_duckdns_get_domain() {\n\n  # We'll extract the domain/username from full domain\n  _duckdns_domain=\"$(printf \"%s\" \"$fulldomain\" | _lower_case | _egrep_o '^(_acme-challenge\\.)?([a-z0-9-]+\\.)+duckdns\\.org' | sed -n 's/^\\([^.]\\{1,\\}\\.\\)*\\([a-z0-9-]\\{1,\\}\\)\\.duckdns\\.org$/\\2/p;')\"\n\n  if [ -z \"$_duckdns_domain\" ]; then\n    _err \"Error extracting the domain.\"\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: method URI\n_duckdns_rest() {\n  method=$1\n  param=\"$2\"\n  _debug param \"$param\"\n  url=\"$DuckDNS_API?$param\"\n  if [ -n \"$DEBUG\" ] && [ \"$DEBUG\" -gt 0 ]; then\n    url=\"$url&verbose=true\"\n  fi\n  _debug url \"$url\"\n\n  # DuckDNS uses GET to update domain info\n  if [ \"$method\" = \"GET\" ]; then\n    response=\"$(_get \"$url\")\"\n    _debug2 response \"$response\"\n    if [ -n \"$DEBUG\" ] && [ \"$DEBUG\" -gt 0 ] && _contains \"$response\" \"UPDATED\" && _contains \"$response\" \"OK\"; then\n      response=\"OK\"\n    fi\n  else\n    _err \"Unsupported method\"\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_durabledns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_durabledns_info='DurableDNS.com\nSite: DurableDNS.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_durabledns\nOptions:\n DD_API_User API User\n DD_API_Key API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2281\n'\n\n_DD_BASE=\"https://durabledns.com/services/dns\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_durabledns_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DD_API_User=\"${DD_API_User:-$(_readaccountconf_mutable DD_API_User)}\"\n  DD_API_Key=\"${DD_API_Key:-$(_readaccountconf_mutable DD_API_Key)}\"\n  if [ -z \"$DD_API_User\" ] || [ -z \"$DD_API_Key\" ]; then\n    DD_API_User=\"\"\n    DD_API_Key=\"\"\n    _err \"You didn't specify a durabledns api user or key yet.\"\n    _err \"You can get yours from here https://durabledns.com/dashboard/index.php\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable DD_API_User \"$DD_API_User\"\n  _saveaccountconf_mutable DD_API_Key \"$DD_API_Key\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _dd_soap createRecord string zonename \"$_domain.\" string name \"$_sub_domain\" string type \"TXT\" string data \"$txtvalue\" int aux 0 int ttl 10 string ddns_enabled N\n  _contains \"$response\" \"createRecordResponse\"\n}\n\ndns_durabledns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  DD_API_User=\"${DD_API_User:-$(_readaccountconf_mutable DD_API_User)}\"\n  DD_API_Key=\"${DD_API_Key:-$(_readaccountconf_mutable DD_API_Key)}\"\n  if [ -z \"$DD_API_User\" ] || [ -z \"$DD_API_Key\" ]; then\n    DD_API_User=\"\"\n    DD_API_Key=\"\"\n    _err \"You didn't specify a durabledns api user or key yet.\"\n    _err \"You can get yours from here https://durabledns.com/dashboard/index.php\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Find record id\"\n  if ! _dd_soap listRecords string zonename \"$_domain.\"; then\n    _err \"can not listRecords\"\n    return 1\n  fi\n\n  subtxt=\"$(echo \"$txtvalue\" | cut -c 1-30)\"\n  record=\"$(echo \"$response\" | sed 's/<item\\>/#<item>/g' | tr '#' '\\n' | grep \">$subtxt\")\"\n  _debug record \"$record\"\n  if [ -z \"$record\" ]; then\n    _err \"can not find record for txtvalue\" \"$txtvalue\"\n    _err \"$response\"\n    return 1\n  fi\n\n  recordid=\"$(echo \"$record\" | _egrep_o '<id xsi:type=\"xsd:int\">[0-9]*</id>' | cut -d '>' -f 2 | cut -d '<' -f 1)\"\n  _debug recordid \"$recordid\"\n  if [ -z \"$recordid\" ]; then\n    _err \"can not find record id\"\n    return 1\n  fi\n\n  if ! _dd_soap deleteRecord string zonename \"$_domain.\" int id \"$recordid\"; then\n    _err \"delete error\"\n    return 1\n  fi\n\n  _contains \"$response\" \"Success\"\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  if ! _dd_soap \"listZones\"; then\n    return 1\n  fi\n\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \">$h.</origin>\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n\n}\n\n#method\n_dd_soap() {\n  _method=\"$1\"\n  shift\n  _urn=\"${_method}wsdl\"\n  # put the parameters to xml\n  body=\"<tns:$_method>\n      <apiuser xsi:type=\\\"xsd:string\\\">$DD_API_User</apiuser>\n      <apikey xsi:type=\\\"xsd:string\\\">$DD_API_Key</apikey>\n    \"\n  while [ \"$1\" ]; do\n    _t=\"$1\"\n    shift\n    _k=\"$1\"\n    shift\n    _v=\"$1\"\n    shift\n    body=\"$body<$_k xsi:type=\\\"xsd:$_t\\\">$_v</$_k>\"\n  done\n  body=\"$body</tns:$_method>\"\n  _debug2 \"SOAP request ${body}\"\n\n  # build SOAP XML\n  _xml='<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"\nxmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"\nxmlns:tns=\"urn:'$_urn'\"\nxmlns:types=\"urn:'$_urn'/encodedTypes\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n  <soap:Body soap:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">'\"$body\"'</soap:Body>\n</soap:Envelope>'\n\n  _debug2 _xml \"$_xml\"\n  # set SOAP headers\n  _action=\"SOAPAction: \\\"urn:$_urn#$_method\\\"\"\n  _debug2 \"_action\" \"$_action\"\n  export _H1=\"$_action\"\n  export _H2=\"Content-Type: text/xml; charset=utf-8\"\n\n  _url=\"$_DD_BASE/$_method.php\"\n  _debug \"_url\" \"$_url\"\n  if ! response=\"$(_post \"${_xml}\" \"${_url}\")\"; then\n    _err \"Error <$1>\"\n    return 1\n  fi\n  _debug2 \"response\" \"$response\"\n  response=\"$(echo \"$response\" | tr -d \"\\r\\n\" | _egrep_o \":${_method}Response .*:${_method}Response><\")\"\n  _debug2 \"response\" \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dyn.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dyn_info='Dyn.com\nDomains: dynect.net\nSite: Dyn.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dyn\nOptions:\n DYN_Customer Customer\n DYN_Username API Username\n DYN_Password Secret\nAuthor: Gerd Naschenweng <@magicdude4eva>\n'\n\n# Dyn Managed DNS API\n# https://help.dyn.com/dns-api-knowledge-base/\n#\n# It is recommended to add a \"Dyn Managed DNS\" user specific for API access.\n# The \"Zones & Records Permissions\" required by this script are:\n# --\n# RecordAdd\n# RecordUpdate\n# RecordDelete\n# RecordGet\n# ZoneGet\n# ZoneAddNode\n# ZoneRemoveNode\n# ZonePublish\n# --\n\nDYN_API=\"https://api.dynect.net/REST\"\n\n#REST_API\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"Challenge-code\"\ndns_dyn_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  DYN_Customer=\"${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}\"\n  DYN_Username=\"${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}\"\n  DYN_Password=\"${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}\"\n  if [ -z \"$DYN_Customer\" ] || [ -z \"$DYN_Username\" ] || [ -z \"$DYN_Password\" ]; then\n    DYN_Customer=\"\"\n    DYN_Username=\"\"\n    DYN_Password=\"\"\n    _err \"You must export variables: DYN_Customer, DYN_Username and DYN_Password\"\n    return 1\n  fi\n\n  #save the config variables to the account conf file.\n  _saveaccountconf_mutable DYN_Customer \"$DYN_Customer\"\n  _saveaccountconf_mutable DYN_Username \"$DYN_Username\"\n  _saveaccountconf_mutable DYN_Password \"$DYN_Password\"\n\n  if ! _dyn_get_authtoken; then\n    return 1\n  fi\n\n  if [ -z \"$_dyn_authtoken\" ]; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_get_zone; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_add_record; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_publish_zone; then\n    _dyn_end_session\n    return 1\n  fi\n\n  _dyn_end_session\n\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_dyn_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  DYN_Customer=\"${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}\"\n  DYN_Username=\"${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}\"\n  DYN_Password=\"${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}\"\n  if [ -z \"$DYN_Customer\" ] || [ -z \"$DYN_Username\" ] || [ -z \"$DYN_Password\" ]; then\n    DYN_Customer=\"\"\n    DYN_Username=\"\"\n    DYN_Password=\"\"\n    _err \"You must export variables: DYN_Customer, DYN_Username and DYN_Password\"\n    return 1\n  fi\n\n  if ! _dyn_get_authtoken; then\n    return 1\n  fi\n\n  if [ -z \"$_dyn_authtoken\" ]; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_get_zone; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_get_record_id; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if [ -z \"$_dyn_record_id\" ]; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_rm_record; then\n    _dyn_end_session\n    return 1\n  fi\n\n  if ! _dyn_publish_zone; then\n    _dyn_end_session\n    return 1\n  fi\n\n  _dyn_end_session\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n#get Auth-Token\n_dyn_get_authtoken() {\n\n  _info \"Start Dyn API Session\"\n\n  data=\"{\\\"customer_name\\\":\\\"$DYN_Customer\\\", \\\"user_name\\\":\\\"$DYN_Username\\\", \\\"password\\\":\\\"$DYN_Password\\\"}\"\n  dyn_url=\"$DYN_API/Session/\"\n  method=\"POST\"\n\n  _debug data \"$data\"\n  _debug dyn_url \"$dyn_url\"\n\n  export _H1=\"Content-Type: application/json\"\n\n  response=\"$(_post \"$data\" \"$dyn_url\" \"\" \"$method\")\"\n  sessionstatus=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"status\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"status\" *: *\"##')\"\n\n  _debug response \"$response\"\n  _debug sessionstatus \"$sessionstatus\"\n\n  if [ \"$sessionstatus\" = \"success\" ]; then\n    _dyn_authtoken=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"token\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"token\" *: *\"##')\"\n    _info \"Token received\"\n    _debug _dyn_authtoken \"$_dyn_authtoken\"\n    return 0\n  fi\n\n  _dyn_authtoken=\"\"\n  _err \"get token failed\"\n  return 1\n}\n\n#fulldomain=_acme-challenge.www.domain.com\n#returns\n# _dyn_zone=domain.com\n_dyn_get_zone() {\n  i=2\n  while true; do\n    domain=\"$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i-100\")\"\n    if [ -z \"$domain\" ]; then\n      break\n    fi\n\n    dyn_url=\"$DYN_API/Zone/$domain/\"\n\n    export _H1=\"Auth-Token: $_dyn_authtoken\"\n    export _H2=\"Content-Type: application/json\"\n\n    response=\"$(_get \"$dyn_url\" \"\" \"\")\"\n    sessionstatus=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"status\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"status\" *: *\"##')\"\n\n    _debug dyn_url \"$dyn_url\"\n    _debug response \"$response\"\n    _debug sessionstatus \"$sessionstatus\"\n\n    if [ \"$sessionstatus\" = \"success\" ]; then\n      _dyn_zone=\"$domain\"\n      return 0\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n\n  _dyn_zone=\"\"\n  _err \"get zone failed\"\n  return 1\n}\n\n#add TXT record\n_dyn_add_record() {\n\n  _info \"Adding TXT record\"\n\n  data=\"{\\\"rdata\\\":{\\\"txtdata\\\":\\\"$txtvalue\\\"},\\\"ttl\\\":\\\"300\\\"}\"\n  dyn_url=\"$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/\"\n  method=\"POST\"\n\n  export _H1=\"Auth-Token: $_dyn_authtoken\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_post \"$data\" \"$dyn_url\" \"\" \"$method\")\"\n  sessionstatus=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"status\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"status\" *: *\"##')\"\n\n  _debug response \"$response\"\n  _debug sessionstatus \"$sessionstatus\"\n\n  if [ \"$sessionstatus\" = \"success\" ]; then\n    _info \"TXT Record successfully added\"\n    return 0\n  fi\n\n  _err \"add TXT record failed\"\n  return 1\n}\n\n#publish the zone\n_dyn_publish_zone() {\n\n  _info \"Publishing zone\"\n\n  data=\"{\\\"publish\\\":\\\"true\\\"}\"\n  dyn_url=\"$DYN_API/Zone/$_dyn_zone/\"\n  method=\"PUT\"\n\n  export _H1=\"Auth-Token: $_dyn_authtoken\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_post \"$data\" \"$dyn_url\" \"\" \"$method\")\"\n  sessionstatus=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"status\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"status\" *: *\"##')\"\n\n  _debug response \"$response\"\n  _debug sessionstatus \"$sessionstatus\"\n\n  if [ \"$sessionstatus\" = \"success\" ]; then\n    _info \"Zone published\"\n    return 0\n  fi\n\n  _err \"publish zone failed\"\n  return 1\n}\n\n#get record_id of TXT record so we can delete the record\n_dyn_get_record_id() {\n\n  _info \"Getting record_id of TXT record\"\n\n  dyn_url=\"$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/\"\n\n  export _H1=\"Auth-Token: $_dyn_authtoken\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_get \"$dyn_url\" \"\" \"\")\"\n  sessionstatus=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"status\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"status\" *: *\"##')\"\n\n  _debug response \"$response\"\n  _debug sessionstatus \"$sessionstatus\"\n\n  if [ \"$sessionstatus\" = \"success\" ]; then\n    _dyn_record_id=\"$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"data\\\" *: *\\[\\\"/REST/TXTRecord/$_dyn_zone/$fulldomain/[^\\\"]*\" | _head_n 1 | sed \"s#^\\\"data\\\" *: *\\[\\\"/REST/TXTRecord/$_dyn_zone/$fulldomain/##\")\"\n    _debug _dyn_record_id \"$_dyn_record_id\"\n    return 0\n  fi\n\n  _dyn_record_id=\"\"\n  _err \"getting record_id failed\"\n  return 1\n}\n\n#delete TXT record\n_dyn_rm_record() {\n\n  _info \"Deleting TXT record\"\n\n  dyn_url=\"$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/$_dyn_record_id/\"\n  method=\"DELETE\"\n\n  _debug dyn_url \"$dyn_url\"\n\n  export _H1=\"Auth-Token: $_dyn_authtoken\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_post \"\" \"$dyn_url\" \"\" \"$method\")\"\n  sessionstatus=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"status\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"status\" *: *\"##')\"\n\n  _debug response \"$response\"\n  _debug sessionstatus \"$sessionstatus\"\n\n  if [ \"$sessionstatus\" = \"success\" ]; then\n    _info \"TXT record successfully deleted\"\n    return 0\n  fi\n\n  _err \"delete TXT record failed\"\n  return 1\n}\n\n#logout\n_dyn_end_session() {\n\n  _info \"End Dyn API Session\"\n\n  dyn_url=\"$DYN_API/Session/\"\n  method=\"DELETE\"\n\n  _debug dyn_url \"$dyn_url\"\n\n  export _H1=\"Auth-Token: $_dyn_authtoken\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_post \"\" \"$dyn_url\" \"\" \"$method\")\"\n\n  _debug response \"$response\"\n\n  _dyn_authtoken=\"\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dynu.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dynu_info='Dynu.com\nSite: Dynu.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dynu\nOptions:\n Dynu_ClientId Client ID\n Dynu_Secret Secret\nIssues: github.com/shar0119/acme.sh\nAuthor: Dynu Systems Inc\n'\n\n#Token\nDynu_Token=\"\"\n#\n#Endpoint\nDynu_EndPoint=\"https://api.dynu.com/v2\"\n\n########  Public functions #####################\n\n#Usage: add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dynu_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$Dynu_ClientId\" ] || [ -z \"$Dynu_Secret\" ]; then\n    Dynu_ClientId=\"\"\n    Dynu_Secret=\"\"\n    _err \"Dynu client id and secret is not specified.\"\n    _err \"Please create you API client id and secret and try again.\"\n    return 1\n  fi\n\n  #save the client id and secret to the account conf file.\n  _saveaccountconf Dynu_ClientId \"$Dynu_ClientId\"\n  _saveaccountconf Dynu_Secret \"$Dynu_Secret\"\n\n  if [ -z \"$Dynu_Token\" ]; then\n    _info \"Getting Dynu token.\"\n    if ! _dynu_authentication; then\n      _err \"Can not get token.\"\n    fi\n  fi\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain.\"\n    return 1\n  fi\n\n  _debug _node \"$_node\"\n  _debug _domain_name \"$_domain_name\"\n\n  _info \"Creating TXT record.\"\n  if ! _dynu_rest POST \"dns/$dnsId/record\" \"{\\\"domainId\\\":\\\"$dnsId\\\",\\\"nodeName\\\":\\\"$_node\\\",\\\"recordType\\\":\\\"TXT\\\",\\\"textData\\\":\\\"$txtvalue\\\",\\\"state\\\":true,\\\"ttl\\\":90}\"; then\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"200\"; then\n    _err \"Could not add TXT record.\"\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: rm _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dynu_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$Dynu_ClientId\" ] || [ -z \"$Dynu_Secret\" ]; then\n    Dynu_ClientId=\"\"\n    Dynu_Secret=\"\"\n    _err \"Dynu client id and secret is not specified.\"\n    _err \"Please create you API client id and secret and try again.\"\n    return 1\n  fi\n\n  #save the client id and secret to the account conf file.\n  _saveaccountconf Dynu_ClientId \"$Dynu_ClientId\"\n  _saveaccountconf Dynu_Secret \"$Dynu_Secret\"\n\n  if [ -z \"$Dynu_Token\" ]; then\n    _info \"Getting Dynu token.\"\n    if ! _dynu_authentication; then\n      _err \"Can not get token.\"\n    fi\n  fi\n\n  _debug \"Detect root zone.\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain.\"\n    return 1\n  fi\n\n  _debug _node \"$_node\"\n  _debug _domain_name \"$_domain_name\"\n\n  _info \"Checking for TXT record.\"\n  if ! _get_recordid \"$fulldomain\" \"$txtvalue\"; then\n    _err \"Could not get TXT record id.\"\n    return 1\n  fi\n\n  if [ \"$_dns_record_id\" = \"\" ]; then\n    _err \"TXT record not found.\"\n    return 1\n  fi\n\n  _info \"Removing TXT record.\"\n  if ! _delete_txt_record \"$_dns_record_id\"; then\n    _err \"Could not remove TXT record $_dns_record_id.\"\n  fi\n\n  return 0\n}\n\n########  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _node=_acme-challenge.www\n# _domain_name=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _dynu_rest GET \"dns/getroot/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"domainName\\\":\\\"$h\\\"\" >/dev/null; then\n      dnsId=$(printf \"%s\" \"$response\" | tr -d \"{}\" | cut -d , -f 2 | cut -d : -f 2)\n      _domain_name=$h\n      _node=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n\n}\n\n_get_recordid() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _dynu_rest GET \"dns/$dnsId/record\"; then\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _dns_record_id=0\n    return 0\n  fi\n\n  _dns_record_id=$(printf \"%s\" \"$response\" | sed -e 's/[^{]*\\({[^}]*}\\)[^{]*/\\1\\n/g' | grep \"\\\"textData\\\":\\\"$txtvalue\\\"\" | sed -e 's/.*\"id\":\\([^,]*\\).*/\\1/')\n  return 0\n}\n\n_delete_txt_record() {\n  _dns_record_id=$1\n\n  if ! _dynu_rest DELETE \"dns/$dnsId/record/$_dns_record_id\"; then\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"200\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n_dynu_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Authorization: Bearer $Dynu_Token\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$data\" ] || [ \"$m\" = \"DELETE\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$Dynu_EndPoint/$ep\" \"\" \"$m\")\"\n  else\n    _info \"Getting $Dynu_EndPoint/$ep\"\n    response=\"$(_get \"$Dynu_EndPoint/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_dynu_authentication() {\n  realm=\"$(printf \"%s\" \"$Dynu_ClientId:$Dynu_Secret\" | _base64)\"\n\n  export _H1=\"Authorization: Basic $realm\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_get \"$Dynu_EndPoint/oauth2/token\")\"\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Authentication failed.\"\n    return 1\n  fi\n  if _contains \"$response\" \"Authentication Exception\"; then\n    _err \"Authentication failed.\"\n    return 1\n  fi\n  if _contains \"$response\" \"access_token\"; then\n    Dynu_Token=$(printf \"%s\" \"$response\" | tr -d \"{}\" | cut -d , -f 1 | cut -d : -f 2 | cut -d '\"' -f 2)\n  fi\n  if _contains \"$Dynu_Token\" \"null\"; then\n    Dynu_Token=\"\"\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_dynv6.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_dynv6_info='DynV6.com\nSite: DynV6.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dynv6\nOptions:\n DYNV6_TOKEN REST API token. Get from https://DynV6.com/keys\nOptionsAlt:\n KEY Path to SSH private key file. E.g. \"/root/.ssh/dynv6\"\nIssues: github.com/acmesh-official/acme.sh/issues/2702\nAuthor: @StefanAbl\n'\n\ndynv6_api=\"https://dynv6.com/api/v2\"\n########  Public functions #####################\n# Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide\n#Usage: dns_dynv6_add  _acme-challenge.www.domain.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_dynv6_add() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=\"$2\"\n  _info \"Using dynv6 api\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _get_authentication\n  if [ \"$dynv6_token\" ]; then\n    _dns_dynv6_add_http\n    return $?\n  else\n    _info \"using key file $dynv6_keyfile\"\n    _your_hosts=\"$(ssh -i \"$dynv6_keyfile\" api@dynv6.com hosts)\"\n    if ! _get_domain \"$fulldomain\" \"$_your_hosts\"; then\n      _err \"Host not found on your account\"\n      return 1\n    fi\n    _debug \"found host on your account\"\n    returnval=\"$(ssh -i \"$dynv6_keyfile\" api@dynv6.com hosts \\\"\"$_host\"\\\" records set \\\"\"$_record\"\\\" txt data \\\"\"$txtvalue\"\\\")\"\n    _debug \"Dynv6 returned this after record was added: $returnval\"\n    if _contains \"$returnval\" \"created\"; then\n      return 0\n    elif _contains \"$returnval\" \"updated\"; then\n      return 0\n    else\n      _err \"Something went wrong! it does not seem like the record was added successfully\"\n      return 1\n    fi\n  fi\n\n}\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_dynv6_rm() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=\"$2\"\n  _info \"Using dynv6 API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _get_authentication\n  if [ \"$dynv6_token\" ]; then\n    _dns_dynv6_rm_http\n    return $?\n  else\n    _info \"using key file $dynv6_keyfile\"\n    _your_hosts=\"$(ssh -i \"$dynv6_keyfile\" api@dynv6.com hosts)\"\n    if ! _get_domain \"$fulldomain\" \"$_your_hosts\"; then\n      _err \"Host not found on your account\"\n      return 1\n    fi\n    _debug \"found host on your account\"\n    _info \"$(ssh -i \"$dynv6_keyfile\" api@dynv6.com hosts \"\\\"$_host\\\"\" records del \"\\\"$_record\\\"\" txt)\"\n    return 0\n  fi\n}\n#################### Private functions below ##################################\n#Usage: No Input required\n#returns\n#dynv6_keyfile the path to the new key file that has been generated\n_generate_new_key() {\n  dynv6_keyfile=\"$(eval echo ~\"$USER\")/.ssh/dynv6\"\n  _info \"Path to key file used: $dynv6_keyfile\"\n  if [ ! -f \"$dynv6_keyfile\" ] && [ ! -f \"$dynv6_keyfile.pub\" ]; then\n    _debug \"generating key in $dynv6_keyfile and $dynv6_keyfile.pub\"\n    ssh-keygen -f \"$dynv6_keyfile\" -t ssh-ed25519 -N ''\n  else\n    _err \"There is already a file in $dynv6_keyfile or $dynv6_keyfile.pub\"\n    return 1\n  fi\n}\n\n#Usage: _acme-challenge.www.example.dynv6.net \"$_your_hosts\"\n#where _your_hosts is the output of ssh -i ~/.ssh/dynv6.pub api@dynv6.com hosts\n#returns\n#_host= example.dynv6.net\n#_record=_acme-challenge.www\n#aborts if not a valid domain\n_get_domain() {\n  #_your_hosts=\"$(ssh -i ~/.ssh/dynv6.pub api@dynv6.com hosts)\"\n  _full_domain=\"$1\"\n  _your_hosts=\"$2\"\n\n  _your_hosts=\"$(echo \"$_your_hosts\" | awk '/\\./ {print $1}')\"\n  for l in $_your_hosts; do\n    #echo \"host: $l\"\n    if test \"${_full_domain#*\"$l\"}\" != \"$_full_domain\"; then\n      _record=${_full_domain%.\"$l\"}\n      _host=$l\n      _debug \"The host is $_host and the record $_record\"\n      return 0\n    fi\n  done\n  _err \"Either there is no such host on your dynv6 account, or it cannot be accessed with this key\"\n  return 1\n}\n\n# Usage: No input required\n#returns\n#dynv6_keyfile path to the key that will be used\n_get_authentication() {\n  dynv6_token=\"${DYNV6_TOKEN:-$(_readaccountconf_mutable dynv6_token)}\"\n  if [ \"$dynv6_token\" ]; then\n    _debug \"Found HTTP Token. Going to use the HTTP API and not the SSH API\"\n    if [ \"$DYNV6_TOKEN\" ]; then\n      _saveaccountconf_mutable dynv6_token \"$dynv6_token\"\n    fi\n  else\n    _debug \"no HTTP token found. Looking for an SSH key\"\n    dynv6_keyfile=\"${dynv6_keyfile:-$(_readaccountconf_mutable dynv6_keyfile)}\"\n    _debug \"Your key is $dynv6_keyfile\"\n    if [ -z \"$dynv6_keyfile\" ]; then\n      if [ -z \"$KEY\" ]; then\n        _err \"You did not specify a key to use with dynv6\"\n        _info \"Creating new dynv6 API key to add to dynv6.com\"\n        _generate_new_key\n        _info \"Please add this key to dynv6.com $(cat \"$dynv6_keyfile.pub\")\"\n        _info \"Hit Enter to continue\"\n        read -r _\n        #save the credentials to the account conf file.\n      else\n        dynv6_keyfile=\"$KEY\"\n      fi\n      _saveaccountconf_mutable dynv6_keyfile \"$dynv6_keyfile\"\n    fi\n  fi\n}\n\n_dns_dynv6_add_http() {\n  _debug \"Got HTTP token form _get_authentication method. Going to use the HTTP API\"\n  if ! _get_zone_id \"$fulldomain\"; then\n    _err \"Could not find a matching zone for $fulldomain. Maybe your HTTP Token is not authorized to access the zone\"\n    return 1\n  fi\n  _get_zone_name \"$_zone_id\"\n  record=${fulldomain%%.\"$_zone_name\"}\n  _set_record TXT \"$record\" \"$txtvalue\"\n  if _contains \"$response\" \"$txtvalue\"; then\n    _info \"Successfully added record\"\n    return 0\n  else\n    _err \"Something went wrong while adding the record\"\n    return 1\n  fi\n}\n\n_dns_dynv6_rm_http() {\n  _debug \"Got HTTP token form _get_authentication method. Going to use the HTTP API\"\n  if ! _get_zone_id \"$fulldomain\"; then\n    _err \"Could not find a matching zone for $fulldomain. Maybe your HTTP Token is not authorized to access the zone\"\n    return 1\n  fi\n  _get_zone_name \"$_zone_id\"\n  record=${fulldomain%%.\"$_zone_name\"}\n  _get_record_id \"$_zone_id\" \"$record\" \"$txtvalue\"\n  _del_record \"$_zone_id\" \"$_record_id\"\n  if [ -z \"$response\" ]; then\n    _info \"Successfully deleted record\"\n    return 0\n  else\n    _err \"Something went wrong while deleting the record\"\n    return 1\n  fi\n}\n\n#Usage: _get_zone_id $record\n#get the zoneid for a specifc record or zone\n#where $record is the record to get the id for\n#returns _zone_id the id of the zone\n_get_zone_id() {\n  record=\"$1\"\n  _debug \"getting zone id for $record\"\n  _dynv6_rest GET zones\n\n  zones=\"$(echo \"$response\" | tr '}' '\\n' | tr ',' '\\n' | grep name | sed 's/\\[//g' | tr -d '{' | tr -d '\"')\"\n\n  selected=\"\"\n  for z in $zones; do\n    z=\"${z#name:}\"\n    _debug zone: \"$z\"\n    if _contains \"$record\" \"$z\"; then\n      _debug \"$z found in $record\"\n      selected=\"$z\"\n    fi\n  done\n  if [ -z \"$selected\" ]; then\n    _err \"no zone found\"\n    return 1\n  fi\n\n  zone_id=\"$(echo \"$response\" | tr '}' '\\n' | grep \"$selected\" | tr ',' '\\n' | grep '\"id\":' | tr -d '\"')\"\n  _zone_id=\"${zone_id#id:}\"\n  _debug \"zone id: $_zone_id\"\n}\n\n_get_zone_name() {\n  _zone_id=\"$1\"\n  _dynv6_rest GET zones/\"$_zone_id\"\n  _zone_name=\"$(echo \"$response\" | tr ',' '\\n' | tr -d '{' | grep name | tr -d '\"')\"\n  _zone_name=\"${_zone_name#name:}\"\n}\n\n#usage _get_record_id $zone_id $record\n# where zone_id is the value returned by _get_zone_id\n# and record is in the form _acme.www for an fqdn of _acme.www.example.com\n# returns _record_id\n_get_record_id() {\n  _zone_id=\"$1\"\n  record=\"$2\"\n  value=\"$3\"\n  _dynv6_rest GET \"zones/$_zone_id/records\"\n  if ! _get_record_id_from_response \"$response\"; then\n    _err \"no such record $record found in zone $_zone_id\"\n    return 1\n  fi\n}\n\n_get_record_id_from_response() {\n  response=\"$1\"\n  _record_id=\"$(echo \"$response\" | tr '}' '\\n' | grep \"\\\"name\\\":\\\"$record\\\"\" | grep \"\\\"data\\\":\\\"$value\\\"\" | tr ',' '\\n' | grep '\"id\":' | tr -d '\"' | tr -d 'id:' | tr -d '{')\"\n  if [ -z \"$_record_id\" ]; then\n    _err \"no such record: $record found in zone $_zone_id\"\n    return 1\n  fi\n  _debug \"record id: $_record_id\"\n  return 0\n}\n#usage: _set_record TXT _acme_challenge.www longvalue 12345678\n#zone id is optional can also be set as vairable bevor calling this method\n_set_record() {\n  type=\"$1\"\n  record=\"$2\"\n  value=\"$3\"\n  if [ \"$4\" ]; then\n    _zone_id=\"$4\"\n  fi\n  data=\"{\\\"name\\\": \\\"$record\\\", \\\"data\\\": \\\"$value\\\", \\\"type\\\": \\\"$type\\\"}\"\n  #data='{ \"name\": \"acme.test.thorn.dynv6.net\", \"type\": \"A\", \"data\": \"192.168.0.1\"}'\n  echo \"$data\"\n  #\"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"\n  _dynv6_rest POST \"zones/$_zone_id/records\" \"$data\"\n}\n_del_record() {\n  _zone_id=$1\n  _record_id=$2\n  _dynv6_rest DELETE zones/\"$_zone_id\"/records/\"$_record_id\"\n}\n\n_dynv6_rest() {\n  m=$1    #method GET,POST,DELETE or PUT\n  ep=\"$2\" #the endpoint\n  data=\"$3\"\n  _debug \"$ep\"\n\n  token_trimmed=$(echo \"$dynv6_token\" | tr -d '\"')\n\n  export _H1=\"Authorization: Bearer $token_trimmed\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$dynv6_api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$dynv6_api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_easydns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_easydns_info='easyDNS.net\nSite: easyDNS.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_easydns\nOptions:\n EASYDNS_Token API Token\n EASYDNS_Key API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2647\nAuthor: @Neilpang, wurzelpanzer <wurzelpanzer@maximolider.net>\n'\n\n# API Documentation: https://sandbox.rest.easydns.net:3001/\n\n####################  Public functions #################\n\n#EASYDNS_Key=\"xxxxxxxxxxxxxxxxxxxxxxxx\"\n#EASYDNS_Token=\"xxxxxxxxxxxxxxxxxxxxxxxx\"\nEASYDNS_Api=\"https://rest.easydns.net\"\n\n#Usage: add  _acme-challenge.www.domain.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_easydns_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  EASYDNS_Token=\"${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}\"\n  EASYDNS_Key=\"${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}\"\n\n  if [ -z \"$EASYDNS_Token\" ] || [ -z \"$EASYDNS_Key\" ]; then\n    _err \"You didn't specify an easydns.net token or api key. Signup at https://cp.easydns.com/manage/security/api/signup.php\"\n    return 1\n  else\n    _saveaccountconf_mutable EASYDNS_Token \"$EASYDNS_Token\"\n    _saveaccountconf_mutable EASYDNS_Key \"$EASYDNS_Key\"\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _EASYDNS_rest GET \"zones/records/all/${_domain}/search/${_sub_domain}\"\n\n  if ! printf \"%s\" \"$response\" | grep \\\"status\\\":200 >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n\n  _info \"Adding record\"\n  if _EASYDNS_rest PUT \"zones/records/add/$_domain/TXT\" \"{\\\"host\\\":\\\"$_sub_domain\\\",\\\"rdata\\\":\\\"$txtvalue\\\"}\"; then\n    if _contains \"$response\" \"\\\"status\\\":201\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"Record already exists\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\ndns_easydns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  EASYDNS_Token=\"${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}\"\n  EASYDNS_Key=\"${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _EASYDNS_rest GET \"zones/records/all/${_domain}/search/${_sub_domain}\"\n\n  if ! printf \"%s\" \"$response\" | grep \\\"status\\\":200 >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | head -n 1)\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _EASYDNS_rest DELETE \"zones/records/$_domain/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" \"\\\"status\\\":200\"\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _EASYDNS_rest GET \"zones/records/all/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"status\\\":200\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_EASYDNS_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  basicauth=$(printf \"%s\" \"$EASYDNS_Token\":\"$EASYDNS_Key\" | _base64)\n\n  export _H1=\"accept: application/json\"\n  if [ \"$basicauth\" ]; then\n    export _H2=\"Authorization: Basic $basicauth\"\n  fi\n\n  if [ \"$m\" != \"GET\" ]; then\n    export _H3=\"Content-Type: application/json\"\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$EASYDNS_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$EASYDNS_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_edgecenter.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_edgecenter_info='EdgeCenter.ru\nSite: EdgeCenter.ru\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgecenter\nOptions:\n EDGECENTER_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/6313\nAuthor: Konstantin Ruchev <konstantin.ruchev@edgecenter.ru>\n'\n\nEDGECENTER_API=\"https://api.edgecenter.ru\"\nDOMAIN_TYPE=\nDOMAIN_MASTER=\n\n########  Public functions #####################\n\n#Usage: dns_edgecenter_add   _acme-challenge.www.domain.com   \"TXT_RECORD_VALUE\"\ndns_edgecenter_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Using EdgeCenter DNS API\"\n\n  if ! _dns_edgecenter_init_check; then\n    return 1\n  fi\n\n  _debug \"Detecting root zone for $fulldomain\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  subdomain=\"${fulldomain%.\"$_zone\"}\"\n  subdomain=${subdomain%.}\n\n  _debug \"Zone: $_zone\"\n  _debug \"Subdomain: $subdomain\"\n  _debug \"TXT value: $txtvalue\"\n\n  payload='{\"resource_records\": [ { \"content\": [\"'\"$txtvalue\"'\"] } ], \"ttl\": 60 }'\n  _dns_edgecenter_http_api_call \"post\" \"dns/v2/zones/$_zone/$subdomain.$_zone/txt\" \"$payload\"\n\n  if _contains \"$response\" '\"error\":\"rrset is already exists\"'; then\n    _debug \"RRSet exists, merging values\"\n    _dns_edgecenter_http_api_call \"get\" \"dns/v2/zones/$_zone/$subdomain.$_zone/txt\"\n    current=\"$response\"\n    newlist=\"\"\n    for v in $(echo \"$current\" | sed -n 's/.*\"content\":\\[\"\\([^\"]*\\)\"\\].*/\\1/p'); do\n      newlist=\"$newlist {\\\"content\\\":[\\\"$v\\\"]},\"\n    done\n    newlist=\"$newlist{\\\"content\\\":[\\\"$txtvalue\\\"]}\"\n    putdata=\"{\\\"resource_records\\\":[${newlist}]}\n\"\n    _dns_edgecenter_http_api_call \"put\" \"dns/v2/zones/$_zone/$subdomain.$_zone/txt\" \"$putdata\"\n    _info \"Updated existing RRSet with new TXT value.\"\n    return 0\n  fi\n\n  if _contains \"$response\" '\"exception\":'; then\n    _err \"Record cannot be added.\"\n    return 1\n  fi\n\n  _info \"TXT record added successfully.\"\n  return 0\n}\n\n#Usage: dns_edgecenter_rm   _acme-challenge.www.domain.com   \"TXT_RECORD_VALUE\"\ndns_edgecenter_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Removing TXT record for $fulldomain\"\n\n  if ! _dns_edgecenter_init_check; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  subdomain=\"${fulldomain%.\"$_zone\"}\"\n  subdomain=${subdomain%.}\n\n  _dns_edgecenter_http_api_call \"delete\" \"dns/v2/zones/$_zone/$subdomain.$_zone/txt\"\n\n  if [ -z \"$response\" ]; then\n    _info \"TXT record deleted successfully.\"\n  else\n    _info \"TXT record may not have been deleted: $response\"\n  fi\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_dns_edgecenter_init_check() {\n  EDGECENTER_API_KEY=\"${EDGECENTER_API_KEY:-$(_readaccountconf_mutable EDGECENTER_API_KEY)}\"\n  if [ -z \"$EDGECENTER_API_KEY\" ]; then\n    _err \"EDGECENTER_API_KEY was not exported.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable EDGECENTER_API_KEY \"$EDGECENTER_API_KEY\"\n  export _H1=\"Authorization: APIKey $EDGECENTER_API_KEY\"\n\n  _dns_edgecenter_http_api_call \"get\" \"dns/v2/clients/me/features\"\n  if ! _contains \"$response\" '\"id\":'; then\n    _err \"Invalid API key.\"\n    return 1\n  fi\n  return 0\n}\n\n_get_root() {\n  domain=\"$1\"\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-)\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n    _dns_edgecenter_http_api_call \"get\" \"dns/v2/zones/$h\"\n    if ! _contains \"$response\" 'zone is not found'; then\n      _zone=\"$h\"\n      return 0\n    fi\n    i=$((i + 1))\n  done\n  return 1\n}\n\n_dns_edgecenter_http_api_call() {\n  mtd=\"$1\"\n  endpoint=\"$2\"\n  data=\"$3\"\n\n  export _H1=\"Authorization: APIKey $EDGECENTER_API_KEY\"\n\n  case \"$mtd\" in\n  get)\n    response=\"$(_get \"$EDGECENTER_API/$endpoint\")\"\n    ;;\n  post)\n    response=\"$(_post \"$data\" \"$EDGECENTER_API/$endpoint\")\"\n    ;;\n  delete)\n    response=\"$(_post \"\" \"$EDGECENTER_API/$endpoint\" \"\" \"DELETE\")\"\n    ;;\n  put)\n    response=\"$(_post \"$data\" \"$EDGECENTER_API/$endpoint\" \"\" \"PUT\")\"\n    ;;\n  *)\n    _err \"Unknown HTTP method $mtd\"\n    return 1\n    ;;\n  esac\n\n  _debug \"HTTP $mtd response: $response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_edgedns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_edgedns_info='Akamai.com Edge DNS\nSite: techdocs.Akamai.com/edge-dns/reference/edge-dns-api\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgedns\nOptions: Specify individual credentials\n AKAMAI_HOST Host\n AKAMAI_ACCESS_TOKEN Access token\n AKAMAI_CLIENT_TOKEN Client token\n AKAMAI_CLIENT_SECRET Client secret\nIssues: github.com/acmesh-official/acme.sh/issues/3157\n'\n\n# Akamai Edge DNS v2  API\n# User must provide Open Edgegrid API credentials to the EdgeDNS installation. The remote user in EdgeDNS must have CRUD access to\n# Edge DNS Zones and Recordsets, e.g. DNS—Zone Record Management authorization\n\n# Report bugs to https://control.akamai.com/apps/support-ui/#/contact-support\n\n# *** TBD. NOT IMPLEMENTED YET ***\n# Specify Edgegrid credentials file and section.\n# AKAMAI_EDGERC Edge RC. Full file path\n# AKAMAI_EDGERC_SECTION Edge RC Section. E.g. \"default\"\n\nACME_EDGEDNS_VERSION=\"0.1.0\"\n\n########  Public functions #####################\n\n# Usage: dns_edgedns_add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\n#\ndns_edgedns_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"ENTERING DNS_EDGEDNS_ADD\"\n  _debug2 \"fulldomain\" \"$fulldomain\"\n  _debug2 \"txtvalue\" \"$txtvalue\"\n\n  if ! _EDGEDNS_credentials; then\n    _err \"$@\"\n    return 1\n  fi\n  if ! _EDGEDNS_getZoneInfo \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug2 \"Add: zone\" \"$zone\"\n  acmeRecordURI=$(printf \"%s/%s/names/%s/types/TXT\" \"$edge_endpoint\" \"$zone\" \"$fulldomain\")\n  _debug3 \"Add URL\" \"$acmeRecordURI\"\n  # Get existing TXT record\n  _edge_result=$(_edgedns_rest GET \"$acmeRecordURI\")\n  _api_status=\"$?\"\n  _debug3 \"_edge_result\" \"$_edge_result\"\n  if [ \"$_api_status\" -ne 0 ]; then\n    if [ \"$curResult\" = \"FATAL\" ]; then\n      _err \"$(printf \"Fatal error: acme API function call : %s\" \"$retVal\")\"\n    fi\n    if [ \"$_edge_result\" != \"404\" ]; then\n      _err \"$(printf \"Failure accessing Akamai Edge DNS API Server. Error: %s\" \"$_edge_result\")\"\n      return 1\n    fi\n  fi\n  rdata=\"\\\"${txtvalue}\\\"\"\n  record_op=\"POST\"\n  if [ \"$_api_status\" -eq 0 ]; then\n    # record already exists. Get existing record data and update\n    record_op=\"PUT\"\n    rdlist=\"${_edge_result#*\\\"rdata\\\":[}\"\n    rdlist=\"${rdlist%%]*}\"\n    rdlist=$(echo \"$rdlist\" | tr -d '\"' | tr -d \"\\\\\\\\\")\n    _debug3 \"existing TXT found\"\n    _debug3 \"record data\" \"$rdlist\"\n    # value already there?\n    if _contains \"$rdlist\" \"$txtvalue\"; then\n      return 0\n    fi\n    _txt_val=\"\"\n    while [ \"$_txt_val\" != \"$rdlist\" ] && [ \"${rdlist}\" ]; do\n      _txt_val=\"${rdlist%%,*}\"\n      rdlist=\"${rdlist#*,}\"\n      rdata=\"${rdata},\\\"${_txt_val}\\\"\"\n    done\n  fi\n  # Add the txtvalue TXT Record\n  body=\"{\\\"name\\\":\\\"$fulldomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":600, \\\"rdata\\\":\"[${rdata}]\"}\"\n  _debug3 \"Add body '${body}'\"\n  _edge_result=$(_edgedns_rest \"$record_op\" \"$acmeRecordURI\" \"$body\")\n  _api_status=\"$?\"\n  if [ \"$_api_status\" -eq 0 ]; then\n    _log \"$(printf \"Text value %s added to recordset %s\" \"$txtvalue\" \"$fulldomain\")\"\n    return 0\n  else\n    _err \"$(printf \"error adding TXT record for validation. Error: %s\" \"$_edge_result\")\"\n    return 1\n  fi\n}\n\n# Usage: dns_edgedns_rm   _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to delete txt record\n#\ndns_edgedns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"ENTERING DNS_EDGEDNS_RM\"\n  _debug2 \"fulldomain\" \"$fulldomain\"\n  _debug2 \"txtvalue\" \"$txtvalue\"\n\n  if ! _EDGEDNS_credentials; then\n    _err \"$@\"\n    return 1\n  fi\n  if ! _EDGEDNS_getZoneInfo \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n  _debug2 \"RM: zone\" \"${zone}\"\n  acmeRecordURI=$(printf \"%s/%s/names/%s/types/TXT\" \"${edge_endpoint}\" \"$zone\" \"$fulldomain\")\n  _debug3 \"RM URL\" \"$acmeRecordURI\"\n  # Get existing TXT record\n  _edge_result=$(_edgedns_rest GET \"$acmeRecordURI\")\n  _api_status=\"$?\"\n  if [ \"$_api_status\" -ne 0 ]; then\n    if [ \"$curResult\" = \"FATAL\" ]; then\n      _err \"$(printf \"Fatal error: acme API function call : %s\" \"$retVal\")\"\n    fi\n    if [ \"$_edge_result\" != \"404\" ]; then\n      _err \"$(printf \"Failure accessing Akamai Edge DNS API Server. Error: %s\" \"$_edge_result\")\"\n      return 1\n    fi\n  fi\n  _debug3 \"_edge_result\" \"$_edge_result\"\n  record_op=\"DELETE\"\n  body=\"\"\n  if [ \"$_api_status\" -eq 0 ]; then\n    # record already exists. Get existing record data and update\n    rdlist=\"${_edge_result#*\\\"rdata\\\":[}\"\n    rdlist=\"${rdlist%%]*}\"\n    rdlist=$(echo \"$rdlist\" | tr -d '\"' | tr -d \"\\\\\\\\\")\n    _debug3 \"rdlist\" \"$rdlist\"\n    if [ -n \"$rdlist\" ]; then\n      record_op=\"PUT\"\n      comma=\"\"\n      rdata=\"\"\n      _txt_val=\"\"\n      while [ \"$_txt_val\" != \"$rdlist\" ] && [ \"$rdlist\" ]; do\n        _txt_val=\"${rdlist%%,*}\"\n        rdlist=\"${rdlist#*,}\"\n        _debug3 \"_txt_val\" \"$_txt_val\"\n        _debug3 \"txtvalue\" \"$txtvalue\"\n        if ! _contains \"$_txt_val\" \"$txtvalue\"; then\n          rdata=\"${rdata}${comma}\\\"${_txt_val}\\\"\"\n          comma=\",\"\n        fi\n      done\n      if [ -z \"$rdata\" ]; then\n        record_op=\"DELETE\"\n      else\n        # Recreate the txtvalue TXT Record\n        body=\"{\\\"name\\\":\\\"$fulldomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":600, \\\"rdata\\\":\"[${rdata}]\"}\"\n        _debug3 \"body\" \"$body\"\n      fi\n    fi\n  fi\n  _edge_result=$(_edgedns_rest \"$record_op\" \"$acmeRecordURI\" \"$body\")\n  _api_status=\"$?\"\n  if [ \"$_api_status\" -eq 0 ]; then\n    _log \"$(printf \"Text value %s removed from recordset %s\" \"$txtvalue\" \"$fulldomain\")\"\n    return 0\n  else\n    _err \"$(printf \"error removing TXT record for validation. Error: %s\" \"$_edge_result\")\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n_EDGEDNS_credentials() {\n  _debug \"GettingEdge DNS credentials\"\n  _log \"$(printf \"ACME DNSAPI Edge DNS version %s\" ${ACME_EDGEDNS_VERSION})\"\n  args_missing=0\n  AKAMAI_ACCESS_TOKEN=\"${AKAMAI_ACCESS_TOKEN:-$(_readaccountconf_mutable AKAMAI_ACCESS_TOKEN)}\"\n  if [ -z \"$AKAMAI_ACCESS_TOKEN\" ]; then\n    AKAMAI_ACCESS_TOKEN=\"\"\n    AKAMAI_CLIENT_TOKEN=\"\"\n    AKAMAI_HOST=\"\"\n    AKAMAI_CLIENT_SECRET=\"\"\n    _err \"AKAMAI_ACCESS_TOKEN is missing\"\n    args_missing=1\n  fi\n  AKAMAI_CLIENT_TOKEN=\"${AKAMAI_CLIENT_TOKEN:-$(_readaccountconf_mutable AKAMAI_CLIENT_TOKEN)}\"\n  if [ -z \"$AKAMAI_CLIENT_TOKEN\" ]; then\n    AKAMAI_ACCESS_TOKEN=\"\"\n    AKAMAI_CLIENT_TOKEN=\"\"\n    AKAMAI_HOST=\"\"\n    AKAMAI_CLIENT_SECRET=\"\"\n    _err \"AKAMAI_CLIENT_TOKEN is missing\"\n    args_missing=1\n  fi\n  AKAMAI_HOST=\"${AKAMAI_HOST:-$(_readaccountconf_mutable AKAMAI_HOST)}\"\n  if [ -z \"$AKAMAI_HOST\" ]; then\n    AKAMAI_ACCESS_TOKEN=\"\"\n    AKAMAI_CLIENT_TOKEN=\"\"\n    AKAMAI_HOST=\"\"\n    AKAMAI_CLIENT_SECRET=\"\"\n    _err \"AKAMAI_HOST is missing\"\n    args_missing=1\n  fi\n  AKAMAI_CLIENT_SECRET=\"${AKAMAI_CLIENT_SECRET:-$(_readaccountconf_mutable AKAMAI_CLIENT_SECRET)}\"\n  if [ -z \"$AKAMAI_CLIENT_SECRET\" ]; then\n    AKAMAI_ACCESS_TOKEN=\"\"\n    AKAMAI_CLIENT_TOKEN=\"\"\n    AKAMAI_HOST=\"\"\n    AKAMAI_CLIENT_SECRET=\"\"\n    _err \"AKAMAI_CLIENT_SECRET is missing\"\n    args_missing=1\n  fi\n\n  if [ \"$args_missing\" = 1 ]; then\n    _err \"You have not properly specified the EdgeDNS Open Edgegrid API credentials. Please try again.\"\n    return 1\n  else\n    _saveaccountconf_mutable AKAMAI_ACCESS_TOKEN \"$AKAMAI_ACCESS_TOKEN\"\n    _saveaccountconf_mutable AKAMAI_CLIENT_TOKEN \"$AKAMAI_CLIENT_TOKEN\"\n    _saveaccountconf_mutable AKAMAI_HOST \"$AKAMAI_HOST\"\n    _saveaccountconf_mutable AKAMAI_CLIENT_SECRET \"$AKAMAI_CLIENT_SECRET\"\n    # Set whether curl should use secure or insecure mode\n  fi\n  export HTTPS_INSECURE=0 # All Edgegrid API calls are secure\n  edge_endpoint=$(printf \"https://%s/config-dns/v2/zones\" \"$AKAMAI_HOST\")\n  _debug3 \"Edge API Endpoint:\" \"$edge_endpoint\"\n\n}\n\n_EDGEDNS_getZoneInfo() {\n  _debug \"Getting Zoneinfo\"\n  zoneEnd=false\n  curZone=$1\n  while [ -n \"$zoneEnd\" ]; do\n    # we can strip the first part of the fulldomain, since its just the _acme-challenge string\n    curZone=\"${curZone#*.}\"\n    # suffix . needed for zone -> domain.tld.\n    # create zone get url\n    get_zone_url=$(printf \"%s/%s\" \"$edge_endpoint\" \"$curZone\")\n    _debug3 \"Zone Get: \" \"${get_zone_url}\"\n    curResult=$(_edgedns_rest GET \"$get_zone_url\")\n    retVal=$?\n    if [ \"$retVal\" -ne 0 ]; then\n      if [ \"$curResult\" = \"FATAL\" ]; then\n        _err \"$(printf \"Fatal error: acme API function call : %s\" \"$retVal\")\"\n      fi\n      if [ \"$curResult\" != \"404\" ]; then\n        _err \"$(printf \"Managed zone validation failed. Error response: %s\" \"$retVal\")\"\n        return 1\n      fi\n    fi\n    if _contains \"$curResult\" \"\\\"zone\\\":\"; then\n      _debug2 \"Zone data\" \"${curResult}\"\n      zone=$(echo \"${curResult}\" | _egrep_o \"\\\"zone\\\"\\\\s*:\\\\s*\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \"\\\"\")\n      _debug3 \"Zone\" \"${zone}\"\n      zoneEnd=\"\"\n      return 0\n    fi\n\n    if [ \"${curZone#*.}\" != \"$curZone\" ]; then\n      _debug3 \"$(printf \"%s still contains a '.' - so we can check next higher level\" \"$curZone\")\"\n    else\n      zoneEnd=true\n      _err \"Couldn't retrieve zone data.\"\n      return 1\n    fi\n  done\n  _err \"Failed to  retrieve zone data.\"\n  return 2\n}\n\n_edgedns_headers=\"\"\n\n_edgedns_rest() {\n  _debug \"Handling API Request\"\n  m=$1\n  # Assume endpoint is complete path, including query args if applicable\n  ep=$2\n  body_data=$3\n  _edgedns_content_type=\"\"\n  _request_url_path=\"$ep\"\n  _request_body=\"$body_data\"\n  _request_method=\"$m\"\n  _edgedns_headers=\"\"\n  tab=\"\"\n  _edgedns_headers=\"${_edgedns_headers}${tab}Host: ${AKAMAI_HOST}\"\n  tab=\"\\t\"\n  # Set in acme.sh _post/_get\n  #_edgedns_headers=\"${_edgedns_headers}${tab}User-Agent:ACME DNSAPI Edge DNS version ${ACME_EDGEDNS_VERSION}\"\n  _edgedns_headers=\"${_edgedns_headers}${tab}Accept: application/json,*/*\"\n  if [ \"$m\" != \"GET\" ] && [ \"$m\" != \"DELETE\" ]; then\n    _edgedns_content_type=\"application/json\"\n    _debug3 \"_request_body\" \"$_request_body\"\n    _body_len=$(echo \"$_request_body\" | tr -d \"\\n\\r\" | awk '{print length}')\n    _edgedns_headers=\"${_edgedns_headers}${tab}Content-Length: ${_body_len}\"\n  fi\n  _edgedns_make_auth_header\n  _edgedns_headers=\"${_edgedns_headers}${tab}Authorization: ${_signed_auth_header}\"\n  _secure_debug2 \"Made Auth Header\" \"$_signed_auth_header\"\n  hdr_indx=1\n  work_header=\"${_edgedns_headers}${tab}\"\n  _debug3 \"work_header\" \"$work_header\"\n  while [ \"$work_header\" ]; do\n    entry=\"${work_header%%\\\\t*}\"\n    work_header=\"${work_header#*\\\\t}\"\n    export \"$(printf \"_H%s=%s\" \"$hdr_indx\" \"$entry\")\"\n    _debug2 \"Request Header \" \"$entry\"\n    hdr_indx=$((hdr_indx + 1))\n  done\n\n  # clear headers from previous request to avoid getting wrong http code on timeouts\n  : >\"$HTTP_HEADER\"\n  _debug2 \"$ep\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug3 \"Method data\" \"$data\"\n    # body  url [needbase64] [POST|PUT|DELETE] [ContentType]\n    response=$(_post \"$_request_body\" \"$ep\" false \"$m\" \"$_edgedns_content_type\")\n  else\n    response=$(_get \"$ep\")\n  fi\n  _ret=\"$?\"\n  if [ \"$_ret\" -ne 0 ]; then\n    _err \"$(printf \"acme.sh API function call failed. Error: %s\" \"$_ret\")\"\n    echo \"FATAL\"\n    return \"$_ret\"\n  fi\n  _debug2 \"response\" \"${response}\"\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  _debug2 \"http response code\" \"$_code\"\n  if [ \"$_code\" = \"200\" ] || [ \"$_code\" = \"201\" ]; then\n    # All good\n    response=\"$(echo \"${response}\" | _normalizeJson)\"\n    echo \"$response\"\n    return 0\n  fi\n\n  if [ \"$_code\" = \"204\" ]; then\n    # Success, no body\n    echo \"$_code\"\n    return 0\n  fi\n\n  if [ \"$_code\" = \"400\" ]; then\n    _err \"Bad request presented\"\n    _log \"$(printf \"Headers: %s\" \"$_edgedns_headers\")\"\n    _log \"$(printf \"Method: %s\" \"$_request_method\")\"\n    _log \"$(printf \"URL: %s\" \"$ep\")\"\n    _log \"$(printf \"Data: %s\" \"$data\")\"\n  fi\n\n  if [ \"$_code\" = \"403\" ]; then\n    _err \"access denied make sure your Edgegrid cedentials are correct.\"\n  fi\n\n  echo \"$_code\"\n  return 1\n}\n\n_edgedns_eg_timestamp() {\n  _debug \"Generating signature Timestamp\"\n  _debug3 \"Retriving ntp time\"\n  _timeheaders=\"$(_get \"https://www.ntp.org\" \"onlyheader\")\"\n  _debug3 \"_timeheaders\" \"$_timeheaders\"\n  _ntpdate=\"$(echo \"$_timeheaders\" | grep -i \"Date:\" | _head_n 1 | cut -d ':' -f 2- | tr -d \"\\r\\n\")\"\n  _debug3 \"_ntpdate\" \"$_ntpdate\"\n  _ntpdate=\"$(echo \"${_ntpdate}\" | sed -e 's/^[[:space:]]*//')\"\n  _debug3 \"_NTPDATE\" \"$_ntpdate\"\n  _ntptime=\"$(echo \"${_ntpdate}\" | _head_n 1 | cut -d \" \" -f 5 | tr -d \"\\r\\n\")\"\n  _debug3 \"_ntptime\" \"$_ntptime\"\n  _eg_timestamp=$(date -u \"+%Y%m%dT\")\n  _eg_timestamp=\"$(printf \"%s%s+0000\" \"$_eg_timestamp\" \"$_ntptime\")\"\n  _debug \"_eg_timestamp\" \"$_eg_timestamp\"\n}\n\n_edgedns_new_nonce() {\n  _debug \"Generating Nonce\"\n  _nonce=$(echo \"EDGEDNS$(_time)\" | _digest sha1 hex | cut -c 1-32)\n  _debug3 \"_nonce\" \"$_nonce\"\n}\n\n_edgedns_make_auth_header() {\n  _debug \"Constructing Auth Header\"\n  _edgedns_new_nonce\n  _edgedns_eg_timestamp\n  # \"Unsigned authorization header: 'EG1-HMAC-SHA256 client_token=block;access_token=block;timestamp=20200806T14:16:33+0000;nonce=72cde72c-82d9-4721-9854-2ba057929d67;'\"\n  _auth_header=\"$(printf \"EG1-HMAC-SHA256 client_token=%s;access_token=%s;timestamp=%s;nonce=%s;\" \"$AKAMAI_CLIENT_TOKEN\" \"$AKAMAI_ACCESS_TOKEN\" \"$_eg_timestamp\" \"$_nonce\")\"\n  _secure_debug2 \"Unsigned Auth Header: \" \"$_auth_header\"\n\n  _edgedns_sign_request\n  _signed_auth_header=\"$(printf \"%ssignature=%s\" \"$_auth_header\" \"$_signed_req\")\"\n  _secure_debug2 \"Signed Auth Header: \" \"${_signed_auth_header}\"\n}\n\n_edgedns_sign_request() {\n  _debug2 \"Signing http request\"\n  _edgedns_make_data_to_sign \"$_auth_header\"\n  _secure_debug2 \"Returned signed data\" \"$_mdata\"\n  _edgedns_make_signing_key \"$_eg_timestamp\"\n  _edgedns_base64_hmac_sha256 \"$_mdata\" \"$_signing_key\"\n  _signed_req=\"$_hmac_out\"\n  _secure_debug2 \"Signed Request\" \"$_signed_req\"\n}\n\n_edgedns_make_signing_key() {\n  _debug2 \"Creating sigining key\"\n  ts=$1\n  _edgedns_base64_hmac_sha256 \"$ts\" \"$AKAMAI_CLIENT_SECRET\"\n  _signing_key=\"$_hmac_out\"\n  _secure_debug2 \"Signing Key\" \"$_signing_key\"\n\n}\n\n_edgedns_make_data_to_sign() {\n  _debug2 \"Processing data to sign\"\n  hdr=$1\n  _secure_debug2 \"hdr\" \"$hdr\"\n  _edgedns_make_content_hash\n  path=\"$(echo \"$_request_url_path\" | tr -d \"\\n\\r\" | sed 's/https\\?:\\/\\///')\"\n  path=${path#*\"$AKAMAI_HOST\"}\n  _debug \"hier path\" \"$path\"\n  # dont expose headers to sign so use MT string\n  _mdata=\"$(printf \"%s\\thttps\\t%s\\t%s\\t%s\\t%s\\t%s\" \"$_request_method\" \"$AKAMAI_HOST\" \"$path\" \"\" \"$_hash\" \"$hdr\")\"\n  _secure_debug2 \"Data to Sign\" \"$_mdata\"\n}\n\n_edgedns_make_content_hash() {\n  _debug2 \"Generating content hash\"\n  _hash=\"\"\n  _debug2 \"Request method\" \"${_request_method}\"\n  if [ \"$_request_method\" != \"POST\" ] || [ -z \"$_request_body\" ]; then\n    return 0\n  fi\n  _debug2 \"Req body\" \"$_request_body\"\n  _edgedns_base64_sha256 \"$_request_body\"\n  _hash=\"$_sha256_out\"\n  _debug2 \"Content hash\" \"$_hash\"\n}\n\n_edgedns_base64_hmac_sha256() {\n  _debug2 \"Generating hmac\"\n  data=$1\n  key=$2\n  encoded_data=\"$(echo \"$data\" | iconv -t utf-8)\"\n  encoded_key=\"$(echo \"$key\" | iconv -t utf-8)\"\n  _secure_debug2 \"encoded data\" \"$encoded_data\"\n  _secure_debug2 \"encoded key\" \"$encoded_key\"\n\n  encoded_key_hex=$(printf \"%s\" \"$encoded_key\" | _hex_dump | tr -d ' ')\n  data_sig=\"$(echo \"$encoded_data\" | tr -d \"\\n\\r\" | _hmac sha256 \"$encoded_key_hex\" | _base64)\"\n\n  _secure_debug2 \"data_sig:\" \"$data_sig\"\n  _hmac_out=\"$(echo \"$data_sig\" | tr -d \"\\n\\r\" | iconv -f utf-8)\"\n  _secure_debug2 \"hmac\" \"$_hmac_out\"\n}\n\n_edgedns_base64_sha256() {\n  _debug2 \"Creating sha256 digest\"\n  trg=$1\n  _secure_debug2 \"digest data\" \"$trg\"\n  digest=\"$(echo \"$trg\" | tr -d \"\\n\\r\" | _digest \"sha256\")\"\n  _sha256_out=\"$(echo \"$digest\" | tr -d \"\\n\\r\" | iconv -f utf-8)\"\n  _secure_debug2 \"digest decode\" \"$_sha256_out\"\n}\n\n#_edgedns_parse_edgerc() {\n#  filepath=$1\n#  section=$2\n#}\n"
  },
  {
    "path": "dnsapi/dns_efficientip.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_efficientip_info='efficientip.com\nSite: https://efficientip.com/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip\nOptions:\n  EfficientIP_Creds HTTP Basic Authentication credentials. E.g. \"username:password\"\n  EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN.\n  EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional.\n  EfficientIP_View Name of the DNS view hosting the zone. Optional.\nOptionsAlt:\n  EfficientIP_Token_Key Alternative API token key, prefered over basic authentication.\n  EfficientIP_Token_Secret Alternative API token secret, required when using a token key.\n  EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN.\n  EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional.\n  EfficientIP_View Name of the DNS view hosting the zone. Optional.\nIssues: github.com/acmesh-official/acme.sh/issues/6325\nAuthor: EfficientIP-Labs <contact@efficientip.com>\n'\n\ndns_efficientip_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using EfficientIP API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if { [ -z \"${EfficientIP_Creds}\" ] && { [ -z \"${EfficientIP_Token_Key}\" ] || [ -z \"${EfficientIP_Token_Secret}\" ]; }; } || [ -z \"${EfficientIP_Server}\" ]; then\n    EfficientIP_Creds=\"\"\n    EfficientIP_Token_Key=\"\"\n    EfficientIP_Token_Secret=\"\"\n    EfficientIP_Server=\"\"\n    _err \"You didn't specify any EfficientIP credentials or token or server (EfficientIP_Creds; EfficientIP_Token_Key; EfficientIP_Token_Secret; EfficientIP_Server).\"\n    _err \"Please set them via EXPORT EfficientIP_Creds=username:password or EXPORT EfficientIP_server=ip/hostname\"\n    _err \"or if you want to use Token instead EXPORT EfficientIP_Token_Key=yourkey\"\n    _err \"and EXPORT EfficientIP_Token_Secret=yoursecret\"\n    _err \"then try again.\"\n    return 1\n  fi\n\n  if [ -z \"${EfficientIP_DNS_Name}\" ]; then\n    EfficientIP_DNS_Name=\"\"\n  fi\n\n  EfficientIP_DNSNameEncoded=$(printf \"%b\" \"${EfficientIP_DNS_Name}\" | _url_encode)\n\n  if [ -z \"${EfficientIP_View}\" ]; then\n    EfficientIP_View=\"\"\n  fi\n\n  EfficientIP_ViewEncoded=$(printf \"%b\" \"${EfficientIP_View}\" | _url_encode)\n\n  _saveaccountconf EfficientIP_Creds \"${EfficientIP_Creds}\"\n  _saveaccountconf EfficientIP_Token_Key \"${EfficientIP_Token_Key}\"\n  _saveaccountconf EfficientIP_Token_Secret \"${EfficientIP_Token_Secret}\"\n  _saveaccountconf EfficientIP_Server \"${EfficientIP_Server}\"\n  _saveaccountconf EfficientIP_DNS_Name \"${EfficientIP_DNS_Name}\"\n  _saveaccountconf EfficientIP_View \"${EfficientIP_View}\"\n\n  export _H1=\"Accept-Language:en-US\"\n  baseurlnObject=\"https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_ttl=300&rr_name=${fulldomain}&rr_value1=${txtvalue}\"\n\n  if [ \"${EfficientIP_DNSNameEncoded}\" != \"\" ]; then\n    baseurlnObject=\"${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}\"\n  fi\n\n  if [ \"${EfficientIP_ViewEncoded}\" != \"\" ]; then\n    baseurlnObject=\"${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}\"\n  fi\n\n  if [ -z \"${EfficientIP_Token_Secret}\" ] || [ -z \"${EfficientIP_Token_Key}\" ]; then\n    EfficientIP_CredsEncoded=$(printf \"%b\" \"${EfficientIP_Creds}\" | _base64)\n    export _H2=\"Authorization: Basic ${EfficientIP_CredsEncoded}\"\n  else\n    TS=$(date +%s)\n    Sig=$(printf \"%b\\n$TS\\nPOST\\n$baseurlnObject\" \"${EfficientIP_Token_Secret}\" | _digest sha3-256 hex)\n    EfficientIP_CredsEncoded=$(printf \"%b:%b\" \"${EfficientIP_Token_Key}\" \"$Sig\")\n    export _H2=\"Authorization: SDS ${EfficientIP_CredsEncoded}\"\n    export _H3=\"X-SDS-TS: ${TS}\"\n  fi\n\n  result=\"$(_post \"\" \"${baseurlnObject}\" \"\" \"POST\")\"\n\n  if [ \"$(echo \"${result}\" | _egrep_o \"ret_oid\")\" ]; then\n    _info \"DNS record successfully created\"\n    return 0\n  else\n    _err \"Error creating DNS record\"\n    _err \"${result}\"\n    return 1\n  fi\n}\n\ndns_efficientip_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using EfficientIP API\"\n  _debug fulldomain \"${fulldomain}\"\n  _debug txtvalue \"${txtvalue}\"\n\n  EfficientIP_ViewEncoded=$(printf \"%b\" \"${EfficientIP_View}\" | _url_encode)\n  EfficientIP_DNSNameEncoded=$(printf \"%b\" \"${EfficientIP_DNS_Name}\" | _url_encode)\n  EfficientIP_CredsEncoded=$(printf \"%b\" \"${EfficientIP_Creds}\" | _base64)\n\n  export _H1=\"Accept-Language:en-US\"\n\n  baseurlnObject=\"https://${EfficientIP_Server}/rest/dns_rr_delete?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue\"\n  if [ \"${EfficientIP_DNSNameEncoded}\" != \"\" ]; then\n    baseurlnObject=\"${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}\"\n  fi\n\n  if [ \"${EfficientIP_ViewEncoded}\" != \"\" ]; then\n    baseurlnObject=\"${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}\"\n  fi\n\n  if [ -z \"$EfficientIP_Token_Secret\" ] || [ -z \"$EfficientIP_Token_Key\" ]; then\n    EfficientIP_CredsEncoded=$(printf \"%b\" \"${EfficientIP_Creds}\" | _base64)\n    export _H2=\"Authorization: Basic $EfficientIP_CredsEncoded\"\n  else\n    TS=$(date +%s)\n    Sig=$(printf \"%b\\n$TS\\nDELETE\\n${baseurlnObject}\" \"${EfficientIP_Token_Secret}\" | _digest sha3-256 hex)\n    EfficientIP_CredsEncoded=$(printf \"%b:%b\" \"${EfficientIP_Token_Key}\" \"$Sig\")\n    export _H2=\"Authorization: SDS ${EfficientIP_CredsEncoded}\"\n    export _H3=\"X-SDS-TS: $TS\"\n  fi\n\n  result=\"$(_post \"\" \"${baseurlnObject}\" \"\" \"DELETE\")\"\n\n  if [ \"$(echo \"${result}\" | _egrep_o \"ret_oid\")\" ]; then\n    _info \"DNS Record successfully deleted\"\n    return 0\n  else\n    _err \"Error deleting DNS record\"\n    _err \"${result}\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_euserv.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_euserv_info='EUserv.com\nDomains: EUserv.eu\nSite: EUserv.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_euserv\nOptions:\n EUSERV_Username Username\n EUSERV_Password Password\nAuthor: Michael Brueckner\n'\n\nEUSERV_Api=\"https://api.euserv.net\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_euserv_add() {\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=$2\n\n  EUSERV_Username=\"${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}\"\n  EUSERV_Password=\"${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}\"\n  if [ -z \"$EUSERV_Username\" ] || [ -z \"$EUSERV_Password\" ]; then\n    EUSERV_Username=\"\"\n    EUSERV_Password=\"\"\n    _err \"You don't specify euserv user and password yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the user and email to the account conf file.\n  _saveaccountconf_mutable EUSERV_Username \"$EUSERV_Username\"\n  _saveaccountconf_mutable EUSERV_Password \"$EUSERV_Password\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug \"_sub_domain\" \"$_sub_domain\"\n  _debug \"_domain\" \"$_domain\"\n  _info \"Adding record\"\n  if ! _euserv_add_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n}\n\n#fulldomain txtvalue\ndns_euserv_rm() {\n\n  fulldomain=\"$(echo \"$1\" | _lower_case)\"\n  txtvalue=$2\n\n  EUSERV_Username=\"${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}\"\n  EUSERV_Password=\"${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}\"\n  if [ -z \"$EUSERV_Username\" ] || [ -z \"$EUSERV_Password\" ]; then\n    EUSERV_Username=\"\"\n    EUSERV_Password=\"\"\n    _err \"You don't specify euserv user and password yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the user and email to the account conf file.\n  _saveaccountconf_mutable EUSERV_Username \"$EUSERV_Username\"\n  _saveaccountconf_mutable EUSERV_Password \"$EUSERV_Password\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug \"_sub_domain\" \"$_sub_domain\"\n  _debug \"_domain\" \"$_domain\"\n\n  _debug \"Getting txt records\"\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>domain.dns_get_active_records</methodName>\n    <params>\n      <param>\n       <value>\n         <struct>\n           <member>\n             <name>login</name>\n             <value>\n               <string>%s</string>\n             </value>\n            </member>\n            <member>\n              <name>password</name>\n              <value>\n                <string>%s</string>\n              </value>\n            </member>\n            <member>\n              <name>domain_id</name>\n              <value>\n                <int>%s</int>\n              </value>\n            </member>\n          </struct>\n        </value>\n      </param>\n    </params>\n  </methodCall>' \"$EUSERV_Username\" \"$EUSERV_Password\" \"$_euserv_domain_id\")\n\n  export _H1=\"Content-Type: text/xml\"\n  response=\"$(_post \"$xml_content\" \"$EUSERV_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"<member><name>status</name><value><i4>100</i4></value></member>\"; then\n    _err \"Error could not get txt records\"\n    _debug \"xml_content\" \"$xml_content\"\n    _debug \"response\" \"$response\"\n    return 1\n  fi\n\n  if ! echo \"$response\" | grep '>dns_record_content<.*>'\"$txtvalue\"'<' >/dev/null; then\n    _info \"Do not need to delete record\"\n  else\n    # find XML block where txtvalue is in. The record_id is allways prior this line!\n    _endLine=$(echo \"$response\" | grep -n '>dns_record_content<.*>'\"$txtvalue\"'<' | cut -d ':' -f 1)\n    # record_id is the last <name> Tag with a number before the row _endLine, identified by </name><value><struct>\n    _record_id=$(echo \"$response\" | sed -n '1,'\"$_endLine\"'p' | grep '</name><value><struct>' | _tail_n 1 | sed 's/.*<name>\\([0-9]*\\)<\\/name>.*/\\1/')\n    _info \"Deleting record\"\n    _euserv_delete_record \"$_record_id\"\n  fi\n\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  _debug \"get root\"\n\n  # Just to read the domain_orders once\n\n  domain=$1\n  i=2\n  p=1\n\n  if ! _euserv_get_domain_orders; then\n    return 1\n  fi\n\n  # Get saved response with domain_orders\n  response=\"$_euserv_domain_orders\"\n\n  while true; do\n    h=$(echo \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"$h\"; then\n      _sub_domain=$(echo \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      if ! _euserv_get_domain_id \"$_domain\"; then\n        _err \"invalid domain\"\n        return 1\n      fi\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_euserv_get_domain_orders() {\n  # returns: _euserv_domain_orders\n\n  _debug \"get domain_orders\"\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>domain.get_domain_orders</methodName>\n    <params>\n      <param>\n        <value>\n          <struct>\n            <member>\n              <name>login</name>\n              <value><string>%s</string></value>\n            </member>\n            <member>\n              <name>password</name>\n              <value><string>%s</string></value>\n            </member>\n          </struct>\n        </value>\n      </param>\n    </params>\n  </methodCall>' \"$EUSERV_Username\" \"$EUSERV_Password\")\n\n  export _H1=\"Content-Type: text/xml\"\n  response=\"$(_post \"$xml_content\" \"$EUSERV_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"<member><name>status</name><value><i4>100</i4></value></member>\"; then\n    _err \"Error could not get domain orders\"\n    _debug \"xml_content\" \"$xml_content\"\n    _debug \"response\" \"$response\"\n    return 1\n  fi\n\n  # save response to reduce API calls\n  _euserv_domain_orders=\"$response\"\n  return 0\n}\n\n_euserv_get_domain_id() {\n  # returns: _euserv_domain_id\n  domain=$1\n  _debug \"get domain_id\"\n\n  # find line where the domain name is within the $response\n  _startLine=$(echo \"$_euserv_domain_orders\" | grep -n '>domain_name<.*>'\"$domain\"'<' | cut -d ':' -f 1)\n  # next occurency of domain_id after the domain_name is the correct one\n  _euserv_domain_id=$(echo \"$_euserv_domain_orders\" | sed -n \"$_startLine\"',$p' | grep '>domain_id<' | _head_n 1 | sed 's/.*<i4>\\([0-9]*\\)<\\/i4>.*/\\1/')\n\n  if [ -z \"$_euserv_domain_id\" ]; then\n    _err \"Could not find domain_id for domain $domain\"\n    _debug \"_euserv_domain_orders\" \"$_euserv_domain_orders\"\n    return 1\n  fi\n\n  return 0\n}\n\n_euserv_delete_record() {\n  record_id=$1\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>domain.dns_delete_record</methodName>\n    <params>\n      <param>\n       <value>\n         <struct>\n           <member>\n             <name>login</name>\n             <value>\n               <string>%s</string>\n             </value>\n            </member>\n            <member>\n              <name>password</name>\n              <value>\n                <string>%s</string>\n              </value>\n            </member>\n            <member>\n              <name>dns_record_id</name>\n              <value>\n                <int>%s</int>\n              </value>\n            </member>\n          </struct>\n        </value>\n      </param>\n    </params>\n  </methodCall>' \"$EUSERV_Username\" \"$EUSERV_Password\" \"$record_id\")\n\n  export _H1=\"Content-Type: text/xml\"\n  response=\"$(_post \"$xml_content\" \"$EUSERV_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"<member><name>status</name><value><i4>100</i4></value></member>\"; then\n    _err \"Error deleting record\"\n    _debug \"xml_content\" \"$xml_content\"\n    _debug \"response\" \"$response\"\n    return 1\n  fi\n\n  return 0\n\n}\n\n_euserv_add_record() {\n  domain=$1\n  sub_domain=$2\n  txtval=$3\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>domain.dns_create_record</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>login</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>password</name>\n       <value>\n        <string>%s</string></value>\n      </member>\n      <member>\n       <name>domain_id</name>\n       <value>\n        <int>%s</int>\n       </value>\n      </member>\n      <member>\n       <name>dns_record_subdomain</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>dns_record_type</name>\n       <value>\n        <string>TXT</string>\n       </value>\n      </member>\n      <member>\n       <name>dns_record_value</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>dns_record_ttl</name>\n       <value>\n        <int>300</int>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>' \"$EUSERV_Username\" \"$EUSERV_Password\" \"$_euserv_domain_id\" \"$sub_domain\" \"$txtval\")\n\n  export _H1=\"Content-Type: text/xml\"\n  response=\"$(_post \"$xml_content\" \"$EUSERV_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"<member><name>status</name><value><i4>100</i4></value></member>\"; then\n    _err \"Error could not create record\"\n    _debug \"xml_content\" \"$xml_content\"\n    _debug \"response\" \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_exoscale.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_exoscale_info='Exoscale.com\nSite: Exoscale.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_exoscale\nOptions:\n EXOSCALE_API_KEY API Key\n EXOSCALE_SECRET_KEY API Secret key\n'\n\nEXOSCALE_API=\"https://api-ch-gva-2.exoscale.com/v2\"\n\n########  Public functions  ########\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_exoscale_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"Using Exoscale DNS v2 API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _check_auth; then\n    return 1\n  fi\n\n  root_domain_id=$(_get_root_domain_id \"$fulldomain\")\n  if [ -z \"$root_domain_id\" ]; then\n    _err \"Unable to determine root domain ID for $fulldomain\"\n    return 1\n  fi\n  _debug root_domain_id \"$root_domain_id\"\n\n  # Always get the subdomain part first\n  sub_domain=$(_get_sub_domain \"$fulldomain\" \"$root_domain_id\")\n  _debug sub_domain \"$sub_domain\"\n\n  # Build the record name properly\n  if [ -z \"$sub_domain\" ]; then\n    record_name=\"_acme-challenge\"\n  else\n    record_name=\"_acme-challenge.$sub_domain\"\n  fi\n\n  payload=$(printf '{\"name\":\"%s\",\"type\":\"TXT\",\"content\":\"%s\",\"ttl\":120}' \"$record_name\" \"$txtvalue\")\n  _debug payload \"$payload\"\n\n  response=$(_exoscale_rest POST \"/dns-domain/${root_domain_id}/record\" \"$payload\")\n  if _contains \"$response\" \"\\\"id\\\"\"; then\n    _info \"TXT record added successfully.\"\n    return 0\n  else\n    _err \"Error adding TXT record: $response\"\n    return 1\n  fi\n}\n\ndns_exoscale_rm() {\n  fulldomain=$1\n\n  _debug \"Using Exoscale DNS v2 API for removal\"\n  _debug fulldomain \"$fulldomain\"\n\n  if ! _check_auth; then\n    return 1\n  fi\n\n  root_domain_id=$(_get_root_domain_id \"$fulldomain\")\n  if [ -z \"$root_domain_id\" ]; then\n    _err \"Unable to determine root domain ID for $fulldomain\"\n    return 1\n  fi\n\n  record_name=\"_acme-challenge\"\n  sub_domain=$(_get_sub_domain \"$fulldomain\" \"$root_domain_id\")\n  if [ -n \"$sub_domain\" ]; then\n    record_name=\"_acme-challenge.$sub_domain\"\n  fi\n\n  record_id=$(_find_record_id \"$root_domain_id\" \"$record_name\")\n  if [ -z \"$record_id\" ]; then\n    _err \"TXT record not found for deletion.\"\n    return 1\n  fi\n\n  response=$(_exoscale_rest DELETE \"/dns-domain/$root_domain_id/record/$record_id\")\n  if _contains \"$response\" \"\\\"state\\\":\\\"success\\\"\"; then\n    _info \"TXT record deleted successfully.\"\n    return 0\n  else\n    _err \"Error deleting TXT record: $response\"\n    return 1\n  fi\n}\n\n########  Private helpers  ########\n\n_check_auth() {\n  EXOSCALE_API_KEY=\"${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}\"\n  EXOSCALE_SECRET_KEY=\"${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}\"\n  if [ -z \"$EXOSCALE_API_KEY\" ] || [ -z \"$EXOSCALE_SECRET_KEY\" ]; then\n    _err \"EXOSCALE_API_KEY and EXOSCALE_SECRET_KEY must be set.\"\n    return 1\n  fi\n  _saveaccountconf_mutable EXOSCALE_API_KEY \"$EXOSCALE_API_KEY\"\n  _saveaccountconf_mutable EXOSCALE_SECRET_KEY \"$EXOSCALE_SECRET_KEY\"\n  return 0\n}\n\n_get_root_domain_id() {\n  domain=$1\n  i=1\n  while true; do\n    candidate=$(printf \"%s\" \"$domain\" | cut -d . -f \"${i}-100\")\n    [ -z \"$candidate\" ] && return 1\n    _debug \"Trying root domain candidate: $candidate\"\n    domains=$(_exoscale_rest GET \"/dns-domain\")\n    # Extract from dns-domains array\n    result=$(echo \"$domains\" | _egrep_o '\"dns-domains\":\\[.*\\]' | _egrep_o '\\{\"id\":\"[^\"]*\",\"created-at\":\"[^\"]*\",\"unicode-name\":\"[^\"]*\"\\}' | while read -r item; do\n      name=$(echo \"$item\" | _egrep_o '\"unicode-name\":\"[^\"]*\"' | cut -d'\"' -f4)\n      id=$(echo \"$item\" | _egrep_o '\"id\":\"[^\"]*\"' | cut -d'\"' -f4)\n      if [ \"$name\" = \"$candidate\" ]; then\n        echo \"$id\"\n        break\n      fi\n    done)\n    if [ -n \"$result\" ]; then\n      echo \"$result\"\n      return 0\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n}\n\n_get_sub_domain() {\n  fulldomain=$1\n  root_id=$2\n  root_info=$(_exoscale_rest GET \"/dns-domain/$root_id\")\n  _debug root_info \"$root_info\"\n  root_name=$(echo \"$root_info\" | _egrep_o \"\\\"unicode-name\\\":\\\"[^\\\"]*\\\"\" | cut -d\\\" -f4)\n  sub=${fulldomain%%.\"$root_name\"}\n\n  if [ \"$sub\" = \"_acme-challenge\" ]; then\n    echo \"\"\n  else\n    # Remove _acme-challenge. prefix to get the actual subdomain\n    echo \"${sub#_acme-challenge.}\"\n  fi\n}\n\n_find_record_id() {\n  root_id=$1\n  name=$2\n  records=$(_exoscale_rest GET \"/dns-domain/$root_id/record\")\n\n  # Convert search name to lowercase for case-insensitive matching\n  name_lower=$(echo \"$name\" | tr '[:upper:]' '[:lower:]')\n\n  echo \"$records\" | _egrep_o '\\{[^}]*\"name\":\"[^\"]*\"[^}]*\\}' | while read -r record; do\n    record_name=$(echo \"$record\" | _egrep_o '\"name\":\"[^\"]*\"' | cut -d'\"' -f4)\n    record_name_lower=$(echo \"$record_name\" | tr '[:upper:]' '[:lower:]')\n    if [ \"$record_name_lower\" = \"$name_lower\" ]; then\n      echo \"$record\" | _egrep_o '\"id\":\"[^\"]*\"' | _head_n 1 | cut -d'\"' -f4\n      break\n    fi\n  done\n}\n\n_exoscale_sign() {\n  k=$1\n  shift\n  hex_key=$(printf %b \"$k\" | _hex_dump | tr -d ' ')\n  printf %s \"$@\" | _hmac sha256 \"$hex_key\"\n}\n\n_exoscale_rest() {\n  method=$1\n  path=$2\n  data=$3\n\n  url=\"${EXOSCALE_API}${path}\"\n  expiration=$(_math \"$(date +%s)\" + 300) # 5m from now\n\n  # Build the message with the actual body or empty line\n  message=$(printf \"%s %s\\n%s\\n\\n\\n%s\" \"$method\" \"/v2$path\" \"$data\" \"$expiration\")\n  signature=$(_exoscale_sign \"$EXOSCALE_SECRET_KEY\" \"$message\" | _base64)\n  auth=\"EXO2-HMAC-SHA256 credential=${EXOSCALE_API_KEY},expires=${expiration},signature=${signature}\"\n\n  _debug \"API request: $method $url\"\n  _debug \"Signed message: [$message]\"\n  _debug \"Authorization header: [$auth]\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Authorization: ${auth}\"\n\n  if [ \"$data\" ] || [ \"$method\" = \"DELETE\" ]; then\n    export _H3=\"Content-Type: application/json\"\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$url\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$url\" \"\" \"\" \"$method\")\"\n  fi\n\n  # shellcheck disable=SC2181\n  if [ \"$?\" -ne 0 ]; then\n    _err \"error $url\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  echo \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_fornex.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_fornex_info='Fornex.com\nSite: Fornex.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_fornex\nOptions:\n FORNEX_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/3998\nAuthor: Timur Umarov <inbox@tumarov.com>\n'\n\nFORNEX_API_URL=\"https://fornex.com/api\"\n\n########  Public functions #####################\n\n#Usage: dns_fornex_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_fornex_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _Fornex_API; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Unable to determine root domain\"\n    return 1\n  else\n    _debug _domain \"$_domain\"\n  fi\n\n  _info \"Adding record\"\n  if _rest POST \"dns/domain/$_domain/entry_set/\" \"{\\\"host\\\" : \\\"${fulldomain}\\\" , \\\"type\\\" : \\\"TXT\\\" , \\\"value\\\" : \\\"${txtvalue}\\\" , \\\"ttl\\\" : null}\"; then\n    _debug _response \"$response\"\n    _info \"Added, OK\"\n    return 0\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#Usage: dns_fornex_rm   _acme-challenge.www.domain.com\ndns_fornex_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _Fornex_API; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Unable to determine root domain\"\n    return 1\n  else\n    _debug _domain \"$_domain\"\n  fi\n\n  _debug \"Getting txt records\"\n  _rest GET \"dns/domain/$_domain/entry_set?type=TXT&q=$fulldomain\"\n\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _err \"Txt record not found\"\n    return 1\n  fi\n\n  _record_id=\"$(echo \"$response\" | _egrep_o \"\\{[^\\{]*\\\"value\\\"*:*\\\"$txtvalue\\\"[^\\}]*\\}\" | sed -n -e 's#.*\"id\":\\([0-9]*\\).*#\\1#p')\"\n  _debug \"_record_id\" \"$_record_id\"\n  if [ -z \"$_record_id\" ]; then\n    _err \"can not find _record_id\"\n    return 1\n  fi\n\n  if ! _rest DELETE \"dns/domain/$_domain/entry_set/$_record_id/\"; then\n    _err \"Delete record error.\"\n    return 1\n  fi\n  return 0\n}\n\n####################  Private functions below ##################################\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _rest GET \"dns/domain/?q=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _domain=$h\n      return 0\n    else\n      _debug \"$h not found\"\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_Fornex_API() {\n  FORNEX_API_KEY=\"${FORNEX_API_KEY:-$(_readaccountconf_mutable FORNEX_API_KEY)}\"\n  if [ -z \"$FORNEX_API_KEY\" ]; then\n    FORNEX_API_KEY=\"\"\n\n    _err \"You didn't specify the Fornex API key yet.\"\n    _err \"Please create your key and try again.\"\n\n    return 1\n  fi\n\n  _saveaccountconf_mutable FORNEX_API_KEY \"$FORNEX_API_KEY\"\n}\n\n#method method action data\n_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Authorization: Api-Key $FORNEX_API_KEY\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Accept: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$FORNEX_API_URL/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$FORNEX_API_URL/$ep\" | _normalizeJson)\"\n  fi\n\n  _ret=\"$?\"\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_freedns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_freedns_info='FreeDNS\nSite: FreeDNS.afraid.org\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_freedns\nOptions:\n FREEDNS_User Username\n FREEDNS_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2305\nAuthor: David Kerr <@dkerr64>\n'\n\n########  Public functions #####################\n\n# Export FreeDNS userid and password in following variables...\n#  FREEDNS_User=username\n#  FREEDNS_Password=password\n# login cookie is saved in acme account config file so userid / pw\n# need to be set only when changed.\n\n#Usage: dns_freedns_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_freedns_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Add TXT record using FreeDNS\"\n  _debug \"fulldomain: $fulldomain\"\n  _debug \"txtvalue: $txtvalue\"\n\n  if [ -z \"$FREEDNS_User\" ] || [ -z \"$FREEDNS_Password\" ]; then\n    FREEDNS_User=\"\"\n    FREEDNS_Password=\"\"\n    if [ -z \"$FREEDNS_COOKIE\" ]; then\n      _err \"You did not specify the FreeDNS username and password yet.\"\n      _err \"Please export as FREEDNS_User / FREEDNS_Password and try again.\"\n      return 1\n    fi\n    using_cached_cookies=\"true\"\n  else\n    FREEDNS_COOKIE=\"$(_freedns_login \"$FREEDNS_User\" \"$FREEDNS_Password\")\"\n    if [ -z \"$FREEDNS_COOKIE\" ]; then\n      return 1\n    fi\n    using_cached_cookies=\"false\"\n  fi\n\n  _debug \"FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)\"\n\n  _saveaccountconf FREEDNS_COOKIE \"$FREEDNS_COOKIE\"\n\n  # We may have to cycle through the domain name to find the\n  # TLD that we own...\n  i=1\n  wmax=\"$(echo \"$fulldomain\" | tr '.' ' ' | wc -w)\"\n  while [ \"$i\" -lt \"$wmax\" ]; do\n    # split our full domain name into two parts...\n    sub_domain=\"$(echo \"$fulldomain\" | cut -d. -f -\"$i\")\"\n    i=\"$(_math \"$i\" + 1)\"\n    top_domain=\"$(echo \"$fulldomain\" | cut -d. -f \"$i\"-100)\"\n    _debug \"sub_domain: $sub_domain\"\n    _debug \"top_domain: $top_domain\"\n\n    DNSdomainid=\"$(_freedns_domain_id \"$top_domain\")\"\n    if [ \"$?\" = \"0\" ]; then\n      _info \"Domain $top_domain found at FreeDNS, domain_id $DNSdomainid\"\n      break\n    else\n      _info \"Domain $top_domain not found at FreeDNS, try with next level of TLD\"\n    fi\n  done\n\n  if [ -z \"$DNSdomainid\" ]; then\n    # If domain ID is empty then something went wrong (top level\n    # domain not found at FreeDNS).\n    _err \"Domain $top_domain not found at FreeDNS\"\n    return 1\n  fi\n\n  # Add in new TXT record with the value provided\n  _debug \"Adding TXT record for $fulldomain, $txtvalue\"\n  _freedns_add_txt_record \"$FREEDNS_COOKIE\" \"$DNSdomainid\" \"$sub_domain\" \"$txtvalue\"\n  return $?\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_freedns_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Delete TXT record using FreeDNS\"\n  _debug \"fulldomain: $fulldomain\"\n  _debug \"txtvalue: $txtvalue\"\n\n  # Need to read cookie from conf file again in case new value set\n  # during login to FreeDNS when TXT record was created.\n  FREEDNS_COOKIE=\"$(_readaccountconf \"FREEDNS_COOKIE\")\"\n  _debug \"FreeDNS login cookies: $FREEDNS_COOKIE\"\n\n  TXTdataid=\"$(_freedns_data_id \"$fulldomain\" \"TXT\")\"\n  if [ \"$?\" != \"0\" ]; then\n    _info \"Cannot delete TXT record for $fulldomain, record does not exist at FreeDNS\"\n    return 1\n  fi\n  _debug \"Data ID's found, $TXTdataid\"\n\n  # now we have one (or more) TXT record data ID's. Load the page\n  # for that record and search for the record txt value.  If match\n  # then we can delete it.\n  lines=\"$(echo \"$TXTdataid\" | wc -l)\"\n  _debug \"Found $lines TXT data records for $fulldomain\"\n  i=0\n  while [ \"$i\" -lt \"$lines\" ]; do\n    i=\"$(_math \"$i\" + 1)\"\n    dataid=\"$(echo \"$TXTdataid\" | sed -n \"${i}p\")\"\n    _debug \"$dataid\"\n\n    htmlpage=\"$(_freedns_retrieve_data_page \"$FREEDNS_COOKIE\" \"$dataid\")\"\n    if [ \"$?\" != \"0\" ]; then\n      if [ \"$using_cached_cookies\" = \"true\" ]; then\n        _err \"Has your FreeDNS username and password changed?  If so...\"\n        _err \"Please export as FREEDNS_User / FREEDNS_Password and try again.\"\n      fi\n      return 1\n    fi\n\n    echo \"$htmlpage\" | grep \"value=\\\"&quot;$txtvalue&quot;\\\"\" >/dev/null\n    if [ \"$?\" = \"0\" ]; then\n      # Found a match... delete the record and return\n      _info \"Deleting TXT record for $fulldomain, $txtvalue\"\n      _freedns_delete_txt_record \"$FREEDNS_COOKIE\" \"$dataid\"\n      return $?\n    fi\n  done\n\n  # If we get this far we did not find a match\n  # Not necessarily an error, but log anyway.\n  _info \"Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS\"\n  return 0\n}\n\n####################  Private functions below ##################################\n\n# usage: _freedns_login username password\n# print string \"cookie=value\" etc.\n# returns 0 success\n_freedns_login() {\n  export _H1=\"Accept-Language:en-US\"\n  username=\"$1\"\n  password=\"$2\"\n  url=\"https://freedns.afraid.org/zc.php?step=2\"\n\n  _debug \"Login to FreeDNS as user $username\"\n\n  htmlpage=\"$(_post \"username=$(printf '%s' \"$username\" | _url_encode)&password=$(printf '%s' \"$password\" | _url_encode)&submit=Login&action=auth\" \"$url\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"FreeDNS login failed for user $username bad RC from _post\"\n    return 1\n  fi\n\n  cookies=\"$(grep -i '^Set-Cookie.*dns_cookie.*$' \"$HTTP_HEADER\" | _head_n 1 | tr -d \"\\r\\n\" | cut -d \" \" -f 2)\"\n\n  # if cookies is not empty then logon successful\n  if [ -z \"$cookies\" ]; then\n    _debug3 \"htmlpage: $htmlpage\"\n    _err \"FreeDNS login failed for user $username. Check $HTTP_HEADER file\"\n    return 1\n  fi\n\n  printf \"%s\" \"$cookies\"\n  return 0\n}\n\n# usage _freedns_retrieve_subdomain_page login_cookies\n# echo page retrieved (html)\n# returns 0 success\n_freedns_retrieve_subdomain_page() {\n  export _H1=\"Cookie:$1\"\n  export _H2=\"Accept-Language:en-US\"\n  url=\"https://freedns.afraid.org/subdomain/\"\n\n  _debug \"Retrieve subdomain page from FreeDNS\"\n\n  htmlpage=\"$(_get \"$url\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"FreeDNS retrieve subdomains failed bad RC from _get\"\n    return 1\n  elif [ -z \"$htmlpage\" ]; then\n    _err \"FreeDNS returned empty subdomain page\"\n    return 1\n  fi\n\n  _debug3 \"htmlpage: $htmlpage\"\n\n  printf \"%s\" \"$htmlpage\"\n  return 0\n}\n\n# usage _freedns_retrieve_data_page login_cookies data_id\n# echo page retrieved (html)\n# returns 0 success\n_freedns_retrieve_data_page() {\n  export _H1=\"Cookie:$1\"\n  export _H2=\"Accept-Language:en-US\"\n  data_id=\"$2\"\n  url=\"https://freedns.afraid.org/subdomain/edit.php?data_id=$2\"\n\n  _debug \"Retrieve data page for ID $data_id from FreeDNS\"\n\n  htmlpage=\"$(_get \"$url\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"FreeDNS retrieve data page failed bad RC from _get\"\n    return 1\n  elif [ -z \"$htmlpage\" ]; then\n    _err \"FreeDNS returned empty data page\"\n    return 1\n  fi\n\n  _debug3 \"htmlpage: $htmlpage\"\n\n  printf \"%s\" \"$htmlpage\"\n  return 0\n}\n\n# usage _freedns_add_txt_record login_cookies domain_id subdomain value\n# returns 0 success\n_freedns_add_txt_record() {\n  export _H1=\"Cookie:$1\"\n  export _H2=\"Accept-Language:en-US\"\n  domain_id=\"$2\"\n  subdomain=\"$3\"\n  value=\"$(printf '%s' \"$4\" | _url_encode)\"\n  url=\"https://freedns.afraid.org/subdomain/save.php?step=2\"\n\n  htmlpage=\"$(_post \"type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21\" \"$url\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"FreeDNS failed to add TXT record for $subdomain bad RC from _post\"\n    return 1\n  elif ! grep \"200 OK\" \"$HTTP_HEADER\" >/dev/null; then\n    _debug3 \"htmlpage: $htmlpage\"\n    _err \"FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file\"\n    return 1\n  elif _contains \"$htmlpage\" \"security code was incorrect\"; then\n    _debug3 \"htmlpage: $htmlpage\"\n    _err \"FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code\"\n    _err \"Note that you cannot use automatic DNS validation for FreeDNS public domains\"\n    return 1\n  fi\n\n  _debug3 \"htmlpage: $htmlpage\"\n  _info \"Added acme challenge TXT record for $fulldomain at FreeDNS\"\n  return 0\n}\n\n# usage _freedns_delete_txt_record login_cookies data_id\n# returns 0 success\n_freedns_delete_txt_record() {\n  export _H1=\"Cookie:$1\"\n  export _H2=\"Accept-Language:en-US\"\n  data_id=\"$2\"\n  url=\"https://freedns.afraid.org/subdomain/delete2.php\"\n\n  htmlheader=\"$(_get \"$url?data_id%5B%5D=$data_id&submit=delete+selected\" \"onlyheader\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"FreeDNS failed to delete TXT record for $data_id bad RC from _get\"\n    return 1\n  elif ! _contains \"$htmlheader\" \"200 OK\"; then\n    _debug2 \"htmlheader: $htmlheader\"\n    _err \"FreeDNS failed to delete TXT record $data_id\"\n    return 1\n  fi\n\n  _info \"Deleted acme challenge TXT record for $fulldomain at FreeDNS\"\n  return 0\n}\n\n# usage _freedns_domain_id domain_name\n# echo the domain_id if found\n# return 0 success\n_freedns_domain_id() {\n  # Start by escaping the dots in the domain name\n  search_domain=\"$(echo \"$1\" | sed 's/\\./\\\\./g')\"\n\n  # Sometimes FreeDNS does not return the subdomain page but rather\n  # returns a page regarding becoming a premium member.  This usually\n  # happens after a period of inactivity.  Immediately trying again\n  # returns the correct subdomain page.  So, we will try twice to\n  # load the page and obtain our domain ID\n  attempts=2\n  while [ \"$attempts\" -gt \"0\" ]; do\n    attempts=\"$(_math \"$attempts\" - 1)\"\n\n    htmlpage=\"$(_freedns_retrieve_subdomain_page \"$FREEDNS_COOKIE\")\"\n    if [ \"$?\" != \"0\" ]; then\n      if [ \"$using_cached_cookies\" = \"true\" ]; then\n        _err \"Has your FreeDNS username and password changed?  If so...\"\n        _err \"Please export as FREEDNS_User / FREEDNS_Password and try again.\"\n      fi\n      return 1\n    fi\n\n    domain_id=\"$(echo \"$htmlpage\" | tr -d \" \\t\\r\\n\\v\\f\" | sed 's/<tr>/@<tr>/g' | tr '@' '\\n' |\n      grep \"<td>$search_domain</td>\\|<td>$search_domain(.*)</td>\" |\n      sed -n 's/.*\\(edit\\.php?edit_domain_id=[0-9a-zA-Z]*\\).*/\\1/p' |\n      cut -d = -f 2)\"\n    # The above beauty extracts domain ID from the html page...\n    # strip out all blank space and new lines. Then insert newlines\n    # before each table row <tr>\n    # search for the domain within each row (which may or may not have\n    # a text string in brackets (.*) after it.\n    # And finally extract the domain ID.\n    if [ -n \"$domain_id\" ]; then\n      printf \"%s\" \"$domain_id\"\n      return 0\n    fi\n    _debug \"Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)\"\n  done\n  _debug \"Domain $search_domain not found after retry\"\n  return 1\n}\n\n# usage _freedns_data_id domain_name record_type\n# echo the data_id(s) if found\n# return 0 success\n_freedns_data_id() {\n  # Start by escaping the dots in the domain name\n  search_domain=\"$(echo \"$1\" | sed 's/\\./\\\\./g')\"\n  record_type=\"$2\"\n\n  # Sometimes FreeDNS does not return the subdomain page but rather\n  # returns a page regarding becoming a premium member.  This usually\n  # happens after a period of inactivity.  Immediately trying again\n  # returns the correct subdomain page.  So, we will try twice to\n  # load the page and obtain our domain ID\n  attempts=2\n  while [ \"$attempts\" -gt \"0\" ]; do\n    attempts=\"$(_math \"$attempts\" - 1)\"\n\n    htmlpage=\"$(_freedns_retrieve_subdomain_page \"$FREEDNS_COOKIE\")\"\n    if [ \"$?\" != \"0\" ]; then\n      if [ \"$using_cached_cookies\" = \"true\" ]; then\n        _err \"Has your FreeDNS username and password changed?  If so...\"\n        _err \"Please export as FREEDNS_User / FREEDNS_Password and try again.\"\n      fi\n      return 1\n    fi\n\n    data_id=\"$(echo \"$htmlpage\" | tr -d \" \\t\\r\\n\\v\\f\" | sed 's/<tr>/@<tr>/g' | tr '@' '\\n' |\n      grep \"<td[a-zA-Z=#]*>$record_type</td>\" |\n      grep \"<ahref.*>$search_domain</a>\" |\n      sed -n 's/.*\\(edit\\.php?data_id=[0-9a-zA-Z]*\\).*/\\1/p' |\n      cut -d = -f 2)\"\n    # The above beauty extracts data ID from the html page...\n    # strip out all blank space and new lines. Then insert newlines\n    # before each table row <tr>\n    # search for the record type withing each row (e.g. TXT)\n    # search for the domain within each row (which is within a <a..>\n    # </a> anchor. And finally extract the domain ID.\n    if [ -n \"$data_id\" ]; then\n      printf \"%s\" \"$data_id\"\n      return 0\n    fi\n    _debug \"Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)\"\n  done\n  _debug \"Domain $search_domain not found after retry\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_freemyip.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_freemyip_info='FreeMyIP.com\nSite: FreeMyIP.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip\nOptions:\n FREEMYIP_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/6247\nAuthor: Recolic Keghart <root@recolic.net>, @Giova96\n'\n\nFREEMYIP_DNS_API=\"https://freemyip.com/update?\"\n\n################ Public functions ################\n\n#Usage: dns_freemyip_add    fulldomain    txtvalue\ndns_freemyip_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Add TXT record $txtvalue for $fulldomain using freemyip.com api\"\n\n  FREEMYIP_Token=\"${FREEMYIP_Token:-$(_readaccountconf_mutable FREEMYIP_Token)}\"\n  if [ -z \"$FREEMYIP_Token\" ]; then\n    FREEMYIP_Token=\"\"\n    _err \"You don't specify FREEMYIP_Token yet.\"\n    _err \"Please specify your token and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable FREEMYIP_Token \"$FREEMYIP_Token\"\n\n  if _is_root_domain_published \"$fulldomain\"; then\n    _err \"freemyip API don't allow you to set multiple TXT record for the same subdomain!\"\n    _err \"You must apply certificate for only one domain at a time!\"\n    _err \"====\"\n    _err \"For example, aaa.yourdomain.freemyip.com and bbb.yourdomain.freemyip.com and yourdomain.freemyip.com ALWAYS share the same TXT record. They will overwrite each other if you apply multiple domain at the same time.\"\n    _debug \"If you are testing this workflow in github pipeline or acmetest, please set TEST_DNS_NO_SUBDOMAIN=1 and TEST_DNS_NO_WILDCARD=1\"\n    return 1\n  fi\n\n  # txtvalue must be url-encoded. But it's not necessary for acme txt value.\n  _freemyip_get_until_ok \"${FREEMYIP_DNS_API}token=$FREEMYIP_Token&domain=$fulldomain&txt=$txtvalue\" 2>&1\n  return $?\n}\n\n#Usage: dns_freemyip_rm    fulldomain    txtvalue\ndns_freemyip_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Delete TXT record $txtvalue for $fulldomain using freemyip.com api\"\n\n  FREEMYIP_Token=\"${FREEMYIP_Token:-$(_readaccountconf_mutable FREEMYIP_Token)}\"\n  if [ -z \"$FREEMYIP_Token\" ]; then\n    FREEMYIP_Token=\"\"\n    _err \"You don't specify FREEMYIP_Token yet.\"\n    _err \"Please specify your token and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable FREEMYIP_Token \"$FREEMYIP_Token\"\n\n  # Leave the TXT record as empty or \"null\" to delete the record.\n  _freemyip_get_until_ok \"${FREEMYIP_DNS_API}token=$FREEMYIP_Token&domain=$fulldomain&txt=\" 2>&1\n  return $?\n}\n\n################ Private functions below  ################\n_get_root() {\n  _fmi_d=\"$1\"\n\n  echo \"$_fmi_d\" | rev | cut -d '.' -f 1-3 | rev\n}\n\n# There is random failure while calling freemyip API too fast. This function automatically retry until success.\n_freemyip_get_until_ok() {\n  _fmi_url=\"$1\"\n  for i in $(seq 1 8); do\n    _debug \"HTTP GET freemyip.com API '$_fmi_url', retry $i/8...\"\n    _get \"$_fmi_url\" | tee /dev/fd/2 | grep OK && return 0\n    _sleep 1 # DO NOT send the request too fast\n  done\n  _err \"Failed to request freemyip API: $_fmi_url . Server does not say 'OK'\"\n  return 1\n}\n\n# Verify in public dns if domain is already there.\n_is_root_domain_published() {\n  _fmi_d=\"$1\"\n  _webroot=\"$(_get_root \"$_fmi_d\")\"\n\n  _info \"Verifying '\"\"$_fmi_d\"\"' freemyip webroot (\"\"$_webroot\"\") is not published yet\"\n  for i in $(seq 1 3); do\n    _debug \"'$_webroot' ns lookup, retry $i/3...\"\n    if [ \"$(_ns_lookup \"$_fmi_d\" TXT)\" ]; then\n      _debug \"'$_webroot' already has a TXT record published!\"\n      return 0\n    fi\n    _sleep 10 # Give it some time to propagate the TXT record\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_gandi_livedns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_gandi_livedns_info='Gandi.net LiveDNS\nSite: Gandi.net/domain/dns\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gandi_livedns\nOptions:\n GANDI_LIVEDNS_KEY API Key\nIssues: github.com/fcrozat/acme.sh\nAuthor: Frédéric Crozat <fcrozat@suse.com>, Dominik Röttsches <drott@google.com>\n'\n\n# Gandi LiveDNS v5 API\n# https://api.gandi.net/docs/livedns/\n# https://api.gandi.net/docs/authentication/ for token + apikey (deprecated) authentication\n# currently under beta\n\n########  Public functions #####################\n\nGANDI_LIVEDNS_API=\"https://api.gandi.net/v5/livedns\"\n\n#Usage: dns_gandi_livedns_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_gandi_livedns_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  GANDI_LIVEDNS_KEY=\"${GANDI_LIVEDNS_KEY:-$(_readaccountconf_mutable GANDI_LIVEDNS_KEY)}\"\n  GANDI_LIVEDNS_TOKEN=\"${GANDI_LIVEDNS_TOKEN:-$(_readaccountconf_mutable GANDI_LIVEDNS_TOKEN)}\"\n  if [ -z \"$GANDI_LIVEDNS_KEY\" ] && [ -z \"$GANDI_LIVEDNS_TOKEN\" ]; then\n    _err \"No Token or API key (deprecated) specified for Gandi LiveDNS.\"\n    _err \"Create your token or key and export it as GANDI_LIVEDNS_KEY or GANDI_LIVEDNS_TOKEN respectively\"\n    return 1\n  fi\n\n  # Keep only one secret in configuration\n  if [ -n \"$GANDI_LIVEDNS_TOKEN\" ]; then\n    _saveaccountconf_mutable GANDI_LIVEDNS_TOKEN \"$GANDI_LIVEDNS_TOKEN\"\n    _clearaccountconf_mutable GANDI_LIVEDNS_KEY\n  elif [ -n \"$GANDI_LIVEDNS_KEY\" ]; then\n    _saveaccountconf_mutable GANDI_LIVEDNS_KEY \"$GANDI_LIVEDNS_KEY\"\n    _clearaccountconf_mutable GANDI_LIVEDNS_TOKEN\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _debug domain \"$_domain\"\n  _debug sub_domain \"$_sub_domain\"\n\n  _dns_gandi_append_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_gandi_livedns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug fulldomain \"$fulldomain\"\n  _debug domain \"$_domain\"\n  _debug sub_domain \"$_sub_domain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _dns_gandi_existing_rrset_values \"$_domain\" \"$_sub_domain\"; then\n    return 1\n  fi\n  _new_rrset_values=$(echo \"$_rrset_values\" | sed \"s/...$txtvalue...//g\")\n  # Cleanup dangling commata.\n  _new_rrset_values=$(echo \"$_new_rrset_values\" | sed \"s/, ,/ ,/g\")\n  _new_rrset_values=$(echo \"$_new_rrset_values\" | sed \"s/, *\\]/\\]/g\")\n  _new_rrset_values=$(echo \"$_new_rrset_values\" | sed \"s/\\[ *,/\\[/g\")\n  _debug \"New rrset_values\" \"$_new_rrset_values\"\n\n  _gandi_livedns_rest PUT \\\n    \"domains/$_domain/records/$_sub_domain/TXT\" \\\n    \"{\\\"rrset_ttl\\\": 300, \\\"rrset_values\\\": $_new_rrset_values}\" &&\n    _contains \"$response\" '{\"message\":\"DNS Record Created\"}' &&\n    _info \"Removing record $(__green \"success\")\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _gandi_livedns_rest GET \"domains/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" '\"code\": 401'; then\n      _err \"$response\"\n      return 1\n    elif _contains \"$response\" '\"code\": 404'; then\n      _debug \"$h not found\"\n    else\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_dns_gandi_append_record() {\n  domain=$1\n  sub_domain=$2\n  txtvalue=$3\n\n  if _dns_gandi_existing_rrset_values \"$domain\" \"$sub_domain\"; then\n    _debug \"Appending new value\"\n    _rrset_values=$(echo \"$_rrset_values\" | sed \"s/\\\"]/\\\",\\\"$txtvalue\\\"]/\")\n  else\n    _debug \"Creating new record\" \"$_rrset_values\"\n    _rrset_values=\"[\\\"$txtvalue\\\"]\"\n  fi\n  _debug new_rrset_values \"$_rrset_values\"\n  _gandi_livedns_rest PUT \"domains/$_domain/records/$sub_domain/TXT\" \\\n    \"{\\\"rrset_ttl\\\": 300, \\\"rrset_values\\\": $_rrset_values}\" &&\n    _contains \"$response\" '{\"message\":\"DNS Record Created\"}' &&\n    _info \"Adding record $(__green \"success\")\"\n}\n\n_dns_gandi_existing_rrset_values() {\n  domain=$1\n  sub_domain=$2\n  if ! _gandi_livedns_rest GET \"domains/$domain/records/$sub_domain\"; then\n    return 1\n  fi\n  if ! _contains \"$response\" '\"rrset_type\":\"TXT\"'; then\n    _debug \"Does not have a _acme-challenge TXT record yet.\"\n    return 1\n  fi\n  if _contains \"$response\" '\"rrset_values\":\\[\\]'; then\n    _debug \"Empty rrset_values for TXT record, no previous TXT record.\"\n    return 1\n  fi\n  _debug \"Already has TXT record.\"\n  _rrset_values=$(echo \"$response\" | _egrep_o 'rrset_values.*\\[.*\\]' |\n    _egrep_o '\\[\".*\\\"]')\n  return 0\n}\n\n_gandi_livedns_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/json\"\n\n  if [ -n \"$GANDI_LIVEDNS_TOKEN\" ]; then\n    export _H2=\"Authorization: Bearer $GANDI_LIVEDNS_TOKEN\"\n  else\n    export _H2=\"Authorization: Apikey $GANDI_LIVEDNS_KEY\"\n  fi\n\n  if [ \"$m\" = \"GET\" ]; then\n    response=\"$(_get \"$GANDI_LIVEDNS_API/$ep\")\"\n  else\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$GANDI_LIVEDNS_API/$ep\" \"\" \"$m\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_gcloud.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_gcloud_info='Google Cloud DNS\nSite: Cloud.Google.com/dns\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcloud\nOptions:\n CLOUDSDK_ACTIVE_CONFIG_NAME Active config name. E.g. \"default\"\nAuthor: Janos Lenart <janos@lenart.io>\n'\n\n########  Public functions #####################\n\n# Usage: dns_gcloud_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_gcloud_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using gcloud\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _dns_gcloud_find_zone || return $?\n\n  # Add an extra RR\n  _dns_gcloud_start_tr || return $?\n  _dns_gcloud_get_rrdatas || return $?\n  echo \"$rrdatas\" | _dns_gcloud_remove_rrs || return $?\n  printf \"%s\\n%s\\n\" \"$rrdatas\" \"\\\"$txtvalue\\\"\" | grep -v '^$' | _dns_gcloud_add_rrs || return $?\n  _dns_gcloud_execute_tr || return $?\n\n  _info \"$fulldomain record added\"\n}\n\n# Usage: dns_gcloud_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Remove the txt record after validation.\ndns_gcloud_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using gcloud\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _dns_gcloud_find_zone || return $?\n\n  # Remove one RR\n  _dns_gcloud_start_tr || return $?\n  _dns_gcloud_get_rrdatas || return $?\n  echo \"$rrdatas\" | _dns_gcloud_remove_rrs || return $?\n  echo \"$rrdatas\" | grep -F -v -- \"\\\"$txtvalue\\\"\" | _dns_gcloud_add_rrs || return $?\n  _dns_gcloud_execute_tr || return $?\n\n  _info \"$fulldomain record removed\"\n}\n\n####################  Private functions below ##################################\n\n_dns_gcloud_start_tr() {\n  if ! trd=$(mktemp -d); then\n    _err \"_dns_gcloud_start_tr: failed to create temporary directory\"\n    return 1\n  fi\n  tr=\"$trd/tr.yaml\"\n  _debug tr \"$tr\"\n\n  if ! gcloud dns record-sets transaction start \\\n    --transaction-file=\"$tr\" \\\n    --zone=\"$managedZone\"; then\n    rm -r \"$trd\"\n    _err \"_dns_gcloud_start_tr: failed to execute transaction\"\n    return 1\n  fi\n}\n\n_dns_gcloud_execute_tr() {\n  if ! gcloud dns record-sets transaction execute \\\n    --transaction-file=\"$tr\" \\\n    --zone=\"$managedZone\"; then\n    _debug tr \"$(cat \"$tr\")\"\n    rm -r \"$trd\"\n    _err \"_dns_gcloud_execute_tr: failed to execute transaction\"\n    return 1\n  fi\n  rm -r \"$trd\"\n\n  for i in $(seq 1 120); do\n    if gcloud dns record-sets changes list \\\n      --zone=\"$managedZone\" \\\n      --filter='status != done' |\n      grep -q '^.*'; then\n      _info \"_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)...\"\n      sleep 5\n    else\n      return 0\n    fi\n  done\n\n  _err \"_dns_gcloud_execute_tr: transaction is still pending after 10 minutes\"\n  rm -r \"$trd\"\n  return 1\n}\n\n_dns_gcloud_remove_rrs() {\n  if ! xargs -r gcloud dns record-sets transaction remove \\\n    --name=\"$fulldomain.\" \\\n    --ttl=\"$ttl\" \\\n    --type=TXT \\\n    --zone=\"$managedZone\" \\\n    --transaction-file=\"$tr\" --; then\n    _debug tr \"$(cat \"$tr\")\"\n    rm -r \"$trd\"\n    _err \"_dns_gcloud_remove_rrs: failed to remove RRs\"\n    return 1\n  fi\n}\n\n_dns_gcloud_add_rrs() {\n  ttl=60\n  if ! xargs -r gcloud dns record-sets transaction add \\\n    --name=\"$fulldomain.\" \\\n    --ttl=\"$ttl\" \\\n    --type=TXT \\\n    --zone=\"$managedZone\" \\\n    --transaction-file=\"$tr\" --; then\n    _debug tr \"$(cat \"$tr\")\"\n    rm -r \"$trd\"\n    _err \"_dns_gcloud_add_rrs: failed to add RRs\"\n    return 1\n  fi\n}\n\n_dns_gcloud_find_zone() {\n  # Prepare a filter that matches zones that are suiteable for this entry.\n  # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;\n  # this function finds the longest postfix that has a managed zone.\n  part=\"$fulldomain\"\n  filter=\"dnsName=( \"\n  while [ \"$part\" != \"\" ]; do\n    filter=\"$filter$part. \"\n    part=\"$(echo \"$part\" | sed 's/[^.]*\\.*//')\"\n  done\n  filter=\"$filter) AND visibility=public\"\n  _debug filter \"$filter\"\n\n  # List domains and find the zone with the deepest sub-domain (in case of some levels of delegation)\n  if ! match=$(gcloud dns managed-zones list \\\n    --format=\"value(name, dnsName)\" \\\n    --filter=\"$filter\" |\n    while read -r dnsName name; do\n      printf \"%s\\t%s\\t%s\\n\" \"$(echo \"$name\" | awk -F\".\" '{print NF-1}')\" \"$dnsName\" \"$name\"\n    done |\n    sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then\n    _err \"_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?\"\n    return 1\n  fi\n\n  dnsName=$(echo \"$match\" | cut -f2)\n  _debug dnsName \"$dnsName\"\n  managedZone=$(echo \"$match\" | cut -f1)\n  _debug managedZone \"$managedZone\"\n}\n\n_dns_gcloud_get_rrdatas() {\n  if ! rrdatas=$(gcloud dns record-sets list \\\n    --zone=\"$managedZone\" \\\n    --name=\"$fulldomain.\" \\\n    --type=TXT \\\n    --format=\"value(ttl,rrdatas)\"); then\n    _err \"_dns_gcloud_get_rrdatas: Failed to list record-sets\"\n    rm -r \"$trd\"\n    return 1\n  fi\n  ttl=$(echo \"$rrdatas\" | cut -f1)\n  # starting with version 353.0.0 gcloud seems to\n  # separate records with a semicolon instead of commas\n  # see also https://cloud.google.com/sdk/docs/release-notes#35300_2021-08-17\n  rrdatas=$(echo \"$rrdatas\" | cut -f2 | sed 's/\"[,;]\"/\"\\n\"/g')\n}\n"
  },
  {
    "path": "dnsapi/dns_gcore.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_gcore_info='Gcore.com\nSite: Gcore.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcore\nOptions:\n GCORE_Key API Key\nIssues: github.com/acmesh-official/acme.sh/issues/4460\n'\n\nGCORE_Api=\"https://api.gcore.com/dns/v2\"\nGCORE_Doc=\"https://api.gcore.com/docs/dns\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_gcore_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  GCORE_Key=\"${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}\"\n\n  if [ -z \"$GCORE_Key\" ]; then\n    GCORE_Key=\"\"\n    _err \"You didn't specify a Gcore api key yet.\"\n    _err \"You can get yours from here $GCORE_Doc\"\n    return 1\n  fi\n\n  #save the api key to the account conf file.\n  _saveaccountconf_mutable GCORE_Key \"$GCORE_Key\" \"base64\"\n\n  _debug \"First detect the zone name\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _zone_name \"$_zone_name\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _gcore_rest GET \"zones/$_zone_name/$fulldomain/TXT\"\n  payload=\"\"\n\n  if echo \"$response\" | grep \"record is not found\" >/dev/null; then\n    _info \"Record doesn't exists\"\n    payload=\"{\\\"resource_records\\\":[{\\\"content\\\":[\\\"$txtvalue\\\"],\\\"enabled\\\":true}],\\\"ttl\\\":120}\"\n  elif echo \"$response\" | grep \"$txtvalue\" >/dev/null; then\n    _info \"Already exists, OK\"\n    return 0\n  elif echo \"$response\" | tr -d \" \" | grep \\\"name\\\":\\\"\"$fulldomain\"\\\",\\\"type\\\":\\\"TXT\\\" >/dev/null; then\n    _info \"Record with mismatch txtvalue, try update it\"\n    payload=$(echo \"$response\" | tr -d \" \" | sed 's/\"updated_at\":[0-9]\\+,//g' | sed 's/\"meta\":{}}]}/\"meta\":{}},{\"content\":['\\\"\"$txtvalue\"\\\"'],\"enabled\":true}]}/')\n  fi\n\n  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so\n  # we can not use updating anymore.\n  #  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  #  _debug count \"$count\"\n  #  if [ \"$count\" = \"0\" ]; then\n  _info \"Adding record\"\n  if _gcore_rest PUT \"zones/$_zone_name/$fulldomain/TXT\" \"$payload\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"rrset is already exists\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#fulldomain txtvalue\ndns_gcore_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  GCORE_Key=\"${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _zone_name \"$_zone_name\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _gcore_rest GET \"zones/$_zone_name/$fulldomain/TXT\"\n\n  if echo \"$response\" | grep \"record is not found\" >/dev/null; then\n    _info \"No such txt recrod\"\n    return 0\n  fi\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"name\\\":\\\"\"$fulldomain\"\\\",\\\"type\\\":\\\"TXT\\\" >/dev/null; then\n    _err \"Error: $response\"\n    return 1\n  fi\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"\"$txtvalue\"\\\" >/dev/null; then\n    _info \"No such txt recrod\"\n    return 0\n  fi\n\n  count=\"$(echo \"$response\" | grep -o \"content\" | wc -l)\"\n\n  if [ \"$count\" = \"1\" ]; then\n    if ! _gcore_rest DELETE \"zones/$_zone_name/$fulldomain/TXT\"; then\n      _err \"Delete record error. $response\"\n      return 1\n    fi\n    return 0\n  fi\n\n  payload=\"$(echo \"$response\" | tr -d \" \" | sed 's/\"updated_at\":[0-9]\\+,//g' | sed 's/{\"id\":[0-9]\\+,\"content\":\\[\"'\"$txtvalue\"'\"\\],\"enabled\":true,\"meta\":{}}//' | sed 's/\\[,/\\[/' | sed 's/,,/,/' | sed 's/,\\]/\\]/')\"\n  if ! _gcore_rest PUT \"zones/$_zone_name/$fulldomain/TXT\" \"$payload\"; then\n    _err \"Delete record error. $response\"\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.sub.domain.com\n#returns\n# _sub_domain=_acme-challenge.sub or _acme-challenge\n# _domain=domain.com\n# _zone_name=domain.com or sub.domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _gcore_rest GET \"zones/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _zone_name=$h\n      if [ \"$_zone_name\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_gcore_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  key_trimmed=$(echo \"$GCORE_Key\" | tr -d '\"')\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: APIKey $key_trimmed\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$GCORE_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$GCORE_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_gd.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_gd_info='GoDaddy.com\nSite: GoDaddy.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gd\nOptions:\n GD_Key API Key\n GD_Secret API Secret\n'\n\nGD_Api=\"https://api.godaddy.com/v1\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_gd_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  GD_Key=\"${GD_Key:-$(_readaccountconf_mutable GD_Key)}\"\n  GD_Secret=\"${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}\"\n  if [ -z \"$GD_Key\" ] || [ -z \"$GD_Secret\" ]; then\n    GD_Key=\"\"\n    GD_Secret=\"\"\n    _err \"You didn't specify godaddy api key and secret yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable GD_Key \"$GD_Key\"\n  _saveaccountconf_mutable GD_Secret \"$GD_Secret\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting existing records\"\n  if ! _gd_rest GET \"domains/$_domain/records/TXT/$_sub_domain\"; then\n    return 1\n  fi\n\n  if _contains \"$response\" \"$txtvalue\"; then\n    _info \"This record already exists, skipping\"\n    return 0\n  fi\n\n  _add_data=\"{\\\"data\\\":\\\"$txtvalue\\\"}\"\n  for t in $(echo \"$response\" | tr '{' \"\\n\" | grep \"\\\"name\\\":\\\"$_sub_domain\\\"\" | tr ',' \"\\n\" | grep '\"data\"' | cut -d : -f 2); do\n    _debug2 t \"$t\"\n    # ignore empty (previously removed) records, to prevent useless _acme-challenge TXT entries\n    if [ \"$t\" ] && [ \"$t\" != '\"\"' ]; then\n      _add_data=\"$_add_data,{\\\"data\\\":$t}\"\n    fi\n  done\n  _debug2 _add_data \"$_add_data\"\n\n  _info \"Adding record\"\n  if _gd_rest PUT \"domains/$_domain/records/TXT/$_sub_domain\" \"[$_add_data]\"; then\n    _debug \"Checking updated records of '${fulldomain}'\"\n\n    if ! _gd_rest GET \"domains/$_domain/records/TXT/$_sub_domain\"; then\n      _err \"Validating TXT record for '${fulldomain}' with rest error [$?].\" \"$response\"\n      return 1\n    fi\n\n    if ! _contains \"$response\" \"$txtvalue\"; then\n      _err \"TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!\"\n      return 1\n    fi\n  else\n    _err \"Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set.\"\n    return 1\n  fi\n\n  _sleep 10\n  _info \"Added TXT record '${txtvalue}' for '${fulldomain}'.\"\n  return 0\n}\n\n#fulldomain\ndns_gd_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  GD_Key=\"${GD_Key:-$(_readaccountconf_mutable GD_Key)}\"\n  GD_Secret=\"${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting existing records\"\n  if ! _gd_rest GET \"domains/$_domain/records/TXT/$_sub_domain\"; then\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _info \"The record does not exist, skip\"\n    return 0\n  fi\n\n  _add_data=\"\"\n  for t in $(echo \"$response\" | tr '{' \"\\n\" | grep \"\\\"name\\\":\\\"$_sub_domain\\\"\" | tr ',' \"\\n\" | grep '\"data\"' | cut -d : -f 2); do\n    _debug2 t \"$t\"\n    if [ \"$t\" ] && [ \"$t\" != \"\\\"$txtvalue\\\"\" ]; then\n      if [ \"$_add_data\" ]; then\n        _add_data=\"$_add_data,{\\\"data\\\":$t}\"\n      else\n        _add_data=\"{\\\"data\\\":$t}\"\n      fi\n    fi\n  done\n  if [ -z \"$_add_data\" ]; then\n    # delete empty record\n    _debug \"Delete last record for '${fulldomain}'\"\n    if ! _gd_rest DELETE \"domains/$_domain/records/TXT/$_sub_domain\"; then\n      _err \"Cannot delete empty TXT record for '$fulldomain'\"\n      return 1\n    fi\n  else\n    # remove specific TXT value, keeping other entries\n    _debug2 _add_data \"$_add_data\"\n    if ! _gd_rest PUT \"domains/$_domain/records/TXT/$_sub_domain\" \"[$_add_data]\"; then\n      _err \"Cannot update TXT record for '$fulldomain'\"\n      return 1\n    fi\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _gd_rest GET \"domains/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" '\"code\":\"NOT_FOUND\"'; then\n      _debug \"$h not found\"\n    else\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_gd_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Authorization: sso-key $GD_Key:$GD_Secret\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$data\" ] || [ \"$m\" = \"DELETE\" ]; then\n    _debug \"data ($m): \" \"$data\"\n    response=\"$(_post \"$data\" \"$GD_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$GD_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error on rest call ($m): $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  if _contains \"$response\" \"UNABLE_TO_AUTHENTICATE\"; then\n    _err \"It seems that your api key or secret is not correct.\"\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_geoscaling.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_geoscaling_info='GeoScaling.com\nSite: GeoScaling.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_geoscaling\nOptions:\n GEOSCALING_Username Username. This is usually NOT an email address\n GEOSCALING_Password Password\n'\n\n#-- dns_geoscaling_add() - Add TXT record --------------------------------------\n# Usage: dns_geoscaling_add _acme-challenge.subdomain.domain.com \"XyZ123...\"\n\ndns_geoscaling_add() {\n  full_domain=$1\n  txt_value=$2\n  _info \"Using DNS-01 Geoscaling DNS2 hook\"\n\n  GEOSCALING_Username=\"${GEOSCALING_Username:-$(_readaccountconf_mutable GEOSCALING_Username)}\"\n  GEOSCALING_Password=\"${GEOSCALING_Password:-$(_readaccountconf_mutable GEOSCALING_Password)}\"\n  if [ -z \"$GEOSCALING_Username\" ] || [ -z \"$GEOSCALING_Password\" ]; then\n    GEOSCALING_Username=\n    GEOSCALING_Password=\n    _err \"No auth details provided. Please set user credentials using the \\$GEOSCALING_Username and \\$GEOSCALING_Password environment variables.\"\n    return 1\n  fi\n  _saveaccountconf_mutable GEOSCALING_Username \"${GEOSCALING_Username}\"\n  _saveaccountconf_mutable GEOSCALING_Password \"${GEOSCALING_Password}\"\n\n  # Fills in the $zone_id and $zone_name\n  find_zone \"${full_domain}\" || return 1\n  _debug \"Zone id '${zone_id}' will be used.\"\n\n  # We're logged in here\n\n  # we should add ${full_domain} minus the trailing ${zone_name}\n\n  prefix=$(echo \"${full_domain}\" | sed \"s|\\\\.${zone_name}\\$||\")\n\n  body=\"id=${zone_id}&name=${prefix}&type=TXT&content=${txt_value}&ttl=300&prio=0\"\n\n  do_post \"$body\" \"https://www.geoscaling.com/dns2/ajax/add_record.php\"\n  exit_code=\"$?\"\n  if [ \"${exit_code}\" -eq 0 ]; then\n    _info \"TXT record added successfully.\"\n  else\n    _err \"Couldn't add the TXT record.\"\n  fi\n  do_logout\n  return \"${exit_code}\"\n}\n\n#-- dns_geoscaling_rm() - Remove TXT record ------------------------------------\n# Usage: dns_geoscaling_rm _acme-challenge.subdomain.domain.com \"XyZ123...\"\n\ndns_geoscaling_rm() {\n  full_domain=$1\n  txt_value=$2\n  _info \"Cleaning up after DNS-01 Geoscaling DNS2 hook\"\n\n  GEOSCALING_Username=\"${GEOSCALING_Username:-$(_readaccountconf_mutable GEOSCALING_Username)}\"\n  GEOSCALING_Password=\"${GEOSCALING_Password:-$(_readaccountconf_mutable GEOSCALING_Password)}\"\n  if [ -z \"$GEOSCALING_Username\" ] || [ -z \"$GEOSCALING_Password\" ]; then\n    GEOSCALING_Username=\n    GEOSCALING_Password=\n    _err \"No auth details provided. Please set user credentials using the \\$GEOSCALING_Username and \\$GEOSCALING_Password environment variables.\"\n    return 1\n  fi\n  _saveaccountconf_mutable GEOSCALING_Username \"${GEOSCALING_Username}\"\n  _saveaccountconf_mutable GEOSCALING_Password \"${GEOSCALING_Password}\"\n\n  # fills in the $zone_id\n  find_zone \"${full_domain}\" || return 1\n  _debug \"Zone id '${zone_id}' will be used.\"\n\n  # Here we're logged in\n  # Find the record id to clean\n\n  # get the domain\n  response=$(do_get \"https://www.geoscaling.com/dns2/index.php?module=domain&id=${zone_id}\")\n  _debug2 \"response\" \"$response\"\n\n  table=\"$(echo \"${response}\" | tr -d '\\n' | sed 's|.*<div class=\"box\"><div class=\"boxtitle\">Basic Records</div><div class=\"boxtext\"><table|<table|; s|</table>.*|</table>|')\"\n  _debug2 table \"${table}\"\n  names=$(echo \"${table}\" | _egrep_o 'id=\"[0-9]+\\.name\">[^<]*</td>' | sed 's|</td>||; s|.*>||')\n  ids=$(echo \"${table}\" | _egrep_o 'id=\"[0-9]+\\.name\">[^<]*</td>' | sed 's|\\.name\">.*||; s|id=\"||')\n  types=$(echo \"${table}\" | _egrep_o 'id=\"[0-9]+\\.type\">[^<]*</td>' | sed 's|</td>||; s|.*>||')\n  values=$(echo \"${table}\" | _egrep_o 'id=\"[0-9]+\\.content\">[^<]*</td>' | sed 's|</td>||; s|.*>||')\n\n  _debug2 names \"${names}\"\n  _debug2 ids \"${ids}\"\n  _debug2 types \"${types}\"\n  _debug2 values \"${values}\"\n\n  # look for line whose name is ${full_domain}, whose type is TXT, and whose value is ${txt_value}\n  line_num=\"$(echo \"${values}\" | grep -F -n -- \"${txt_value}\" | _head_n 1 | cut -d ':' -f 1)\"\n  _debug2 line_num \"${line_num}\"\n  found_id=\n  if [ -n \"$line_num\" ]; then\n    type=$(echo \"${types}\" | sed -n \"${line_num}p\")\n    name=$(echo \"${names}\" | sed -n \"${line_num}p\")\n    id=$(echo \"${ids}\" | sed -n \"${line_num}p\")\n\n    _debug2 type \"$type\"\n    _debug2 name \"$name\"\n    _debug2 id \"$id\"\n    _debug2 full_domain \"$full_domain\"\n\n    if [ \"${type}\" = \"TXT\" ] && [ \"${name}\" = \"${full_domain}\" ]; then\n      found_id=${id}\n    fi\n  fi\n\n  if [ \"${found_id}\" = \"\" ]; then\n    _err \"Can not find record id.\"\n    return 0\n  fi\n\n  # Remove the record\n  body=\"id=${zone_id}&record_id=${found_id}\"\n  response=$(do_post \"$body\" \"https://www.geoscaling.com/dns2/ajax/delete_record.php\")\n  exit_code=\"$?\"\n  if [ \"$exit_code\" -eq 0 ]; then\n    _info \"Record removed successfully.\"\n  else\n    _err \"Could not clean (remove) up the record. Please go to Geoscaling administration interface and clean it by hand.\"\n  fi\n  do_logout\n  return \"${exit_code}\"\n}\n\n########################## PRIVATE FUNCTIONS ###########################\n\ndo_get() {\n  _url=$1\n  export _H1=\"Cookie: $geoscaling_phpsessid_cookie\"\n  _get \"${_url}\"\n}\n\ndo_post() {\n  _body=$1\n  _url=$2\n  export _H1=\"Cookie: $geoscaling_phpsessid_cookie\"\n  _post \"${_body}\" \"${_url}\"\n}\n\ndo_login() {\n\n  _info \"Logging in...\"\n\n  username_encoded=\"$(printf \"%s\" \"${GEOSCALING_Username}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${GEOSCALING_Password}\" | _url_encode)\"\n  body=\"username=${username_encoded}&password=${password_encoded}\"\n\n  response=$(_post \"$body\" \"https://www.geoscaling.com/dns2/index.php?module=auth\")\n  _debug2 response \"${response}\"\n\n  #retcode=$(grep '^HTTP[^ ]*' \"${HTTP_HEADER}\" | _head_n 1 | _egrep_o '[0-9]+$')\n  retcode=$(grep '^HTTP[^ ]*' \"${HTTP_HEADER}\" | _head_n 1 | cut -d ' ' -f 2)\n\n  if [ \"$retcode\" != \"302\" ]; then\n    _err \"Geoscaling login failed for user ${GEOSCALING_Username}. Check ${HTTP_HEADER} file\"\n    return 1\n  fi\n\n  geoscaling_phpsessid_cookie=\"$(grep -i '^set-cookie:' \"${HTTP_HEADER}\" | _egrep_o 'PHPSESSID=[^;]*;' | tr -d ';')\"\n  return 0\n\n}\n\ndo_logout() {\n  _info \"Logging out.\"\n  response=\"$(do_get \"https://www.geoscaling.com/dns2/index.php?module=auth\")\"\n  _debug2 response \"$response\"\n  return 0\n}\n\nfind_zone() {\n  domain=\"$1\"\n\n  # do login\n  do_login || return 1\n\n  # get zones\n  response=\"$(do_get \"https://www.geoscaling.com/dns2/index.php?module=domains\")\"\n\n  table=\"$(echo \"${response}\" | tr -d '\\n' | sed 's|.*<div class=\"box\"><div class=\"boxtitle\">Your domains</div><div class=\"boxtext\"><table|<table|; s|</table>.*|</table>|')\"\n  _debug2 table \"${table}\"\n  zone_names=\"$(echo \"${table}\" | _egrep_o '<b>[^<]*</b>' | sed 's|<b>||;s|</b>||')\"\n  _debug2 _matches \"${zone_names}\"\n  # Zone names and zone IDs are in same order\n  zone_ids=$(echo \"${table}\" | _egrep_o '<a href=.index\\.php\\?module=domain&id=[0-9]+. onclick=\"javascript:show_loader\\(\\);\">' | sed 's|.*id=||;s|. .*||')\n\n  _debug2 \"These are the zones on this Geoscaling account:\"\n  _debug2 \"zone_names\" \"${zone_names}\"\n  _debug2 \"And these are their respective IDs:\"\n  _debug2 \"zone_ids\" \"${zone_ids}\"\n  if [ -z \"${zone_names}\" ] || [ -z \"${zone_ids}\" ]; then\n    _err \"Can not get zone names or IDs.\"\n    return 1\n  fi\n  # Walk through all possible zone names\n  strip_counter=1\n  while true; do\n    attempted_zone=$(echo \"${domain}\" | cut -d . -f \"${strip_counter}\"-)\n\n    # All possible zone names have been tried\n    if [ -z \"${attempted_zone}\" ]; then\n      _err \"No zone for domain '${domain}' found.\"\n      return 1\n    fi\n\n    _debug \"Looking for zone '${attempted_zone}'\"\n\n    line_num=\"$(echo \"${zone_names}\" | grep -n \"^${attempted_zone}\\$\" | _head_n 1 | cut -d : -f 1)\"\n    _debug2 line_num \"${line_num}\"\n    if [ \"$line_num\" ]; then\n      zone_id=$(echo \"${zone_ids}\" | sed -n \"${line_num}p\")\n      zone_name=$(echo \"${zone_names}\" | sed -n \"${line_num}p\")\n      if [ -z \"${zone_id}\" ]; then\n        _err \"Can not find zone id.\"\n        return 1\n      fi\n      _debug \"Found relevant zone '${attempted_zone}' with id '${zone_id}' - will be used for domain '${domain}'.\"\n      return 0\n    fi\n\n    _debug \"Zone '${attempted_zone}' doesn't exist, let's try a less specific zone.\"\n    strip_counter=$(_math \"${strip_counter}\" + 1)\n  done\n}\n# vim: et:ts=2:sw=2:\n"
  },
  {
    "path": "dnsapi/dns_googledomains.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_googledomains_info='Google Domains\nSite: Domains.Google.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_googledomains\nOptions:\n GOOGLEDOMAINS_ACCESS_TOKEN API Access Token\n GOOGLEDOMAINS_ZONE Zone\nIssues: github.com/acmesh-official/acme.sh/issues/4545\nAuthor: Alex Leigh <leigh@alexleigh.me>\n'\n\nGOOGLEDOMAINS_API=\"https://acmedns.googleapis.com/v1/acmeChallengeSets\"\n\n######## Public functions ########\n\n#Usage: dns_googledomains_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_googledomains_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Invoking Google Domains ACME DNS API.\"\n\n  if ! _dns_googledomains_setup; then\n    return 1\n  fi\n\n  zone=\"$(_dns_googledomains_get_zone \"$fulldomain\")\"\n  if [ -z \"$zone\" ]; then\n    _err \"Could not find a Google Domains-managed zone containing the requested domain.\"\n    return 1\n  fi\n\n  _debug zone \"$zone\"\n  _debug txtvalue \"$txtvalue\"\n\n  _info \"Adding TXT record for $fulldomain.\"\n  if _dns_googledomains_api \"$zone\" \":rotateChallenges\" \"{\\\"accessToken\\\":\\\"$GOOGLEDOMAINS_ACCESS_TOKEN\\\",\\\"recordsToAdd\\\":[{\\\"fqdn\\\":\\\"$fulldomain\\\",\\\"digest\\\":\\\"$txtvalue\\\"}],\\\"keepExpiredRecords\\\":true}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"TXT record added.\"\n      return 0\n    else\n      _err \"Error adding TXT record.\"\n      return 1\n    fi\n  fi\n\n  _err \"Error adding TXT record.\"\n  return 1\n}\n\n#Usage: dns_googledomains_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_googledomains_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Invoking Google Domains ACME DNS API.\"\n\n  if ! _dns_googledomains_setup; then\n    return 1\n  fi\n\n  zone=\"$(_dns_googledomains_get_zone \"$fulldomain\")\"\n  if [ -z \"$zone\" ]; then\n    _err \"Could not find a Google Domains-managed domain based on request.\"\n    return 1\n  fi\n\n  _debug zone \"$zone\"\n  _debug txtvalue \"$txtvalue\"\n\n  _info \"Removing TXT record for $fulldomain.\"\n  if _dns_googledomains_api \"$zone\" \":rotateChallenges\" \"{\\\"accessToken\\\":\\\"$GOOGLEDOMAINS_ACCESS_TOKEN\\\",\\\"recordsToRemove\\\":[{\\\"fqdn\\\":\\\"$fulldomain\\\",\\\"digest\\\":\\\"$txtvalue\\\"}],\\\"keepExpiredRecords\\\":true}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _err \"Error removing TXT record.\"\n      return 1\n    else\n      _info \"TXT record removed.\"\n      return 0\n    fi\n  fi\n\n  _err \"Error removing TXT record.\"\n  return 1\n}\n\n######## Private functions ########\n\n_dns_googledomains_setup() {\n  if [ -n \"$GOOGLEDOMAINS_SETUP_COMPLETED\" ]; then\n    return 0\n  fi\n\n  GOOGLEDOMAINS_ACCESS_TOKEN=\"${GOOGLEDOMAINS_ACCESS_TOKEN:-$(_readaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN)}\"\n  GOOGLEDOMAINS_ZONE=\"${GOOGLEDOMAINS_ZONE:-$(_readaccountconf_mutable GOOGLEDOMAINS_ZONE)}\"\n\n  if [ -z \"$GOOGLEDOMAINS_ACCESS_TOKEN\" ]; then\n    GOOGLEDOMAINS_ACCESS_TOKEN=\"\"\n    _err \"Google Domains access token was not specified.\"\n    _err \"Please visit Google Domains Security settings to provision an ACME DNS API access token.\"\n    return 1\n  fi\n\n  if [ \"$GOOGLEDOMAINS_ZONE\" ]; then\n    _savedomainconf GOOGLEDOMAINS_ACCESS_TOKEN \"$GOOGLEDOMAINS_ACCESS_TOKEN\"\n    _savedomainconf GOOGLEDOMAINS_ZONE \"$GOOGLEDOMAINS_ZONE\"\n  else\n    _saveaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN \"$GOOGLEDOMAINS_ACCESS_TOKEN\"\n    _clearaccountconf_mutable GOOGLEDOMAINS_ZONE\n    _clearaccountconf GOOGLEDOMAINS_ZONE\n  fi\n\n  _debug GOOGLEDOMAINS_ACCESS_TOKEN \"$GOOGLEDOMAINS_ACCESS_TOKEN\"\n  _debug GOOGLEDOMAINS_ZONE \"$GOOGLEDOMAINS_ZONE\"\n\n  GOOGLEDOMAINS_SETUP_COMPLETED=1\n  return 0\n}\n\n_dns_googledomains_get_zone() {\n  domain=$1\n\n  # Use zone directly if provided\n  if [ \"$GOOGLEDOMAINS_ZONE\" ]; then\n    if ! _dns_googledomains_api \"$GOOGLEDOMAINS_ZONE\"; then\n      return 1\n    fi\n\n    echo \"$GOOGLEDOMAINS_ZONE\"\n    return 0\n  fi\n\n  i=2\n  while true; do\n    curr=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug curr \"$curr\"\n\n    if [ -z \"$curr\" ]; then\n      return 1\n    fi\n\n    if _dns_googledomains_api \"$curr\"; then\n      echo \"$curr\"\n      return 0\n    fi\n\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_dns_googledomains_api() {\n  zone=$1\n  apimethod=$2\n  data=\"$3\"\n\n  if [ -z \"$data\" ]; then\n    response=\"$(_get \"$GOOGLEDOMAINS_API/$zone$apimethod\")\"\n  else\n    _debug data \"$data\"\n    export _H1=\"Content-Type: application/json\"\n    response=\"$(_post \"$data\" \"$GOOGLEDOMAINS_API/$zone$apimethod\")\"\n  fi\n\n  _debug response \"$response\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error\"\n    return 1\n  fi\n\n  if _contains \"$response\" \"\\\"error\\\": {\"; then\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_he.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_he_info='Hurricane Electric HE.net\nSite: dns.he.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_he\nOptions:\n HE_Username Username\n HE_Password Password\nIssues: github.com/angel333/acme.sh/issues/\nAuthor: Ondrej Simek <me@ondrejsimek.com>\n'\n\n#-- dns_he_add() - Add TXT record --------------------------------------\n# Usage: dns_he_add _acme-challenge.subdomain.domain.com \"XyZ123...\"\n\ndns_he_add() {\n  _full_domain=$1\n  _txt_value=$2\n  _info \"Using DNS-01 Hurricane Electric hook\"\n\n  HE_Username=\"${HE_Username:-$(_readaccountconf_mutable HE_Username)}\"\n  HE_Password=\"${HE_Password:-$(_readaccountconf_mutable HE_Password)}\"\n  if [ -z \"$HE_Username\" ] || [ -z \"$HE_Password\" ]; then\n    HE_Username=\n    HE_Password=\n    _err \"No auth details provided. Please set user credentials using the \\$HE_Username and \\$HE_Password environment variables.\"\n    return 1\n  fi\n  _saveaccountconf_mutable HE_Username \"$HE_Username\"\n  _saveaccountconf_mutable HE_Password \"$HE_Password\"\n\n  # Fills in the $_zone_id\n  _find_zone \"$_full_domain\" || return 1\n  _debug \"Zone id \\\"$_zone_id\\\" will be used.\"\n  username_encoded=\"$(printf \"%s\" \"${HE_Username}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${HE_Password}\" | _url_encode)\"\n  body=\"email=${username_encoded}&pass=${password_encoded}\"\n  body=\"$body&account=\"\n  body=\"$body&menu=edit_zone\"\n  body=\"$body&Type=TXT\"\n  body=\"$body&hosted_dns_zoneid=$_zone_id\"\n  body=\"$body&hosted_dns_recordid=\"\n  body=\"$body&hosted_dns_editzone=1\"\n  body=\"$body&Priority=\"\n  body=\"$body&Name=$_full_domain\"\n  body=\"$body&Content=$_txt_value\"\n  body=\"$body&TTL=300\"\n  body=\"$body&hosted_dns_editrecord=Submit\"\n  response=\"$(_post \"$body\" \"https://dns.he.net/\")\"\n  exit_code=\"$?\"\n  if [ \"$exit_code\" -eq 0 ]; then\n    _info \"TXT record added successfully.\"\n  else\n    _err \"Couldn't add the TXT record.\"\n  fi\n  _debug2 response \"$response\"\n  return \"$exit_code\"\n}\n\n#-- dns_he_rm() - Remove TXT record ------------------------------------\n# Usage: dns_he_rm _acme-challenge.subdomain.domain.com \"XyZ123...\"\n\ndns_he_rm() {\n  _full_domain=$1\n  _txt_value=$2\n  _info \"Cleaning up after DNS-01 Hurricane Electric hook\"\n  HE_Username=\"${HE_Username:-$(_readaccountconf_mutable HE_Username)}\"\n  HE_Password=\"${HE_Password:-$(_readaccountconf_mutable HE_Password)}\"\n  # fills in the $_zone_id\n  _find_zone \"$_full_domain\" || return 1\n  _debug \"Zone id \\\"$_zone_id\\\" will be used.\"\n\n  # Find the record id to clean\n  username_encoded=\"$(printf \"%s\" \"${HE_Username}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${HE_Password}\" | _url_encode)\"\n  body=\"email=${username_encoded}&pass=${password_encoded}\"\n  body=\"$body&hosted_dns_zoneid=$_zone_id\"\n  body=\"$body&menu=edit_zone\"\n  body=\"$body&hosted_dns_editzone=\"\n\n  response=\"$(_post \"$body\" \"https://dns.he.net/\")\"\n  _debug2 \"response\" \"$response\"\n  if ! _contains \"$response\" \"$_txt_value\"; then\n    _debug \"The txt record is not found, just skip\"\n    return 0\n  fi\n  _record_id=\"$(echo \"$response\" | tr -d \"#\" | sed \"s/<tr/#<tr/g\" | tr -d \"\\n\" | tr \"#\" \"\\n\" | grep \"$_full_domain\" | grep '\"dns_tr\"' | grep -- \"$_txt_value\" | cut -d '\"' -f 4)\"\n  _debug2 _record_id \"$_record_id\"\n  if [ -z \"$_record_id\" ]; then\n    _err \"Can not find record id\"\n    return 1\n  fi\n  # Remove the record\n  username_encoded=\"$(printf \"%s\" \"${HE_Username}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${HE_Password}\" | _url_encode)\"\n  body=\"email=${username_encoded}&pass=${password_encoded}\"\n  body=\"$body&menu=edit_zone\"\n  body=\"$body&hosted_dns_zoneid=$_zone_id\"\n  body=\"$body&hosted_dns_recordid=$_record_id\"\n  body=\"$body&hosted_dns_editzone=1\"\n  body=\"$body&hosted_dns_delrecord=1\"\n  body=\"$body&hosted_dns_delconfirm=delete\"\n  _post \"$body\" \"https://dns.he.net/\" |\n    grep '<div id=\"dns_status\" onClick=\"hideThis(this);\">Successfully removed record.</div>' \\\n      >/dev/null\n  exit_code=\"$?\"\n  if [ \"$exit_code\" -eq 0 ]; then\n    _info \"Record removed successfully.\"\n  else\n    _err \"Could not clean (remove) up the record. Please go to HE administration interface and clean it by hand.\"\n    return \"$exit_code\"\n  fi\n}\n\n########################## PRIVATE FUNCTIONS ###########################\n\n_find_zone() {\n  _domain=\"$1\"\n  username_encoded=\"$(printf \"%s\" \"${HE_Username}\" | _url_encode)\"\n  password_encoded=\"$(printf \"%s\" \"${HE_Password}\" | _url_encode)\"\n  body=\"email=${username_encoded}&pass=${password_encoded}\"\n  response=\"$(_post \"$body\" \"https://dns.he.net/\")\"\n  _debug2 response \"$response\"\n  if _contains \"$response\" '>Incorrect<'; then\n    _err \"Unable to login to dns.he.net please check username and password\"\n    return 1\n  fi\n  _table=\"$(echo \"$response\" | tr -d \"#\" | sed \"s/<table/#<table/g\" | tr -d \"\\n\" | tr \"#\" \"\\n\" | grep 'id=\"domains_table\"')\"\n  _debug2 _table \"$_table\"\n  _matches=\"$(echo \"$_table\" | sed \"s/<tr/#<tr/g\" | tr \"#\" \"\\n\" | grep 'alt=\"edit\"' | tr -d \" \" | sed \"s/<td/#<td/g\" | tr \"#\" \"\\n\" | grep 'hosted_dns_zoneid')\"\n  _debug2 _matches \"$_matches\"\n  # Zone names and zone IDs are in same order\n  _zone_ids=$(echo \"$_matches\" | _egrep_o \"hosted_dns_zoneid=[0-9]*&\" | cut -d = -f 2 | tr -d '&')\n  _zone_names=$(echo \"$_matches\" | _egrep_o \"name=.*onclick\" | cut -d '\"' -f 2)\n  _debug2 \"These are the zones on this HE account:\"\n  _debug2 \"_zone_names\" \"$_zone_names\"\n  _debug2 \"And these are their respective IDs:\"\n  _debug2 \"_zone_ids\" \"$_zone_ids\"\n  if [ -z \"$_zone_names\" ] || [ -z \"$_zone_ids\" ]; then\n    _err \"Can not get zone names.\"\n    return 1\n  fi\n  # Walk through all possible zone names\n  _strip_counter=1\n  while true; do\n    _attempted_zone=$(echo \"$_domain\" | cut -d . -f \"${_strip_counter}\"-)\n\n    # All possible zone names have been tried\n    if [ -z \"$_attempted_zone\" ]; then\n      _err \"No zone for domain \\\"$_domain\\\" found.\"\n      return 1\n    fi\n\n    _debug \"Looking for zone \\\"${_attempted_zone}\\\"\"\n\n    line_num=\"$(echo \"$_zone_names\" | grep -n \"^$_attempted_zone\\$\" | _head_n 1 | cut -d : -f 1)\"\n    _debug2 line_num \"$line_num\"\n    if [ \"$line_num\" ]; then\n      _zone_id=$(echo \"$_zone_ids\" | sed -n \"${line_num}p\")\n      if [ -z \"$_zone_id\" ]; then\n        _err \"Can not find zone id.\"\n        return 1\n      fi\n      _debug \"Found relevant zone \\\"$_attempted_zone\\\" with id \\\"$_zone_id\\\" - will be used for domain \\\"$_domain\\\".\"\n      return 0\n    fi\n\n    _debug \"Zone \\\"$_attempted_zone\\\" doesn't exist, let's try a less specific zone.\"\n    _strip_counter=$(_math \"$_strip_counter\" + 1)\n  done\n}\n# vim: et:ts=2:sw=2:\n"
  },
  {
    "path": "dnsapi/dns_he_ddns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_he_ddns_info='Hurricane Electric HE.net DDNS\nSite: dns.he.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns\nOptions:\n HE_DDNS_KEY The DDNS key\nIssues: https://github.com/acmesh-official/acme.sh/issues/5238\nAuthor: Markku Leiniö\n'\n\nHE_DDNS_URL=\"https://dyn.dns.he.net/nic/update\"\n\n########  Public functions #####################\n\n#Usage: dns_he_ddns_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_he_ddns_add() {\n  fulldomain=$1\n  txtvalue=$2\n  HE_DDNS_KEY=\"${HE_DDNS_KEY:-$(_readaccountconf_mutable HE_DDNS_KEY)}\"\n  if [ -z \"$HE_DDNS_KEY\" ]; then\n    HE_DDNS_KEY=\"\"\n    _err \"You didn't specify a DDNS key for accessing the TXT record in HE API.\"\n    return 1\n  fi\n  #Save the DDNS key to the account conf file.\n  _saveaccountconf_mutable HE_DDNS_KEY \"$HE_DDNS_KEY\"\n\n  _info \"Using Hurricane Electric DDNS API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  response=\"$(_post \"hostname=$fulldomain&password=$HE_DDNS_KEY&txt=$txtvalue\" \"$HE_DDNS_URL\")\"\n  _info \"Response: $response\"\n  _contains \"$response\" \"good\" && return 0 || return 1\n}\n\n# dns_he_ddns_rm() is not doing anything because the API call always updates the\n# contents of the existing record (that the API key gives access to).\n\ndns_he_ddns_rm() {\n  fulldomain=$1\n  _debug \"Delete TXT record called for '${fulldomain}', not doing anything.\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_hetzner.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_hetzner_info='Hetzner.com\nSite: Hetzner.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_hetzner\nOptions:\n HETZNER_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/2943\n'\n\nHETZNER_Api=\"https://dns.hetzner.com/api/v1\"\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\n# Ref: https://dns.hetzner.com/api-docs/\ndns_hetzner_add() {\n  full_domain=$1\n  txt_value=$2\n\n  HETZNER_Token=\"${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}\"\n\n  if [ -z \"$HETZNER_Token\" ]; then\n    HETZNER_Token=\"\"\n    _err \"You didn't specify a Hetzner api token.\"\n    _err \"You can get yours from here https://dns.hetzner.com/settings/api-token.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable HETZNER_Token \"$HETZNER_Token\"\n\n  _debug \"First detect the root zone\"\n\n  if ! _get_root \"$full_domain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting TXT records\"\n  if ! _find_record \"$_sub_domain\" \"$txt_value\"; then\n    return 1\n  fi\n\n  if [ -z \"$_record_id\" ]; then\n    _info \"Adding record\"\n    if _hetzner_rest POST \"records\" \"{\\\"zone_id\\\":\\\"${HETZNER_Zone_ID}\\\",\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"value\\\":\\\"$txt_value\\\",\\\"ttl\\\":120}\"; then\n      if _contains \"$response\" \"$txt_value\"; then\n        _info \"Record added, OK\"\n        _sleep 2\n        return 0\n      fi\n    fi\n    _err \"Add txt record error${_response_error}\"\n    return 1\n  else\n    _info \"Found record id: $_record_id.\"\n    _info \"Record found, do nothing.\"\n    return 0\n    # we could modify a record, if the names for txt records for *.example.com and example.com would be not the same\n    #if _hetzner_rest PUT \"records/${_record_id}\" \"{\\\"zone_id\\\":\\\"${HETZNER_Zone_ID}\\\",\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$full_domain\\\",\\\"value\\\":\\\"$txt_value\\\",\\\"ttl\\\":120}\"; then\n    #  if _contains \"$response\" \"$txt_value\"; then\n    #    _info \"Modified, OK\"\n    #    return 0\n    #  fi\n    #fi\n    #_err \"Add txt record error (modify).\"\n    #return 1\n  fi\n}\n\n# Usage: full_domain txt_value\n# Used to remove the txt record after validation\ndns_hetzner_rm() {\n  full_domain=$1\n  txt_value=$2\n\n  HETZNER_Token=\"${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$full_domain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting TXT records\"\n  if ! _find_record \"$_sub_domain\" \"$txt_value\"; then\n    return 1\n  fi\n\n  if [ -z \"$_record_id\" ]; then\n    _info \"Remove not needed. Record not found.\"\n  else\n    if ! _hetzner_rest DELETE \"records/$_record_id\"; then\n      _err \"Delete record error${_response_error}\"\n      return 1\n    fi\n    _sleep 2\n    _info \"Record deleted\"\n  fi\n}\n\n####################  Private functions below ##################################\n#returns\n# _record_id=a8d58f22d6931bf830eaa0ec6464bf81  if found; or 1 if error\n_find_record() {\n  unset _record_id\n  _record_name=$1\n  _record_value=$2\n\n  if [ -z \"$_record_value\" ]; then\n    _record_value='[^\"]*'\n  fi\n\n  _debug \"Getting all records\"\n  _hetzner_rest GET \"records?zone_id=${_domain_id}\"\n\n  if _response_has_error; then\n    _err \"Error${_response_error}\"\n    return 1\n  else\n    _record_id=$(\n      echo \"$response\" |\n        grep -o \"{[^\\{\\}]*\\\"name\\\":\\\"$_record_name\\\"[^\\}]*}\" |\n        grep \"\\\"value\\\":\\\"$_record_value\\\"\" |\n        while read -r record; do\n          # test for type and\n          if [ -n \"$(echo \"$record\" | _egrep_o '\"type\":\"TXT\"')\" ]; then\n            echo \"$record\" | _egrep_o '\"id\":\"[^\"]*\"' | cut -d : -f 2 | tr -d \\\"\n            break\n          fi\n        done\n    )\n  fi\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  domain_without_acme=$(echo \"$domain\" | cut -d . -f 2-)\n  domain_param_name=$(echo \"HETZNER_Zone_ID_for_${domain_without_acme}\" | sed 's/[\\.\\-]/_/g')\n\n  _debug \"Reading zone_id for '$domain_without_acme' from config...\"\n  HETZNER_Zone_ID=$(_readdomainconf \"$domain_param_name\")\n  if [ \"$HETZNER_Zone_ID\" ]; then\n    _debug \"Found, using: $HETZNER_Zone_ID\"\n    if ! _hetzner_rest GET \"zones/${HETZNER_Zone_ID}\"; then\n      _debug \"Zone with id '$HETZNER_Zone_ID' does not exist.\"\n      _cleardomainconf \"$domain_param_name\"\n      unset HETZNER_Zone_ID\n    else\n      if _contains \"$response\" \"\\\"id\\\":\\\"$HETZNER_Zone_ID\\\"\"; then\n        _domain=$(printf \"%s\\n\" \"$response\" | _egrep_o '\"name\":\"[^\"]*\"' | cut -d : -f 2 | tr -d \\\" | head -n 1)\n        if [ \"$_domain\" ]; then\n          _cut_length=$((${#domain} - ${#_domain} - 1))\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -c \"1-$_cut_length\")\n          _domain_id=\"$HETZNER_Zone_ID\"\n          return 0\n        else\n          return 1\n        fi\n      else\n        return 1\n      fi\n    fi\n  fi\n\n  _debug \"Trying to get zone id by domain name for '$domain_without_acme'.\"\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    _debug h \"$h\"\n\n    _hetzner_rest GET \"zones?name=$h\"\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" || _contains \"$response\" '\"total_entries\":1'; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"\\[.\\\"id\\\":\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\")\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        HETZNER_Zone_ID=$_domain_id\n        _savedomainconf \"$domain_param_name\" \"$HETZNER_Zone_ID\"\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n#returns\n# _response_error\n_response_has_error() {\n  unset _response_error\n\n  err_part=\"$(echo \"$response\" | _egrep_o '\"error\":\\{[^\\}]*\\}')\"\n\n  if [ -n \"$err_part\" ]; then\n    err_code=$(echo \"$err_part\" | _egrep_o '\"code\":[0-9]+' | cut -d : -f 2)\n    err_message=$(echo \"$err_part\" | _egrep_o '\"message\":\"[^\"]+\"' | cut -d : -f 2 | tr -d \\\")\n\n    if [ -n \"$err_code\" ] && [ -n \"$err_message\" ]; then\n      _response_error=\" - message: ${err_message}, code: ${err_code}\"\n      return 0\n    fi\n  fi\n\n  return 1\n}\n\n#returns\n# response\n_hetzner_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  key_trimmed=$(echo \"$HETZNER_Token\" | tr -d \\\")\n\n  export _H1=\"Content-TType: application/json\"\n  export _H2=\"Auth-API-Token: $key_trimmed\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$HETZNER_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$HETZNER_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ] || _response_has_error; then\n    _debug \"Error$_response_error\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_hetznercloud.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_hetznercloud_info='Hetzner Cloud DNS\nSite: Hetzner.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hetznercloud\nOptions:\n HETZNER_TOKEN API token for the Hetzner Cloud DNS API\nOptional:\n HETZNER_TTL Custom TTL for new TXT rrsets (default 120)\n HETZNER_API Override API endpoint (default https://api.hetzner.cloud/v1)\n HETZNER_MAX_ATTEMPTS Number of 1s polls to wait for async actions (default 120)\nIssues: github.com/acmesh-official/acme.sh/issues\n'\n\nHETZNERCLOUD_API_DEFAULT=\"https://api.hetzner.cloud/v1\"\nHETZNERCLOUD_TTL_DEFAULT=120\nHETZNER_MAX_ATTEMPTS_DEFAULT=120\n\n########  Public functions #####################\n\ndns_hetznercloud_add() {\n  fulldomain=\"$(_idn \"${1}\")\"\n  txtvalue=\"${2}\"\n\n  _info \"Using Hetzner Cloud DNS API to add record\"\n\n  if ! _hetznercloud_init; then\n    return 1\n  fi\n\n  if ! _hetznercloud_prepare_zone \"${fulldomain}\"; then\n    _err \"Unable to determine Hetzner Cloud zone for ${fulldomain}\"\n    return 1\n  fi\n\n  if ! _hetznercloud_get_rrset; then\n    return 1\n  fi\n\n  if [ \"${_hetznercloud_last_http_code}\" = \"200\" ]; then\n    if _hetznercloud_rrset_contains_value \"${txtvalue}\"; then\n      _info \"TXT record already present; nothing to do.\"\n      return 0\n    fi\n  elif [ \"${_hetznercloud_last_http_code}\" != \"404\" ]; then\n    _hetznercloud_log_http_error \"Failed to query existing TXT rrset\" \"${_hetznercloud_last_http_code}\"\n    return 1\n  fi\n\n  add_payload=\"$(_hetznercloud_build_add_payload \"${txtvalue}\")\"\n  if [ -z \"${add_payload}\" ]; then\n    _err \"Failed to build request payload.\"\n    return 1\n  fi\n\n  if ! _hetznercloud_api POST \"${_hetznercloud_rrset_action_add}\" \"${add_payload}\"; then\n    return 1\n  fi\n\n  case \"${_hetznercloud_last_http_code}\" in\n  200 | 201 | 202 | 204)\n    if ! _hetznercloud_handle_action_response \"TXT record add\"; then\n      return 1\n    fi\n    _info \"Hetzner Cloud TXT record added.\"\n    return 0\n    ;;\n  401 | 403)\n    _err \"Hetzner Cloud DNS API authentication failed (HTTP ${_hetznercloud_last_http_code}). Check HETZNER_TOKEN for the new API.\"\n    _hetznercloud_log_http_error \"\" \"${_hetznercloud_last_http_code}\"\n    return 1\n    ;;\n  409 | 422)\n    _hetznercloud_log_http_error \"Hetzner Cloud DNS rejected the add_records request\" \"${_hetznercloud_last_http_code}\"\n    return 1\n    ;;\n  *)\n    _hetznercloud_log_http_error \"Hetzner Cloud DNS add_records request failed\" \"${_hetznercloud_last_http_code}\"\n    return 1\n    ;;\n  esac\n}\n\ndns_hetznercloud_rm() {\n  fulldomain=\"$(_idn \"${1}\")\"\n  txtvalue=\"${2}\"\n\n  _info \"Using Hetzner Cloud DNS API to remove record\"\n\n  if ! _hetznercloud_init; then\n    return 1\n  fi\n\n  if ! _hetznercloud_prepare_zone \"${fulldomain}\"; then\n    _err \"Unable to determine Hetzner Cloud zone for ${fulldomain}\"\n    return 1\n  fi\n\n  if ! _hetznercloud_get_rrset; then\n    return 1\n  fi\n\n  if [ \"${_hetznercloud_last_http_code}\" = \"404\" ]; then\n    _info \"TXT rrset does not exist; nothing to remove.\"\n    return 0\n  fi\n\n  if [ \"${_hetznercloud_last_http_code}\" != \"200\" ]; then\n    _hetznercloud_log_http_error \"Failed to query existing TXT rrset\" \"${_hetznercloud_last_http_code}\"\n    return 1\n  fi\n\n  if _hetznercloud_rrset_contains_value \"${txtvalue}\"; then\n    remove_payload=\"$(_hetznercloud_build_remove_payload \"${txtvalue}\")\"\n    if [ -z \"${remove_payload}\" ]; then\n      _err \"Failed to build remove_records payload.\"\n      return 1\n    fi\n    if ! _hetznercloud_api POST \"${_hetznercloud_rrset_action_remove}\" \"${remove_payload}\"; then\n      return 1\n    fi\n    case \"${_hetznercloud_last_http_code}\" in\n    200 | 201 | 202 | 204)\n      if ! _hetznercloud_handle_action_response \"TXT record remove\"; then\n        return 1\n      fi\n      _info \"Hetzner Cloud TXT record removed.\"\n      return 0\n      ;;\n    401 | 403)\n      _err \"Hetzner Cloud DNS API authentication failed (HTTP ${_hetznercloud_last_http_code}). Check HETZNER_TOKEN for the new API.\"\n      _hetznercloud_log_http_error \"\" \"${_hetznercloud_last_http_code}\"\n      return 1\n      ;;\n    404)\n      _info \"TXT rrset already absent after remove action.\"\n      return 0\n      ;;\n    409 | 422)\n      _hetznercloud_log_http_error \"Hetzner Cloud DNS rejected the remove_records request\" \"${_hetznercloud_last_http_code}\"\n      return 1\n      ;;\n    *)\n      _hetznercloud_log_http_error \"Hetzner Cloud DNS remove_records request failed\" \"${_hetznercloud_last_http_code}\"\n      return 1\n      ;;\n    esac\n  else\n    _info \"TXT value not present; nothing to remove.\"\n    return 0\n  fi\n}\n\n####################  Private functions ##################################\n\n_hetznercloud_init() {\n  HETZNER_TOKEN=\"${HETZNER_TOKEN:-$(_readaccountconf_mutable HETZNER_TOKEN)}\"\n  if [ -z \"${HETZNER_TOKEN}\" ]; then\n    _err \"The environment variable HETZNER_TOKEN must be set for the Hetzner Cloud DNS API.\"\n    return 1\n  fi\n  HETZNER_TOKEN=$(echo \"${HETZNER_TOKEN}\" | tr -d '\"')\n  _saveaccountconf_mutable HETZNER_TOKEN \"${HETZNER_TOKEN}\"\n\n  HETZNER_API=\"${HETZNER_API:-$(_readaccountconf_mutable HETZNER_API)}\"\n  if [ -z \"${HETZNER_API}\" ]; then\n    HETZNER_API=\"${HETZNERCLOUD_API_DEFAULT}\"\n  fi\n  _saveaccountconf_mutable HETZNER_API \"${HETZNER_API}\"\n\n  HETZNER_TTL=\"${HETZNER_TTL:-$(_readaccountconf_mutable HETZNER_TTL)}\"\n  if [ -z \"${HETZNER_TTL}\" ]; then\n    HETZNER_TTL=\"${HETZNERCLOUD_TTL_DEFAULT}\"\n  fi\n  ttl_check=$(printf \"%s\" \"${HETZNER_TTL}\" | tr -d '0-9')\n  if [ -n \"${ttl_check}\" ]; then\n    _err \"HETZNER_TTL must be an integer value.\"\n    return 1\n  fi\n  _saveaccountconf_mutable HETZNER_TTL \"${HETZNER_TTL}\"\n\n  HETZNER_MAX_ATTEMPTS=\"${HETZNER_MAX_ATTEMPTS:-$(_readaccountconf_mutable HETZNER_MAX_ATTEMPTS)}\"\n  if [ -z \"${HETZNER_MAX_ATTEMPTS}\" ]; then\n    HETZNER_MAX_ATTEMPTS=\"${HETZNER_MAX_ATTEMPTS_DEFAULT}\"\n  fi\n  attempts_check=$(printf \"%s\" \"${HETZNER_MAX_ATTEMPTS}\" | tr -d '0-9')\n  if [ -n \"${attempts_check}\" ]; then\n    _err \"HETZNER_MAX_ATTEMPTS must be an integer value.\"\n    return 1\n  fi\n  _saveaccountconf_mutable HETZNER_MAX_ATTEMPTS \"${HETZNER_MAX_ATTEMPTS}\"\n\n  return 0\n}\n\n_hetznercloud_prepare_zone() {\n  _hetznercloud_zone_id=\"\"\n  _hetznercloud_zone_name=\"\"\n  _hetznercloud_zone_name_lc=\"\"\n  _hetznercloud_rr_name=\"\"\n  _hetznercloud_rrset_path=\"\"\n  _hetznercloud_rrset_action_add=\"\"\n  _hetznercloud_rrset_action_remove=\"\"\n  fulldomain_lc=$(printf \"%s\" \"${1}\" | sed 's/\\.$//' | _lower_case)\n\n  i=2\n  p=1\n  while true; do\n    candidate=$(printf \"%s\" \"${fulldomain_lc}\" | cut -d . -f \"${i}\"-100)\n    if [ -z \"${candidate}\" ]; then\n      return 1\n    fi\n\n    if _hetznercloud_get_zone_by_candidate \"${candidate}\"; then\n      zone_name_lc=\"${_hetznercloud_zone_name_lc}\"\n      if [ \"${fulldomain_lc}\" = \"${zone_name_lc}\" ]; then\n        _hetznercloud_rr_name=\"@\"\n      else\n        suffix=\".${zone_name_lc}\"\n        if _endswith \"${fulldomain_lc}\" \"${suffix}\"; then\n          _hetznercloud_rr_name=\"${fulldomain_lc%\"${suffix}\"}\"\n        else\n          _hetznercloud_rr_name=\"${fulldomain_lc}\"\n        fi\n      fi\n      _hetznercloud_rrset_path=$(printf \"%s\" \"${_hetznercloud_rr_name}\" | _url_encode)\n      _hetznercloud_rrset_action_add=\"/zones/${_hetznercloud_zone_id}/rrsets/${_hetznercloud_rrset_path}/TXT/actions/add_records\"\n      _hetznercloud_rrset_action_remove=\"/zones/${_hetznercloud_zone_id}/rrsets/${_hetznercloud_rrset_path}/TXT/actions/remove_records\"\n      return 0\n    fi\n    p=${i}\n    i=$(_math \"${i}\" + 1)\n  done\n}\n\n_hetznercloud_get_zone_by_candidate() {\n  candidate=\"${1}\"\n  zone_key=$(printf \"%s\" \"${candidate}\" | sed 's/[^A-Za-z0-9]/_/g')\n  zone_conf_key=\"HETZNERCLOUD_ZONE_ID_for_${zone_key}\"\n\n  cached_zone_id=$(_readdomainconf \"${zone_conf_key}\")\n  if [ -n \"${cached_zone_id}\" ]; then\n    if _hetznercloud_api GET \"/zones/${cached_zone_id}\"; then\n      if [ \"${_hetznercloud_last_http_code}\" = \"200\" ]; then\n        zone_data=$(printf \"%s\" \"${response}\" | _normalizeJson | sed 's/^{\"zone\"://' | sed 's/}$//')\n        if _hetznercloud_parse_zone_fields \"${zone_data}\"; then\n          zone_name_lc=$(printf \"%s\" \"${_hetznercloud_zone_name}\" | _lower_case)\n          if [ \"${zone_name_lc}\" = \"${candidate}\" ]; then\n            return 0\n          fi\n        fi\n      elif [ \"${_hetznercloud_last_http_code}\" = \"404\" ]; then\n        _cleardomainconf \"${zone_conf_key}\"\n      fi\n    else\n      return 1\n    fi\n  fi\n\n  if _hetznercloud_api GET \"/zones/${candidate}\"; then\n    if [ \"${_hetznercloud_last_http_code}\" = \"200\" ]; then\n      zone_data=$(printf \"%s\" \"${response}\" | _normalizeJson | sed 's/^{\"zone\"://' | sed 's/}$//')\n      if _hetznercloud_parse_zone_fields \"${zone_data}\"; then\n        zone_name_lc=$(printf \"%s\" \"${_hetznercloud_zone_name}\" | _lower_case)\n        if [ \"${zone_name_lc}\" = \"${candidate}\" ]; then\n          _savedomainconf \"${zone_conf_key}\" \"${_hetznercloud_zone_id}\"\n          return 0\n        fi\n      fi\n    elif [ \"${_hetznercloud_last_http_code}\" != \"404\" ]; then\n      _hetznercloud_log_http_error \"Hetzner Cloud zone lookup failed\" \"${_hetznercloud_last_http_code}\"\n      return 1\n    fi\n  else\n    return 1\n  fi\n\n  encoded_candidate=$(printf \"%s\" \"${candidate}\" | _url_encode)\n  if ! _hetznercloud_api GET \"/zones?name=${encoded_candidate}\"; then\n    return 1\n  fi\n  if [ \"${_hetznercloud_last_http_code}\" != \"200\" ]; then\n    if [ \"${_hetznercloud_last_http_code}\" = \"404\" ]; then\n      return 1\n    fi\n    _hetznercloud_log_http_error \"Hetzner Cloud zone search failed\" \"${_hetznercloud_last_http_code}\"\n    return 1\n  fi\n\n  zone_data=$(_hetznercloud_extract_zone_from_list \"${response}\" \"${candidate}\")\n  if [ -z \"${zone_data}\" ]; then\n    return 1\n  fi\n  if ! _hetznercloud_parse_zone_fields \"${zone_data}\"; then\n    return 1\n  fi\n  _savedomainconf \"${zone_conf_key}\" \"${_hetznercloud_zone_id}\"\n  return 0\n}\n\n_hetznercloud_parse_zone_fields() {\n  zone_json=\"${1}\"\n  if [ -z \"${zone_json}\" ]; then\n    return 1\n  fi\n  normalized=$(printf \"%s\" \"${zone_json}\" | _normalizeJson)\n  zone_id=$(printf \"%s\" \"${normalized}\" | _egrep_o '\"id\":[^,}]*' | _head_n 1 | cut -d : -f 2 | tr -d ' \"')\n  zone_name=$(printf \"%s\" \"${normalized}\" | _egrep_o '\"name\":\"[^\"]*\"' | _head_n 1 | cut -d : -f 2 | tr -d '\"')\n  if [ -z \"${zone_id}\" ] || [ -z \"${zone_name}\" ]; then\n    return 1\n  fi\n  zone_name_trimmed=$(printf \"%s\" \"${zone_name}\" | sed 's/\\.$//')\n  if zone_name_ascii=$(_idn \"${zone_name_trimmed}\"); then\n    zone_name=\"${zone_name_ascii}\"\n  else\n    zone_name=\"${zone_name_trimmed}\"\n  fi\n  _hetznercloud_zone_id=\"${zone_id}\"\n  _hetznercloud_zone_name=\"${zone_name}\"\n  _hetznercloud_zone_name_lc=$(printf \"%s\" \"${zone_name}\" | _lower_case)\n  return 0\n}\n\n_hetznercloud_extract_zone_from_list() {\n  list_response=$(printf \"%s\" \"${1}\" | _normalizeJson)\n  candidate=\"${2}\"\n  escaped_candidate=$(_hetznercloud_escape_regex \"${candidate}\")\n  printf \"%s\" \"${list_response}\" | _egrep_o \"{[^{}]*\\\"name\\\":\\\"${escaped_candidate}\\\"[^{}]*}\" | _head_n 1\n}\n\n_hetznercloud_escape_regex() {\n  printf \"%s\" \"${1}\" | sed 's/\\\\/\\\\\\\\/g' | sed 's/\\./\\\\./g' | sed 's/-/\\\\-/g'\n}\n\n_hetznercloud_get_rrset() {\n  if [ -z \"${_hetznercloud_zone_id}\" ] || [ -z \"${_hetznercloud_rrset_path}\" ]; then\n    return 1\n  fi\n  if ! _hetznercloud_api GET \"/zones/${_hetznercloud_zone_id}/rrsets/${_hetznercloud_rrset_path}/TXT\"; then\n    return 1\n  fi\n  return 0\n}\n\n_hetznercloud_rrset_contains_value() {\n  wanted_value=\"${1}\"\n  normalized=$(printf \"%s\" \"${response}\" | _normalizeJson)\n  escaped_value=$(_hetznercloud_escape_value \"${wanted_value}\")\n  search_pattern=\"\\\"value\\\":\\\"\\\\\\\\\\\"${escaped_value}\\\\\\\\\\\"\\\"\"\n  if _contains \"${normalized}\" \"${search_pattern}\"; then\n    return 0\n  fi\n  return 1\n}\n\n_hetznercloud_build_add_payload() {\n  value=\"${1}\"\n  escaped_value=$(_hetznercloud_escape_value \"${value}\")\n  printf '{\"ttl\":%s,\"records\":[{\"value\":\"\\\\\"%s\\\\\"\"}]}' \"${HETZNER_TTL}\" \"${escaped_value}\"\n}\n\n_hetznercloud_build_remove_payload() {\n  value=\"${1}\"\n  escaped_value=$(_hetznercloud_escape_value \"${value}\")\n  printf '{\"records\":[{\"value\":\"\\\\\"%s\\\\\"\"}]}' \"${escaped_value}\"\n}\n\n_hetznercloud_escape_value() {\n  printf \"%s\" \"${1}\" | sed 's/\\\\/\\\\\\\\/g' | sed 's/\"/\\\\\"/g'\n}\n\n_hetznercloud_error_message() {\n  if [ -z \"${response}\" ]; then\n    return 1\n  fi\n  message=$(printf \"%s\" \"${response}\" | _normalizeJson | _egrep_o '\"message\":\"[^\"]*\"' | _head_n 1 | cut -d : -f 2 | tr -d '\"')\n  if [ -n \"${message}\" ]; then\n    printf \"%s\" \"${message}\"\n    return 0\n  fi\n  return 1\n}\n\n_hetznercloud_log_http_error() {\n  context=\"${1}\"\n  code=\"${2}\"\n  message=\"$(_hetznercloud_error_message)\"\n  if [ -n \"${context}\" ]; then\n    if [ -n \"${message}\" ]; then\n      _err \"${context} (HTTP ${code}): ${message}\"\n    else\n      _err \"${context} (HTTP ${code})\"\n    fi\n  else\n    if [ -n \"${message}\" ]; then\n      _err \"Hetzner Cloud DNS API error (HTTP ${code}): ${message}\"\n    else\n      _err \"Hetzner Cloud DNS API error (HTTP ${code})\"\n    fi\n  fi\n}\n\n_hetznercloud_api() {\n  method=\"${1}\"\n  ep=\"${2}\"\n  data=\"${3}\"\n  retried=\"${4}\"\n\n  if [ -z \"${method}\" ]; then\n    method=\"GET\"\n  fi\n\n  if ! _startswith \"${ep}\" \"/\"; then\n    ep=\"/${ep}\"\n  fi\n  url=\"${HETZNER_API}${ep}\"\n\n  export _H1=\"Authorization: Bearer ${HETZNER_TOKEN}\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"\"\n  export _H4=\"\"\n  export _H5=\"\"\n\n  : >\"${HTTP_HEADER}\"\n\n  if [ \"${method}\" = \"GET\" ]; then\n    response=\"$(_get \"${url}\")\"\n  else\n    if [ -z \"${data}\" ]; then\n      data=\"{}\"\n    fi\n    response=\"$(_post \"${data}\" \"${url}\" \"\" \"${method}\" \"application/json\")\"\n  fi\n  ret=\"${?}\"\n\n  _hetznercloud_last_http_code=$(grep \"^HTTP\" \"${HTTP_HEADER}\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d '\\r\\n')\n\n  if [ \"${ret}\" != \"0\" ]; then\n    return 1\n  fi\n\n  if [ \"${_hetznercloud_last_http_code}\" = \"429\" ] && [ \"${retried}\" != \"retried\" ]; then\n    retry_after=$(grep -i \"^Retry-After\" \"${HTTP_HEADER}\" | _tail_n 1 | cut -d : -f 2 | tr -d ' \\r')\n    if [ -z \"${retry_after}\" ]; then\n      retry_after=1\n    fi\n    _info \"Hetzner Cloud DNS API rate limit hit; retrying in ${retry_after} seconds.\"\n    _sleep \"${retry_after}\"\n    if ! _hetznercloud_api \"${method}\" \"${ep}\" \"${data}\" \"retried\"; then\n      return 1\n    fi\n    return 0\n  fi\n\n  return 0\n}\n\n_hetznercloud_handle_action_response() {\n  context=\"${1}\"\n  if [ -z \"${response}\" ]; then\n    return 0\n  fi\n\n  normalized=$(printf \"%s\" \"${response}\" | _normalizeJson)\n\n  failed_message=\"\"\n  if failed_message=$(_hetznercloud_extract_failed_action_message \"${normalized}\"); then\n    if [ -n \"${failed_message}\" ]; then\n      _err \"Hetzner Cloud DNS ${context} failed: ${failed_message}\"\n    else\n      _err \"Hetzner Cloud DNS ${context} failed.\"\n    fi\n    return 1\n  fi\n\n  action_ids=\"\"\n  if action_ids=$(_hetznercloud_extract_action_ids \"${normalized}\"); then\n    for action_id in ${action_ids}; do\n      if [ -z \"${action_id}\" ]; then\n        continue\n      fi\n      if ! _hetznercloud_wait_for_action \"${action_id}\" \"${context}\"; then\n        return 1\n      fi\n    done\n  fi\n\n  return 0\n}\n\n_hetznercloud_extract_failed_action_message() {\n  normalized=\"${1}\"\n  failed_section=$(printf \"%s\" \"${normalized}\" | _egrep_o '\"failed_actions\":\\[[^]]*\\]')\n  if [ -z \"${failed_section}\" ]; then\n    return 1\n  fi\n  if _contains \"${failed_section}\" '\"failed_actions\":[]'; then\n    return 1\n  fi\n  message=$(printf \"%s\" \"${failed_section}\" | _egrep_o '\"message\":\"[^\"]*\"' | _head_n 1 | cut -d : -f 2 | tr -d '\"')\n  if [ -n \"${message}\" ]; then\n    printf \"%s\" \"${message}\"\n  else\n    printf \"%s\" \"${failed_section}\"\n  fi\n  return 0\n}\n\n_hetznercloud_extract_action_ids() {\n  normalized=\"${1}\"\n  actions_section=$(printf \"%s\" \"${normalized}\" | _egrep_o '\"actions\":\\[[^]]*\\]')\n  if [ -z \"${actions_section}\" ]; then\n    return 1\n  fi\n  action_ids=$(printf \"%s\" \"${actions_section}\" | _egrep_o '\"id\":[0-9]*' | cut -d : -f 2 | tr -d '\"' | tr '\\n' ' ')\n  action_ids=$(printf \"%s\" \"${action_ids}\" | tr -s ' ')\n  action_ids=$(printf \"%s\" \"${action_ids}\" | sed 's/^ //;s/ $//')\n  if [ -z \"${action_ids}\" ]; then\n    return 1\n  fi\n  printf \"%s\" \"${action_ids}\"\n  return 0\n}\n\n_hetznercloud_wait_for_action() {\n  action_id=\"${1}\"\n  context=\"${2}\"\n  attempts=\"0\"\n\n  while true; do\n    if ! _hetznercloud_api GET \"/actions/${action_id}\"; then\n      return 1\n    fi\n    if [ \"${_hetznercloud_last_http_code}\" != \"200\" ]; then\n      _hetznercloud_log_http_error \"Hetzner Cloud DNS action ${action_id} query failed\" \"${_hetznercloud_last_http_code}\"\n      return 1\n    fi\n\n    normalized=$(printf \"%s\" \"${response}\" | _normalizeJson)\n    action_status=$(_hetznercloud_action_status_from_normalized \"${normalized}\")\n\n    if [ -z \"${action_status}\" ]; then\n      _err \"Hetzner Cloud DNS ${context} action ${action_id} returned no status.\"\n      return 1\n    fi\n\n    if [ \"${action_status}\" = \"success\" ]; then\n      return 0\n    fi\n\n    if [ \"${action_status}\" = \"error\" ]; then\n      if action_error=$(_hetznercloud_action_error_from_normalized \"${normalized}\"); then\n        _err \"Hetzner Cloud DNS ${context} action ${action_id} failed: ${action_error}\"\n      else\n        _err \"Hetzner Cloud DNS ${context} action ${action_id} failed.\"\n      fi\n      return 1\n    fi\n\n    attempts=$(_math \"${attempts}\" + 1)\n    if [ \"${attempts}\" -ge \"${HETZNER_MAX_ATTEMPTS}\" ]; then\n      _err \"Hetzner Cloud DNS ${context} action ${action_id} did not complete after ${HETZNER_MAX_ATTEMPTS} attempts.\"\n      return 1\n    fi\n\n    _sleep 1\n  done\n}\n\n_hetznercloud_action_status_from_normalized() {\n  normalized=\"${1}\"\n  status=$(printf \"%s\" \"${normalized}\" | _egrep_o '\"status\":\"[^\"]*\"' | _head_n 1 | cut -d : -f 2 | tr -d '\"')\n  printf \"%s\" \"${status}\"\n}\n\n_hetznercloud_action_error_from_normalized() {\n  normalized=\"${1}\"\n  error_section=$(printf \"%s\" \"${normalized}\" | _egrep_o '\"error\":{[^}]*}')\n  if [ -z \"${error_section}\" ]; then\n    return 1\n  fi\n  message=$(printf \"%s\" \"${error_section}\" | _egrep_o '\"message\":\"[^\"]*\"' | _head_n 1 | cut -d : -f 2 | tr -d '\"')\n  if [ -n \"${message}\" ]; then\n    printf \"%s\" \"${message}\"\n    return 0\n  fi\n  code=$(printf \"%s\" \"${error_section}\" | _egrep_o '\"code\":\"[^\"]*\"' | _head_n 1 | cut -d : -f 2 | tr -d '\"')\n  if [ -n \"${code}\" ]; then\n    printf \"%s\" \"${code}\"\n    return 0\n  fi\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_hexonet.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_hexonet_info='Hexonet.com\nSite: Hexonet.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hexonet\nOptions:\n Hexonet_Login Login. E.g. \"username!roleId\"\n Hexonet_Password Role Password\nIssues: github.com/acmesh-official/acme.sh/issues/2389\n'\n\nHexonet_Api=\"https://coreapi.1api.net/api/call.cgi\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_hexonet_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Hexonet_Login=\"${Hexonet_Login:-$(_readaccountconf_mutable Hexonet_Login)}\"\n  Hexonet_Password=\"${Hexonet_Password:-$(_readaccountconf_mutable Hexonet_Password)}\"\n  if [ -z \"$Hexonet_Login\" ] || [ -z \"$Hexonet_Password\" ]; then\n    Hexonet_Login=\"\"\n    Hexonet_Password=\"\"\n    _err \"You must export variables: Hexonet_Login and Hexonet_Password\"\n    return 1\n  fi\n\n  if ! _contains \"$Hexonet_Login\" \"!\"; then\n    _err \"It seems that the Hexonet_Login=$Hexonet_Login is not a restrivteed user.\"\n    _err \"Please check and retry.\"\n    return 1\n  fi\n\n  #save the username and password to the account conf file.\n  _saveaccountconf_mutable Hexonet_Login \"$Hexonet_Login\"\n  _saveaccountconf_mutable Hexonet_Password \"$Hexonet_Password\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _hexonet_rest \"command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT\"\n\n  if ! _contains \"$response\" \"CODE=200\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  _info \"Adding record\"\n  if _hexonet_rest \"command=UpdateDNSZone&dnszone=${_domain}.&addrr0=${_sub_domain}%20IN%20TXT%20${txtvalue}\"; then\n    if _contains \"$response\" \"CODE=200\"; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_hexonet_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Hexonet_Login=\"${Hexonet_Login:-$(_readaccountconf_mutable Hexonet_Login)}\"\n  Hexonet_Password=\"${Hexonet_Password:-$(_readaccountconf_mutable Hexonet_Password)}\"\n  if [ -z \"$Hexonet_Login\" ] || [ -z \"$Hexonet_Password\" ]; then\n    Hexonet_Login=\"\"\n    Hexonet_Password=\"\"\n    _err \"You must export variables: Hexonet_Login and Hexonet_Password\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _hexonet_rest \"command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT&RR=${_sub_domain}%20IN%20TXT%20\\\"${txtvalue}\\\"\"\n\n  if ! _contains \"$response\" \"CODE=200\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"PROPERTY[TOTAL][0]=\" | cut -d = -f 2)\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    if ! _hexonet_rest \"command=UpdateDNSZone&dnszone=${_domain}.&delrr0=${_sub_domain}%20IN%20TXT%20\\\"${txtvalue}\\\"\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" \"CODE=200\"\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _hexonet_rest \"command=QueryDNSZoneRRList&dnszone=${h}.\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"CODE=200\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_hexonet_rest() {\n  query_params=\"$1\"\n  _debug \"$query_params\"\n\n  response=\"$(_get \"${Hexonet_Api}?s_login=${Hexonet_Login}&s_pw=${Hexonet_Password}&${query_params}\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $query_params\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_hostingde.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_hostingde_info='Hosting.de\nSite: Hosting.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_hostingde\nOptions:\n HOSTINGDE_ENDPOINT Endpoint. E.g. \"https://secure.hosting.de\"\n HOSTINGDE_APIKEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2058\n'\n\n########  Public functions #####################\n\ndns_hostingde_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  _debug \"Calling: _hostingde_addRecord() '${fulldomain}' '${txtvalue}'\"\n  _hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_addRecord\n  return $?\n}\n\ndns_hostingde_rm() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  _debug \"Calling: _hostingde_removeRecord() '${fulldomain}' '${txtvalue}'\"\n  _hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_removeRecord\n  return $?\n}\n\n#################### own Private functions below ##################################\n\n_hostingde_apiKey() {\n  HOSTINGDE_APIKEY=\"${HOSTINGDE_APIKEY:-$(_readaccountconf_mutable HOSTINGDE_APIKEY)}\"\n  HOSTINGDE_ENDPOINT=\"${HOSTINGDE_ENDPOINT:-$(_readaccountconf_mutable HOSTINGDE_ENDPOINT)}\"\n  if [ -z \"$HOSTINGDE_APIKEY\" ] || [ -z \"$HOSTINGDE_ENDPOINT\" ]; then\n    HOSTINGDE_APIKEY=\"\"\n    HOSTINGDE_ENDPOINT=\"\"\n    _err \"You haven't specified hosting.de API key or endpoint yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable HOSTINGDE_APIKEY \"$HOSTINGDE_APIKEY\"\n  _saveaccountconf_mutable HOSTINGDE_ENDPOINT \"$HOSTINGDE_ENDPOINT\"\n}\n\n_hostingde_parse() {\n  find=\"${1}\"\n  if [ \"${2}\" ]; then\n    notfind=\"${2}\"\n  fi\n  if [ \"${notfind}\" ]; then\n    _egrep_o \\\"\"${find}\\\":.*\" | grep -v \"${notfind}\" | cut -d ':' -f 2 | cut -d ',' -f 1 | tr -d ' '\n  else\n    _egrep_o \\\"\"${find}\\\":.*\" | cut -d ':' -f 2 | cut -d ',' -f 1 | tr -d ' '\n  fi\n}\n\n_hostingde_getZoneConfig() {\n  _info \"Getting ZoneConfig\"\n  curZone=\"${fulldomain#*.}\"\n  returnCode=1\n  while _contains \"${curZone}\" \"\\\\.\"; do\n    curData=\"{\\\"filter\\\":{\\\"field\\\":\\\"zoneName\\\",\\\"value\\\":\\\"${curZone}\\\"},\\\"limit\\\":1,\\\"authToken\\\":\\\"${HOSTINGDE_APIKEY}\\\"}\"\n    curResult=\"$(_post \"${curData}\" \"${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind\")\"\n    _debug \"Calling zoneConfigsFind: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind'\"\n    _debug \"Result of zoneConfigsFind: '$curResult'\"\n    if _contains \"${curResult}\" '\"status\": \"error\"'; then\n      if _contains \"${curResult}\" '\"code\": 10109'; then\n        _err \"The API-Key is invalid or could not be found\"\n      else\n        _err \"UNKNOWN API ERROR\"\n      fi\n      returnCode=1\n      break\n    fi\n    if _contains \"${curResult}\" '\"totalEntries\": 1'; then\n      _info \"Retrieved zone data.\"\n      _debug \"Zone data: '${curResult}'\"\n      zoneConfigId=$(echo \"${curResult}\" | _hostingde_parse \"id\")\n      zoneConfigName=$(echo \"${curResult}\" | _hostingde_parse \"name\")\n      zoneConfigType=$(echo \"${curResult}\" | _hostingde_parse \"type\" \"FindZoneConfigsResult\")\n      zoneConfigExpire=$(echo \"${curResult}\" | _hostingde_parse \"expire\")\n      zoneConfigNegativeTtl=$(echo \"${curResult}\" | _hostingde_parse \"negativeTtl\")\n      zoneConfigRefresh=$(echo \"${curResult}\" | _hostingde_parse \"refresh\")\n      zoneConfigRetry=$(echo \"${curResult}\" | _hostingde_parse \"retry\")\n      zoneConfigTtl=$(echo \"${curResult}\" | _hostingde_parse \"ttl\")\n      zoneConfigDnsServerGroupId=$(echo \"${curResult}\" | _hostingde_parse \"dnsServerGroupId\")\n      zoneConfigEmailAddress=$(echo \"${curResult}\" | _hostingde_parse \"emailAddress\")\n      zoneConfigDnsSecMode=$(echo \"${curResult}\" | _hostingde_parse \"dnsSecMode\")\n      zoneConfigTemplateValues=$(echo \"${curResult}\" | _hostingde_parse \"templateValues\")\n\n      if [ \"$zoneConfigTemplateValues\" != \"null\" ]; then\n        _debug \"Zone is tied to a template.\"\n        zoneConfigTemplateValuesTemplateId=$(echo \"${curResult}\" | _hostingde_parse \"templateId\")\n        zoneConfigTemplateValuesTemplateName=$(echo \"${curResult}\" | _hostingde_parse \"templateName\")\n        zoneConfigTemplateValuesTemplateReplacementsIPv4=$(echo \"${curResult}\" | _hostingde_parse \"ipv4Replacement\")\n        zoneConfigTemplateValuesTemplateReplacementsIPv6=$(echo \"${curResult}\" | _hostingde_parse \"ipv6Replacement\")\n        zoneConfigTemplateValuesTemplateReplacementsMailIPv4=$(echo \"${curResult}\" | _hostingde_parse \"mailIpv4Replacement\")\n        zoneConfigTemplateValuesTemplateReplacementsMailIPv6=$(echo \"${curResult}\" | _hostingde_parse \"mailIpv6Replacement\")\n        zoneConfigTemplateValuesTemplateTieToTemplate=$(echo \"${curResult}\" | _hostingde_parse \"tieToTemplate\")\n\n        zoneConfigTemplateValues=\"{\\\"templateId\\\":${zoneConfigTemplateValuesTemplateId},\\\"templateName\\\":${zoneConfigTemplateValuesTemplateName},\\\"templateReplacements\\\":{\\\"ipv4Replacement\\\":${zoneConfigTemplateValuesTemplateReplacementsIPv4},\\\"ipv6Replacement\\\":${zoneConfigTemplateValuesTemplateReplacementsIPv6},\\\"mailIpv4Replacement\\\":${zoneConfigTemplateValuesTemplateReplacementsMailIPv4},\\\"mailIpv6Replacement\\\":${zoneConfigTemplateValuesTemplateReplacementsMailIPv6}},\\\"tieToTemplate\\\":${zoneConfigTemplateValuesTemplateTieToTemplate}}\"\n        _debug \"Template values: '{$zoneConfigTemplateValues}'\"\n      fi\n\n      if [ \"${zoneConfigType}\" != \"\\\"NATIVE\\\"\" ]; then\n        _err \"Zone is not native\"\n        returnCode=1\n        break\n      fi\n      _debug \"zoneConfigId '${zoneConfigId}'\"\n      returnCode=0\n      break\n    fi\n    curZone=\"${curZone#*.}\"\n  done\n  if [ $returnCode -ne 0 ]; then\n    _info \"ZoneEnd reached, Zone ${curZone} not found in hosting.de API\"\n  fi\n  return $returnCode\n}\n\n_hostingde_getZoneStatus() {\n  _debug \"Checking Zone status\"\n  curData=\"{\\\"filter\\\":{\\\"field\\\":\\\"zoneConfigId\\\",\\\"value\\\":${zoneConfigId}},\\\"limit\\\":1,\\\"authToken\\\":\\\"${HOSTINGDE_APIKEY}\\\"}\"\n  curResult=\"$(_post \"${curData}\" \"${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind\")\"\n  _debug \"Calling zonesFind '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind'\"\n  _debug \"Result of zonesFind '$curResult'\"\n  zoneStatus=$(echo \"${curResult}\" | _hostingde_parse \"status\" \"success\")\n  _debug \"zoneStatus '${zoneStatus}'\"\n  return 0\n}\n\n_hostingde_addRecord() {\n  _info \"Adding record to zone\"\n  _hostingde_getZoneStatus\n  _debug \"Result of zoneStatus: '${zoneStatus}'\"\n  while [ \"${zoneStatus}\" != \"\\\"active\\\"\" ]; do\n    _sleep 5\n    _hostingde_getZoneStatus\n    _debug \"Result of zoneStatus: '${zoneStatus}'\"\n  done\n  curData=\"{\\\"authToken\\\":\\\"${HOSTINGDE_APIKEY}\\\",\\\"zoneConfig\\\":{\\\"id\\\":${zoneConfigId},\\\"name\\\":${zoneConfigName},\\\"type\\\":${zoneConfigType},\\\"dnsServerGroupId\\\":${zoneConfigDnsServerGroupId},\\\"dnsSecMode\\\":${zoneConfigDnsSecMode},\\\"emailAddress\\\":${zoneConfigEmailAddress},\\\"soaValues\\\":{\\\"expire\\\":${zoneConfigExpire},\\\"negativeTtl\\\":${zoneConfigNegativeTtl},\\\"refresh\\\":${zoneConfigRefresh},\\\"retry\\\":${zoneConfigRetry},\\\"ttl\\\":${zoneConfigTtl}},\\\"templateValues\\\":${zoneConfigTemplateValues}},\\\"recordsToAdd\\\":[{\\\"name\\\":\\\"${fulldomain}\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"\\\\\\\"${txtvalue}\\\\\\\"\\\",\\\"ttl\\\":3600}]}\"\n  curResult=\"$(_post \"${curData}\" \"${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate\")\"\n  _debug \"Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'\"\n  _debug \"Result of zoneUpdate: '$curResult'\"\n  if _contains \"${curResult}\" '\"status\": \"error\"'; then\n    if _contains \"${curResult}\" '\"code\": 10109'; then\n      _err \"The API-Key is invalid or could not be found\"\n    else\n      _err \"UNKNOWN API ERROR\"\n    fi\n    return 1\n  fi\n  return 0\n}\n\n_hostingde_removeRecord() {\n  _info \"Removing record from zone\"\n  _hostingde_getZoneStatus\n  _debug \"Result of zoneStatus: '$zoneStatus'\"\n  while [ \"$zoneStatus\" != \"\\\"active\\\"\" ]; do\n    _sleep 5\n    _hostingde_getZoneStatus\n    _debug \"Result of zoneStatus: '$zoneStatus'\"\n  done\n  curData=\"{\\\"authToken\\\":\\\"${HOSTINGDE_APIKEY}\\\",\\\"zoneConfig\\\":{\\\"id\\\":${zoneConfigId},\\\"name\\\":${zoneConfigName},\\\"type\\\":${zoneConfigType},\\\"dnsServerGroupId\\\":${zoneConfigDnsServerGroupId},\\\"dnsSecMode\\\":${zoneConfigDnsSecMode},\\\"emailAddress\\\":${zoneConfigEmailAddress},\\\"soaValues\\\":{\\\"expire\\\":${zoneConfigExpire},\\\"negativeTtl\\\":${zoneConfigNegativeTtl},\\\"refresh\\\":${zoneConfigRefresh},\\\"retry\\\":${zoneConfigRetry},\\\"ttl\\\":${zoneConfigTtl}},\\\"templateValues\\\":${zoneConfigTemplateValues}},\\\"recordsToDelete\\\":[{\\\"name\\\":\\\"${fulldomain}\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"\\\\\\\"${txtvalue}\\\\\\\"\\\"}]}\"\n  curResult=\"$(_post \"${curData}\" \"${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate\")\"\n  _debug \"Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'\"\n  _debug \"Result of zoneUpdate: '$curResult'\"\n  if _contains \"${curResult}\" '\"status\": \"error\"'; then\n    if _contains \"${curResult}\" '\"code\": 10109'; then\n      _err \"The API-Key is invalid or could not be found\"\n    else\n      _err \"UNKNOWN API ERROR\"\n    fi\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_hostup.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034,SC2154\n\ndns_hostup_info='HostUp DNS\nSite: hostup.se\nDocs: https://developer.hostup.se/\nOptions:\n HOSTUP_API_KEY     Required. HostUp API key with read:dns + write:dns + read:domains scopes.\n HOSTUP_API_BASE    Optional. Override API base URL (default: https://cloud.hostup.se/api).\n HOSTUP_TTL         Optional. TTL for TXT records (default: 60 seconds).\n HOSTUP_ZONE_ID     Optional. Force a specific zone ID (skip auto-detection).\nAuthor: HostUp (https://cloud.hostup.se/contact/en)\n'\n\nHOSTUP_API_BASE_DEFAULT=\"https://cloud.hostup.se/api\"\nHOSTUP_DEFAULT_TTL=60\n\n# Public: add TXT record\n# Usage: dns_hostup_add _acme-challenge.example.com \"txt-value\"\ndns_hostup_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Using HostUp DNS API\"\n\n  if ! _hostup_init; then\n    return 1\n  fi\n\n  if ! _hostup_detect_zone \"$fulldomain\"; then\n    _err \"Unable to determine HostUp zone for $fulldomain\"\n    return 1\n  fi\n\n  record_name=\"$(_hostup_record_name \"$fulldomain\" \"$HOSTUP_ZONE_DOMAIN\")\"\n  record_name=\"$(_hostup_sanitize_name \"$record_name\")\"\n  record_value=\"$(_hostup_json_escape \"$txtvalue\")\"\n\n  ttl=\"${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}\"\n\n  _debug \"zone_id\" \"$HOSTUP_ZONE_ID\"\n  _debug \"zone_domain\" \"$HOSTUP_ZONE_DOMAIN\"\n  _debug \"record_name\" \"$record_name\"\n  _debug \"ttl\" \"$ttl\"\n\n  request_body=\"{\\\"name\\\":\\\"$record_name\\\",\\\"type\\\":\\\"TXT\\\",\\\"value\\\":\\\"$record_value\\\",\\\"ttl\\\":$ttl}\"\n\n  if ! _hostup_rest \"POST\" \"/dns/zones/$HOSTUP_ZONE_ID/records\" \"$request_body\"; then\n    return 1\n  fi\n\n  if ! _contains \"$_hostup_response\" '\"success\":true'; then\n    _err \"HostUp DNS API: failed to create TXT record for $fulldomain\"\n    _debug2 \"_hostup_response\" \"$_hostup_response\"\n    return 1\n  fi\n\n  record_id=\"$(_hostup_extract_record_id \"$_hostup_response\")\"\n  if [ -n \"$record_id\" ]; then\n    _hostup_save_record_id \"$HOSTUP_ZONE_ID\" \"$fulldomain\" \"$record_id\"\n    _debug \"hostup_saved_record_id\" \"$record_id\"\n  fi\n\n  _info \"Added TXT record for $fulldomain\"\n  return 0\n}\n\n# Public: remove TXT record\n# Usage: dns_hostup_rm _acme-challenge.example.com \"txt-value\"\ndns_hostup_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Using HostUp DNS API\"\n\n  if ! _hostup_init; then\n    return 1\n  fi\n\n  if ! _hostup_detect_zone \"$fulldomain\"; then\n    _err \"Unable to determine HostUp zone for $fulldomain\"\n    return 1\n  fi\n\n  record_name_fqdn=\"$(_hostup_fqdn \"$fulldomain\")\"\n  record_value=\"$txtvalue\"\n\n  record_id_cached=\"$(_hostup_get_saved_record_id \"$HOSTUP_ZONE_ID\" \"$fulldomain\")\"\n  if [ -n \"$record_id_cached\" ]; then\n    _debug \"hostup_record_id_cached\" \"$record_id_cached\"\n    if _hostup_delete_record_by_id \"$HOSTUP_ZONE_ID\" \"$record_id_cached\"; then\n      _info \"Deleted TXT record $record_id_cached\"\n      _hostup_clear_record_id \"$HOSTUP_ZONE_ID\" \"$fulldomain\"\n      HOSTUP_ZONE_ID=\"\"\n      return 0\n    fi\n  fi\n\n  if ! _hostup_find_record \"$HOSTUP_ZONE_ID\" \"$record_name_fqdn\" \"$record_value\"; then\n    _info \"TXT record not found for $record_name_fqdn. Skipping removal.\"\n    _hostup_clear_record_id \"$HOSTUP_ZONE_ID\" \"$fulldomain\"\n    return 0\n  fi\n\n  _debug \"Deleting record\" \"$HOSTUP_RECORD_ID\"\n\n  if ! _hostup_delete_record_by_id \"$HOSTUP_ZONE_ID\" \"$HOSTUP_RECORD_ID\"; then\n    return 1\n  fi\n\n  _info \"Deleted TXT record $HOSTUP_RECORD_ID\"\n  _hostup_clear_record_id \"$HOSTUP_ZONE_ID\" \"$fulldomain\"\n  HOSTUP_ZONE_ID=\"\"\n  return 0\n}\n\n##########################\n# Private helper methods #\n##########################\n\n_hostup_init() {\n  HOSTUP_API_KEY=\"${HOSTUP_API_KEY:-$(_readaccountconf_mutable HOSTUP_API_KEY)}\"\n  HOSTUP_API_BASE=\"${HOSTUP_API_BASE:-$(_readaccountconf_mutable HOSTUP_API_BASE)}\"\n  HOSTUP_TTL=\"${HOSTUP_TTL:-$(_readaccountconf_mutable HOSTUP_TTL)}\"\n  HOSTUP_ZONE_ID=\"${HOSTUP_ZONE_ID:-$(_readaccountconf_mutable HOSTUP_ZONE_ID)}\"\n\n  if [ -z \"$HOSTUP_API_BASE\" ]; then\n    HOSTUP_API_BASE=\"$HOSTUP_API_BASE_DEFAULT\"\n  fi\n\n  if [ -z \"$HOSTUP_API_KEY\" ]; then\n    HOSTUP_API_KEY=\"\"\n    _err \"HOSTUP_API_KEY is not set.\"\n    _err \"Please export your HostUp API key with read:dns and write:dns scopes.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable HOSTUP_API_KEY \"$HOSTUP_API_KEY\"\n  _saveaccountconf_mutable HOSTUP_API_BASE \"$HOSTUP_API_BASE\"\n\n  if [ -n \"$HOSTUP_TTL\" ]; then\n    _saveaccountconf_mutable HOSTUP_TTL \"$HOSTUP_TTL\"\n  fi\n\n  if [ -n \"$HOSTUP_ZONE_ID\" ]; then\n    _saveaccountconf_mutable HOSTUP_ZONE_ID \"$HOSTUP_ZONE_ID\"\n  fi\n\n  return 0\n}\n\n_hostup_detect_zone() {\n  fulldomain=\"$1\"\n\n  if [ -n \"$HOSTUP_ZONE_ID\" ] && [ -n \"$HOSTUP_ZONE_DOMAIN\" ]; then\n    return 0\n  fi\n\n  HOSTUP_ZONE_DOMAIN=\"\"\n  _debug \"hostup_full_domain\" \"$fulldomain\"\n\n  if [ -n \"$HOSTUP_ZONE_ID\" ] && [ -z \"$HOSTUP_ZONE_DOMAIN\" ]; then\n    # Attempt to fetch domain name for provided zone ID\n    if _hostup_fetch_zone_details \"$HOSTUP_ZONE_ID\"; then\n      return 0\n    fi\n    HOSTUP_ZONE_ID=\"\"\n  fi\n\n  if ! _hostup_load_zones; then\n    return 1\n  fi\n\n  _domain_candidate=\"$(printf \"%s\" \"$fulldomain\" | _lower_case)\"\n  _debug \"hostup_initial_candidate\" \"$_domain_candidate\"\n\n  while [ -n \"$_domain_candidate\" ]; do\n    _debug \"hostup_zone_candidate\" \"$_domain_candidate\"\n    if _hostup_lookup_zone \"$_domain_candidate\"; then\n      HOSTUP_ZONE_DOMAIN=\"$_lookup_zone_domain\"\n      HOSTUP_ZONE_ID=\"$_lookup_zone_id\"\n      return 0\n    fi\n\n    case \"$_domain_candidate\" in\n    *.*) ;;\n    *) break ;;\n    esac\n\n    _domain_candidate=\"${_domain_candidate#*.}\"\n  done\n\n  HOSTUP_ZONE_ID=\"\"\n  return 1\n}\n\n_hostup_record_name() {\n  fulldomain=\"$1\"\n  zonedomain=\"$2\"\n\n  # Remove trailing dot, if any\n  fulldomain=\"${fulldomain%.}\"\n  zonedomain=\"${zonedomain%.}\"\n\n  if [ \"$fulldomain\" = \"$zonedomain\" ]; then\n    printf \"%s\" \"@\"\n    return 0\n  fi\n\n  suffix=\".$zonedomain\"\n  case \"$fulldomain\" in\n  *\"$suffix\")\n    printf \"%s\" \"${fulldomain%\"$suffix\"}\"\n    ;;\n  *)\n    # Domain not within zone, fall back to full host\n    printf \"%s\" \"$fulldomain\"\n    ;;\n  esac\n}\n\n_hostup_sanitize_name() {\n  name=\"$1\"\n\n  if [ -z \"$name\" ] || [ \"$name\" = \".\" ]; then\n    printf \"%s\" \"@\"\n    return 0\n  fi\n\n  # Remove any trailing dot\n  name=\"${name%.}\"\n  printf \"%s\" \"$name\"\n}\n\n_hostup_fqdn() {\n  domain=\"$1\"\n  printf \"%s\" \"${domain%.}\"\n}\n\n_hostup_fetch_zone_details() {\n  zone_id=\"$1\"\n\n  if ! _hostup_rest \"GET\" \"/dns/zones/$zone_id/records\" \"\"; then\n    return 1\n  fi\n\n  zonedomain=\"$(printf \"%s\" \"$_hostup_response\" | _egrep_o '\"domain\":\"[^\"]*\"' | sed -n '1p' | cut -d ':' -f 2 | tr -d '\"')\"\n  if [ -n \"$zonedomain\" ]; then\n    HOSTUP_ZONE_DOMAIN=\"$zonedomain\"\n    return 0\n  fi\n\n  return 1\n}\n\n_hostup_load_zones() {\n  if ! _hostup_rest \"GET\" \"/dns/zones\" \"\"; then\n    return 1\n  fi\n\n  HOSTUP_ZONES_CACHE=\"\"\n  data=\"$(printf \"%s\" \"$_hostup_response\" | tr '{' '\\n')\"\n\n  while IFS= read -r line; do\n    case \"$line\" in\n    *'\"domain_id\"'*'\"domain\"'*)\n      zone_id=\"$(printf \"%s\" \"$line\" | _hostup_json_extract \"domain_id\")\"\n      zone_domain=\"$(printf \"%s\" \"$line\" | _hostup_json_extract \"domain\")\"\n      if [ -n \"$zone_id\" ] && [ -n \"$zone_domain\" ]; then\n        HOSTUP_ZONES_CACHE=\"${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id}\n\"\n        _debug \"hostup_zone_loaded\" \"$zone_domain|$zone_id\"\n      fi\n      ;;\n    esac\n  done <<EOF\n$data\nEOF\n\n  if [ -z \"$HOSTUP_ZONES_CACHE\" ]; then\n    _err \"HostUp DNS API: no zones returned for the current API key.\"\n    return 1\n  fi\n\n  return 0\n}\n\n_hostup_lookup_zone() {\n  lookup_domain=\"$1\"\n  _lookup_zone_id=\"\"\n  _lookup_zone_domain=\"\"\n\n  while IFS='|' read -r domain zone_id; do\n    [ -z \"$domain\" ] && continue\n    if [ \"$domain\" = \"$lookup_domain\" ]; then\n      _lookup_zone_domain=\"$domain\"\n      _lookup_zone_id=\"$zone_id\"\n      HOSTUP_ZONE_DOMAIN=\"$domain\"\n      HOSTUP_ZONE_ID=\"$zone_id\"\n      return 0\n    fi\n  done <<EOF\n$HOSTUP_ZONES_CACHE\nEOF\n\n  return 1\n}\n\n_hostup_find_record() {\n  zone_id=\"$1\"\n  fqdn=\"$2\"\n  txtvalue=\"$3\"\n\n  if ! _hostup_rest \"GET\" \"/dns/zones/$zone_id/records\" \"\"; then\n    return 1\n  fi\n\n  HOSTUP_RECORD_ID=\"\"\n  records=\"$(printf \"%s\" \"$_hostup_response\" | tr '{' '\\n')\"\n\n  while IFS= read -r line; do\n    # Normalize line to make TXT value matching reliable\n    line_clean=\"$(printf \"%s\" \"$line\" | tr -d '\\r\\n')\"\n    line_value_clean=\"$(printf \"%s\" \"$line_clean\" | sed 's/\\\\\"//g')\"\n\n    case \"$line_clean\" in\n    *'\"type\":\"TXT\"'*'\"name\"'*'\"value\"'*)\n      name_value=\"$(_hostup_json_extract \"name\" \"$line_clean\")\"\n      record_value=\"$(_hostup_json_extract \"value\" \"$line_value_clean\")\"\n\n      _debug \"hostup_record_raw\" \"$record_value\"\n      if [ \"${record_value#\\\"}\" != \"$record_value\" ] && [ \"${record_value%\\\"}\" != \"$record_value\" ]; then\n        record_value=\"${record_value#\\\"}\"\n        record_value=\"${record_value%\\\"}\"\n      fi\n      if [ \"${record_value#\\'}\" != \"$record_value\" ] && [ \"${record_value%\\'}\" != \"$record_value\" ]; then\n        record_value=\"${record_value#\\'}\"\n        record_value=\"${record_value%\\'}\"\n      fi\n      record_value=\"$(printf \"%s\" \"$record_value\" | tr -d '\\r\\n')\"\n      _debug \"hostup_record_value\" \"$record_value\"\n\n      if [ \"$name_value\" = \"$fqdn\" ] && [ \"$record_value\" = \"$txtvalue\" ]; then\n        record_id=\"$(_hostup_json_extract \"id\" \"$line_clean\")\"\n        if [ -n \"$record_id\" ]; then\n          HOSTUP_RECORD_ID=\"$record_id\"\n          return 0\n        fi\n      fi\n      ;;\n    esac\n  done <<EOF\n$records\nEOF\n\n  return 1\n}\n\n_hostup_json_extract() {\n  key=\"$1\"\n  input=\"${2:-$line}\"\n\n  # First try to extract quoted values (strings)\n  quoted_match=\"$(printf \"%s\" \"$input\" | _egrep_o \"\\\"$key\\\":\\\"[^\\\"]*\\\"\" | _head_n 1)\"\n  if [ -n \"$quoted_match\" ]; then\n    printf \"%s\" \"$quoted_match\" |\n      cut -d : -f2- |\n      sed 's/^\"//' |\n      sed 's/\"$//' |\n      sed 's/\\\\\"/\"/g'\n    return 0\n  fi\n\n  # Fallback for unquoted values (e.g., numeric IDs)\n  unquoted_match=\"$(printf \"%s\" \"$input\" | _egrep_o \"\\\"$key\\\":[^,}]*\" | _head_n 1)\"\n  if [ -n \"$unquoted_match\" ]; then\n    printf \"%s\" \"$unquoted_match\" |\n      cut -d : -f2- |\n      tr -d '\", ' |\n      tr -d '\\r\\n'\n    return 0\n  fi\n\n  return 1\n}\n\n_hostup_json_escape() {\n  printf \"%s\" \"$1\" | sed 's/\\\\/\\\\\\\\/g; s/\"/\\\\\"/g'\n}\n\n_hostup_record_key() {\n  zone_id=\"$1\"\n  domain=\"$2\"\n  safe_zone=\"$(printf \"%s\" \"$zone_id\" | sed 's/[^A-Za-z0-9]/_/g')\"\n  safe_domain=\"$(printf \"%s\" \"$domain\" | _lower_case | sed 's/[^a-z0-9]/_/g')\"\n  printf \"%s_%s\" \"$safe_zone\" \"$safe_domain\"\n}\n\n_hostup_save_record_id() {\n  zone_id=\"$1\"\n  domain=\"$2\"\n  record_id=\"$3\"\n  key=\"$(_hostup_record_key \"$zone_id\" \"$domain\")\"\n  _saveaccountconf_mutable \"HOSTUP_RECORD_$key\" \"$record_id\"\n}\n\n_hostup_get_saved_record_id() {\n  zone_id=\"$1\"\n  domain=\"$2\"\n  key=\"$(_hostup_record_key \"$zone_id\" \"$domain\")\"\n  _readaccountconf_mutable \"HOSTUP_RECORD_$key\"\n}\n\n_hostup_clear_record_id() {\n  zone_id=\"$1\"\n  domain=\"$2\"\n  key=\"$(_hostup_record_key \"$zone_id\" \"$domain\")\"\n  _clearaccountconf_mutable \"HOSTUP_RECORD_$key\"\n}\n\n_hostup_extract_record_id() {\n  record_id=\"$(_hostup_json_extract \"id\" \"$1\")\"\n  if [ -n \"$record_id\" ]; then\n    printf \"%s\" \"$record_id\"\n    return 0\n  fi\n\n  printf \"%s\" \"$1\" | _egrep_o '\"id\":[0-9]+' | _head_n 1 | cut -d: -f2\n}\n\n_hostup_delete_record_by_id() {\n  zone_id=\"$1\"\n  record_id=\"$2\"\n\n  if ! _hostup_rest \"DELETE\" \"/dns/zones/$zone_id/records/$record_id\" \"\"; then\n    return 1\n  fi\n\n  if ! _contains \"$_hostup_response\" '\"success\":true'; then\n    return 1\n  fi\n\n  return 0\n}\n\n_hostup_rest() {\n  method=\"$1\"\n  route=\"$2\"\n  data=\"$3\"\n\n  _hostup_response=\"\"\n\n  export _H1=\"Authorization: Bearer $HOSTUP_API_KEY\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Accept: application/json\"\n\n  if [ \"$method\" = \"GET\" ]; then\n    _hostup_response=\"$(_get \"$HOSTUP_API_BASE$route\")\"\n  else\n    _hostup_response=\"$(_post \"$data\" \"$HOSTUP_API_BASE$route\" \"\" \"$method\" \"application/json\")\"\n  fi\n\n  ret=\"$?\"\n\n  unset _H1\n  unset _H2\n  unset _H3\n\n  if [ \"$ret\" != \"0\" ]; then\n    _err \"HTTP request failed for $route\"\n    return 1\n  fi\n\n  http_status=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n  _debug2 \"HTTP status\" \"$http_status\"\n  _debug2 \"_hostup_response\" \"$_hostup_response\"\n\n  case \"$http_status\" in\n  200 | 201 | 204) return 0 ;;\n  401)\n    _err \"HostUp API returned 401 Unauthorized. Check HOSTUP_API_KEY scopes and IP restrictions.\"\n    return 1\n    ;;\n  403)\n    _err \"HostUp API returned 403 Forbidden. The API key lacks required DNS scopes.\"\n    return 1\n    ;;\n  404)\n    _err \"HostUp API returned 404 Not Found for $route\"\n    return 1\n    ;;\n  429)\n    _err \"HostUp API rate limit exceeded. Please retry later.\"\n    return 1\n    ;;\n  *)\n    _err \"HostUp API request failed with status $http_status\"\n    return 1\n    ;;\n  esac\n}\n"
  },
  {
    "path": "dnsapi/dns_huaweicloud.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_huaweicloud_info='HuaweiCloud.com\nSite: HuaweiCloud.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_huaweicloud\nOptions:\n HUAWEICLOUD_Username Username\n HUAWEICLOUD_Password Password\n HUAWEICLOUD_DomainName DomainName\nIssues: github.com/acmesh-official/acme.sh/issues/3265\n'\n\niam_api=\"https://iam.myhuaweicloud.com\"\ndns_api=\"https://dns.ap-southeast-1.myhuaweicloud.com\" # Should work\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\n#\n# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html\n#\n# About \"DomainName\" parameters see: https://support.huaweicloud.com/api-iam/iam_01_0006.html\n#\n\ndns_huaweicloud_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  HUAWEICLOUD_Username=\"${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}\"\n  HUAWEICLOUD_Password=\"${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}\"\n  HUAWEICLOUD_DomainName=\"${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}\"\n\n  # Check information\n  if [ -z \"${HUAWEICLOUD_Username}\" ] || [ -z \"${HUAWEICLOUD_Password}\" ] || [ -z \"${HUAWEICLOUD_DomainName}\" ]; then\n    _err \"Not enough information provided to dns_huaweicloud!\"\n    return 1\n  fi\n\n  unset token # Clear token\n  token=\"$(_get_token \"${HUAWEICLOUD_Username}\" \"${HUAWEICLOUD_Password}\" \"${HUAWEICLOUD_DomainName}\")\"\n  if [ -z \"${token}\" ]; then # Check token\n    _err \"dns_api(dns_huaweicloud): Error getting token.\"\n    return 1\n  fi\n  _secure_debug \"Access token is:\" \"${token}\"\n\n  unset zoneid\n  zoneid=\"$(_get_zoneid \"${token}\" \"${fulldomain}\")\"\n  if [ -z \"${zoneid}\" ]; then\n    _err \"dns_api(dns_huaweicloud): Error getting zone id.\"\n    return 1\n  fi\n  _debug \"Zone ID is:\" \"${zoneid}\"\n\n  _debug \"Adding Record\"\n  _add_record \"${token}\" \"${fulldomain}\" \"${txtvalue}\"\n  ret=\"$?\"\n  if [ \"${ret}\" != \"0\" ]; then\n    _err \"dns_api(dns_huaweicloud): Error adding record.\"\n    return 1\n  fi\n\n  # Do saving work if all succeeded\n  _saveaccountconf_mutable HUAWEICLOUD_Username \"${HUAWEICLOUD_Username}\"\n  _saveaccountconf_mutable HUAWEICLOUD_Password \"${HUAWEICLOUD_Password}\"\n  _saveaccountconf_mutable HUAWEICLOUD_DomainName \"${HUAWEICLOUD_DomainName}\"\n  return 0\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\n#\n# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/dns_api_64005.html\n#\n\ndns_huaweicloud_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  HUAWEICLOUD_Username=\"${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}\"\n  HUAWEICLOUD_Password=\"${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}\"\n  HUAWEICLOUD_DomainName=\"${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}\"\n\n  # Check information\n  if [ -z \"${HUAWEICLOUD_Username}\" ] || [ -z \"${HUAWEICLOUD_Password}\" ] || [ -z \"${HUAWEICLOUD_DomainName}\" ]; then\n    _err \"Not enough information provided to dns_huaweicloud!\"\n    return 1\n  fi\n\n  unset token # Clear token\n  token=\"$(_get_token \"${HUAWEICLOUD_Username}\" \"${HUAWEICLOUD_Password}\" \"${HUAWEICLOUD_DomainName}\")\"\n  if [ -z \"${token}\" ]; then # Check token\n    _err \"dns_api(dns_huaweicloud): Error getting token.\"\n    return 1\n  fi\n  _secure_debug \"Access token is:\" \"${token}\"\n\n  unset zoneid\n  zoneid=\"$(_get_zoneid \"${token}\" \"${fulldomain}\")\"\n  if [ -z \"${zoneid}\" ]; then\n    _err \"dns_api(dns_huaweicloud): Error getting zone id.\"\n    return 1\n  fi\n  _debug \"Zone ID is:\" \"${zoneid}\"\n\n  record_id=\"$(_get_recordset_id \"${token}\" \"${fulldomain}\" \"${zoneid}\")\"\n  _recursive_rm_record \"${token}\" \"${fulldomain}\" \"${zoneid}\" \"${record_id}\"\n  ret=\"$?\"\n  if [ \"${ret}\" != \"0\" ]; then\n    _err \"dns_api(dns_huaweicloud): Error removing record.\"\n    return 1\n  fi\n\n  return 0\n}\n\n###################  Private functions below ##################################\n\n# _recursive_rm_record\n# remove all records from the record set\n#\n# _token=$1\n# _domain=$2\n# _zoneid=$3\n# _record_id=$4\n#\n# Returns 0 on success\n_recursive_rm_record() {\n  _token=$1\n  _domain=$2\n  _zoneid=$3\n  _record_id=$4\n\n  # Most likely to have problems will huaweicloud side if more than 50 attempts but still cannot fully remove the record set\n  # Maybe can be removed manually in the dashboard\n  _retry_cnt=50\n\n  # Remove all records\n  # Therotically HuaweiCloud does not allow more than one record set\n  # But remove them recurringly to increase robusty\n\n  while [ \"${_record_id}\" != \"0\" ] && [ \"${_retry_cnt}\" != \"0\" ]; do\n    _debug \"Removing Record\"\n    _retry_cnt=$((_retry_cnt - 1))\n    _rm_record \"${_token}\" \"${_zoneid}\" \"${_record_id}\"\n    _record_id=\"$(_get_recordset_id \"${_token}\" \"${_domain}\" \"${_zoneid}\")\"\n    _debug2 \"Checking record exists: record_id=${_record_id}\"\n  done\n\n  # Check if retry count is reached\n  if [ \"${_retry_cnt}\" = \"0\" ]; then\n    _debug \"Failed to remove record after 50 attempts, please try removing it manually in the dashboard\"\n    return 1\n  fi\n\n  return 0\n}\n\n# _get_zoneid\n#\n# _token=$1\n# _domain_string=$2\n#\n# printf \"%s\" \"${_zoneid}\"\n_get_zoneid() {\n  _token=$1\n  _domain_string=$2\n  export _H1=\"X-Auth-Token: ${_token}\"\n\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"${_domain_string}\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    _debug \"$h\"\n    response=$(_get \"${dns_api}/v2/zones?name=${h}\")\n    _debug2 \"$response\"\n    if _contains \"${response}\" '\"id\"'; then\n      zoneidlist=$(echo \"${response}\" | _egrep_o \"\\\"id\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\n      zonenamelist=$(echo \"${response}\" | _egrep_o \"\\\"name\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\n      _debug2 \"Returned Zone ID(s):\" \"${zoneidlist}\"\n      _debug2 \"Returned Zone Name(s):\" \"${zonenamelist}\"\n      zoneidnum=0\n      zoneidcount=$(echo \"${zoneidlist}\" | grep -c '^')\n      _debug \"Returned Zone ID(s) Count:\" \"${zoneidcount}\"\n      while [ \"${zoneidnum}\" -lt \"${zoneidcount}\" ]; do\n        zoneidnum=$(_math \"$zoneidnum\" + 1)\n        _zoneid=$(echo \"${zoneidlist}\" | sed -n \"${zoneidnum}p\")\n        zonename=$(echo \"${zonenamelist}\" | sed -n \"${zoneidnum}p\")\n        _debug \"Check Zone Name\" \"${zonename}\"\n        if [ \"${zonename}\" = \"${h}.\" ]; then\n          _debug \"Get Zone ID Success.\"\n          _debug \"ZoneID:\" \"${_zoneid}\"\n          printf \"%s\" \"${_zoneid}\"\n          return 0\n        fi\n      done\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_get_recordset_id() {\n  _token=$1\n  _domain=$2\n  _zoneid=$3\n  export _H1=\"X-Auth-Token: ${_token}\"\n\n  response=$(_get \"${dns_api}/v2/zones/${_zoneid}/recordsets?name=${_domain}&status=ACTIVE\")\n  if _contains \"${response}\" '\"id\"'; then\n    _id=\"$(echo \"${response}\" | _egrep_o \"\\\"id\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\"\n    printf \"%s\" \"${_id}\"\n    return 0\n  fi\n  printf \"%s\" \"0\"\n  return 1\n}\n\n_add_record() {\n  _token=$1\n  _domain=$2\n  _txtvalue=$3\n\n  # Get Existing Records\n  export _H1=\"X-Auth-Token: ${_token}\"\n  response=$(_get \"${dns_api}/v2/zones/${zoneid}/recordsets?name=${_domain}&status=ACTIVE\")\n\n  _debug2 \"${response}\"\n  _exist_record=$(echo \"${response}\" | _egrep_o '\"records\":[^]]*' | sed 's/\\\"records\\\"\\:\\[//g')\n  _debug \"${_exist_record}\"\n\n  # Check if record exist\n  # Generate body data\n  if [ -z \"${_exist_record}\" ]; then\n    _post_body=\"{\n      \\\"name\\\": \\\"${_domain}.\\\",\n      \\\"description\\\": \\\"ACME Challenge\\\",\n      \\\"type\\\": \\\"TXT\\\",\n      \\\"ttl\\\": 1,\n      \\\"records\\\": [\n        \\\"\\\\\\\"${_txtvalue}\\\\\\\"\\\"\n      ]\n    }\"\n  else\n    _post_body=\"{\n      \\\"name\\\": \\\"${_domain}.\\\",\n      \\\"description\\\": \\\"ACME Challenge\\\",\n      \\\"type\\\": \\\"TXT\\\",\n      \\\"ttl\\\": 1,\n      \\\"records\\\": [\n        ${_exist_record},\\\"\\\\\\\"${_txtvalue}\\\\\\\"\\\"\n      ]\n    }\"\n  fi\n\n  _record_id=\"$(_get_recordset_id \"${_token}\" \"${_domain}\" \"${zoneid}\")\"\n  _debug \"Record Set ID is:\" \"${_record_id}\"\n\n  # Add brand new records with all old and new records\n  export _H2=\"Content-Type: application/json\"\n  export _H1=\"X-Auth-Token: ${_token}\"\n\n  _debug2 \"${_post_body}\"\n  if [ -z \"${_exist_record}\" ]; then\n    _post \"${_post_body}\" \"${dns_api}/v2/zones/${zoneid}/recordsets\" >/dev/null\n  else\n    _post \"${_post_body}\" \"${dns_api}/v2/zones/${zoneid}/recordsets/${_record_id}\" false \"PUT\" >/dev/null\n  fi\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  if [ \"$_code\" != \"202\" ]; then\n    _err \"dns_huaweicloud: http code ${_code}\"\n    return 1\n  fi\n  return 0\n}\n\n# _rm_record $token $zoneid $recordid\n# assume ${dns_api} exist\n# no output\n# return 0\n_rm_record() {\n  _token=$1\n  _zone_id=$2\n  _record_id=$3\n\n  export _H2=\"Content-Type: application/json\"\n  export _H1=\"X-Auth-Token: ${_token}\"\n\n  _post \"\" \"${dns_api}/v2/zones/${_zone_id}/recordsets/${_record_id}\" false \"DELETE\" >/dev/null\n  return $?\n}\n\n_get_token() {\n  _username=$1\n  _password=$2\n  _domain_name=$3\n\n  _debug \"Getting Token\"\n  body=\"{\n    \\\"auth\\\": {\n      \\\"identity\\\": {\n        \\\"methods\\\": [\n          \\\"password\\\"\n        ],\n        \\\"password\\\": {\n          \\\"user\\\": {\n            \\\"name\\\": \\\"${_username}\\\",\n            \\\"password\\\": \\\"${_password}\\\",\n            \\\"domain\\\": {\n              \\\"name\\\": \\\"${_domain_name}\\\"\n            }\n          }\n        }\n      },\n      \\\"scope\\\": {\n        \\\"project\\\": {\n          \\\"name\\\": \\\"ap-southeast-1\\\"\n        }\n      }\n    }\n  }\"\n  export _H1=\"Content-Type: application/json;charset=utf8\"\n  _post \"${body}\" \"${iam_api}/v3/auth/tokens\" >/dev/null\n  _code=$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\n  _token=$(grep \"^X-Subject-Token\" \"$HTTP_HEADER\" | cut -d \" \" -f 2-)\n  _secure_debug \"${_code}\"\n  printf \"%s\" \"${_token}\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_infoblox.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_infoblox_info='Infoblox.com\nSite: Infoblox.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_infoblox\nOptions:\n Infoblox_Creds Credentials. E.g. \"username:password\"\n Infoblox_Server Server hostname. IP or FQDN of infoblox appliance\nIssues: github.com/jasonkeller/acme.sh\nAuthor: Jason Keller, Elijah Tenai\n'\n\ndns_infoblox_add() {\n\n  ## Nothing to see here, just some housekeeping\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using Infoblox API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ## Check for the credentials\n  if [ -z \"$Infoblox_Creds\" ] || [ -z \"$Infoblox_Server\" ]; then\n    Infoblox_Creds=\"\"\n    Infoblox_Server=\"\"\n    _err \"You didn't specify the Infoblox credentials or server (Infoblox_Creds; Infoblox_Server).\"\n    _err \"Please set them via EXPORT Infoblox_Creds=username:password or EXPORT Infoblox_server=ip/hostname and try again.\"\n    return 1\n  fi\n\n  if [ -z \"$Infoblox_View\" ]; then\n    _info \"No Infoblox_View set, using fallback value 'default'\"\n    Infoblox_View=\"default\"\n  fi\n\n  ## Save the credentials to the account file\n  _saveaccountconf Infoblox_Creds \"$Infoblox_Creds\"\n  _saveaccountconf Infoblox_Server \"$Infoblox_Server\"\n  _saveaccountconf Infoblox_View \"$Infoblox_View\"\n\n  ## URLencode Infoblox View to deal with e.g. spaces\n  Infoblox_ViewEncoded=$(printf \"%b\" \"$Infoblox_View\" | _url_encode)\n\n  ## Base64 encode the credentials\n  Infoblox_CredsEncoded=$(printf \"%b\" \"$Infoblox_Creds\" | _base64)\n\n  ## Construct the HTTP Authorization header\n  export _H1=\"Accept-Language:en-US\"\n  export _H2=\"Authorization: Basic $Infoblox_CredsEncoded\"\n\n  ## Construct the request URL\n  baseurlnObject=\"https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}\"\n\n  ## Add the challenge record to the Infoblox grid member\n  result=\"$(_post \"\" \"$baseurlnObject\" \"\" \"POST\")\"\n\n  ## Let's see if we get something intelligible back from the unit\n  if [ \"$(echo \"$result\" | _egrep_o \"record:txt/.*:.*/${Infoblox_ViewEncoded}\")\" ]; then\n    _info \"Successfully created the txt record\"\n    return 0\n  else\n    _err \"Error encountered during record addition\"\n    _err \"$result\"\n    return 1\n  fi\n\n}\n\ndns_infoblox_rm() {\n\n  ## Nothing to see here, just some housekeeping\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using Infoblox API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  ## URLencode Infoblox View to deal with e.g. spaces\n  Infoblox_ViewEncoded=$(printf \"%b\" \"$Infoblox_View\" | _url_encode)\n\n  ## Base64 encode the credentials\n  Infoblox_CredsEncoded=\"$(printf \"%b\" \"$Infoblox_Creds\" | _base64)\"\n\n  ## Construct the HTTP Authorization header\n  export _H1=\"Accept-Language:en-US\"\n  export _H2=\"Authorization: Basic $Infoblox_CredsEncoded\"\n\n  ## Does the record exist?  Let's check.\n  baseurlnObject=\"https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}&_return_type=xml-pretty\"\n  result=\"$(_get \"$baseurlnObject\")\"\n\n  ## Let's see if we get something intelligible back from the grid\n  if [ \"$(echo \"$result\" | _egrep_o \"record:txt/.*:.*/${Infoblox_ViewEncoded}\")\" ]; then\n    ## Extract the object reference\n    objRef=\"$(printf \"%b\" \"$result\" | _egrep_o \"record:txt/.*:.*/${Infoblox_ViewEncoded}\")\"\n    objRmUrl=\"https://$Infoblox_Server/wapi/v2.2.2/$objRef\"\n    ## Delete them! All the stale records!\n    rmResult=\"$(_post \"\" \"$objRmUrl\" \"\" \"DELETE\")\"\n    ## Let's see if that worked\n    if [ \"$(echo \"$rmResult\" | _egrep_o \"record:txt/.*:.*/${Infoblox_ViewEncoded}\")\" ]; then\n      _info \"Successfully deleted $objRef\"\n      return 0\n    else\n      _err \"Error occurred during txt record delete\"\n      _err \"$rmResult\"\n      return 1\n    fi\n  else\n    _err \"Record to delete didn't match an existing record\"\n    _err \"$result\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n"
  },
  {
    "path": "dnsapi/dns_infoblox_uddi.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_infoblox_uddi_info='Infoblox UDDI\nSite: Infoblox.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infoblox_uddi\nOptions:\n Infoblox_UDDI_Key API Key for Infoblox UDDI\n Infoblox_Portal URL, e.g. \"csp.infoblox.com\" or \"csp.eu.infoblox.com\"\nIssues: github.com/acmesh-official/acme.sh/issues\nAuthor: Stefan Riegel\n'\n\nInfoblox_UDDI_Api=\"https://\"\n\n########  Public functions #####################\n\n#Usage: dns_infoblox_uddi_add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_infoblox_uddi_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Infoblox_UDDI_Key=\"${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}\"\n  Infoblox_Portal=\"${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}\"\n\n  _info \"Using Infoblox UDDI API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if [ -z \"$Infoblox_UDDI_Key\" ] || [ -z \"$Infoblox_Portal\" ]; then\n    Infoblox_UDDI_Key=\"\"\n    Infoblox_Portal=\"\"\n    _err \"You didn't specify the Infoblox UDDI key or server (Infoblox_UDDI_Key; Infoblox_Portal).\"\n    _err \"Please set them via EXPORT Infoblox_UDDI_Key=your_key, EXPORT Infoblox_Portal=csp.infoblox.com and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable Infoblox_UDDI_Key \"$Infoblox_UDDI_Key\"\n  _saveaccountconf_mutable Infoblox_Portal \"$Infoblox_Portal\"\n\n  export _H1=\"Authorization: Token $Infoblox_UDDI_Key\"\n  export _H2=\"Content-Type: application/json\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting existing txt records\"\n  _infoblox_rest GET \"dns/record?_filter=type%20eq%20'TXT'%20and%20name_in_zone%20eq%20'$_sub_domain'%20and%20zone%20eq%20'$_domain_id'\"\n\n  _info \"Adding record\"\n  body=\"{\\\"type\\\":\\\"TXT\\\",\\\"name_in_zone\\\":\\\"$_sub_domain\\\",\\\"zone\\\":\\\"$_domain_id\\\",\\\"ttl\\\":120,\\\"inheritance_sources\\\":{\\\"ttl\\\":{\\\"action\\\":\\\"override\\\"}},\\\"rdata\\\":{\\\"text\\\":\\\"$txtvalue\\\"}}\"\n\n  if _infoblox_rest POST \"dns/record\" \"$body\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" '\"error\"'; then\n      # Check if record already exists\n      if _contains \"$response\" \"already exists\" || _contains \"$response\" \"duplicate\"; then\n        _info \"Already exists, OK\"\n        return 0\n      else\n        _err \"Add txt record error.\"\n        _err \"Response: $response\"\n        return 1\n      fi\n    else\n      _info \"Added, OK\"\n      return 0\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#Usage: dns_infoblox_uddi_rm _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_infoblox_uddi_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Infoblox_UDDI_Key=\"${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}\"\n  Infoblox_Portal=\"${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}\"\n\n  if [ -z \"$Infoblox_UDDI_Key\" ] || [ -z \"$Infoblox_Portal\" ]; then\n    _err \"Credentials not found\"\n    return 1\n  fi\n\n  _info \"Using Infoblox UDDI API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  export _H1=\"Authorization: Token $Infoblox_UDDI_Key\"\n  export _H2=\"Content-Type: application/json\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records to delete\"\n  # Filter by txtvalue to support wildcard certs (multiple TXT records)\n  filter=\"type%20eq%20'TXT'%20and%20name_in_zone%20eq%20'$_sub_domain'%20and%20zone%20eq%20'$_domain_id'%20and%20rdata.text%20eq%20'$txtvalue'\"\n  _infoblox_rest GET \"dns/record?_filter=$filter\"\n\n  if ! _contains \"$response\" '\"results\"'; then\n    _info \"Don't need to remove, record not found.\"\n    return 0\n  fi\n\n  record_id=$(echo \"$response\" | _egrep_o '\"id\":[[:space:]]*\"[^\"]*\"' | _head_n 1 | cut -d '\"' -f 4)\n  _debug \"record_id\" \"$record_id\"\n\n  if [ -z \"$record_id\" ]; then\n    _info \"Don't need to remove, record not found.\"\n    return 0\n  fi\n\n  # Extract UUID from the full record ID (format: dns/record/uuid)\n  record_uuid=$(echo \"$record_id\" | sed 's|.*/||')\n  _debug \"record_uuid\" \"$record_uuid\"\n\n  if ! _infoblox_rest DELETE \"dns/record/$record_uuid\"; then\n    _err \"Delete record error.\"\n    return 1\n  fi\n\n  _info \"Removed record successfully\"\n  return 0\n}\n\n####################  Private functions below ##################################\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=dns/auth_zone/xxxx-xxxx\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  # Remove _acme-challenge prefix if present\n  domain_no_acme=$(echo \"$domain\" | sed 's/^_acme-challenge\\.//')\n\n  while true; do\n    h=$(printf \"%s\" \"$domain_no_acme\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      # not valid\n      return 1\n    fi\n\n    # Query for the zone with both trailing dot and without\n    filter=\"fqdn%20eq%20'$h.'%20or%20fqdn%20eq%20'$h'\"\n    if ! _infoblox_rest GET \"dns/auth_zone?_filter=$filter\"; then\n      # API error - don't continue if we get auth errors\n      if _contains \"$response\" \"401\" || _contains \"$response\" \"Authorization\"; then\n        _err \"Authentication failed. Please check your Infoblox_UDDI_Key.\"\n        return 1\n      fi\n      # For other errors, continue to parent domain\n      p=$i\n      i=$((i + 1))\n      continue\n    fi\n\n    # Check if response contains results (even if empty)\n    if _contains \"$response\" '\"results\"'; then\n      # Extract zone ID - must match the pattern dns/auth_zone/...\n      zone_id=$(echo \"$response\" | _egrep_o '\"id\":[[:space:]]*\"dns/auth_zone/[^\"]*\"' | _head_n 1 | cut -d '\"' -f 4)\n      if [ -n \"$zone_id\" ]; then\n        # Found the zone\n        _domain=\"$h\"\n        _domain_id=\"$zone_id\"\n\n        # Calculate subdomain\n        if [ \"$_domain\" = \"$domain\" ]; then\n          _sub_domain=\"\"\n        else\n          _cutlength=$((${#domain} - ${#_domain} - 1))\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -c \"1-$_cutlength\")\n        fi\n\n        return 0\n      fi\n    fi\n\n    p=$i\n    i=$((i + 1))\n  done\n\n  return 1\n}\n\n# _infoblox_rest GET \"dns/record?_filter=...\"\n# _infoblox_rest POST \"dns/record\" \"{json body}\"\n# _infoblox_rest DELETE \"dns/record/uuid\"\n_infoblox_rest() {\n  method=$1\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug \"$ep\"\n\n  # Ensure credentials are available (when called from _get_root)\n  Infoblox_UDDI_Key=\"${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}\"\n  Infoblox_Portal=\"${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}\"\n\n  Infoblox_UDDI_Api=\"https://$Infoblox_Portal/api/ddi/v1\"\n  export _H1=\"Authorization: Token $Infoblox_UDDI_Key\"\n  export _H2=\"Content-Type: application/json\"\n\n  # Debug (masked)\n  _tok_len=$(printf \"%s\" \"$Infoblox_UDDI_Key\" | wc -c | tr -d ' \\n')\n  _debug2 \"Auth header set\" \"Token len=${_tok_len} on $Infoblox_Portal\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$Infoblox_UDDI_Api/$ep\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$Infoblox_UDDI_Api/$ep\")\"\n  fi\n\n  _ret=\"$?\"\n  _debug2 response \"$response\"\n\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"Error: $ep\"\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_infomaniak.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_infomaniak_info='Infomaniak.com\nSite: Infomaniak.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infomaniak\nOptions:\n INFOMANIAK_API_TOKEN API Token\nIssues: github.com/acmesh-official/acme.sh/issues/3188\n\n'\n\n# To use this API you need visit the API dashboard of your account.\n# Note: the URL looks like this:\n# https://manager.infomaniak.com/v3/<account_id>/ng/profile/user/token/list\n# Then generate a token with following scopes :\n#  - domain:read\n#  - dns:read\n#  - dns:write\n# this is given as an environment variable INFOMANIAK_API_TOKEN\n\n# base variables\n\nDEFAULT_INFOMANIAK_API_URL=\"https://api.infomaniak.com\"\nDEFAULT_INFOMANIAK_TTL=300\n\n########  Public functions #####################\n\n#Usage: dns_infomaniak_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_infomaniak_add() {\n\n  INFOMANIAK_API_TOKEN=\"${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}\"\n  INFOMANIAK_API_URL=\"${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}\"\n  INFOMANIAK_TTL=\"${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}\"\n\n  if [ -z \"$INFOMANIAK_API_TOKEN\" ]; then\n    INFOMANIAK_API_TOKEN=\"\"\n    _err \"Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN\"\n    return 1\n  fi\n\n  if [ -z \"$INFOMANIAK_API_URL\" ]; then\n    INFOMANIAK_API_URL=\"$DEFAULT_INFOMANIAK_API_URL\"\n  fi\n\n  if [ -z \"$INFOMANIAK_TTL\" ]; then\n    INFOMANIAK_TTL=\"$DEFAULT_INFOMANIAK_TTL\"\n  fi\n\n  #save the token to the account conf file.\n  _saveaccountconf_mutable INFOMANIAK_API_TOKEN \"$INFOMANIAK_API_TOKEN\"\n\n  if [ \"$INFOMANIAK_API_URL\" != \"$DEFAULT_INFOMANIAK_API_URL\" ]; then\n    _saveaccountconf_mutable INFOMANIAK_API_URL \"$INFOMANIAK_API_URL\"\n  fi\n\n  if [ \"$INFOMANIAK_TTL\" != \"$DEFAULT_INFOMANIAK_TTL\" ]; then\n    _saveaccountconf_mutable INFOMANIAK_TTL \"$INFOMANIAK_TTL\"\n  fi\n\n  export _H1=\"Authorization: Bearer $INFOMANIAK_API_TOKEN\"\n  export _H2=\"Content-Type: application/json\"\n\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Infomaniak DNS API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  # guess which base domain to add record to\n  zone=$(_get_zone \"$fulldomain\")\n  if [ -z \"$zone\" ]; then\n    _err \"cannot find zone:<${zone}> to modify\"\n    return 1\n  fi\n\n  # extract first part of domain\n  key=${fulldomain%.\"$zone\"}\n\n  _debug \"key:$key\"\n  _debug \"txtvalue: $txtvalue\"\n\n  # payload\n  data=\"{\\\"type\\\": \\\"TXT\\\", \\\"source\\\": \\\"$key\\\", \\\"target\\\": \\\"$txtvalue\\\", \\\"ttl\\\": $INFOMANIAK_TTL}\"\n\n  # API call\n  response=$(_post \"$data\" \"${INFOMANIAK_API_URL}/2/zones/${zone}/records\")\n  if [ -n \"$response\" ]; then\n    if [ ! \"$(echo \"$response\" | _contains '\"result\":\"success\"')\" ]; then\n      _info \"Record added\"\n      _debug \"response: $response\"\n      return 0\n    fi\n  fi\n  _err \"Could not create record.\"\n  _debug \"Response: $response\"\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_infomaniak_rm() {\n\n  INFOMANIAK_API_TOKEN=\"${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}\"\n  INFOMANIAK_API_URL=\"${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}\"\n  INFOMANIAK_TTL=\"${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}\"\n\n  if [ -z \"$INFOMANIAK_API_TOKEN\" ]; then\n    INFOMANIAK_API_TOKEN=\"\"\n    _err \"Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN.\"\n    return 1\n  fi\n\n  if [ -z \"$INFOMANIAK_API_URL\" ]; then\n    INFOMANIAK_API_URL=\"$DEFAULT_INFOMANIAK_API_URL\"\n  fi\n\n  if [ -z \"$INFOMANIAK_TTL\" ]; then\n    INFOMANIAK_TTL=\"$DEFAULT_INFOMANIAK_TTL\"\n  fi\n\n  #save the token to the account conf file.\n  _saveaccountconf_mutable INFOMANIAK_API_TOKEN \"$INFOMANIAK_API_TOKEN\"\n\n  if [ \"$INFOMANIAK_API_URL\" != \"$DEFAULT_INFOMANIAK_API_URL\" ]; then\n    _saveaccountconf_mutable INFOMANIAK_API_URL \"$INFOMANIAK_API_URL\"\n  fi\n\n  if [ \"$INFOMANIAK_TTL\" != \"$DEFAULT_INFOMANIAK_TTL\" ]; then\n    _saveaccountconf_mutable INFOMANIAK_TTL \"$INFOMANIAK_TTL\"\n  fi\n\n  export _H1=\"Authorization: Bearer $INFOMANIAK_API_TOKEN\"\n  export _H2=\"ContentType: application/json\"\n\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Infomaniak DNS API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  # guess which base domain to add record to\n  zone=$(_get_zone \"$fulldomain\")\n  if [ -z \"$zone\" ]; then\n    _err \"cannot find zone:<$zone> to modify\"\n    return 1\n  fi\n\n  # extract first part of domain\n  key=${fulldomain%.\"$zone\"}\n  key=$(echo \"$key\" | _lower_case)\n\n  _debug \"zone:$zone\"\n  _debug \"key:$key\"\n\n  # find previous record\n  # shellcheck disable=SC2086\n  response=$(_get \"${INFOMANIAK_API_URL}/2/zones/${zone}/records\" | sed 's/.*\"data\":\\[\\(.*\\)\\]}/\\1/; s/},{/}{/g')\n  record_id=$(echo \"$response\" | sed -n 's/.*\"id\":\"*\\([0-9]*\\)\"*.*\"source\":\"'\"$key\"'\".*\"target\":\"\\\\\"'\"$txtvalue\"'\\\\\"\".*/\\1/p')\n  _debug \"key: $key\"\n  _debug \"txtvalue: $txtvalue\"\n  _debug \"record_id: $record_id\"\n\n  if [ -z \"$record_id\" ]; then\n    _err \"could not find record to delete\"\n    _debug \"response: $response\"\n    return 1\n  fi\n\n  # API call\n  response=$(_post \"\" \"${INFOMANIAK_API_URL}/2/zones/${zone}/records/${record_id}\" \"\" DELETE)\n  if [ -n \"$response\" ]; then\n    if [ ! \"$(echo \"$response\" | _contains '\"result\":\"success\"')\" ]; then\n      _info \"Record deleted\"\n      return 0\n    fi\n  fi\n  _err \"Could not delete record.\"\n  _debug \"Response: $response\"\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_get_zone() {\n  domain=\"$1\"\n  # Whatever the domain is, you can get the fqdn with the following.\n  # shellcheck disable=SC1004\n  response=$(_get \"${INFOMANIAK_API_URL}/2/domains/${domain}/zones\" | sed 's/.*\\[{\"fqdn\"\\:\"\\(.*\\)/\\1/')\n  echo \"${response%%\\\"*}\"\n}\n"
  },
  {
    "path": "dnsapi/dns_internetbs.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_internetbs_info='InternetBS.net\nSite: InternetBS.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_internetbs\nOptions:\n INTERNETBS_API_KEY API Key\n INTERNETBS_API_PASSWORD API Password\nIssues: github.com/acmesh-official/acme.sh/issues/2261\nAuthor: Ne-Lexa <alexey@nelexa.ru>\n'\n\nINTERNETBS_API_URL=\"https://api.internet.bs\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_internetbs_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  INTERNETBS_API_KEY=\"${INTERNETBS_API_KEY:-$(_readaccountconf_mutable INTERNETBS_API_KEY)}\"\n  INTERNETBS_API_PASSWORD=\"${INTERNETBS_API_PASSWORD:-$(_readaccountconf_mutable INTERNETBS_API_PASSWORD)}\"\n\n  if [ -z \"$INTERNETBS_API_KEY\" ] || [ -z \"$INTERNETBS_API_PASSWORD\" ]; then\n    INTERNETBS_API_KEY=\"\"\n    INTERNETBS_API_PASSWORD=\"\"\n    _err \"You didn't specify the INTERNET.BS api key and password yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable INTERNETBS_API_KEY \"$INTERNETBS_API_KEY\"\n  _saveaccountconf_mutable INTERNETBS_API_PASSWORD \"$INTERNETBS_API_PASSWORD\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # https://testapi.internet.bs/Domain/DnsRecord/Add?ApiKey=testapi&Password=testpass&FullRecordName=w3.test-api-domain7.net&Type=CNAME&Value=www.internet.bs%&ResponseFormat=json\n  if _internetbs_rest POST \"Domain/DnsRecord/Add\" \"FullRecordName=${_sub_domain}.${_domain}&Type=TXT&Value=${txtvalue}&ResponseFormat=json\"; then\n    if ! _contains \"$response\" \"\\\"status\\\":\\\"SUCCESS\\\"\"; then\n      _err \"ERROR add TXT record\"\n      _err \"$response\"\n      return 1\n    fi\n\n    _info \"txt record add success.\"\n    return 0\n  fi\n\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_internetbs_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  INTERNETBS_API_KEY=\"${INTERNETBS_API_KEY:-$(_readaccountconf_mutable INTERNETBS_API_KEY)}\"\n  INTERNETBS_API_PASSWORD=\"${INTERNETBS_API_PASSWORD:-$(_readaccountconf_mutable INTERNETBS_API_PASSWORD)}\"\n\n  if [ -z \"$INTERNETBS_API_KEY\" ] || [ -z \"$INTERNETBS_API_PASSWORD\" ]; then\n    INTERNETBS_API_KEY=\"\"\n    INTERNETBS_API_PASSWORD=\"\"\n    _err \"You didn't specify the INTERNET.BS api key and password yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  # https://testapi.internet.bs/Domain/DnsRecord/List?ApiKey=testapi&Password=testpass&Domain=test-api-domain7.net&FilterType=CNAME&ResponseFormat=json\n  _internetbs_rest POST \"Domain/DnsRecord/List\" \"Domain=$_domain&FilterType=TXT&ResponseFormat=json\"\n\n  if ! _contains \"$response\" \"\\\"status\\\":\\\"SUCCESS\\\"\"; then\n    _err \"ERROR list dns records\"\n    _err \"$response\"\n    return 1\n  fi\n\n  if _contains \"$response\" \"\\name\\\":\\\"${_sub_domain}.${_domain}\\\"\"; then\n    _info \"txt record find.\"\n\n    # https://testapi.internet.bs/Domain/DnsRecord/Remove?ApiKey=testapi&Password=testpass&FullRecordName=www.test-api-domain7.net&Type=cname&ResponseFormat=json\n    _internetbs_rest POST \"Domain/DnsRecord/Remove\" \"FullRecordName=${_sub_domain}.${_domain}&Type=TXT&ResponseFormat=json\"\n\n    if ! _contains \"$response\" \"\\\"status\\\":\\\"SUCCESS\\\"\"; then\n      _err \"ERROR remove dns record\"\n      _err \"$response\"\n      return 1\n    fi\n\n    _info \"txt record deleted success.\"\n    return 0\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=12345\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n\n  # https://testapi.internet.bs/Domain/List?ApiKey=testapi&Password=testpass&CompactList=yes&ResponseFormat=json\n  if _internetbs_rest POST \"Domain/List\" \"CompactList=yes&ResponseFormat=json\"; then\n\n    if ! _contains \"$response\" \"\\\"status\\\":\\\"SUCCESS\\\"\"; then\n      _err \"ERROR fetch domain list\"\n      _err \"$response\"\n      return 1\n    fi\n\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"${i}\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        #not valid\n        return 1\n      fi\n\n      if _contains \"$response\" \"\\\"$h\\\"\"; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"${p}\")\n        _domain=${h}\n        return 0\n      fi\n\n      p=${i}\n      i=$(_math \"$i\" + 1)\n    done\n  fi\n  return 1\n}\n\n#Usage: method  URI  data\n_internetbs_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  url=\"${INTERNETBS_API_URL}/${ep}\"\n\n  _debug url \"$url\"\n\n  apiKey=\"$(printf \"%s\" \"${INTERNETBS_API_KEY}\" | _url_encode)\"\n  password=\"$(printf \"%s\" \"${INTERNETBS_API_PASSWORD}\" | _url_encode)\"\n\n  if [ \"$m\" = \"GET\" ]; then\n    response=\"$(_get \"${url}?ApiKey=${apiKey}&Password=${password}&${data}\" | tr -d '\\r')\"\n  else\n    _debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"${url}?ApiKey=${apiKey}&Password=${password}\" | tr -d '\\r')\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_inwx.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_inwx_info='INWX.de\nSite: INWX.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx\nOptions:\n INWX_User Username\n INWX_Password Password\n INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional requires oathtool)\n'\n\n# Dependencies:\n# -------------\n# - oathtool (When using 2 Factor Authentication)\n\nINWX_Api=\"https://api.domrobot.com/xmlrpc/\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_inwx_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  INWX_User=\"${INWX_User:-$(_readaccountconf_mutable INWX_User)}\"\n  INWX_Password=\"${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}\"\n  INWX_Shared_Secret=\"${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}\"\n  if [ -z \"$INWX_User\" ] || [ -z \"$INWX_Password\" ]; then\n    INWX_User=\"\"\n    INWX_Password=\"\"\n    _err \"You don't specify inwx user and password yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable INWX_User \"$INWX_User\"\n  _saveaccountconf_mutable INWX_Password \"$INWX_Password\"\n  _saveaccountconf_mutable INWX_Shared_Secret \"$INWX_Shared_Secret\"\n\n  if ! _inwx_login; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  _inwx_add_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n\n}\n\n#fulldomain txtvalue\ndns_inwx_rm() {\n\n  fulldomain=$1\n  txtvalue=$2\n\n  INWX_User=\"${INWX_User:-$(_readaccountconf_mutable INWX_User)}\"\n  INWX_Password=\"${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}\"\n  INWX_Shared_Secret=\"${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}\"\n  if [ -z \"$INWX_User\" ] || [ -z \"$INWX_Password\" ]; then\n    INWX_User=\"\"\n    INWX_Password=\"\"\n    _err \"You don't specify inwx user and password yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  if ! _inwx_login; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>nameserver.info</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>domain</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>type</name>\n       <value>\n        <string>TXT</string>\n       </value>\n      </member>\n      <member>\n       <name>name</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>content</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>' \"$_domain\" \"$_sub_domain\" \"$txtvalue\")\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"Command completed successfully\"; then\n    _err \"Error could not get txt records\"\n    return 1\n  fi\n\n  if ! printf \"%s\" \"$response\" | grep \"count\" >/dev/null; then\n    _info \"Do not need to delete record\"\n  else\n    _record_id=$(printf '%s' \"$response\" | _egrep_o '.*(<member><name>record){1}(.*)([0-9]+){1}' | _egrep_o '<name>id<\\/name><value><string>[0-9]+' | _egrep_o '[0-9]+')\n    _info \"Deleting record\"\n    _inwx_delete_record \"$_record_id\"\n  fi\n\n}\n\n####################  Private functions below ##################################\n\n_inwx_check_cookie() {\n  INWX_Cookie=\"${INWX_Cookie:-$(_readaccountconf_mutable INWX_Cookie)}\"\n  if [ -z \"$INWX_Cookie\" ]; then\n    _debug \"No cached cookie found\"\n    return 1\n  fi\n  _H1=\"$INWX_Cookie\"\n  export _H1\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>account.info</methodName>\n  </methodCall>')\n\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n  if _contains \"$response\" \"<member><name>code</name><value><int>1000</int></value></member>\"; then\n    _debug \"Cached cookie still valid\"\n    return 0\n  fi\n\n  _debug \"Cached cookie no longer valid\"\n  _H1=\"\"\n  export _H1\n  INWX_Cookie=\"\"\n  _saveaccountconf_mutable INWX_Cookie \"$INWX_Cookie\"\n  return 1\n}\n\n_htmlEscape() {\n  _s=\"$1\"\n  _s=$(echo \"$_s\" | sed \"s/&/&amp;/g\")\n  _s=$(echo \"$_s\" | sed \"s/</\\&lt;/g\")\n  _s=$(echo \"$_s\" | sed \"s/>/\\&gt;/g\")\n  _s=$(echo \"$_s\" | sed 's/\"/\\&quot;/g')\n  printf -- %s \"$_s\"\n}\n\n_inwx_login() {\n\n  if _inwx_check_cookie; then\n    _debug \"Already logged in\"\n    return 0\n  fi\n\n  XML_PASS=$(_htmlEscape \"$INWX_Password\")\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>account.login</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>user</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>pass</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>' \"$INWX_User\" \"$XML_PASS\")\n\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n  INWX_Cookie=$(printf \"Cookie: %s\" \"$(grep \"domrobot=\" \"$HTTP_HEADER\" | grep -i \"^Set-Cookie:\" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')\")\n  _H1=$INWX_Cookie\n  export _H1\n  export INWX_Cookie\n  _saveaccountconf_mutable INWX_Cookie \"$INWX_Cookie\"\n\n  if ! _contains \"$response\" \"<member><name>code</name><value><int>1000</int></value></member>\"; then\n    _err \"INWX API: Authentication error (username/password correct?)\"\n    return 1\n  fi\n\n  #https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71\n  if _contains \"$response\" \"<member><name>tfa</name><value><string>GOOGLE-AUTH</string></value></member>\"; then\n    if [ -z \"$INWX_Shared_Secret\" ]; then\n      _err \"INWX API: Mobile TAN detected.\"\n      _err \"Please define a shared secret.\"\n      return 1\n    fi\n\n    if ! _exists oathtool; then\n      _err \"Please install oathtool to use 2 Factor Authentication.\"\n      _err \"\"\n      return 1\n    fi\n\n    tan=\"$(oathtool --base32 --totp \"${INWX_Shared_Secret}\" 2>/dev/null)\"\n\n    xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n    <methodCall>\n    <methodName>account.unlock</methodName>\n    <params>\n     <param>\n      <value>\n       <struct>\n        <member>\n         <name>tan</name>\n         <value>\n          <string>%s</string>\n         </value>\n        </member>\n       </struct>\n      </value>\n     </param>\n    </params>\n    </methodCall>' \"$tan\")\n\n    response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n    if ! _contains \"$response\" \"<member><name>code</name><value><int>1000</int></value></member>\"; then\n      _err \"INWX API: Mobile TAN not correct.\"\n      return 1\n    fi\n  fi\n\n}\n\n_get_root() {\n  domain=$1\n  _debug \"get root\"\n\n  domain=$1\n  i=2\n  p=1\n\n  xml_content='<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>nameserver.list</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>pagelimit</name>\n       <value>\n        <int>9999</int>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>'\n\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"$h\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n\n}\n\n_inwx_delete_record() {\n  record_id=$1\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>nameserver.deleteRecord</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>id</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>' \"$record_id\")\n\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n  if ! printf \"%s\" \"$response\" | grep \"Command completed successfully\" >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n  return 0\n\n}\n\n_inwx_update_record() {\n  record_id=$1\n  txtval=$2\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>nameserver.updateRecord</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>content</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>id</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>' \"$txtval\" \"$record_id\")\n\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n  if ! printf \"%s\" \"$response\" | grep \"Command completed successfully\" >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n  return 0\n\n}\n\n_inwx_add_record() {\n\n  domain=$1\n  sub_domain=$2\n  txtval=$3\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>nameserver.createRecord</methodName>\n  <params>\n   <param>\n    <value>\n     <struct>\n      <member>\n       <name>domain</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>type</name>\n       <value>\n        <string>TXT</string>\n       </value>\n      </member>\n      <member>\n       <name>content</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n      <member>\n       <name>name</name>\n       <value>\n        <string>%s</string>\n       </value>\n      </member>\n     </struct>\n    </value>\n   </param>\n  </params>\n  </methodCall>' \"$domain\" \"$txtval\" \"$sub_domain\")\n\n  response=\"$(_post \"$xml_content\" \"$INWX_Api\" \"\" \"POST\")\"\n\n  if ! printf \"%s\" \"$response\" | grep \"Command completed successfully\" >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ionos.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ionos_info='IONOS.de\nSite: IONOS.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ionos\nOptions:\n IONOS_PREFIX Prefix\n IONOS_SECRET Secret\nIssues: github.com/acmesh-official/acme.sh/issues/3379\n'\n\nIONOS_API=\"https://api.hosting.ionos.com/dns\"\nIONOS_ROUTE_ZONES=\"/v1/zones\"\n\nIONOS_TXT_TTL=60 # minimum accepted by API\nIONOS_TXT_PRIO=10\n\ndns_ionos_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _ionos_init; then\n    return 1\n  fi\n\n  _body=\"[{\\\"name\\\":\\\"$_sub_domain.$_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":$IONOS_TXT_TTL,\\\"prio\\\":$IONOS_TXT_PRIO,\\\"disabled\\\":false}]\"\n\n  if _ionos_rest POST \"$IONOS_ROUTE_ZONES/$_zone_id/records\" \"$_body\" && [ \"$_code\" = \"201\" ]; then\n    _info \"TXT record has been created successfully.\"\n    return 0\n  fi\n\n  return 1\n}\n\ndns_ionos_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _ionos_init; then\n    return 1\n  fi\n\n  if ! _ionos_get_record \"$fulldomain\" \"$_zone_id\" \"$txtvalue\"; then\n    _err \"Could not find _acme-challenge TXT record.\"\n    return 1\n  fi\n\n  if _ionos_rest DELETE \"$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id\" && [ \"$_code\" = \"200\" ]; then\n    _info \"TXT record has been deleted successfully.\"\n    return 0\n  fi\n\n  return 1\n}\n\n_ionos_init() {\n  IONOS_PREFIX=\"${IONOS_PREFIX:-$(_readaccountconf_mutable IONOS_PREFIX)}\"\n  IONOS_SECRET=\"${IONOS_SECRET:-$(_readaccountconf_mutable IONOS_SECRET)}\"\n\n  if [ -z \"$IONOS_PREFIX\" ] || [ -z \"$IONOS_SECRET\" ]; then\n    _err \"You didn't specify an IONOS api prefix and secret yet.\"\n    _err \"Read https://beta.developer.hosting.ionos.de/docs/getstarted to learn how to get a prefix and secret.\"\n    _err \"\"\n    _err \"Then set them before calling acme.sh:\"\n    _err \"\\$ export IONOS_PREFIX=\\\"...\\\"\"\n    _err \"\\$ export IONOS_SECRET=\\\"...\\\"\"\n    _err \"\\$ acme.sh --issue -d ... --dns dns_ionos\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable IONOS_PREFIX \"$IONOS_PREFIX\"\n  _saveaccountconf_mutable IONOS_SECRET \"$IONOS_SECRET\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Cannot find this domain in your IONOS account.\"\n    return 1\n  fi\n}\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  if _ionos_rest GET \"$IONOS_ROUTE_ZONES\"; then\n    _response=\"$(echo \"$_response\" | tr -d \"\\n\")\"\n\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      if [ -z \"$h\" ]; then\n        return 1\n      fi\n\n      _zone=\"$(echo \"$_response\" | _egrep_o \"\\\"name\\\":\\\"$h\\\".*\\}\")\"\n      if [ \"$_zone\" ]; then\n        _zone_id=$(printf \"%s\\n\" \"$_zone\" | _egrep_o \"\\\"id\\\":\\\"[a-fA-F0-9\\-]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d '\\\"')\n        if [ \"$_zone_id\" ]; then\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n          _domain=$h\n\n          return 0\n        fi\n\n        return 1\n      fi\n\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n  fi\n\n  return 1\n}\n\n_ionos_get_record() {\n  fulldomain=$1\n  zone_id=$2\n  txtrecord=$3\n\n  if _ionos_rest GET \"$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT\"; then\n    _response=\"$(echo \"$_response\" | tr -d \"\\n\")\"\n\n    _record=\"$(echo \"$_response\" | _egrep_o \"\\\"name\\\":\\\"$fulldomain\\\"[^\\}]*\\\"type\\\":\\\"TXT\\\"[^\\}]*\\\"content\\\":\\\"\\\\\\\\\\\"$txtrecord\\\\\\\\\\\"\\\".*\\}\")\"\n    if [ \"$_record\" ]; then\n      _record_id=$(printf \"%s\\n\" \"$_record\" | _egrep_o \"\\\"id\\\":\\\"[a-fA-F0-9\\-]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d '\\\"')\n\n      return 0\n    fi\n  fi\n\n  return 1\n}\n\n_ionos_rest() {\n  method=\"$1\"\n  route=\"$2\"\n  data=\"$3\"\n\n  IONOS_API_KEY=\"$(printf \"%s.%s\" \"$IONOS_PREFIX\" \"$IONOS_SECRET\")\"\n\n  export _H1=\"X-API-Key: $IONOS_API_KEY\"\n\n  # clear headers\n  : >\"$HTTP_HEADER\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    export _H2=\"Accept: application/json\"\n    export _H3=\"Content-Type: application/json\"\n\n    _response=\"$(_post \"$data\" \"$IONOS_API$route\" \"\" \"$method\" \"application/json\")\"\n  else\n    export _H2=\"Accept: */*\"\n    export _H3=\n\n    _response=\"$(_get \"$IONOS_API$route\")\"\n  fi\n\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $route: $_response\"\n    return 1\n  fi\n\n  _debug2 \"_response\" \"$_response\"\n  _debug2 \"_code\" \"$_code\"\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ionos_cloud.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ionos_cloud_info='IONOS Cloud DNS\nSite: ionos.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ionos_cloud\nOptions:\n IONOS_TOKEN API Token.\nIssues: github.com/acmesh-official/acme.sh/issues/5243\n'\n\n# Supports IONOS Cloud DNS API v1.15.4\n\nIONOS_CLOUD_API=\"https://dns.de-fra.ionos.com\"\nIONOS_CLOUD_ROUTE_ZONES=\"/zones\"\n\ndns_ionos_cloud_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _ionos_init; then\n    return 1\n  fi\n\n  _record_name=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 1)\n  _body=\"{\\\"properties\\\":{\\\"name\\\":\\\"$_record_name\\\", \\\"type\\\":\\\"TXT\\\", \\\"content\\\":\\\"$txtvalue\\\"}}\"\n\n  if _ionos_cloud_rest POST \"$IONOS_CLOUD_ROUTE_ZONES/$_zone_id/records\" \"$_body\" && [ \"$_code\" = \"202\" ]; then\n    _info \"TXT record has been created successfully.\"\n    return 0\n  fi\n\n  return 1\n}\n\ndns_ionos_cloud_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _ionos_init; then\n    return 1\n  fi\n\n  if ! _ionos_cloud_get_record \"$_zone_id\" \"$txtvalue\" \"$fulldomain\"; then\n    _err \"Could not find _acme-challenge TXT record.\"\n    return 1\n  fi\n\n  if _ionos_cloud_rest DELETE \"$IONOS_CLOUD_ROUTE_ZONES/$_zone_id/records/$_record_id\" && [ \"$_code\" = \"202\" ]; then\n    _info \"TXT record has been deleted successfully.\"\n    return 0\n  fi\n\n  return 1\n}\n\n_ionos_init() {\n  IONOS_TOKEN=\"${IONOS_TOKEN:-$(_readaccountconf_mutable IONOS_TOKEN)}\"\n\n  if [ -z \"$IONOS_TOKEN\" ]; then\n    _err \"You didn't specify an IONOS token yet.\"\n    _err \"Read https://api.ionos.com/docs/authentication/v1/#tag/tokens/operation/tokensGenerate to learn how to get a token.\"\n    _err \"You need to set it before calling acme.sh:\"\n    _err \"\\$ export IONOS_TOKEN=\\\"...\\\"\"\n    _err \"\\$ acme.sh --issue -d ... --dns dns_ionos_cloud\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable IONOS_TOKEN \"$IONOS_TOKEN\"\n\n  if ! _get_cloud_zone \"$fulldomain\"; then\n    _err \"Cannot find zone $zone in your IONOS account.\"\n    return 1\n  fi\n\n  return 0\n}\n\n_get_cloud_zone() {\n  domain=$1\n  zone=$(printf \"%s\" \"$domain\" | cut -d . -f 2-)\n\n  if _ionos_cloud_rest GET \"$IONOS_CLOUD_ROUTE_ZONES?filter.zoneName=$zone\"; then\n    _response=\"$(echo \"$_response\" | tr -d \"\\n\")\"\n\n    _zone_list_items=$(echo \"$_response\" | _egrep_o \"\\\"items\\\":.*\")\n\n    _zone_id=$(printf \"%s\\n\" \"$_zone_list_items\" | _egrep_o \"\\\"id\\\":\\\"[a-fA-F0-9\\-]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d '\\\"')\n    if [ \"$_zone_id\" ]; then\n      return 0\n    fi\n  fi\n\n  return 1\n}\n\n_ionos_cloud_get_record() {\n  zone_id=$1\n  txtrecord=$2\n  # this is to transform the domain to lower case\n  fulldomain=$(printf \"%s\" \"$3\" | _lower_case)\n  # this is to transform record name to lower case\n  # IONOS Cloud API transforms all record names to lower case\n  _record_name=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 1 | _lower_case)\n\n  if _ionos_cloud_rest GET \"$IONOS_CLOUD_ROUTE_ZONES/$zone_id/records\"; then\n    _response=\"$(echo \"$_response\" | tr -d \"\\n\")\"\n\n    pattern=\"\\{\\\"id\\\":\\\"[a-fA-F0-9\\-]*\\\",\\\"type\\\":\\\"record\\\",\\\"href\\\":\\\"/zones/$zone_id/records/[a-fA-F0-9\\-]*\\\",\\\"metadata\\\":\\{\\\"createdDate\\\":\\\"[A-Z0-9\\:\\.\\-]*\\\",\\\"lastModifiedDate\\\":\\\"[A-Z0-9\\:\\.\\-]*\\\",\\\"fqdn\\\":\\\"$fulldomain\\\",\\\"state\\\":\\\"AVAILABLE\\\",\\\"zoneId\\\":\\\"$zone_id\\\"\\},\\\"properties\\\":\\{\\\"content\\\":\\\"$txtrecord\\\",\\\"enabled\\\":true,\\\"name\\\":\\\"$_record_name\\\",\\\"priority\\\":[0-9]*,\\\"ttl\\\":[0-9]*,\\\"type\\\":\\\"TXT\\\"\\}\\}\"\n\n    _record=\"$(echo \"$_response\" | _egrep_o \"$pattern\")\"\n    if [ \"$_record\" ]; then\n      _record_id=$(printf \"%s\\n\" \"$_record\" | _egrep_o \"\\\"id\\\":\\\"[a-fA-F0-9\\-]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d '\\\"')\n      return 0\n    fi\n  fi\n\n  return 1\n}\n\n_ionos_cloud_rest() {\n  method=\"$1\"\n  route=\"$2\"\n  data=\"$3\"\n\n  export _H1=\"Authorization: Bearer $IONOS_TOKEN\"\n\n  # clear headers\n  : >\"$HTTP_HEADER\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    _response=\"$(_post \"$data\" \"$IONOS_CLOUD_API$route\" \"\" \"$method\" \"application/json\")\"\n  else\n    _response=\"$(_get \"$IONOS_CLOUD_API$route\")\"\n  fi\n\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $route: $_response\"\n    return 1\n  fi\n\n  _debug2 \"_response\" \"$_response\"\n  _debug2 \"_code\" \"$_code\"\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ipv64.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ipv64_info='IPv64.net\nSite: IPv64.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ipv64\nOptions:\n IPv64_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/4419\nAuthor: Roman Lumetsberger\n'\n\nIPv64_API=\"https://ipv64.net/api\"\n\n########  Public functions ######################\n\n#Usage: dns_ipv64_add _acme-challenge.domain.ipv64.net \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_ipv64_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  IPv64_Token=\"${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}\"\n  if [ -z \"$IPv64_Token\" ]; then\n    _err \"You must export variable: IPv64_Token\"\n    _err \"The API Key for your IPv64 account is necessary.\"\n    _err \"You can look it up in your IPv64 account.\"\n    return 1\n  fi\n\n  # Now save the credentials.\n  _saveaccountconf_mutable IPv64_Token \"$IPv64_Token\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\" \"$fulldomain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # convert to lower case\n  _domain=\"$(echo \"$_domain\" | _lower_case)\"\n  _sub_domain=\"$(echo \"$_sub_domain\" | _lower_case)\"\n  # Now add the TXT record\n  _info \"Trying to add TXT record\"\n  if _ipv64_rest \"POST\" \"add_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue\"; then\n    _info \"TXT record has been successfully added.\"\n    return 0\n  else\n    _err \"Errors happened during adding the TXT record, response=$_response\"\n    return 1\n  fi\n\n}\n\n#Usage: fulldomain txtvalue\n#Usage: dns_ipv64_rm _acme-challenge.domain.ipv64.net \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n#Remove the txt record after validation.\ndns_ipv64_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  IPv64_Token=\"${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}\"\n  if [ -z \"$IPv64_Token\" ]; then\n    _err \"You must export variable: IPv64_Token\"\n    _err \"The API Key for your IPv64 account is necessary.\"\n    _err \"You can look it up in your IPv64 account.\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\" \"$fulldomain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # convert to lower case\n  _domain=\"$(echo \"$_domain\" | _lower_case)\"\n  _sub_domain=\"$(echo \"$_sub_domain\" | _lower_case)\"\n  # Now delete the TXT record\n  _info \"Trying to delete TXT record\"\n  if _ipv64_rest \"DELETE\" \"del_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue\"; then\n    _info \"TXT record has been successfully deleted.\"\n    return 0\n  else\n    _err \"Errors happened during deleting the TXT record, response=$_response\"\n    return 1\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=\"$1\"\n  i=1\n  p=1\n\n  _ipv64_get \"get_domains\"\n  domain_data=$_response\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    #if _contains \"$domain_data\" \"\\\"\"$h\"\\\"\\:\"; then\n    if _contains \"$domain_data\" \"\\\"\"\"$h\"\"\\\"\\:\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n#send get request to api\n# $1 has to set the api-function\n_ipv64_get() {\n  url=\"$IPv64_API?$1\"\n  export _H1=\"Authorization: Bearer $IPv64_Token\"\n\n  _response=$(_get \"$url\")\n  _response=\"$(echo \"$_response\" | _normalizeJson)\"\n\n  if _contains \"$_response\" \"429 Too Many Requests\"; then\n    _info \"API throttled, sleeping to reset the limit\"\n    _sleep 10\n    _response=$(_get \"$url\")\n    _response=\"$(echo \"$_response\" | _normalizeJson)\"\n  fi\n}\n\n_ipv64_rest() {\n  url=\"$IPv64_API\"\n  export _H1=\"Authorization: Bearer $IPv64_Token\"\n  export _H2=\"Content-Type: application/x-www-form-urlencoded\"\n  _response=$(_post \"$2\" \"$url\" \"\" \"$1\")\n\n  if _contains \"$_response\" \"429 Too Many Requests\"; then\n    _info \"API throttled, sleeping to reset the limit\"\n    _sleep 10\n    _response=$(_post \"$2\" \"$url\" \"\" \"$1\")\n  fi\n\n  if ! _contains \"$_response\" \"\\\"info\\\":\\\"success\\\"\"; then\n    return 1\n  fi\n  _debug2 response \"$_response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ispconfig.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ispconfig_info='ISPConfig Server API\nSite: ISPConfig.org\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ispconfig\nOptions:\n ISPC_User Remote User\n ISPC_Password Remote Password\n ISPC_Api API URL. E.g. \"https://ispc.domain.tld:8080/remote/json.php\"\n ISPC_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept\n'\n\n# ISPConfig 3.1 API\n# User must provide login data and URL to the ISPConfig installation incl. port.\n# The remote user in ISPConfig must have access to:\n# - DNS txt Functions\n# - DNS zone functions\n# - Client functions\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_ispconfig_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n  _debug \"Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'\"\n  _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt\n}\n\n#Usage: dns_myapi_rm   _acme-challenge.www.domain.com\ndns_ispconfig_rm() {\n  fulldomain=\"${1}\"\n  _debug \"Calling: dns_ispconfig_rm() '${fulldomain}'\"\n  _ISPC_credentials && _ISPC_login && _ISPC_rmTxt\n}\n\n####################  Private functions below ##################################\n\n_ISPC_credentials() {\n  ISPC_User=\"${ISPC_User:-$(_readaccountconf_mutable ISPC_User)}\"\n  ISPC_Password=\"${ISPC_Password:-$(_readaccountconf_mutable ISPC_Password)}\"\n  ISPC_Api=\"${ISPC_Api:-$(_readaccountconf_mutable ISPC_Api)}\"\n  ISPC_Api_Insecure=\"${ISPC_Api_Insecure:-$(_readaccountconf_mutable ISPC_Api_Insecure)}\"\n  if [ -z \"${ISPC_User}\" ] || [ -z \"${ISPC_Password}\" ] || [ -z \"${ISPC_Api}\" ] || [ -z \"${ISPC_Api_Insecure}\" ]; then\n    ISPC_User=\"\"\n    ISPC_Password=\"\"\n    ISPC_Api=\"\"\n    ISPC_Api_Insecure=\"\"\n    _err \"You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again.\"\n    return 1\n  else\n    _saveaccountconf_mutable ISPC_User \"${ISPC_User}\"\n    _saveaccountconf_mutable ISPC_Password \"${ISPC_Password}\"\n    _saveaccountconf_mutable ISPC_Api \"${ISPC_Api}\"\n    _saveaccountconf_mutable ISPC_Api_Insecure \"${ISPC_Api_Insecure}\"\n    # Set whether curl should use secure or insecure mode\n    export HTTPS_INSECURE=\"${ISPC_Api_Insecure}\"\n  fi\n}\n\n_ISPC_login() {\n  _info \"Getting Session ID\"\n  curData=\"{\\\"username\\\":\\\"${ISPC_User}\\\",\\\"password\\\":\\\"${ISPC_Password}\\\",\\\"client_login\\\":false}\"\n  curResult=\"$(_post \"${curData}\" \"${ISPC_Api}?login\")\"\n  _debug \"Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'\"\n  _debug \"Result of _ISPC_login: '$curResult'\"\n  if _contains \"${curResult}\" '\"code\":\"ok\"'; then\n    sessionID=$(echo \"${curResult}\" | _egrep_o \"response.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n    _info \"Retrieved Session ID.\"\n    _debug \"Session ID: '${sessionID}'\"\n  else\n    _err \"Couldn't retrieve the Session ID.\"\n    return 1\n  fi\n}\n\n_ISPC_getZoneInfo() {\n  _info \"Getting Zoneinfo\"\n  zoneEnd=false\n  curZone=\"${fulldomain}\"\n  while [ \"${zoneEnd}\" = false ]; do\n    # we can strip the first part of the fulldomain, since it's just the _acme-challenge string\n    curZone=\"${curZone#*.}\"\n    # suffix . needed for zone -> domain.tld.\n    curData=\"{\\\"session_id\\\":\\\"${sessionID}\\\",\\\"primary_id\\\":{\\\"origin\\\":\\\"${curZone}.\\\"}}\"\n    curResult=\"$(_post \"${curData}\" \"${ISPC_Api}?dns_zone_get\")\"\n    _debug \"Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?dns_zone_get'\"\n    _debug \"Result of _ISPC_getZoneInfo: '$curResult'\"\n    if _contains \"${curResult}\" '\"id\":\"'; then\n      zoneFound=true\n      zoneEnd=true\n      _info \"Retrieved zone data.\"\n      _debug \"Zone data: '${curResult}'\"\n    fi\n    if [ \"${curZone#*.}\" != \"$curZone\" ]; then\n      _debug2 \"$curZone still contains a '.' - so we can check next higher level\"\n    else\n      zoneEnd=true\n      _err \"Couldn't retrieve zone data.\"\n      return 1\n    fi\n  done\n  if [ \"${zoneFound}\" ]; then\n    server_id=$(echo \"${curResult}\" | _egrep_o \"server_id.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n    _debug \"Server ID: '${server_id}'\"\n    case \"${server_id}\" in\n    '' | *[!0-9]*)\n      _err \"Server ID is not numeric.\"\n      return 1\n      ;;\n    *) _info \"Retrieved Server ID\" ;;\n    esac\n    zone=$(echo \"${curResult}\" | _egrep_o \"\\\"id.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n    _debug \"Zone: '${zone}'\"\n    case \"${zone}\" in\n    '' | *[!0-9]*)\n      _err \"Zone ID is not numeric.\"\n      return 1\n      ;;\n    *) _info \"Retrieved Zone ID\" ;;\n    esac\n    sys_userid=$(echo \"${curResult}\" | _egrep_o \"sys_userid.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n    _debug \"SYS User ID: '${sys_userid}'\"\n    case \"${sys_userid}\" in\n    '' | *[!0-9]*)\n      _err \"SYS User ID is not numeric.\"\n      return 1\n      ;;\n    *) _info \"Retrieved SYS User ID.\" ;;\n    esac\n    zoneFound=\"\"\n    zoneEnd=\"\"\n  fi\n  # Need to get client_id as it is different from sys_userid\n  curData=\"{\\\"session_id\\\":\\\"${sessionID}\\\",\\\"sys_userid\\\":\\\"${sys_userid}\\\"}\"\n  curResult=\"$(_post \"${curData}\" \"${ISPC_Api}?client_get_id\")\"\n  _debug \"Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'\"\n  _debug \"Result of _ISPC_ClientGetID: '$curResult'\"\n  client_id=$(echo \"${curResult}\" | _egrep_o \"response.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2 | tr -d '{}')\n  _debug \"Client ID: '${client_id}'\"\n  case \"${client_id}\" in\n  '' | *[!0-9]*)\n    _err \"Client ID is not numeric.\"\n    return 1\n    ;;\n  *) _info \"Retrieved Client ID.\" ;;\n  esac\n}\n\n_ISPC_addTxt() {\n  curSerial=\"$(date +%s)\"\n  curStamp=\"$(date +'%F %T')\"\n  params=\"\\\"server_id\\\":\\\"${server_id}\\\",\\\"zone\\\":\\\"${zone}\\\",\\\"name\\\":\\\"${fulldomain}.\\\",\\\"type\\\":\\\"txt\\\",\\\"data\\\":\\\"${txtvalue}\\\",\\\"aux\\\":\\\"0\\\",\\\"ttl\\\":\\\"3600\\\",\\\"active\\\":\\\"y\\\",\\\"stamp\\\":\\\"${curStamp}\\\",\\\"serial\\\":\\\"${curSerial}\\\"\"\n  curData=\"{\\\"session_id\\\":\\\"${sessionID}\\\",\\\"client_id\\\":\\\"${client_id}\\\",\\\"params\\\":{${params}},\\\"update_serial\\\":true}\"\n  curResult=\"$(_post \"${curData}\" \"${ISPC_Api}?dns_txt_add\")\"\n  _debug \"Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'\"\n  _debug \"Result of _ISPC_addTxt: '$curResult'\"\n  record_id=$(echo \"${curResult}\" | _egrep_o \"\\\"response.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n  _debug \"Record ID: '${record_id}'\"\n  case \"${record_id}\" in\n  '' | *[!0-9]*)\n    _err \"Couldn't add ACME Challenge TXT record to zone.\"\n    return 1\n    ;;\n  *) _info \"Added ACME Challenge TXT record to zone.\" ;;\n  esac\n}\n\n_ISPC_rmTxt() {\n  # Need to get the record ID.\n  curData=\"{\\\"session_id\\\":\\\"${sessionID}\\\",\\\"primary_id\\\":{\\\"name\\\":\\\"${fulldomain}.\\\",\\\"type\\\":\\\"TXT\\\"}}\"\n  curResult=\"$(_post \"${curData}\" \"${ISPC_Api}?dns_txt_get\")\"\n  _debug \"Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'\"\n  _debug \"Result of _ISPC_rmTxt: '$curResult'\"\n  if _contains \"${curResult}\" '\"code\":\"ok\"'; then\n    record_id=$(echo \"${curResult}\" | _egrep_o \"\\\"id.*\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n    _debug \"Record ID: '${record_id}'\"\n    case \"${record_id}\" in\n    '' | *[!0-9]*)\n      _err \"Record ID is not numeric.\"\n      return 1\n      ;;\n    *)\n      unset IFS\n      _info \"Retrieved Record ID.\"\n      curData=\"{\\\"session_id\\\":\\\"${sessionID}\\\",\\\"primary_id\\\":\\\"${record_id}\\\",\\\"update_serial\\\":true}\"\n      curResult=\"$(_post \"${curData}\" \"${ISPC_Api}?dns_txt_delete\")\"\n      _debug \"Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'\"\n      _debug \"Result of _ISPC_rmTxt: '$curResult'\"\n      if _contains \"${curResult}\" '\"code\":\"ok\"'; then\n        _info \"Removed ACME Challenge TXT record from zone.\"\n      else\n        _err \"Couldn't remove ACME Challenge TXT record from zone.\"\n        return 1\n      fi\n      ;;\n    esac\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_jd.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_jd_info='jdcloud.com\nSite: jdcloud.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_jd\nOptions:\n JD_ACCESS_KEY_ID Access key ID\n JD_ACCESS_KEY_SECRET Access key secret\n JD_REGION Region. E.g. \"cn-north-1\"\nIssues: github.com/acmesh-official/acme.sh/issues/2388\n'\n\n_JD_ACCOUNT=\"https://uc.jdcloud.com/account/accesskey\"\n\n_JD_PROD=\"clouddnsservice\"\n_JD_API=\"jdcloud-api.com\"\n\n_JD_API_VERSION=\"v1\"\n_JD_DEFAULT_REGION=\"cn-north-1\"\n\n_JD_HOST=\"$_JD_PROD.$_JD_API\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_jd_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  JD_ACCESS_KEY_ID=\"${JD_ACCESS_KEY_ID:-$(_readaccountconf_mutable JD_ACCESS_KEY_ID)}\"\n  JD_ACCESS_KEY_SECRET=\"${JD_ACCESS_KEY_SECRET:-$(_readaccountconf_mutable JD_ACCESS_KEY_SECRET)}\"\n  JD_REGION=\"${JD_REGION:-$(_readaccountconf_mutable JD_REGION)}\"\n\n  if [ -z \"$JD_ACCESS_KEY_ID\" ] || [ -z \"$JD_ACCESS_KEY_SECRET\" ]; then\n    JD_ACCESS_KEY_ID=\"\"\n    JD_ACCESS_KEY_SECRET=\"\"\n    _err \"You haven't specifed the jdcloud api key id or api key secret yet.\"\n    _err \"Please create your key and try again. see $(__green $_JD_ACCOUNT)\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable JD_ACCESS_KEY_ID \"$JD_ACCESS_KEY_ID\"\n  _saveaccountconf_mutable JD_ACCESS_KEY_SECRET \"$JD_ACCESS_KEY_SECRET\"\n  if [ -z \"$JD_REGION\" ]; then\n    _debug \"Using default region: $_JD_DEFAULT_REGION\"\n    JD_REGION=\"$_JD_DEFAULT_REGION\"\n  else\n    _saveaccountconf_mutable JD_REGION \"$JD_REGION\"\n  fi\n  _JD_BASE_URI=\"$_JD_API_VERSION/regions/$JD_REGION\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  #_debug \"Getting getViewTree\"\n\n  _debug \"Adding records\"\n\n  _addrr=\"{\\\"req\\\":{\\\"hostRecord\\\":\\\"$_sub_domain\\\",\\\"hostValue\\\":\\\"$txtvalue\\\",\\\"ttl\\\":300,\\\"type\\\":\\\"TXT\\\",\\\"viewValue\\\":-1},\\\"regionId\\\":\\\"$JD_REGION\\\",\\\"domainId\\\":\\\"$_domain_id\\\"}\"\n  #_addrr='{\"req\":{\"hostRecord\":\"xx\",\"hostValue\":\"\\\"value4\\\"\",\"jcloudRes\":false,\"mxPriority\":null,\"port\":null,\"ttl\":300,\"type\":\"TXT\",\"weight\":null,\"viewValue\":-1},\"regionId\":\"cn-north-1\",\"domainId\":\"8824\"}'\n  if jd_rest POST \"domain/$_domain_id/RRAdd\" \"\" \"$_addrr\"; then\n    _rid=\"$(echo \"$response\" | tr '{},' '\\n' | grep '\"id\":' | cut -d : -f 2)\"\n    if [ -z \"$_rid\" ]; then\n      _err \"Can not find record id from the result.\"\n      return 1\n    fi\n    _info \"TXT record added successfully.\"\n    _srid=\"$(_readdomainconf \"JD_CLOUD_RIDS\")\"\n    if [ \"$_srid\" ]; then\n      _rid=\"$_srid,$_rid\"\n    fi\n    _savedomainconf \"JD_CLOUD_RIDS\" \"$_rid\"\n    return 0\n  fi\n\n  return 1\n}\n\ndns_jd_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  JD_ACCESS_KEY_ID=\"${JD_ACCESS_KEY_ID:-$(_readaccountconf_mutable JD_ACCESS_KEY_ID)}\"\n  JD_ACCESS_KEY_SECRET=\"${JD_ACCESS_KEY_SECRET:-$(_readaccountconf_mutable JD_ACCESS_KEY_SECRET)}\"\n  JD_REGION=\"${JD_REGION:-$(_readaccountconf_mutable JD_REGION)}\"\n\n  if [ -z \"$JD_REGION\" ]; then\n    _debug \"Using default region: $_JD_DEFAULT_REGION\"\n    JD_REGION=\"$_JD_DEFAULT_REGION\"\n  fi\n\n  _JD_BASE_URI=\"$_JD_API_VERSION/regions/$JD_REGION\"\n\n  _info \"Getting existing records for $fulldomain\"\n  _srid=\"$(_readdomainconf \"JD_CLOUD_RIDS\")\"\n  _debug _srid \"$_srid\"\n\n  if [ -z \"$_srid\" ]; then\n    _err \"Not rid skip\"\n    return 0\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _cleardomainconf JD_CLOUD_RIDS\n\n  _aws_tmpl_xml=\"{\\\"ids\\\":[$_srid],\\\"action\\\":\\\"del\\\",\\\"regionId\\\":\\\"$JD_REGION\\\",\\\"domainId\\\":\\\"$_domain_id\\\"}\"\n\n  if jd_rest POST \"domain/$_domain_id/RROperate\" \"\" \"$_aws_tmpl_xml\" && _contains \"$response\" \"\\\"code\\\":\\\"OK\\\"\"; then\n    _info \"TXT record deleted successfully.\"\n    return 0\n  fi\n  return 1\n\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug2 \"Checking domain: $h\"\n    if ! jd_rest GET \"domain\"; then\n      _err \"error get domain list\"\n      return 1\n    fi\n    if [ -z \"$h\" ]; then\n      #not valid\n      _err \"Invalid domain\"\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"domainName\\\":\\\"$h\\\"\"; then\n      hostedzone=\"$(echo \"$response\" | tr '{}' '\\n' | grep \"\\\"domainName\\\":\\\"$h\\\"\")\"\n      _debug hostedzone \"$hostedzone\"\n      if [ \"$hostedzone\" ]; then\n        _domain_id=\"$(echo \"$hostedzone\" | tr ',' '\\n' | grep \"\\\"id\\\":\" | cut -d : -f 2)\"\n        if [ \"$_domain_id\" ]; then\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n          _domain=$h\n          return 0\n        fi\n      fi\n      _err \"Can't find domain with id: $h\"\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n#method uri qstr data\njd_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  qsr=\"$3\"\n  data=\"$4\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n  _debug qsr \"$qsr\"\n  _debug data \"$data\"\n\n  CanonicalURI=\"/$_JD_BASE_URI/$ep\"\n  _debug2 CanonicalURI \"$CanonicalURI\"\n\n  CanonicalQueryString=\"$qsr\"\n  _debug2 CanonicalQueryString \"$CanonicalQueryString\"\n\n  RequestDate=\"$(date -u +\"%Y%m%dT%H%M%SZ\")\"\n  #RequestDate=\"20190713T082155Z\" ######################################################\n  _debug2 RequestDate \"$RequestDate\"\n  export _H1=\"X-Jdcloud-Date: $RequestDate\"\n\n  RequestNonce=\"2bd0852a-8bae-4087-b2d5-$(_time)\"\n  #RequestNonce=\"894baff5-72d4-4244-883a-7b2eb51e7fbe\" #################################\n  _debug2 RequestNonce \"$RequestNonce\"\n  export _H2=\"X-Jdcloud-Nonce: $RequestNonce\"\n\n  if [ \"$data\" ]; then\n    CanonicalHeaders=\"content-type:application/json\\n\"\n    SignedHeaders=\"content-type;\"\n  else\n    CanonicalHeaders=\"\"\n    SignedHeaders=\"\"\n  fi\n  CanonicalHeaders=\"${CanonicalHeaders}host:$_JD_HOST\\nx-jdcloud-date:$RequestDate\\nx-jdcloud-nonce:$RequestNonce\\n\"\n  SignedHeaders=\"${SignedHeaders}host;x-jdcloud-date;x-jdcloud-nonce\"\n\n  _debug2 CanonicalHeaders \"$CanonicalHeaders\"\n  _debug2 SignedHeaders \"$SignedHeaders\"\n\n  Hash=\"sha256\"\n\n  RequestPayload=\"$data\"\n  _debug2 RequestPayload \"$RequestPayload\"\n\n  RequestPayloadHash=\"$(printf \"%s\" \"$RequestPayload\" | _digest \"$Hash\" hex | _lower_case)\"\n  _debug2 RequestPayloadHash \"$RequestPayloadHash\"\n\n  CanonicalRequest=\"$mtd\\n$CanonicalURI\\n$CanonicalQueryString\\n$CanonicalHeaders\\n$SignedHeaders\\n$RequestPayloadHash\"\n  _debug2 CanonicalRequest \"$CanonicalRequest\"\n\n  HashedCanonicalRequest=\"$(printf \"$CanonicalRequest%s\" | _digest \"$Hash\" hex)\"\n  _debug2 HashedCanonicalRequest \"$HashedCanonicalRequest\"\n\n  Algorithm=\"JDCLOUD2-HMAC-SHA256\"\n  _debug2 Algorithm \"$Algorithm\"\n\n  RequestDateOnly=\"$(echo \"$RequestDate\" | cut -c 1-8)\"\n  _debug2 RequestDateOnly \"$RequestDateOnly\"\n\n  Region=\"$JD_REGION\"\n  Service=\"$_JD_PROD\"\n\n  CredentialScope=\"$RequestDateOnly/$Region/$Service/jdcloud2_request\"\n  _debug2 CredentialScope \"$CredentialScope\"\n\n  StringToSign=\"$Algorithm\\n$RequestDate\\n$CredentialScope\\n$HashedCanonicalRequest\"\n\n  _debug2 StringToSign \"$StringToSign\"\n\n  kSecret=\"JDCLOUD2$JD_ACCESS_KEY_SECRET\"\n\n  _secure_debug2 kSecret \"$kSecret\"\n\n  kSecretH=\"$(printf \"%s\" \"$kSecret\" | _hex_dump | tr -d \" \")\"\n  _secure_debug2 kSecretH \"$kSecretH\"\n\n  kDateH=\"$(printf \"$RequestDateOnly%s\" | _hmac \"$Hash\" \"$kSecretH\" hex)\"\n  _debug2 kDateH \"$kDateH\"\n\n  kRegionH=\"$(printf \"$Region%s\" | _hmac \"$Hash\" \"$kDateH\" hex)\"\n  _debug2 kRegionH \"$kRegionH\"\n\n  kServiceH=\"$(printf \"$Service%s\" | _hmac \"$Hash\" \"$kRegionH\" hex)\"\n  _debug2 kServiceH \"$kServiceH\"\n\n  kSigningH=\"$(printf \"%s\" \"jdcloud2_request\" | _hmac \"$Hash\" \"$kServiceH\" hex)\"\n  _debug2 kSigningH \"$kSigningH\"\n\n  signature=\"$(printf \"$StringToSign%s\" | _hmac \"$Hash\" \"$kSigningH\" hex)\"\n  _debug2 signature \"$signature\"\n\n  Authorization=\"$Algorithm Credential=$JD_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature\"\n  _debug2 Authorization \"$Authorization\"\n\n  _H3=\"Authorization: $Authorization\"\n  _debug _H3 \"$_H3\"\n\n  url=\"https://$_JD_HOST$CanonicalURI\"\n  if [ \"$qsr\" ]; then\n    url=\"https://$_JD_HOST$CanonicalURI?$qsr\"\n  fi\n\n  if [ \"$mtd\" = \"GET\" ]; then\n    response=\"$(_get \"$url\")\"\n  else\n    response=\"$(_post \"$data\" \"$url\" \"\" \"$mtd\" \"application/json\")\"\n  fi\n\n  _ret=\"$?\"\n  _debug2 response \"$response\"\n  if [ \"$_ret\" = \"0\" ]; then\n    if _contains \"$response\" \"\\\"error\\\"\"; then\n      _err \"Response error:$response\"\n      return 1\n    fi\n  fi\n\n  return \"$_ret\"\n}\n"
  },
  {
    "path": "dnsapi/dns_joker.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_joker_info='Joker.com\nSite: Joker.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_joker\nOptions:\n JOKER_USERNAME Username\n JOKER_PASSWORD Password\nIssues: github.com/acmesh-official/acme.sh/issues/2840\nAuthor: @aattww\n'\n\nJOKER_API=\"https://svc.joker.com/nic/replace\"\n\n########  Public functions #####################\n\n#Usage: dns_joker_add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_joker_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  JOKER_USERNAME=\"${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}\"\n  JOKER_PASSWORD=\"${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}\"\n\n  if [ -z \"$JOKER_USERNAME\" ] || [ -z \"$JOKER_PASSWORD\" ]; then\n    _err \"No Joker.com username and password specified.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable JOKER_USERNAME \"$JOKER_USERNAME\"\n  _saveaccountconf_mutable JOKER_PASSWORD \"$JOKER_PASSWORD\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _info \"Adding TXT record\"\n  if _joker_rest \"username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue\"; then\n    if _startswith \"$response\" \"OK\"; then\n      _info \"Added, OK\"\n      return 0\n    fi\n  fi\n  _err \"Error adding TXT record.\"\n  return 1\n}\n\n#fulldomain txtvalue\ndns_joker_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  JOKER_USERNAME=\"${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}\"\n  JOKER_PASSWORD=\"${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _info \"Removing TXT record\"\n  # TXT record is removed by setting its value to empty.\n  if _joker_rest \"username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=\"; then\n    if _startswith \"$response\" \"OK\"; then\n      _info \"Removed, OK\"\n      return 0\n    fi\n  fi\n  _err \"Error removing TXT record.\"\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  fulldomain=$1\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    # Try to remove a test record. With correct root domain, username and password this will return \"OK: ...\" regardless\n    # of record in question existing or not.\n    if _joker_rest \"username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$h&label=jokerTXTUpdateTest&type=TXT&value=\"; then\n      if _startswith \"$response\" \"OK\"; then\n        _sub_domain=\"$(echo \"$fulldomain\" | sed \"s/\\\\.$h\\$//\")\"\n        _domain=$h\n        return 0\n      fi\n    fi\n\n    i=$(_math \"$i\" + 1)\n  done\n\n  _debug \"Root domain not found\"\n  return 1\n}\n\n_joker_rest() {\n  data=\"$1\"\n  _debug data \"$data\"\n\n  if ! response=\"$(_post \"$data\" \"$JOKER_API\" \"\" \"POST\")\"; then\n    _err \"Error POSTing\"\n    return 1\n  fi\n  _debug response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_kappernet.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_kappernet_info='kapper.net\nSite: kapper.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_kappernet\nOptions:\n KAPPERNETDNS_Key API Key\n KAPPERNETDNS_Secret API Secret\nIssues: github.com/acmesh-official/acme.sh/issues/2977\n'\n\n###############################################################################\n# called with\n# fullhostname: something.example.com\n# txtvalue:     someacmegenerated string\ndns_kappernet_add() {\n  fullhostname=$1\n  txtvalue=$2\n\n  KAPPERNETDNS_Key=\"${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}\"\n  KAPPERNETDNS_Secret=\"${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}\"\n  KAPPERNETDNS_Api=\"https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret\"\n\n  if [ -z \"$KAPPERNETDNS_Key\" ] || [ -z \"$KAPPERNETDNS_Secret\" ]; then\n    _err \"Please specify your kapper.net api key and secret.\"\n    _err \"If you have not received yours - send your mail to\"\n    _err \"support@kapper.net to get  your key and secret.\"\n    return 1\n  fi\n\n  #store the api key and email to the account conf file.\n  _saveaccountconf_mutable KAPPERNETDNS_Key \"$KAPPERNETDNS_Key\"\n  _saveaccountconf_mutable KAPPERNETDNS_Secret \"$KAPPERNETDNS_Secret\"\n  _debug \"Checking Domain ...\"\n  if ! _get_root \"$fullhostname\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"SUBDOMAIN: $_sub_domain\"\n  _debug _domain \"DOMAIN: $_domain\"\n\n  _info \"Trying to add TXT DNS Record\"\n  data=\"%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D\"\n  if _kappernet_api GET \"action=new&subject=$_domain&data=$data\"; then\n\n    if _contains \"$response\" \"{\\\"OK\\\":true\"; then\n      _info \"Waiting 1 second for DNS to spread the new record\"\n      _sleep 1\n      return 0\n    else\n      _err \"Error creating a TXT DNS Record: $fullhostname TXT $txtvalue\"\n      _err \"Error Message: $response\"\n      return 1\n    fi\n  fi\n  _err \"Failed creating TXT Record\"\n}\n\n###############################################################################\n# called with\n# fullhostname: something.example.com\ndns_kappernet_rm() {\n  fullhostname=$1\n  txtvalue=$2\n\n  KAPPERNETDNS_Key=\"${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}\"\n  KAPPERNETDNS_Secret=\"${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}\"\n  KAPPERNETDNS_Api=\"https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret\"\n\n  if [ -z \"$KAPPERNETDNS_Key\" ] || [ -z \"$KAPPERNETDNS_Secret\" ]; then\n    _err \"Please specify your kapper.net api key and secret.\"\n    _err \"If you have not received yours - send your mail to\"\n    _err \"support@kapper.net to get  your key and secret.\"\n    return 1\n  fi\n\n  #store the api key and email to the account conf file.\n  _saveaccountconf_mutable KAPPERNETDNS_Key \"$KAPPERNETDNS_Key\"\n  _saveaccountconf_mutable KAPPERNETDNS_Secret \"$KAPPERNETDNS_Secret\"\n\n  _info \"Trying to remove the TXT Record: $fullhostname containing $txtvalue\"\n  data=\"%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D\"\n  if _kappernet_api GET \"action=del&subject=$fullhostname&data=$data\"; then\n    if _contains \"$response\" \"{\\\"OK\\\":true\"; then\n      return 0\n    else\n      _err \"Error deleting DNS Record: $fullhostname containing $txtvalue\"\n      _err \"Problem: $response\"\n      return 1\n    fi\n  fi\n  _err \"Problem deleting TXT DNS record\"\n}\n\n####################  Private functions below ##################################\n# called with hostname\n# e.g._acme-challenge.www.domain.com returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    if ! _kappernet_api GET \"action=list&subject=$h\"; then\n      return 1\n    fi\n    if _contains \"$response\" '\"OK\":false'; then\n      _debug \"$h not found\"\n    else\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n################################################################################\n# calls the kapper.net DNS Panel API\n# with\n# method\n# param\n_kappernet_api() {\n  method=$1\n  param=\"$2\"\n\n  _debug param \"PARAMETER=$param\"\n  url=\"$KAPPERNETDNS_Api&$param\"\n  _debug url \"URL=$url\"\n\n  if [ \"$method\" = \"GET\" ]; then\n    response=\"$(_get \"$url\")\"\n  else\n    _err \"Unsupported method or missing Secret/Key\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_kas.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_kas_info='All-inkl Kas Server\nSite: kas.all-inkl.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_kas\nOptions:\n KAS_Login API login name\n KAS_Authtype API auth type. Default: \"plain\"\n KAS_Authdata API auth data\nIssues: github.com/acmesh-official/acme.sh/issues/2715\nAuthor: squared GmbH <github@squaredgmbh.de>, Martin Kammerlander <martin.kammerlander@phlegx.com>, Marc-Oliver Lange <git@die-lang.es>\n'\n\n########################################################################\nKAS_Api_GET=\"$(_get \"https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl\")\"\nKAS_Api=\"$(echo \"$KAS_Api_GET\" | tr -d ' ' | grep -i \"<soap:addresslocation=\" | sed \"s/='/\\n/g\" | grep -i \"http\" | sed \"s/'\\/>//g\")\"\n_info \"[KAS] -> API URL $KAS_Api\"\n\nKAS_Auth_GET=\"$(_get \"https://kasapi.kasserver.com/soap/wsdl/KasAuth.wsdl\")\"\nKAS_Auth=\"$(echo \"$KAS_Auth_GET\" | tr -d ' ' | grep -i \"<soap:addresslocation=\" | sed \"s/='/\\n/g\" | grep -i \"http\" | sed \"s/'\\/>//g\")\"\n_info \"[KAS] -> AUTH URL $KAS_Auth\"\n\nKAS_default_ratelimit=5 # TODO - Every response delivers a ratelimit (seconds) where KASAPI is blocking a request.\n\n########  Public functions  #####################\ndns_kas_add() {\n  _fulldomain=$1\n  _txtvalue=$2\n\n  _info \"[KAS] -> Using DNS-01 All-inkl/Kasserver hook\"\n  _info \"[KAS] -> Check and Save Props\"\n  _check_and_save\n\n  _info \"[KAS] -> Adding $_fulldomain DNS TXT entry on all-inkl.com/Kasserver\"\n  _info \"[KAS] -> Retriving Credential Token\"\n  _get_credential_token\n\n  _info \"[KAS] -> Checking Zone and Record_Name\"\n  _get_zone_and_record_name \"$_fulldomain\"\n\n  _info \"[KAS] -> Checking for existing Record entries\"\n  _get_record_id\n\n  # If there is a record_id, delete the entry\n  if [ -n \"$_record_id\" ]; then\n    _info \"[KAS] -> Existing records found. Now deleting old entries\"\n    for i in $_record_id; do\n      _delete_RecordByID \"$i\"\n    done\n  else\n    _info \"[KAS] -> No record found.\"\n  fi\n\n  _info \"[KAS] -> Creating TXT DNS record\"\n  action=\"add_dns_settings\"\n  kasReqParam=\"\\\"record_name\\\":\\\"$_record_name\\\"\"\n  kasReqParam=\"$kasReqParam,\\\"record_type\\\":\\\"TXT\\\"\"\n  kasReqParam=\"$kasReqParam,\\\"record_data\\\":\\\"$_txtvalue\\\"\"\n  kasReqParam=\"$kasReqParam,\\\"record_aux\\\":\\\"0\\\"\"\n  kasReqParam=\"$kasReqParam,\\\"zone_host\\\":\\\"$_zone\\\"\"\n  response=\"$(_callAPI \"$action\" \"$kasReqParam\")\"\n  _debug2 \"[KAS] -> Response\" \"$response\"\n\n  if [ -z \"$response\" ]; then\n    _info \"[KAS] -> Response was empty, please check manually.\"\n    return 1\n  elif _contains \"$response\" \"<SOAP-ENV:Fault>\"; then\n    faultstring=\"$(echo \"$response\" | tr -d '\\n\\r' | sed \"s/<faultstring>/\\n=> /g\" | sed \"s/<\\/faultstring>/\\n/g\" | grep \"=>\" | sed \"s/=> //g\")\"\n    case \"${faultstring}\" in\n    \"record_already_exists\")\n      _info \"[KAS] -> The record already exists, which must not be a problem. Please check manually.\"\n      ;;\n    *)\n      _err \"[KAS] -> An error =>$faultstring<= occurred, please check manually.\"\n      return 1\n      ;;\n    esac\n  elif ! _contains \"$response\" \"<item><key xsi:type=\\\"xsd:string\\\">ReturnString</key><value xsi:type=\\\"xsd:string\\\">TRUE</value></item>\"; then\n    _err \"[KAS] -> An unknown error occurred, please check manually.\"\n    return 1\n  fi\n  return 0\n}\n\ndns_kas_rm() {\n  _fulldomain=$1\n  _txtvalue=$2\n\n  _info \"[KAS] -> Using DNS-01 All-inkl/Kasserver hook\"\n  _info \"[KAS] -> Check and Save Props\"\n  _check_and_save\n\n  _info \"[KAS] -> Cleaning up after All-inkl/Kasserver hook\"\n  _info \"[KAS] -> Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver\"\n  _info \"[KAS] -> Retriving Credential Token\"\n  _get_credential_token\n\n  _info \"[KAS] -> Checking Zone and Record_Name\"\n  _get_zone_and_record_name \"$_fulldomain\"\n\n  _info \"[KAS] -> Getting Record ID\"\n  _get_record_id\n\n  _info \"[KAS] -> Removing entries with ID: $_record_id\"\n  # If there is a record_id, delete the entry\n  if [ -n \"$_record_id\" ]; then\n    for i in $_record_id; do\n      _delete_RecordByID \"$i\"\n    done\n  else # Cannot delete or unkown error\n    _info \"[KAS] -> No record_id found that can be deleted. Please check manually.\"\n  fi\n  return 0\n}\n\n########################## PRIVATE FUNCTIONS ###########################\n# Delete Record ID\n_delete_RecordByID() {\n  recId=$1\n  action=\"delete_dns_settings\"\n  kasReqParam=\"\\\"record_id\\\":\\\"$recId\\\"\"\n  response=\"$(_callAPI \"$action\" \"$kasReqParam\")\"\n  _debug2 \"[KAS] -> Response\" \"$response\"\n\n  if [ -z \"$response\" ]; then\n    _info \"[KAS] -> Response was empty, please check manually.\"\n    return 1\n  elif _contains \"$response\" \"<SOAP-ENV:Fault>\"; then\n    faultstring=\"$(echo \"$response\" | tr -d '\\n\\r' | sed \"s/<faultstring>/\\n=> /g\" | sed \"s/<\\/faultstring>/\\n/g\" | grep \"=>\" | sed \"s/=> //g\")\"\n    case \"${faultstring}\" in\n    \"record_id_not_found\")\n      _info \"[KAS] -> The record was not found, which perhaps is not a problem. Please check manually.\"\n      ;;\n    *)\n      _err \"[KAS] -> An error =>$faultstring<= occurred, please check manually.\"\n      return 1\n      ;;\n    esac\n  elif ! _contains \"$response\" \"<item><key xsi:type=\\\"xsd:string\\\">ReturnString</key><value xsi:type=\\\"xsd:string\\\">TRUE</value></item>\"; then\n    _err \"[KAS] -> An unknown error occurred, please check manually.\"\n    return 1\n  fi\n}\n# Checks for the ENV variables and saves them\n_check_and_save() {\n  KAS_Login=\"${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}\"\n  KAS_Authtype=\"${KAS_Authtype:-$(_readaccountconf_mutable KAS_Authtype)}\"\n  KAS_Authdata=\"${KAS_Authdata:-$(_readaccountconf_mutable KAS_Authdata)}\"\n\n  if [ -z \"$KAS_Login\" ] || [ -z \"$KAS_Authtype\" ] || [ -z \"$KAS_Authdata\" ]; then\n    KAS_Login=\n    KAS_Authtype=\n    KAS_Authdata=\n    _err \"[KAS] -> No auth details provided. Please set user credentials using the \\$KAS_Login, \\$KAS_Authtype, and \\$KAS_Authdata environment variables.\"\n    return 1\n  fi\n  _saveaccountconf_mutable KAS_Login \"$KAS_Login\"\n  _saveaccountconf_mutable KAS_Authtype \"$KAS_Authtype\"\n  _saveaccountconf_mutable KAS_Authdata \"$KAS_Authdata\"\n  return 0\n}\n\n# Gets back the base domain/zone and record name.\n# See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide\n_get_zone_and_record_name() {\n  action=\"get_domains\"\n  response=\"$(_callAPI \"$action\")\"\n  _debug2 \"[KAS] -> Response\" \"$response\"\n\n  if [ -z \"$response\" ]; then\n    _info \"[KAS] -> Response was empty, please check manually.\"\n    return 1\n  elif _contains \"$response\" \"<SOAP-ENV:Fault>\"; then\n    faultstring=\"$(echo \"$response\" | tr -d '\\n\\r' | sed \"s/<faultstring>/\\n=> /g\" | sed \"s/<\\/faultstring>/\\n/g\" | grep \"=>\" | sed \"s/=> //g\")\"\n    _err \"[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually.\"\n    return 1\n  fi\n\n  zonen=\"$(echo \"$response\" | sed 's/<item>/\\n/g' | sed -r 's/(.*<key xsi:type=\"xsd:string\">domain_name<\\/key><value xsi:type=\"xsd:string\">)(.*)(<\\/value.*)/\\2/' | sed '/^</d')\"\n  domain=\"$1\"\n  temp_domain=\"$(echo \"$1\" | sed 's/\\.$//')\"\n  rootzone=\"$domain\"\n  for i in $zonen; do\n    l1=${#rootzone}\n    l2=${#i}\n    if _endswith \"$domain\" \"$i\" && [ \"$l1\" -ge \"$l2\" ]; then\n      rootzone=\"$i\"\n    fi\n  done\n  _zone=\"${rootzone}.\"\n  temp_record_name=\"$(echo \"$temp_domain\" | sed \"s/$rootzone//g\")\"\n  _record_name=\"$(echo \"$temp_record_name\" | sed 's/\\.$//')\"\n  _debug \"[KAS] -> Zone:\" \"$_zone\"\n  _debug \"[KAS] -> Domain:\" \"$domain\"\n  _debug \"[KAS] -> Record_Name:\" \"$_record_name\"\n  return 0\n}\n\n# Retrieve the DNS record ID\n_get_record_id() {\n  action=\"get_dns_settings\"\n  kasReqParam=\"\\\"zone_host\\\":\\\"$_zone\\\"\"\n  response=\"$(_callAPI \"$action\" \"$kasReqParam\")\"\n  _debug2 \"[KAS] -> Response\" \"$response\"\n\n  if [ -z \"$response\" ]; then\n    _info \"[KAS] -> Response was empty, please check manually.\"\n    return 1\n  elif _contains \"$response\" \"<SOAP-ENV:Fault>\"; then\n    faultstring=\"$(echo \"$response\" | tr -d '\\n\\r' | sed \"s/<faultstring>/\\n=> /g\" | sed \"s/<\\/faultstring>/\\n/g\" | grep \"=>\" | sed \"s/=> //g\")\"\n    _err \"[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually.\"\n    return 1\n  fi\n\n  _record_id=\"$(echo \"$response\" | tr -d '\\n\\r' | sed \"s/<item xsi:type=\\\"ns2:Map\\\">/\\n/g\" | grep -i \"$_record_name\" | grep -i \">TXT<\" | sed \"s/<item><key xsi:type=\\\"xsd:string\\\">record_id<\\/key><value xsi:type=\\\"xsd:string\\\">/=>/g\" | grep -i \"$_txtvalue\" | sed \"s/<\\/value><\\/item>/\\n/g\" | grep \"=>\" | sed \"s/=>//g\")\"\n  _debug \"[KAS] -> Record Id: \" \"$_record_id\"\n  return 0\n}\n\n# Retrieve credential token\n_get_credential_token() {\n  baseParamAuth=\"\\\"kas_login\\\":\\\"$KAS_Login\\\"\"\n  baseParamAuth=\"$baseParamAuth,\\\"kas_auth_type\\\":\\\"$KAS_Authtype\\\"\"\n  baseParamAuth=\"$baseParamAuth,\\\"kas_auth_data\\\":\\\"$KAS_Authdata\\\"\"\n  baseParamAuth=\"$baseParamAuth,\\\"session_lifetime\\\":600\"\n  baseParamAuth=\"$baseParamAuth,\\\"session_update_lifetime\\\":\\\"Y\\\"\"\n\n  data='<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns1=\"urn:xmethodsKasApiAuthentication\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><SOAP-ENV:Body><ns1:KasAuth><Params xsi:type=\"xsd:string\">{'\n  data=\"$data$baseParamAuth}</Params></ns1:KasAuth></SOAP-ENV:Body></SOAP-ENV:Envelope>\"\n\n  _debug \"[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API.\"\n  _sleep $KAS_default_ratelimit\n\n  contentType=\"text/xml\"\n  export _H1=\"SOAPAction: urn:xmethodsKasApiAuthentication#KasAuth\"\n  response=\"$(_post \"$data\" \"$KAS_Auth\" \"\" \"POST\" \"$contentType\")\"\n  _debug2 \"[KAS] -> Response\" \"$response\"\n\n  if [ -z \"$response\" ]; then\n    _info \"[KAS] -> Response was empty, please check manually.\"\n    return 1\n  elif _contains \"$response\" \"<SOAP-ENV:Fault>\"; then\n    faultstring=\"$(echo \"$response\" | tr -d '\\n\\r' | sed \"s/<faultstring>/\\n=> /g\" | sed \"s/<\\/faultstring>/\\n/g\" | grep \"=>\" | sed \"s/=> //g\")\"\n    _err \"[KAS] -> Could not retrieve login token or antoher error =>$faultstring<= occurred, please check manually.\"\n    return 1\n  fi\n\n  _credential_token=\"$(echo \"$response\" | tr '\\n' ' ' | sed 's/.*return xsi:type=\"xsd:string\">\\(.*\\)<\\/return>/\\1/' | sed 's/<\\/ns1:KasAuthResponse\\(.*\\)Envelope>.*//')\"\n  _debug \"[KAS] -> Credential Token: \" \"$_credential_token\"\n  return 0\n}\n\n_callAPI() {\n  kasaction=$1\n  kasReqParams=$2\n\n  baseParamAuth=\"\\\"kas_login\\\":\\\"$KAS_Login\\\"\"\n  baseParamAuth=\"$baseParamAuth,\\\"kas_auth_type\\\":\\\"session\\\"\"\n  baseParamAuth=\"$baseParamAuth,\\\"kas_auth_data\\\":\\\"$_credential_token\\\"\"\n\n  data='<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns1=\"urn:xmethodsKasApi\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><SOAP-ENV:Body><ns1:KasApi><Params xsi:type=\"xsd:string\">{'\n  data=\"$data$baseParamAuth,\\\"kas_action\\\":\\\"$kasaction\\\"\"\n  if [ -n \"$kasReqParams\" ]; then\n    data=\"$data,\\\"KasRequestParams\\\":{$kasReqParams}\"\n  fi\n  data=\"$data}</Params></ns1:KasApi></SOAP-ENV:Body></SOAP-ENV:Envelope>\"\n\n  _debug2 \"[KAS] -> Request\" \"$data\"\n\n  _debug \"[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API.\"\n  _sleep $KAS_default_ratelimit\n\n  contentType=\"text/xml\"\n  export _H1=\"SOAPAction: urn:xmethodsKasApi#KasApi\"\n  response=\"$(_post \"$data\" \"$KAS_Api\" \"\" \"POST\" \"$contentType\")\"\n  _debug2 \"[KAS] -> Response\" \"$response\"\n  echo \"$response\"\n}\n"
  },
  {
    "path": "dnsapi/dns_kinghost.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_kinghost_info='King.host\nDomains: KingHost.net KingHost.com.br\nSite: King.host\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_kinghost\nOptions:\n KINGHOST_Username Username\n KINGHOST_Password Password\nAuthor: Felipe Keller Braz <felipebraz@kinghost.com.br>\n'\n\n# KingHost API support                                     #\n# https://api.kinghost.net/doc/                             #\n\nKING_Api=\"https://api.kinghost.net/acme\"\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_kinghost_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  KINGHOST_Username=\"${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}\"\n  KINGHOST_Password=\"${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}\"\n  if [ -z \"$KINGHOST_Username\" ] || [ -z \"$KINGHOST_Password\" ]; then\n    KINGHOST_Username=\"\"\n    KINGHOST_Password=\"\"\n    _err \"You don't specify KingHost api password and email yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable KINGHOST_Username \"$KINGHOST_Username\"\n  _saveaccountconf_mutable KINGHOST_Password \"$KINGHOST_Password\"\n\n  _debug \"Getting txt records\"\n  _kinghost_rest GET \"dns\" \"name=$fulldomain&content=$txtvalue\"\n\n  #This API call returns \"status\":\"ok\" if dns record does not exist\n  #We are creating a new txt record here, so we expect the \"ok\" status\n  if ! echo \"$response\" | grep '\"status\":\"ok\"' >/dev/null; then\n    _err \"Error\"\n    _err \"$response\"\n    return 1\n  fi\n\n  _kinghost_rest POST \"dns\" \"name=$fulldomain&content=$txtvalue\"\n  if ! echo \"$response\" | grep '\"status\":\"ok\"' >/dev/null; then\n    _err \"Error\"\n    _err \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_kinghost_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  KINGHOST_Password=\"${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}\"\n  KINGHOST_Username=\"${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}\"\n  if [ -z \"$KINGHOST_Password\" ] || [ -z \"$KINGHOST_Username\" ]; then\n    KINGHOST_Password=\"\"\n    KINGHOST_Username=\"\"\n    _err \"You don't specify KingHost api key and email yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  _kinghost_rest DELETE \"dns\" \"name=$fulldomain&content=$txtvalue\"\n  if ! echo \"$response\" | grep '\"status\":\"ok\"' >/dev/null; then\n    _err \"Error\"\n    _err \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n_kinghost_rest() {\n  method=$1\n  uri=\"$2\"\n  data=\"$3\"\n  _debug \"$uri\"\n\n  export _H1=\"X-Auth-Email: $KINGHOST_Username\"\n  export _H2=\"X-Auth-Key: $KINGHOST_Password\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$KING_Api/$uri.json\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$KING_Api/$uri.json?$data\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $uri\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_knot.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_knot_info='Knot Server knsupdate\nSite: www.knot-dns.cz/docs/2.5/html/man_knsupdate.html\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_knot\nOptions:\n KNOT_SERVER Server hostname. Default: \"localhost\".\n KNOT_KEY File path to TSIG key\n'\n\n# See also dns_nsupdate.sh\n\n########  Public functions #####################\n\n#Usage: dns_knot_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_knot_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _checkKey || return 1\n  [ -n \"${KNOT_SERVER}\" ] || KNOT_SERVER=\"localhost\"\n  # save the dns server and key to the account.conf file.\n  _saveaccountconf KNOT_SERVER \"${KNOT_SERVER}\"\n  _saveaccountconf KNOT_KEY \"${KNOT_KEY}\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain does not exist.\"\n    return 1\n  fi\n\n  _info \"Adding ${fulldomain}. 60 TXT \\\"${txtvalue}\\\"\"\n\n  knsupdate <<EOF\nserver ${KNOT_SERVER}\nkey ${KNOT_KEY}\nzone ${_domain}.\nupdate add ${fulldomain}. 60 TXT \"${txtvalue}\"\nsend\nquit\nEOF\n\n  if [ $? -ne 0 ]; then\n    _err \"Error updating domain.\"\n    return 1\n  fi\n\n  _info \"Domain TXT record successfully added.\"\n  return 0\n}\n\n#Usage: dns_knot_rm   _acme-challenge.www.domain.com\ndns_knot_rm() {\n  fulldomain=$1\n  _checkKey || return 1\n  [ -n \"${KNOT_SERVER}\" ] || KNOT_SERVER=\"localhost\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain does not exist.\"\n    return 1\n  fi\n\n  _info \"Removing ${fulldomain}. TXT\"\n\n  knsupdate <<EOF\nserver ${KNOT_SERVER}\nkey ${KNOT_KEY}\nzone ${_domain}.\nupdate del ${fulldomain}. TXT\nsend\nquit\nEOF\n\n  if [ $? -ne 0 ]; then\n    _err \"error updating domain\"\n    return 1\n  fi\n\n  _info \"Domain TXT record successfully deleted.\"\n  return 0\n}\n\n####################  Private functions below ##################################\n# _acme-challenge.www.domain.com\n# returns\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=\"$(echo \"$fulldomain\" | tr '.' ' ' | wc -w)\"\n  i=$(_math \"$i\" - 1)\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n    _domain=\"$h\"\n    return 0\n  done\n  _debug \"$domain not found\"\n  return 1\n}\n\n_checkKey() {\n  if [ -z \"${KNOT_KEY}\" ]; then\n    _err \"You must specify a TSIG key to authenticate the request.\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_la.sh",
    "content": "#!/usr/bin/env sh\n\n# LA_Id=\"123\"\n# LA_Sk=\"456\"\n# shellcheck disable=SC2034\ndns_la_info='dns.la\nSite: dns.la\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la\nOptions:\n LA_Id APIID\n LA_Sk APISecret\n LA_Token 用冒号连接 APIID APISecret 再base64生成\nIssues: github.com/acmesh-official/acme.sh/issues/4257\n'\nLA_Api=\"https://api.dns.la/api\"\n\n########  Public functions #####################\n\n#Usage: dns_la_add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_la_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  LA_Id=\"${LA_Id:-$(_readaccountconf_mutable LA_Id)}\"\n  LA_Sk=\"${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}\"\n  _log \"LA_Id=$LA_Id\"\n  _log \"LA_Sk=$LA_Sk\"\n\n  if [ -z \"$LA_Id\" ] || [ -z \"$LA_Sk\" ]; then\n    LA_Id=\"\"\n    LA_Sk=\"\"\n    _err \"You didn't specify a dnsla api id and key yet.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable LA_Id \"$LA_Id\"\n  _saveaccountconf_mutable LA_Sk \"$LA_Sk\"\n\n  # generate dnsla token\n  _la_token\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n\n  # record type is enum in new api, 16 for TXT\n  if _la_post \"{\\\"domainId\\\":\\\"$_domain_id\\\",\\\"type\\\":16,\\\"host\\\":\\\"$_sub_domain\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":600}\" \"record\"; then\n    if _contains \"$response\" '\"id\":'; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" '\"msg\":\"与已有记录冲突\"'; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record failed.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_la_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  LA_Id=\"${LA_Id:-$(_readaccountconf_mutable LA_Id)}\"\n  LA_Sk=\"${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}\"\n\n  _la_token\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  # record type is enum in new api, 16 for TXT\n  if ! _la_get \"recordList?pageIndex=1&pageSize=10&domainId=$_domain_id&host=$_sub_domain&type=16&data=$txtvalue\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" '\"id\":'; then\n    _info \"Don't need to remove.\"\n    return 0\n  fi\n\n  record_id=$(printf \"%s\" \"$response\" | grep '\"id\":' | _head_n 1 | sed 's/.*\"id\": *\"\\([^\"]*\\)\".*/\\1/')\n  _debug \"record_id\" \"$record_id\"\n  if [ -z \"$record_id\" ]; then\n    _err \"Can not get record id to remove.\"\n    return 1\n  fi\n  # remove record in new api is RESTful\n  if ! _la_post \"\" \"record?id=$record_id\" \"DELETE\"; then\n    _err \"Delete record error.\"\n    return 1\n  fi\n  _contains \"$response\" '\"code\":200'\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _la_get \"domain?domain=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" '\"domain\":'; then\n      _domain_id=$(echo \"$response\" | sed -n 's/.*\"id\":\"\\([^\"]*\\)\".*/\\1/p')\n      _log \"_domain_id\" \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"$h\"\n        return 0\n      fi\n      return 1\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n#Usage:  URI\n_la_rest() {\n  url=\"$LA_Api/$1\"\n  _debug \"$url\"\n\n  if ! response=\"$(_get \"$url\" \"Authorization: Basic $LA_Token\" | tr -d ' ' | tr \"}\" \",\")\"; then\n    _err \"Error: $url\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n\n_la_get() {\n  url=\"$LA_Api/$1\"\n  _debug \"$url\"\n\n  export _H1=\"Authorization: Basic $LA_Token\"\n\n  if ! response=\"$(_get \"$url\" | tr -d ' ' | tr \"}\" \",\")\"; then\n    _err \"Error: $url\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n\n# Usage:  _la_post body url [POST|PUT|DELETE]\n_la_post() {\n  body=$1\n  url=\"$LA_Api/$2\"\n  http_method=$3\n  _debug \"$body\"\n  _debug \"$url\"\n\n  export _H1=\"Authorization: Basic $LA_Token\"\n\n  if ! response=\"$(_post \"$body\" \"$url\" \"\" \"$http_method\")\"; then\n    _err \"Error: $url\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n\n_la_token() {\n  LA_Token=$(printf \"%s:%s\" \"$LA_Id\" \"$LA_Sk\" | _base64)\n  _debug \"$LA_Token\"\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_leaseweb.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_leaseweb_info='Leaseweb.com\nSite: Leaseweb.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_leaseweb\nOptions:\n LSW_Key API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2558\nAuthor: Rolph Haspers <r.haspers@global.leaseweb.com>\n'\n\n#See https://developer.leaseweb.com for more information.\n########  Public functions #####################\n\nLSW_API=\"https://api.leaseweb.com/hosting/v2/domains\"\n\n#Usage: dns_leaseweb_add   _acme-challenge.www.domain.com\ndns_leaseweb_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  LSW_Key=\"${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}\"\n  if [ -z \"$LSW_Key\" ]; then\n    LSW_Key=\"\"\n    _err \"You don't specify Leaseweb api key yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the api key to the account conf file.\n  _saveaccountconf_mutable LSW_Key \"$LSW_Key\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _root_domain \"$_domain\"\n  _debug _domain \"$fulldomain\"\n\n  if _lsw_api \"POST\" \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n    if [ \"$_code\" = \"201\" ]; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error, invalid code. Code: $_code\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_leaseweb_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  LSW_Key=\"${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _root_domain \"$_domain\"\n  _debug _domain \"$fulldomain\"\n\n  if _lsw_api \"DELETE\" \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n    if [ \"$_code\" = \"204\" ]; then\n      _info \"Deleted, OK\"\n      return 0\n    else\n      _err \"Delete txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Delete txt record error.\"\n\n  return 1\n}\n\n####################  Private functions below ##################################\n# _acme-challenge.www.domain.com\n# returns\n# _domain=domain.com\n_get_root() {\n  rdomain=$1\n  i=\"$(echo \"$rdomain\" | tr '.' ' ' | wc -w)\"\n  i=$(_math \"$i\" - 1)\n\n  while true; do\n    h=$(printf \"%s\" \"$rdomain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      return 1 #not valid domain\n    fi\n\n    #Check API if domain exists\n    if _lsw_api \"GET\" \"$h\"; then\n      if [ \"$_code\" = \"200\" ]; then\n        _domain=\"$h\"\n        return 0\n      fi\n    fi\n    i=$(_math \"$i\" - 1)\n    if [ \"$i\" -lt 2 ]; then\n      return 1 #not found, no need to check _acme-challenge.sub.domain in leaseweb api.\n    fi\n  done\n\n  return 1\n}\n\n_lsw_api() {\n  cmd=$1\n  d=$2\n  fd=$3\n  tvalue=$4\n\n  # Construct the HTTP Authorization header\n  export _H2=\"Content-Type: application/json\"\n  export _H1=\"X-Lsw-Auth: ${LSW_Key}\"\n\n  if [ \"$cmd\" = \"GET\" ]; then\n    response=\"$(_get \"$LSW_API/$d\")\"\n    _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n    _debug \"http response code $_code\"\n    _debug response \"$response\"\n    return 0\n  fi\n\n  if [ \"$cmd\" = \"POST\" ]; then\n    data=\"{\\\"name\\\": \\\"$fd.\\\",\\\"type\\\": \\\"TXT\\\",\\\"content\\\": [\\\"$tvalue\\\"],\\\"ttl\\\": 60}\"\n    response=\"$(_post \"$data\" \"$LSW_API/$d/resourceRecordSets\" \"$data\" \"POST\")\"\n    _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n    _debug \"http response code $_code\"\n    _debug response \"$response\"\n    return 0\n  fi\n\n  if [ \"$cmd\" = \"DELETE\" ]; then\n    response=\"$(_post \"\" \"$LSW_API/$d/resourceRecordSets/$fd/TXT\" \"\" \"DELETE\")\"\n    _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n    _debug \"http response code $_code\"\n    _debug response \"$response\"\n    return 0\n  fi\n\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_lexicon.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_lexicon_info='Lexicon DNS client\nSite: github.com/AnalogJ/lexicon\nDocs: github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-DNS-API\nOptions:\n PROVIDER Provider\n'\n\nlexicon_cmd=\"lexicon\"\n\nwiki=\"https://github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-dns-api\"\n\n_lexicon_init() {\n  if ! _exists \"$lexicon_cmd\"; then\n    _err \"Please install $lexicon_cmd first: $wiki\"\n    return 1\n  fi\n\n  PROVIDER=\"${PROVIDER:-$(_readdomainconf PROVIDER)}\"\n  if [ -z \"$PROVIDER\" ]; then\n    PROVIDER=\"\"\n    _err \"Please define env PROVIDER first: $wiki\"\n    return 1\n  fi\n\n  _savedomainconf PROVIDER \"$PROVIDER\"\n  export PROVIDER\n\n  # e.g. busybox-ash does not know [:upper:]\n  # shellcheck disable=SC2018,SC2019\n  Lx_name=$(echo LEXICON_\"${PROVIDER}\"_USERNAME | tr 'a-z' 'A-Z')\n  eval \"$Lx_name=\\${$Lx_name:-$(_readaccountconf_mutable \"$Lx_name\")}\"\n  Lx_name_v=$(eval echo \\$\"$Lx_name\")\n  _secure_debug \"$Lx_name\" \"$Lx_name_v\"\n  if [ \"$Lx_name_v\" ]; then\n    _saveaccountconf_mutable \"$Lx_name\" \"$Lx_name_v\"\n    eval export \"$Lx_name\"\n  fi\n\n  # shellcheck disable=SC2018,SC2019\n  Lx_token=$(echo LEXICON_\"${PROVIDER}\"_TOKEN | tr 'a-z' 'A-Z')\n  eval \"$Lx_token=\\${$Lx_token:-$(_readaccountconf_mutable \"$Lx_token\")}\"\n  Lx_token_v=$(eval echo \\$\"$Lx_token\")\n  _secure_debug \"$Lx_token\" \"$Lx_token_v\"\n  if [ \"$Lx_token_v\" ]; then\n    _saveaccountconf_mutable \"$Lx_token\" \"$Lx_token_v\"\n    eval export \"$Lx_token\"\n  fi\n\n  # shellcheck disable=SC2018,SC2019\n  Lx_password=$(echo LEXICON_\"${PROVIDER}\"_PASSWORD | tr 'a-z' 'A-Z')\n  eval \"$Lx_password=\\${$Lx_password:-$(_readaccountconf_mutable \"$Lx_password\")}\"\n  Lx_password_v=$(eval echo \\$\"$Lx_password\")\n  _secure_debug \"$Lx_password\" \"$Lx_password_v\"\n  if [ \"$Lx_password_v\" ]; then\n    _saveaccountconf_mutable \"$Lx_password\" \"$Lx_password_v\"\n    eval export \"$Lx_password\"\n  fi\n\n  # shellcheck disable=SC2018,SC2019\n  Lx_domaintoken=$(echo LEXICON_\"${PROVIDER}\"_DOMAINTOKEN | tr 'a-z' 'A-Z')\n  eval \"$Lx_domaintoken=\\${$Lx_domaintoken:-$(_readaccountconf_mutable \"$Lx_domaintoken\")}\"\n  Lx_domaintoken_v=$(eval echo \\$\"$Lx_domaintoken\")\n  _secure_debug \"$Lx_domaintoken\" \"$Lx_domaintoken_v\"\n  if [ \"$Lx_domaintoken_v\" ]; then\n    _saveaccountconf_mutable \"$Lx_domaintoken\" \"$Lx_domaintoken_v\"\n    eval export \"$Lx_domaintoken\"\n  fi\n\n  # shellcheck disable=SC2018,SC2019\n  Lx_api_key=$(echo LEXICON_\"${PROVIDER}\"_API_KEY | tr 'a-z' 'A-Z')\n  eval \"$Lx_api_key=\\${$Lx_api_key:-$(_readaccountconf_mutable \"$Lx_api_key\")}\"\n  Lx_api_key_v=$(eval echo \\$\"$Lx_api_key\")\n  _secure_debug \"$Lx_api_key\" \"$Lx_api_key_v\"\n  if [ \"$Lx_api_key_v\" ]; then\n    _saveaccountconf_mutable \"$Lx_api_key\" \"$Lx_api_key_v\"\n    eval export \"$Lx_api_key\"\n  fi\n}\n\n########  Public functions #####################\n\n#Usage: dns_lexicon_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_lexicon_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _lexicon_init; then\n    return 1\n  fi\n\n  domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 2-999)\n\n  _secure_debug LEXICON_OPTS \"$LEXICON_OPTS\"\n  _savedomainconf LEXICON_OPTS \"$LEXICON_OPTS\"\n\n  # shellcheck disable=SC2086\n  $lexicon_cmd \"$PROVIDER\" $LEXICON_OPTS create \"${domain}\" TXT --name=\"_acme-challenge.${domain}.\" --content=\"${txtvalue}\" --output QUIET\n\n}\n\n#Usage: dns_lexicon_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_lexicon_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _lexicon_init; then\n    return 1\n  fi\n\n  domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 2-999)\n\n  # shellcheck disable=SC2086\n  $lexicon_cmd \"$PROVIDER\" $LEXICON_OPTS delete \"${domain}\" TXT --name=\"_acme-challenge.${domain}.\" --content=\"${txtvalue}\" --output QUIET\n\n}\n"
  },
  {
    "path": "dnsapi/dns_limacity.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_limacity_info='lima-city.de\nSite: www.lima-city.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_limacity\nOptions:\n LIMACITY_APIKEY API Key. Note: The API Key must have following roles: dns.admin, domains.reader\nIssues: github.com/acmesh-official/acme.sh/issues/4758\nAuthor: @Laraveluser\n'\n\n########  Public functions #####################\n\nLIMACITY_APIKEY=\"${LIMACITY_APIKEY:-$(_readaccountconf_mutable LIMACITY_APIKEY)}\"\nAUTH=$(printf \"%s\" \"api:$LIMACITY_APIKEY\" | _base64 -w 0)\nexport _H1=\"Authorization: Basic $AUTH\"\nexport _H2=\"Content-Type: application/json\"\nAPIBASE=https://www.lima-city.de/usercp\n\n#Usage: dns_limacity_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_limacity_add() {\n  _debug LIMACITY_APIKEY \"$LIMACITY_APIKEY\"\n  if [ \"$LIMACITY_APIKEY\" = \"\" ]; then\n    _err \"No Credentials given\"\n    return 1\n  fi\n\n  # save the dns server and key to the account conf file.\n  _saveaccountconf_mutable LIMACITY_APIKEY \"${LIMACITY_APIKEY}\"\n\n  fulldomain=$1\n  txtvalue=$2\n  if ! _lima_get_domain_id \"$fulldomain\"; then return 1; fi\n\n  msg=$(_post \"{\\\"nameserver_record\\\":{\\\"name\\\":\\\"${fulldomain}\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"${txtvalue}\\\",\\\"ttl\\\":60}}\" \"${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json\" \"\" \"POST\")\n  _debug \"$msg\"\n\n  if [ \"$(echo \"$msg\" | _egrep_o \"\\\"status\\\":\\\"ok\\\"\")\" = \"\" ]; then\n    _err \"$msg\"\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: dns_limacity_rm   _acme-challenge.www.domain.com\ndns_limacity_rm() {\n\n  fulldomain=$1\n  txtvalue=$2\n  if ! _lima_get_domain_id \"$fulldomain\"; then return 1; fi\n\n  for recordId in $(_get \"${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json\" | _egrep_o \"{\\\"id\\\":[0-9]*[^}]*,\\\"name\\\":\\\"${fulldomain}\\\"\" | _egrep_o \"[0-9]*\"); do\n    _post \"\" \"${APIBASE}/domains/${LIMACITY_DOMAINID}/records/${recordId}\" \"\" \"DELETE\"\n  done\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_lima_get_domain_id() {\n  domain=\"$1\"\n  _debug \"$domain\"\n  i=2\n  p=1\n\n  domains=$(_get \"${APIBASE}/domains.json\")\n  if [ \"$(echo \"$domains\" | _egrep_o \"\\{.*\"\"domains\"\"\")\" ]; then\n    response=\"$(echo \"$domains\" | tr -d \"\\n\" | tr '{' \"|\" | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        #not valid\n        return 1\n      fi\n\n      hostedzone=\"$(echo \"$response\" | _egrep_o \"\\{.*\"\"unicode_fqdn\"\"[^,]+\"\"$h\"\".*\\}\")\"\n      if [ \"$hostedzone\" ]; then\n        LIMACITY_DOMAINID=$(printf \"%s\\n\" \"$hostedzone\" | _egrep_o \"\\\"id\\\":\\s*[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n        if [ \"$LIMACITY_DOMAINID\" ]; then\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n          _domain=$h\n          return 0\n        fi\n        return 1\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n  fi\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_linode.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_linode_info='Linode.com (Old)\n Deprecated. Use dns_linode_v4\nSite: Linode.com\nOptions:\n LINODE_API_KEY API Key\nAuthor: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>\n'\n\nLINODE_API_URL=\"https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=\"\n\n########  Public functions #####################\n\n#Usage: dns_linode_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_linode_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n\n  if ! _Linode_API; then\n    return 1\n  fi\n\n  _info \"Using Linode\"\n  _debug \"Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain does not exist.\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _parameters=\"&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue\"\n\n  if _rest GET \"domain.resource.create\" \"$_parameters\" && [ -n \"$response\" ]; then\n    _resource_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"ResourceID\\\":\\s*[0-9]+\" | cut -d : -f 2 | tr -d \" \" | _head_n 1)\n    _debug _resource_id \"$_resource_id\"\n\n    if [ -z \"$_resource_id\" ]; then\n      _err \"Error adding the domain resource.\"\n      return 1\n    fi\n\n    _info \"Domain resource successfully added.\"\n    return 0\n  fi\n\n  return 1\n}\n\n#Usage: dns_linode_rm   _acme-challenge.www.domain.com\ndns_linode_rm() {\n  fulldomain=\"${1}\"\n\n  if ! _Linode_API; then\n    return 1\n  fi\n\n  _info \"Using Linode\"\n  _debug \"Calling: dns_linode_rm() '${fulldomain}'\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain does not exist.\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _parameters=\"&DomainID=$_domain_id\"\n\n  if _rest GET \"domain.resource.list\" \"$_parameters\" && [ -n \"$response\" ]; then\n    response=\"$(echo \"$response\" | tr -d \"\\n\" | tr '{' \"|\" | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n\n    resource=\"$(echo \"$response\" | _egrep_o \"{.*\\\"NAME\\\":\\s*\\\"$_sub_domain\\\".*}\")\"\n    if [ \"$resource\" ]; then\n      _resource_id=$(printf \"%s\\n\" \"$resource\" | _egrep_o \"\\\"RESOURCEID\\\":\\s*[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n      if [ \"$_resource_id\" ]; then\n        _debug _resource_id \"$_resource_id\"\n\n        _parameters=\"&DomainID=$_domain_id&ResourceID=$_resource_id\"\n\n        if _rest GET \"domain.resource.delete\" \"$_parameters\" && [ -n \"$response\" ]; then\n          _resource_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"ResourceID\\\":\\s*[0-9]+\" | cut -d : -f 2 | tr -d \" \" | _head_n 1)\n          _debug _resource_id \"$_resource_id\"\n\n          if [ -z \"$_resource_id\" ]; then\n            _err \"Error deleting the domain resource.\"\n            return 1\n          fi\n\n          _info \"Domain resource successfully deleted.\"\n          return 0\n        fi\n      fi\n\n      return 1\n    fi\n\n    return 0\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_Linode_API() {\n  if [ -z \"$LINODE_API_KEY\" ]; then\n    LINODE_API_KEY=\"\"\n\n    _err \"You didn't specify the Linode API key yet.\"\n    _err \"Please create your key and try again.\"\n\n    return 1\n  fi\n\n  _saveaccountconf LINODE_API_KEY \"$LINODE_API_KEY\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=12345\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n\n  if _rest GET \"domain.list\"; then\n    response=\"$(echo \"$response\" | tr -d \"\\n\" | tr '{' \"|\" | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        #not valid\n        return 1\n      fi\n\n      hostedzone=\"$(echo \"$response\" | _egrep_o \"{.*\\\"DOMAIN\\\":\\s*\\\"$h\\\".*}\")\"\n      if [ \"$hostedzone\" ]; then\n        _domain_id=$(printf \"%s\\n\" \"$hostedzone\" | _egrep_o \"\\\"DOMAINID\\\":\\s*[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n        if [ \"$_domain_id\" ]; then\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n          _domain=$h\n          return 0\n        fi\n        return 1\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n  fi\n  return 1\n}\n\n#method method action data\n_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$mtd\" != \"GET\" ]; then\n    # both POST and DELETE.\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$LINODE_API_URL$ep\" \"\" \"$mtd\")\"\n  else\n    response=\"$(_get \"$LINODE_API_URL$ep$data\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_linode_v4.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_linode_v4_info='Linode.com\nSite: Linode.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_linode_v4\nOptions:\n LINODE_V4_API_KEY API Key\nAuthor: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>, Aaron W. Swenson <aaron@grandmasfridge.org>\n'\n\nLINODE_V4_API_URL=\"https://api.linode.com/v4/domains\"\n\n########  Public functions #####################\n\n#Usage: dns_linode_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_linode_v4_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n\n  if ! _Linode_API; then\n    return 1\n  fi\n\n  _info \"Using Linode\"\n  _debug \"Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain does not exist.\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _payload=\"{\n              \\\"type\\\": \\\"TXT\\\",\n              \\\"name\\\": \\\"$_sub_domain\\\",\n              \\\"target\\\": \\\"$txtvalue\\\",\n              \\\"ttl_sec\\\": 300\n            }\"\n\n  if _rest POST \"/$_domain_id/records\" \"$_payload\" && [ -n \"$response\" ]; then\n    _resource_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\": *[0-9]+\" | cut -d : -f 2 | tr -d \" \" | _head_n 1)\n    _debug _resource_id \"$_resource_id\"\n\n    if [ -z \"$_resource_id\" ]; then\n      _err \"Error adding the domain resource.\"\n      return 1\n    fi\n\n    _info \"Domain resource successfully added.\"\n    return 0\n  fi\n\n  return 1\n}\n\n#Usage: dns_linode_rm   _acme-challenge.www.domain.com\ndns_linode_v4_rm() {\n  fulldomain=\"${1}\"\n\n  if ! _Linode_API; then\n    return 1\n  fi\n\n  _info \"Using Linode\"\n  _debug \"Calling: dns_linode_rm() '${fulldomain}'\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Domain does not exist.\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if _H4=\"X-Filter: { \\\"type\\\": \\\"TXT\\\", \\\"name\\\": \\\"$_sub_domain\\\" }\" _rest GET \"/$_domain_id/records\" && [ -n \"$response\" ]; then\n    response=\"$(echo \"$response\" | tr -d \"\\n\" | tr '{' \"|\" | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n\n    resource=\"$(echo \"$response\" | _egrep_o \"\\{.*\\\"name\\\": *\\\"$_sub_domain\\\".*}\")\"\n    if [ \"$resource\" ]; then\n      _resource_id=$(printf \"%s\\n\" \"$resource\" | _egrep_o \"\\\"id\\\": *[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n      if [ \"$_resource_id\" ]; then\n        _debug _resource_id \"$_resource_id\"\n\n        if _rest DELETE \"/$_domain_id/records/$_resource_id\" && [ -n \"$response\" ]; then\n          # On 200/OK, empty set is returned. Check for error, if any.\n          _error_response=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"errors\\\"\" | cut -d : -f 2 | tr -d \" \" | _head_n 1)\n\n          if [ -n \"$_error_response\" ]; then\n            _err \"Error deleting the domain resource: $_error_response\"\n            return 1\n          fi\n\n          _info \"Domain resource successfully deleted.\"\n          return 0\n        fi\n      fi\n\n      return 1\n    fi\n\n    return 0\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_Linode_API() {\n  LINODE_V4_API_KEY=\"${LINODE_V4_API_KEY:-$(_readaccountconf_mutable LINODE_V4_API_KEY)}\"\n  if [ -z \"$LINODE_V4_API_KEY\" ]; then\n    LINODE_V4_API_KEY=\"\"\n\n    _err \"You didn't specify the Linode v4 API key yet.\"\n    _err \"Please create your key and try again.\"\n\n    return 1\n  fi\n\n  _saveaccountconf_mutable LINODE_V4_API_KEY \"$LINODE_V4_API_KEY\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=12345\n_get_root() {\n  full_host_str=\"$1\"\n\n  i=2\n  p=1\n  while true; do\n    # loop through the received string (e.g.  _acme-challenge.sub3.sub2.sub1.domain.tld),\n    # starting from the lowest subdomain, and check if it's a hosted domain\n    tst_hosted_domain=$(printf \"%s\" \"$full_host_str\" | cut -d . -f \"$i\"-100)\n    _debug tst_hosted_domain \"$tst_hosted_domain\"\n    if [ -z \"$tst_hosted_domain\" ]; then\n      #not valid\n      _err \"Couldn't get domain from string '$full_host_str'.\"\n      return 1\n    fi\n\n    _debug \"Querying Linode APIv4 for hosted zone: $tst_hosted_domain\"\n    if _H4=\"X-Filter: {\\\"domain\\\":\\\"$tst_hosted_domain\\\"}\" _rest GET; then\n      _debug \"Got response from API: $response\"\n      response=\"$(echo \"$response\" | tr -d \"\\n\" | tr '{' \"|\" | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n      hostedzone=\"$(echo \"$response\" | _egrep_o \"\\{.*\\\"domain\\\": *\\\"$tst_hosted_domain\\\".*}\")\"\n      if [ \"$hostedzone\" ]; then\n        _domain_id=$(printf \"%s\\n\" \"$hostedzone\" | _egrep_o \"\\\"id\\\": *[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n        _debug \"Found domain hosted on Linode DNS. Zone: $tst_hosted_domain, id: $_domain_id\"\n        if [ \"$_domain_id\" ]; then\n          _sub_domain=$(printf \"%s\" \"$full_host_str\" | cut -d . -f 1-\"$p\")\n          _domain=$tst_hosted_domain\n          return 0\n        fi\n        return 1\n      fi\n\n      p=$i\n      i=$(_math \"$i\" + 1)\n    fi\n  done\n\n  return 1\n}\n\n#method method action data\n_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Authorization: Bearer $LINODE_V4_API_KEY\"\n\n  if [ \"$mtd\" != \"GET\" ]; then\n    # both POST and DELETE.\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$LINODE_V4_API_URL$ep\" \"\" \"$mtd\")\"\n  else\n    response=\"$(_get \"$LINODE_V4_API_URL$ep$data\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_loopia.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_loopia_info='Loopia.se\nSite: Loopia.se\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_loopia\nOptions:\n LOOPIA_Api API URL. E.g. \"https://api.loopia.<TLD>/RPCSERV\" where the <TLD> is one of: com, no, rs, se. Default: \"se\".\n LOOPIA_User Username\n LOOPIA_Password Password\n'\n\nLOOPIA_Api_Default=\"https://api.loopia.se/RPCSERV\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_loopia_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _loopia_load_config; then\n    return 1\n  fi\n\n  _loopia_save_config\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n\n  if ! _loopia_add_sub_domain \"$_domain\" \"$_sub_domain\"; then\n    return 1\n  fi\n  if ! _loopia_add_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n}\n\ndns_loopia_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _loopia_load_config; then\n    return 1\n  fi\n\n  _loopia_save_config\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>removeSubdomain</methodName>\n    <params>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n    </params>\n  </methodCall>' \"$LOOPIA_User\" \"$Encoded_Password\" \"$_domain\" \"$_sub_domain\")\n\n  response=\"$(_post \"$xml_content\" \"$LOOPIA_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"OK\"; then\n    err_response=$(echo \"$response\" | sed 's/.*<string>\\(.*\\)<\\/string>.*/\\1/')\n    _err \"Error could not get txt records: $err_response\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n_loopia_load_config() {\n  LOOPIA_Api=\"${LOOPIA_Api:-$(_readaccountconf_mutable LOOPIA_Api)}\"\n  LOOPIA_User=\"${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}\"\n  LOOPIA_Password=\"${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}\"\n\n  if [ -z \"$LOOPIA_Api\" ]; then\n    LOOPIA_Api=\"$LOOPIA_Api_Default\"\n  fi\n\n  if [ -z \"$LOOPIA_User\" ] || [ -z \"$LOOPIA_Password\" ]; then\n    LOOPIA_User=\"\"\n    LOOPIA_Password=\"\"\n\n    _err \"A valid Loopia API user and password not provided.\"\n    _err \"Please provide a valid API user and try again.\"\n\n    return 1\n  fi\n\n  if _contains \"$LOOPIA_Password\" \"'\" || _contains \"$LOOPIA_Password\" '\"'; then\n    _err \"Password contains a quotation mark or double quotation marks and this is not supported by dns_loopia.sh\"\n    return 1\n  fi\n\n  Encoded_Password=$(_xml_encode \"$LOOPIA_Password\")\n  return 0\n}\n\n_loopia_save_config() {\n  if [ \"$LOOPIA_Api\" != \"$LOOPIA_Api_Default\" ]; then\n    _saveaccountconf_mutable LOOPIA_Api \"$LOOPIA_Api\"\n  fi\n  _saveaccountconf_mutable LOOPIA_User \"$LOOPIA_User\"\n  _saveaccountconf_mutable LOOPIA_Password \"$LOOPIA_Password\"\n}\n\n_loopia_get_records() {\n  domain=$1\n  sub_domain=$2\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>getZoneRecords</methodName>\n    <params>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n    </params>\n  </methodCall>' \"$LOOPIA_User\" \"$Encoded_Password\" \"$domain\" \"$sub_domain\")\n\n  response=\"$(_post \"$xml_content\" \"$LOOPIA_Api\" \"\" \"POST\")\"\n  if ! _contains \"$response\" \"<array>\"; then\n    err_response=$(echo \"$response\" | sed 's/.*<string>\\(.*\\)<\\/string>.*/\\1/')\n    _err \"Error: $err_response\"\n    return 1\n  fi\n  return 0\n}\n\n_get_root() {\n  domain=$1\n  _debug \"get root\"\n\n  domain=$1\n  i=2\n  p=1\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n  <methodName>getDomains</methodName>\n  <params>\n   <param>\n    <value><string>%s</string></value>\n   </param>\n   <param>\n    <value><string>%s</string></value>\n   </param>\n  </params>\n  </methodCall>' \"$LOOPIA_User\" \"$Encoded_Password\")\n\n  response=\"$(_post \"$xml_content\" \"$LOOPIA_Api\" \"\" \"POST\")\"\n  while true; do\n    h=$(echo \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"$h\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n\n}\n\n_loopia_add_record() {\n  domain=$1\n  sub_domain=$2\n  txtval=$3\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>addZoneRecord</methodName>\n    <params>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value>\n          <struct>\n            <member>\n              <name>type</name>\n              <value><string>TXT</string></value>\n            </member>\n            <member>\n              <name>priority</name>\n              <value><int>0</int></value>\n            </member>\n            <member>\n              <name>ttl</name>\n              <value><int>300</int></value>\n            </member>\n            <member>\n              <name>rdata</name>\n              <value><string>%s</string></value>\n            </member>\n          </struct>\n        </value>\n      </param>\n    </params>\n  </methodCall>' \"$LOOPIA_User\" \"$Encoded_Password\" \"$domain\" \"$sub_domain\" \"$txtval\")\n\n  response=\"$(_post \"$xml_content\" \"$LOOPIA_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"OK\"; then\n    err_response=$(echo \"$response\" | sed 's/.*<string>\\(.*\\)<\\/string>.*/\\1/')\n    _err \"Error: $err_response\"\n    return 1\n  fi\n  return 0\n}\n\n_sub_domain_exists() {\n  domain=$1\n  sub_domain=$2\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>getSubdomains</methodName>\n    <params>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n    </params>\n  </methodCall>' \"$LOOPIA_User\" \"$Encoded_Password\" \"$domain\")\n\n  response=\"$(_post \"$xml_content\" \"$LOOPIA_Api\" \"\" \"POST\")\"\n\n  if _contains \"$response\" \"$sub_domain\"; then\n    return 0\n  fi\n  return 1\n}\n\n_loopia_add_sub_domain() {\n  domain=$1\n  sub_domain=$2\n\n  if _sub_domain_exists \"$domain\" \"$sub_domain\"; then\n    return 0\n  fi\n\n  xml_content=$(printf '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n  <methodCall>\n    <methodName>addSubdomain</methodName>\n    <params>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n      <param>\n        <value><string>%s</string></value>\n      </param>\n    </params>\n  </methodCall>' \"$LOOPIA_User\" \"$Encoded_Password\" \"$domain\" \"$sub_domain\")\n\n  response=\"$(_post \"$xml_content\" \"$LOOPIA_Api\" \"\" \"POST\")\"\n\n  if ! _contains \"$response\" \"OK\"; then\n    err_response=$(echo \"$response\" | sed 's/.*<string>\\(.*\\)<\\/string>.*/\\1/')\n    _err \"Error: $err_response\"\n    return 1\n  fi\n  return 0\n}\n\n_xml_encode() {\n  encoded_string=$1\n  encoded_string=$(echo \"$encoded_string\" | sed 's/&/\\&amp;/')\n  encoded_string=$(echo \"$encoded_string\" | sed 's/</\\&lt;/')\n  encoded_string=$(echo \"$encoded_string\" | sed 's/>/\\&gt;/')\n  printf \"%s\" \"$encoded_string\"\n}\n"
  },
  {
    "path": "dnsapi/dns_lua.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_lua_info='LuaDNS.com\nDomains: LuaDNS.net\nSite: LuaDNS.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_lua\nOptions:\n LUA_Key API key\n LUA_Email Email\nAuthor: <dev@1e.ca>\n'\n\nLUA_Api=\"https://api.luadns.com/v1\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_lua_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  LUA_Key=\"${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}\"\n  LUA_Email=\"${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}\"\n  LUA_auth=$(printf \"%s\" \"$LUA_Email:$LUA_Key\" | _base64)\n\n  if [ -z \"$LUA_Key\" ] || [ -z \"$LUA_Email\" ]; then\n    LUA_Key=\"\"\n    LUA_Email=\"\"\n    _err \"You don't specify luadns api key and email yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable LUA_Key \"$LUA_Key\"\n  _saveaccountconf_mutable LUA_Email \"$LUA_Email\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _LUA_rest POST \"zones/$_domain_id/records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain.\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"; then\n    if _contains \"$response\" \"$fulldomain\"; then\n      _info \"Added\"\n      #todo: check if the record takes effect\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n}\n\n#fulldomain\ndns_lua_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  LUA_Key=\"${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}\"\n  LUA_Email=\"${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}\"\n  LUA_auth=$(printf \"%s\" \"$LUA_Email:$LUA_Key\" | _base64)\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _LUA_rest GET \"zones/${_domain_id}/records\"\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$fulldomain.\\\",\\\"type\\\":\\\"TXT\\\"\" | wc -l | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":[^,]*,\\\"name\\\":\\\"$fulldomain.\\\",\\\"type\\\":\\\"TXT\\\"\" | _head_n 1 | cut -d: -f2 | cut -d, -f1)\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _LUA_rest DELETE \"/zones/$_domain_id/records/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" \"$record_id\"\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  if ! _LUA_rest GET \"zones\"; then\n    return 1\n  fi\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":[^,]*,\\\"name\\\":\\\"$h\\\"\" | cut -d : -f 2 | cut -d , -f 1)\n      _debug _domain_id \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"$h\"\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_LUA_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Authorization: Basic $LUA_auth\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$LUA_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$LUA_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_maradns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_maradns_info='MaraDNS Server\nSite: MaraDNS.samiam.org\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_maradns\nOptions:\n MARA_ZONE_FILE Zone file path. E.g. \"/etc/maradns/db.domain.com\"\n MARA_DUENDE_PID_PATH Duende PID Path. E.g. \"/run/maradns/etc_maradns_mararc.pid\"\nIssues: github.com/acmesh-official/acme.sh/issues/2072\n'\n\n#Usage: dns_maradns_add _acme-challenge.www.domain.com \"token\"\ndns_maradns_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  MARA_ZONE_FILE=\"${MARA_ZONE_FILE:-$(_readaccountconf_mutable MARA_ZONE_FILE)}\"\n  MARA_DUENDE_PID_PATH=\"${MARA_DUENDE_PID_PATH:-$(_readaccountconf_mutable MARA_DUENDE_PID_PATH)}\"\n\n  _check_zone_file \"$MARA_ZONE_FILE\" || return 1\n  _check_duende_pid_path \"$MARA_DUENDE_PID_PATH\" || return 1\n\n  _saveaccountconf_mutable MARA_ZONE_FILE \"$MARA_ZONE_FILE\"\n  _saveaccountconf_mutable MARA_DUENDE_PID_PATH \"$MARA_DUENDE_PID_PATH\"\n\n  printf \"%s. TXT '%s' ~\\n\" \"$fulldomain\" \"$txtvalue\" >>\"$MARA_ZONE_FILE\"\n  _reload_maradns \"$MARA_DUENDE_PID_PATH\" || return 1\n}\n\n#Usage: dns_maradns_rm _acme-challenge.www.domain.com \"token\"\ndns_maradns_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  MARA_ZONE_FILE=\"${MARA_ZONE_FILE:-$(_readaccountconf_mutable MARA_ZONE_FILE)}\"\n  MARA_DUENDE_PID_PATH=\"${MARA_DUENDE_PID_PATH:-$(_readaccountconf_mutable MARA_DUENDE_PID_PATH)}\"\n\n  _check_zone_file \"$MARA_ZONE_FILE\" || return 1\n  _check_duende_pid_path \"$MARA_DUENDE_PID_PATH\" || return 1\n\n  _saveaccountconf_mutable MARA_ZONE_FILE \"$MARA_ZONE_FILE\"\n  _saveaccountconf_mutable MARA_DUENDE_PID_PATH \"$MARA_DUENDE_PID_PATH\"\n\n  _sed_i \"/^$fulldomain.\\+TXT '$txtvalue' ~/d\" \"$MARA_ZONE_FILE\"\n  _reload_maradns \"$MARA_DUENDE_PID_PATH\" || return 1\n}\n\n_check_zone_file() {\n  zonefile=\"$1\"\n  if [ -z \"$zonefile\" ]; then\n    _err \"MARA_ZONE_FILE not passed!\"\n    return 1\n  elif [ ! -w \"$zonefile\" ]; then\n    _err \"MARA_ZONE_FILE not writable: $zonefile\"\n    return 1\n  fi\n}\n\n_check_duende_pid_path() {\n  pidpath=\"$1\"\n  if [ -z \"$pidpath\" ]; then\n    _err \"MARA_DUENDE_PID_PATH not passed!\"\n    return 1\n  fi\n  if [ ! -r \"$pidpath\" ]; then\n    _err \"MARA_DUENDE_PID_PATH not readable: $pidpath\"\n    return 1\n  fi\n}\n\n_reload_maradns() {\n  pidpath=\"$1\"\n  kill -s HUP -- \"$(cat \"$pidpath\")\"\n  if [ $? -ne 0 ]; then\n    _err \"Unable to reload MaraDNS, kill returned\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_me.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_me_info='DnsMadeEasy.com\nSite: DnsMadeEasy.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_me\nOptions:\n ME_Key API Key\n ME_Secret API Secret\nAuthor: <dev@1e.ca>\n'\n\nME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_me_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$ME_Key\" ] || [ -z \"$ME_Secret\" ]; then\n    ME_Key=\"\"\n    ME_Secret=\"\"\n    _err \"You didn't specify DNSMadeEasy api key and secret yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf ME_Key \"$ME_Key\"\n  _saveaccountconf ME_Secret \"$ME_Secret\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _me_rest GET \"${_domain_id}/records?recordName=$_sub_domain&type=TXT\"\n\n  if ! _contains \"$response\" \"\\\"totalRecords\\\":\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  _info \"Adding record\"\n  if _me_rest POST \"$_domain_id/records/\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"value\\\":\\\"$txtvalue\\\",\\\"gtdLocation\\\":\\\"DEFAULT\\\",\\\"ttl\\\":120}\"; then\n    if printf -- \"%s\" \"$response\" | grep \\\"id\\\": >/dev/null; then\n      _info \"Added\"\n      #todo: check if the record takes effect\n      return 0\n    elif printf -- \"%s\" \"$response\" | grep -q \"already exists\"; then\n      _info \"Record already exists, skipping.\"\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n\n}\n\n#fulldomain\ndns_me_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _me_rest GET \"${_domain_id}/records?recordName=$_sub_domain&type=TXT\"\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"totalRecords\\\":[^,]*\" | cut -d : -f 2)\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \",\\\"value\\\":\\\"..$txtvalue..\\\",\\\"id\\\":[^,]*\" | cut -d : -f 3 | head -n 1)\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _me_rest DELETE \"$_domain_id/records/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" ''\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _me_rest GET \"name?domainname=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | sed 's/^{//; s/}$//; s/{.*}//' | sed -r 's/^.*\"id\":([0-9]+).*$/\\1/')\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"$h\"\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_me_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  cdate=$(LANG=C date -u +\"%a, %d %b %Y %T %Z\")\n  hmac=$(printf \"%s\" \"$cdate\" | _hmac sha1 \"$(printf \"%s\" \"$ME_Secret\" | _hex_dump | tr -d \" \")\" hex)\n\n  export _H1=\"x-dnsme-apiKey: $ME_Key\"\n  export _H2=\"x-dnsme-requestDate: $cdate\"\n  export _H3=\"x-dnsme-hmac: $hmac\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$ME_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$ME_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_mgwm.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_mgwm_info='mgw-media.de\nSite: mgw-media.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mgwm\nOptions:\n MGWM_CUSTOMER Your customer number\n MGWM_API_HASH Your API Hash\nIssues: github.com/acmesh-official/acme.sh/issues/6669\n'\n# Base URL for the mgw-media.de API\nMGWM_API_BASE=\"https://api.mgw-media.de/record\"\n\n########  Public functions #####################\n\n# This function is called by acme.sh to add a TXT record.\ndns_mgwm_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using mgw-media.de DNS API for domain $fulldomain (add record)\"\n  _debug \"fulldomain: $fulldomain\"\n  _debug \"txtvalue: $txtvalue\"\n\n  # Call the new private function to handle the API request.\n  # The 'add' action, fulldomain, type 'txt' and txtvalue are passed.\n  if _mgwm_request \"add\" \"$fulldomain\" \"txt\" \"$txtvalue\"; then\n    _info \"TXT record for $fulldomain successfully added via mgw-media.de API.\"\n    _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks.\n    return 0\n  else\n    # Error message already logged by _mgwm_request, but a specific one here helps.\n    _err \"mgwm_add: Failed to add TXT record for $fulldomain.\"\n    return 1\n  fi\n}\n# This function is called by acme.sh to remove a TXT record after validation.\ndns_mgwm_rm() {\n  fulldomain=$1\n  txtvalue=$2 # This txtvalue is now used to identify the specific record to be removed.\n  _info \"Removing TXT record for $fulldomain using mgw-media.de DNS API (remove record)\"\n  _debug \"fulldomain: $fulldomain\"\n  _debug \"txtvalue: $txtvalue\"\n\n  # Call the new private function to handle the API request.\n  # The 'rm' action, fulldomain, type 'txt' and txtvalue are passed.\n  if _mgwm_request \"rm\" \"$fulldomain\" \"txt\" \"$txtvalue\"; then\n    _info \"TXT record for $fulldomain successfully removed via mgw-media.de API.\"\n    return 0\n  else\n    # Error message already logged by _mgwm_request, but a specific one here helps.\n    _err \"mgwm_rm: Failed to remove TXT record for $fulldomain.\"\n    return 1\n  fi\n}\n####################  Private functions below ##################################\n\n# _mgwm_request() encapsulates the API call logic, including\n# loading credentials, setting the Authorization header, and executing the request.\n# Arguments:\n#   $1: action (e.g., \"add\", \"rm\")\n#   $2: fulldomain\n#   $3: type (e.g., \"txt\")\n#   $4: content (the txtvalue)\n_mgwm_request() {\n  _action=\"$1\"\n  _fulldomain=\"$2\"\n  _type=\"$3\"\n  _content=\"$4\"\n\n  _debug \"Calling _mgwm_request for action: $_action, domain: $_fulldomain, type: $_type, content: $_content\"\n\n  # Load credentials from environment or acme.sh config\n  MGWM_CUSTOMER=\"${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}\"\n  MGWM_API_HASH=\"${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}\"\n\n  # Check if credentials are set\n  if [ -z \"$MGWM_CUSTOMER\" ] || [ -z \"$MGWM_API_HASH\" ]; then\n    _err \"You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH.\"\n    _err \"Please check these environment variables and try again.\"\n    return 1\n  fi\n\n  # Save credentials for automatic renewal and future calls\n  _saveaccountconf_mutable MGWM_CUSTOMER \"$MGWM_CUSTOMER\"\n  _saveaccountconf_mutable MGWM_API_HASH \"$MGWM_API_HASH\"\n\n  # Create the Basic Auth Header. acme.sh's _base64 function is used for encoding.\n  _credentials=\"$(printf \"%s:%s\" \"$MGWM_CUSTOMER\" \"$MGWM_API_HASH\" | _base64)\"\n  export _H1=\"Authorization: Basic $_credentials\"\n  _debug \"Set Authorization Header: Basic <credentials_encoded>\" # Log debug message without sensitive credentials\n\n  # Construct the API URL based on the action and provided parameters.\n  _request_url=\"${MGWM_API_BASE}/${_action}/${_fulldomain}/${_type}/${_content}\"\n  _debug \"Constructed mgw-media.de API URL for action '$_action': ${_request_url}\"\n\n  # Execute the HTTP GET request with the Authorization Header.\n  # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization.\n  response=\"$(_get \"$_request_url\")\"\n  _debug \"mgw-media.de API response for action '$_action': $response\"\n\n  # Check the API response for success. The API returns \"OK\" on success.\n  if [ \"$response\" = \"OK\" ]; then\n    _info \"mgw-media.de API action '$_action' for record '$_fulldomain' successful.\"\n    return 0\n  else\n    _err \"Failed mgw-media.de API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_miab.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_miab_info='Mail-in-a-Box\nSite: MailInaBox.email\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_miab\nOptions:\n MIAB_Username Admin username\n MIAB_Password Admin password\n MIAB_Server Server hostname. FQDN of your_MIAB Server\nIssues: github.com/acmesh-official/acme.sh/issues/2550\nAuthor: Darven Dissek, William Gertz\n'\n\n########  Public functions #####################\n\n#Usage: dns_miab_add  _acme-challenge.www.domain.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_miab_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using miab challenge add\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  #retrieve MIAB environemt vars\n  if ! _retrieve_miab_env; then\n    return 1\n  fi\n\n  #check domain and seperate into domain and host\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}\"\n    return 1\n  fi\n\n  _debug2 _sub_domain \"$_sub_domain\"\n  _debug2 _domain \"$_domain\"\n\n  #add the challenge record\n  _api_path=\"custom/${fulldomain}/txt\"\n  _miab_rest \"$txtvalue\" \"$_api_path\" \"POST\"\n\n  #check if result was good\n  if _contains \"$response\" \"updated DNS\"; then\n    _info \"Successfully created the txt record\"\n    return 0\n  else\n    _err \"Error encountered during record add\"\n    _err \"$response\"\n    return 1\n  fi\n}\n\n#Usage: dns_miab_rm  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_miab_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using miab challenge delete\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  #retrieve MIAB environemt vars\n  if ! _retrieve_miab_env; then\n    return 1\n  fi\n\n  #check domain and seperate into doamin and host\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}\"\n    return 1\n  fi\n\n  _debug2 _sub_domain \"$_sub_domain\"\n  _debug2 _domain \"$_domain\"\n\n  #Remove the challenge record\n  _api_path=\"custom/${fulldomain}/txt\"\n  _miab_rest \"$txtvalue\" \"$_api_path\" \"DELETE\"\n\n  #check if result was good\n  if _contains \"$response\" \"updated DNS\"; then\n    _info \"Successfully removed the txt record\"\n    return 0\n  else\n    _err \"Error encountered during record remove\"\n    _err \"$response\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n#\n#Usage: _get_root  _acme-challenge.www.domain.com\n#Returns:\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  _passed_domain=$1\n  _debug _passed_domain \"$_passed_domain\"\n  _i=2\n  _p=1\n\n  #get the zones hosed on MIAB server, must be a json stream\n  _miab_rest \"\" \"zones\" \"GET\"\n\n  if ! _is_json \"$response\"; then\n    _err \"ERROR fetching domain list\"\n    _err \"$response\"\n    return 1\n  fi\n\n  #cycle through the passed domain seperating out a test domain discarding\n  #   the subdomain by marching thorugh the dots\n  while true; do\n    _test_domain=$(printf \"%s\" \"$_passed_domain\" | cut -d . -f \"${_i}\"-100)\n    _debug _test_domain \"$_test_domain\"\n\n    if [ -z \"$_test_domain\" ]; then\n      return 1\n    fi\n\n    #report found if the test domain is in the json response and\n    #   report the subdomain\n    if _contains \"$response\" \"\\\"$_test_domain\\\"\"; then\n      _sub_domain=$(printf \"%s\" \"$_passed_domain\" | cut -d . -f 1-\"${_p}\")\n      _domain=${_test_domain}\n      return 0\n    fi\n\n    #cycle to the next dot in the passed domain\n    _p=${_i}\n    _i=$(_math \"$_i\" + 1)\n  done\n\n  return 1\n}\n\n#Usage: _retrieve_miab_env\n#Returns (from store or environment variables):\n# MIAB_Username\n# MIAB_Password\n# MIAB_Server\n#retrieve MIAB environment variables, report errors and quit if problems\n_retrieve_miab_env() {\n  MIAB_Username=\"${MIAB_Username:-$(_readaccountconf_mutable MIAB_Username)}\"\n  MIAB_Password=\"${MIAB_Password:-$(_readaccountconf_mutable MIAB_Password)}\"\n  MIAB_Server=\"${MIAB_Server:-$(_readaccountconf_mutable MIAB_Server)}\"\n\n  #debug log the environmental variables\n  _debug MIAB_Username \"$MIAB_Username\"\n  _debug MIAB_Password \"$MIAB_Password\"\n  _debug MIAB_Server \"$MIAB_Server\"\n\n  #check if MIAB environemt vars set and quit if not\n  if [ -z \"$MIAB_Username\" ] || [ -z \"$MIAB_Password\" ] || [ -z \"$MIAB_Server\" ]; then\n    _err \"You didn't specify one or more of MIAB_Username, MIAB_Password or MIAB_Server.\"\n    _err \"Please check these environment variables and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable MIAB_Username \"$MIAB_Username\"\n  _saveaccountconf_mutable MIAB_Password \"$MIAB_Password\"\n  _saveaccountconf_mutable MIAB_Server \"$MIAB_Server\"\n  return 0\n}\n\n#Useage: _miab_rest  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"  \"custom/_acme-challenge.www.domain.com/txt  \"POST\"\n#Returns: \"updated DNS: domain.com\"\n#rest interface MIAB dns\n_miab_rest() {\n  _data=\"$1\"\n  _api_path=\"$2\"\n  _httpmethod=\"$3\"\n\n  #encode username and password for basic authentication\n  _credentials=\"$(printf \"%s\" \"$MIAB_Username:$MIAB_Password\" | _base64)\"\n  export _H1=\"Authorization: Basic $_credentials\"\n  _url=\"https://${MIAB_Server}/admin/dns/${_api_path}\"\n\n  _debug2 _data \"$_data\"\n  _debug _api_path \"$_api_path\"\n  _debug2 _url \"$_url\"\n  _debug2 _credentails \"$_credentials\"\n  _debug _httpmethod \"$_httpmethod\"\n\n  if [ \"$_httpmethod\" = \"GET\" ]; then\n    response=\"$(_get \"$_url\")\"\n  else\n    response=\"$(_post \"$_data\" \"$_url\" \"\" \"$_httpmethod\")\"\n  fi\n\n  _retcode=\"$?\"\n\n  if [ \"$_retcode\" != \"0\" ]; then\n    _err \"MIAB REST authentication failed on $_httpmethod\"\n    return 1\n  fi\n\n  _debug response \"$response\"\n  return 0\n}\n\n#Usage: _is_json  \"\\[\\n   \"mydomain.com\"\\n]\"\n#Reurns \"\\[\\n   \"mydomain.com\"\\n]\"\n#returns the string if it begins and ends with square braces\n_is_json() {\n  _str=\"$(echo \"$1\" | _normalizeJson)\"\n  echo \"$_str\" | grep '^\\[.*\\]$' >/dev/null 2>&1\n}\n"
  },
  {
    "path": "dnsapi/dns_mijnhost.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_mijnhost_info='mijn.host\nSite: mijn.host\nDocs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mijnhost\nOptions:\n MIJNHOST_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/6177\nAuthor: @peterv99\n'\n\n########  Public functions ######################\nMIJNHOST_API=\"https://mijn.host/api/v2\"\n\n# Add TXT record for domain verification\ndns_mijnhost_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  MIJNHOST_API_KEY=\"${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}\"\n  if [ -z \"$MIJNHOST_API_KEY\" ]; then\n    MIJNHOST_API_KEY=\"\"\n    _err \"You haven't specified your mijn-host API key yet.\"\n    _err \"Please add MIJNHOST_API_KEY to the env.\"\n    return 1\n  fi\n\n  # Save the API key for future use\n  _saveaccountconf_mutable MIJNHOST_API_KEY \"$MIJNHOST_API_KEY\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug2 _sub_domain \"$_sub_domain\"\n  _debug2 _domain \"$_domain\"\n  _debug \"Adding DNS record\" \"${fulldomain}.\"\n\n  # Construct the API URL\n  api_url=\"$MIJNHOST_API/domains/$_domain/dns\"\n\n  # Getting previous records\n  _mijnhost_rest GET \"$api_url\" \"\"\n\n  if [ \"$_code\" != \"200\" ]; then\n    _err \"Error getting current DNS enties ($_code)\"\n    return 1\n  fi\n\n  records=$(echo \"$response\" | _egrep_o '\"records\":\\[.*\\]' | sed 's/\"records\"://')\n\n  _debug2 \"Current records\" \"$records\"\n\n  # Build the payload for the API\n  data=\"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain.\\\",\\\"value\\\":\\\"$txtvalue\\\",\\\"ttl\\\":300}\"\n\n  _debug2 \"Record to add\" \"$data\"\n\n  # Updating the records\n  updated_records=$(echo \"$records\" | sed -E \"s/\\]( *$)/,$data\\]/\")\n\n  _debug2 \"Updated records\" \"$updated_records\"\n\n  # data\n  data=\"{\\\"records\\\": $updated_records}\"\n\n  _mijnhost_rest PUT \"$api_url\" \"$data\"\n\n  if [ \"$_code\" = \"200\" ]; then\n    _info \"DNS record succesfully added.\"\n    return 0\n  else\n    _err \"Error adding DNS record ($_code).\"\n    return 1\n  fi\n}\n\n# Remove TXT record after verification\ndns_mijnhost_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  MIJNHOST_API_KEY=\"${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}\"\n  if [ -z \"$MIJNHOST_API_KEY\" ]; then\n    MIJNHOST_API_KEY=\"\"\n    _err \"You haven't specified your mijn-host API key yet.\"\n    _err \"Please add MIJNHOST_API_KEY to the env.\"\n    return 1\n  fi\n\n  _debug \"Detecting root zone for\" \"${fulldomain}.\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug \"Removing DNS record for TXT value\" \"${txtvalue}.\"\n\n  # Construct the API URL\n  api_url=\"$MIJNHOST_API/domains/$_domain/dns\"\n\n  # Get current records\n  _mijnhost_rest GET \"$api_url\" \"\"\n\n  if [ \"$_code\" != \"200\" ]; then\n    _err \"Error getting current DNS enties ($_code)\"\n    return 1\n  fi\n\n  _debug2 \"Get current records response:\" \"$response\"\n\n  records=$(echo \"$response\" | _egrep_o '\"records\":\\[.*\\]' | sed 's/\"records\"://')\n\n  _debug2 \"Current records:\" \"$records\"\n\n  updated_records=$(echo \"$records\" | sed -E \"s/\\{[^}]*\\\"value\\\":\\\"$txtvalue\\\"[^}]*\\},?//g\" | sed 's/,]/]/g')\n\n  _debug2 \"Updated records:\" \"$updated_records\"\n\n  # Build the new payload\n  data=\"{\\\"records\\\": $updated_records}\"\n\n  # Use the _put method to update the records\n  _mijnhost_rest PUT \"$api_url\" \"$data\"\n\n  if [ \"$_code\" = \"200\" ]; then\n    _info \"DNS record removed successfully.\"\n    return 0\n  else\n    _err \"Error removing DNS record ($_code).\"\n    return 1\n  fi\n}\n\n# Helper function to detect the root zone\n_get_root() {\n  domain=$1\n\n  # Get current records\n  _debug \"Getting current domains\"\n  _mijnhost_rest GET \"$MIJNHOST_API/domains\" \"\"\n\n  if [ \"$_code\" != \"200\" ]; then\n    _err \"error getting current domains ($_code)\"\n    return 1\n  fi\n\n  # Extract root domains from response\n  rootDomains=$(echo \"$response\" | _egrep_o '\"domain\":\"[^\"]*\"' | sed -E 's/\"domain\":\"([^\"]*)\"/\\1/')\n  _debug \"Root domains:\" \"$rootDomains\"\n\n  for rootDomain in $rootDomains; do\n    if _contains \"$domain\" \"$rootDomain\"; then\n      _domain=\"$rootDomain\"\n      _sub_domain=$(echo \"$domain\" | sed \"s/.$rootDomain//g\")\n      _debug \"Found root domain\" \"$_domain\" \"and subdomain\" \"$_sub_domain\" \"for\" \"$domain\"\n      return 0\n    fi\n  done\n  return 1\n}\n\n# Helper function for rest calls\n_mijnhost_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n\n  MAX_REQUEST_RETRY_TIMES=15\n  _request_retry_times=0\n  _retry_sleep=5 #Initial sleep time in seconds.\n\n  while [ \"${_request_retry_times}\" -lt \"$MAX_REQUEST_RETRY_TIMES\" ]; do\n    _debug2 _request_retry_times \"$_request_retry_times\"\n    export _H1=\"API-Key: $MIJNHOST_API_KEY\"\n    export _H2=\"Content-Type: application/json\"\n    # clear headers from previous request to avoid getting wrong http code on timeouts\n    : >\"$HTTP_HEADER\"\n    _debug \"$ep\"\n    if [ \"$m\" != \"GET\" ]; then\n      _debug2 \"data $data\"\n      response=\"$(_post \"$data\" \"$ep\" \"\" \"$m\")\"\n    else\n      response=\"$(_get \"$ep\")\"\n    fi\n    _ret=\"$?\"\n    _debug2 \"response $response\"\n    _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n    _debug \"http response code $_code\"\n    if [ \"$_code\" = \"401\" ]; then\n      # we have an invalid API token, maybe it is expired?\n      _err \"Access denied. Invalid API token.\"\n      return 1\n    fi\n\n    if [ \"$_ret\" != \"0\" ] || [ -z \"$_code\" ] || [ \"$_code\" = \"400\" ] || _contains \"$response\" \"DNS records not managed by mijn.host\"; then #Sometimes API errors out\n      _request_retry_times=\"$(_math \"$_request_retry_times\" + 1)\"\n      _info \"REST call error $_code retrying $ep in ${_retry_sleep}s\"\n      _sleep \"$_retry_sleep\"\n      _retry_sleep=\"$(_math \"$_retry_sleep\" \\* 2)\"\n      continue\n    fi\n    break\n  done\n  if [ \"$_request_retry_times\" = \"$MAX_REQUEST_RETRY_TIMES\" ]; then\n    _err \"Error mijn.host API call was retried $MAX_REQUEST_RETRY_TIMES times.\"\n    _err \"Calling $ep failed.\"\n    return 1\n  fi\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_misaka.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_misaka_info='Misaka.io\nSite: Misaka.io\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_misaka\nOptions:\n Misaka_Key API Key\nAuthor: <support+acmesh@misaka.io>\n'\n\nMisaka_Api=\"https://dnsapi.misaka.io/dns\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_misaka_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$Misaka_Key\" ]; then\n    Misaka_Key=\"\"\n    _err \"You didn't specify misaka.io dns api key yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf Misaka_Key \"$Misaka_Key\"\n\n  _debug \"checking root zone [$fulldomain]\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _misaka_rest GET \"zones/${_domain}/recordsets?search=${_sub_domain}\"\n\n  if ! _contains \"$response\" \"\\\"results\\\":\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$_sub_domain\\\",[^{]*\\\"type\\\":\\\"TXT\\\"\" | wc -l | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Adding record\"\n\n    if _misaka_rest POST \"zones/${_domain}/recordsets/${_sub_domain}/TXT\" \"{\\\"records\\\":[{\\\"value\\\":\\\"\\\\\\\"$txtvalue\\\\\\\"\\\"}],\\\"filters\\\":[],\\\"ttl\\\":1}\"; then\n      _debug response \"$response\"\n      if _contains \"$response\" \"$_sub_domain\"; then\n        _info \"Added\"\n        return 0\n      else\n        _err \"Add txt record error.\"\n        return 1\n      fi\n    fi\n    _err \"Add txt record error.\"\n  else\n    _info \"Updating record\"\n\n    _misaka_rest PUT \"zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true\" \"{\\\"records\\\": [{\\\"value\\\": \\\"\\\\\\\"$txtvalue\\\\\\\"\\\"}],\\\"ttl\\\":1}\"\n    if [ \"$?\" = \"0\" ] && _contains \"$response\" \"$_sub_domain\"; then\n      _info \"Updated!\"\n      #todo: check if the record takes effect\n      return 0\n    fi\n    _err \"Update error\"\n    return 1\n  fi\n\n}\n\n#fulldomain\ndns_misaka_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _misaka_rest GET \"zones/${_domain}/recordsets?search=${_sub_domain}\"\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$_sub_domain\\\",[^{]*\\\"type\\\":\\\"TXT\\\"\" | wc -l | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    if ! _misaka_rest DELETE \"zones/${_domain}/recordsets/${_sub_domain}/TXT\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" \"\"\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  if ! _misaka_rest GET \"zones?limit=1000\"; then\n    return 1\n  fi\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_misaka_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"User-Agent: acme.sh/$VER misaka-dns-acmesh/20191213\"\n  export _H3=\"Authorization: Token $Misaka_Key\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$Misaka_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$Misaka_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_myapi.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_myapi_info='Custom API Example\n A sample custom DNS API script description.\nDomains: example.com example.net\nSite: github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_myapi\nOptions:\n MYAPI_Token API Token. Get API Token from https://example.com/api/\n MYAPI_Variable2 Option 2. Default \"default value\".\n MYAPI_Variable2 Option 3. Optional.\nIssues: github.com/acmesh-official/acme.sh\nAuthor: Neil Pang <neilgit@neilpang.com>\n'\n\n#This file name is \"dns_myapi.sh\"\n#So, here must be a method   dns_myapi_add()\n#Which will be called by acme.sh to add the txt record to your api system.\n#returns 0 means success, otherwise error.\n\n########  Public functions #####################\n\n# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_myapi_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using myapi\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _err \"Not implemented!\"\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_myapi_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using myapi\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n}\n\n####################  Private functions below ##################################\n"
  },
  {
    "path": "dnsapi/dns_mydevil.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_mydevil_info='MyDevil.net\n MyDevil.net already supports automatic Lets Encrypt certificates,\n except for wildcard domains.\n This script depends on devil command that MyDevil.net provides,\n which means that it works only on server side.\nSite: MyDevil.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydevil\nIssues: github.com/acmesh-official/acme.sh/issues/2079\nAuthor: Marcin Konicki <https://ahwayakchih.neoni.net>\n'\n\n########  Public functions #####################\n\n#Usage: dns_mydevil_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_mydevil_add() {\n  fulldomain=$1\n  txtvalue=$2\n  domain=\"\"\n\n  if ! _exists \"devil\"; then\n    _err \"Could not find 'devil' command.\"\n    return 1\n  fi\n\n  _info \"Using mydevil\"\n\n  domain=$(mydevil_get_domain \"$fulldomain\")\n  if [ -z \"$domain\" ]; then\n    _err \"Invalid domain name: could not find root domain of $fulldomain.\"\n    return 1\n  fi\n\n  # No need to check if record name exists, `devil` always adds new record.\n  # In worst case scenario, we end up with multiple identical records.\n\n  _info \"Adding $fulldomain record for domain $domain\"\n  if devil dns add \"$domain\" \"$fulldomain\" TXT \"$txtvalue\"; then\n    _info \"Successfully added TXT record, ready for validation.\"\n    return 0\n  else\n    _err \"Unable to add DNS record.\"\n    return 1\n  fi\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_mydevil_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  domain=\"\"\n\n  if ! _exists \"devil\"; then\n    _err \"Could not find 'devil' command.\"\n    return 1\n  fi\n\n  _info \"Using mydevil\"\n\n  domain=$(mydevil_get_domain \"$fulldomain\")\n  if [ -z \"$domain\" ]; then\n    _err \"Invalid domain name: could not find root domain of $fulldomain.\"\n    return 1\n  fi\n\n  # catch one or more numbers\n  num='[0-9][0-9]*'\n  # catch one or more whitespace\n  w=$(printf '[\\t ][\\t ]*')\n  # catch anything, except newline\n  any='.*'\n  # filter to make sure we do not delete other records\n  validRecords=\"^${num}${w}${fulldomain}${w}TXT${w}${any}${txtvalue}$\"\n  for id in $(devil dns list \"$domain\" | tail -n+2 | grep \"${validRecords}\" | cut -w -s -f 1); do\n    _info \"Removing record $id from domain $domain\"\n    echo \"y\" | devil dns del \"$domain\" \"$id\" || _err \"Could not remove DNS record.\"\n  done\n}\n\n####################  Private functions below ##################################\n\n# Usage: domain=$(mydevil_get_domain \"_acme-challenge.www.domain.com\" || _err \"Invalid domain name\")\n#        echo $domain\nmydevil_get_domain() {\n  fulldomain=$1\n  domain=\"\"\n\n  for domain in $(devil dns list | cut -w -s -f 1 | tail -n+2); do\n    _debug \"Checking domain: $domain\"\n    if _endswith \"$fulldomain\" \"$domain\"; then\n      _debug \"Fulldomain '$fulldomain' matches '$domain'\"\n      printf -- \"%s\" \"$domain\"\n      return 0\n    fi\n  done\n\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_mydnsjp.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_mydnsjp_info='MyDNS.JP\nSite: MyDNS.JP\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydnsjp\nOptions:\n MYDNSJP_MasterID Master ID\n MYDNSJP_Password Password\nAuthor: @tkmsst\n'\n\n########  Public functions #####################\n\n# Export MyDNS.JP MasterID and Password in following variables...\n#  MYDNSJP_MasterID=MasterID\n#  MYDNSJP_Password=Password\n\nMYDNSJP_API=\"https://www.mydns.jp\"\n\n#Usage: dns_mydnsjp_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_mydnsjp_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using mydnsjp\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  # Load the credentials from the account conf file\n  MYDNSJP_MasterID=\"${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}\"\n  MYDNSJP_Password=\"${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}\"\n  if [ -z \"$MYDNSJP_MasterID\" ] || [ -z \"$MYDNSJP_Password\" ]; then\n    MYDNSJP_MasterID=\"\"\n    MYDNSJP_Password=\"\"\n    _err \"You don't specify mydnsjp api MasterID and Password yet.\"\n    _err \"Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again.\"\n    return 1\n  fi\n\n  # Save the credentials to the account conf file\n  _saveaccountconf_mutable MYDNSJP_MasterID \"$MYDNSJP_MasterID\"\n  _saveaccountconf_mutable MYDNSJP_Password \"$MYDNSJP_Password\"\n\n  _debug \"First detect the root zone.\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if _mydnsjp_api \"REGIST\" \"$_domain\" \"$txtvalue\"; then\n    if printf -- \"%s\" \"$response\" | grep \"OK.\" >/dev/null; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_mydnsjp_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Removing TXT record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  # Load the credentials from the account conf file\n  MYDNSJP_MasterID=\"${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}\"\n  MYDNSJP_Password=\"${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}\"\n  if [ -z \"$MYDNSJP_MasterID\" ] || [ -z \"$MYDNSJP_Password\" ]; then\n    MYDNSJP_MasterID=\"\"\n    MYDNSJP_Password=\"\"\n    _err \"You don't specify mydnsjp api MasterID and Password yet.\"\n    _err \"Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if _mydnsjp_api \"DELETE\" \"$_domain\" \"$txtvalue\"; then\n    if printf -- \"%s\" \"$response\" | grep \"OK.\" >/dev/null; then\n      _info \"Deleted, OK\"\n      return 0\n    else\n      _err \"Delete txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Delete txt record error.\"\n\n  return 1\n}\n\n####################  Private functions below ##################################\n# _acme-challenge.www.domain.com\n# returns\n#  _sub_domain=_acme-challenge.www\n#  _domain=domain.com\n_get_root() {\n  fulldomain=$1\n  i=2\n  p=1\n\n  # Get the root domain\n  _mydnsjp_retrieve_domain\n  if [ \"$?\" != \"0\" ]; then\n    # not valid\n    return 1\n  fi\n\n  while true; do\n    _domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i\"-100)\n\n    if [ -z \"$_domain\" ]; then\n      # not valid\n      return 1\n    fi\n\n    if [ \"$_domain\" = \"$_root_domain\" ]; then\n      _sub_domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 1-\"$p\")\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n# Retrieve the root domain\n# returns 0 success\n_mydnsjp_retrieve_domain() {\n  _debug \"Login to MyDNS.JP\"\n\n  response=\"$(_post \"MENU=100&masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password\" \"$MYDNSJP_API/members/\")\"\n  cookie=\"$(grep -i '^set-cookie:' \"$HTTP_HEADER\" | _head_n 1 | cut -d \" \" -f 2)\"\n\n  # If cookies is not empty then logon successful\n  if [ -z \"$cookie\" ]; then\n    _err \"Fail to get a cookie.\"\n    return 1\n  fi\n\n  _root_domain=$(echo \"$response\" | grep \"DNSINFO\\[domainname\\]\" | sed 's/^.*value=\"\\([^\"]*\\)\".*/\\1/')\n\n  _debug _root_domain \"$_root_domain\"\n\n  if [ -z \"$_root_domain\" ]; then\n    _err \"Fail to get the root domain.\"\n    return 1\n  fi\n\n  return 0\n}\n\n_mydnsjp_api() {\n  cmd=$1\n  domain=$2\n  txtvalue=$3\n\n  # Base64 encode the credentials\n  credentials=$(printf \"%s:%s\" \"$MYDNSJP_MasterID\" \"$MYDNSJP_Password\" | _base64)\n\n  # Construct the HTTP Authorization header\n  export _H1=\"Content-Type: application/x-www-form-urlencoded\"\n  export _H2=\"Authorization: Basic ${credentials}\"\n\n  response=\"$(_post \"CERTBOT_DOMAIN=$domain&CERTBOT_VALIDATION=$txtvalue&EDIT_CMD=$cmd\" \"$MYDNSJP_API/directedit.html\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $domain\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_mythic_beasts.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_mythic_beasts_info='Mythic-Beasts.com\nSite: Mythic-Beasts.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mythic_beasts\nOptions:\n MB_AK API Key\n MB_AS API Secret\nIssues: github.com/acmesh-official/acme.sh/issues/3848\n'\n# Mythic Beasts is a long-standing UK service provider using standards-based OAuth2 authentication\n# To test: ./acme.sh --dns dns_mythic_beasts --test --debug 1 --output-insecure --issue --domain domain.com\n# Cannot retest once cert is issued\n# OAuth2 tokens only valid for 300 seconds so we do not store\n# NOTE: This will remove all TXT records matching the fulldomain, not just the added ones (_acme-challenge.www.domain.com)\n\n# Test OAuth2 credentials\n#MB_AK=\"aaaaaaaaaaaaaaaa\"\n#MB_AS=\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"\n\n# URLs\nMB_API='https://api.mythic-beasts.com/dns/v2/zones'\nMB_AUTH='https://auth.mythic-beasts.com/login'\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_mythic_beasts_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"MYTHIC BEASTS Adding record $fulldomain = $txtvalue\"\n  if ! _initAuth; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  # method path body_data\n  if _mb_rest POST \"$_domain/records/$_sub_domain/TXT\" \"$txtvalue\"; then\n\n    if _contains \"$response\" \"1 records added\"; then\n      _info \"Added, verifying...\"\n      # Max 120 seconds to publish\n      for i in $(seq 1 6); do\n        # Retry on error\n        if ! _mb_rest GET \"$_domain/records/$_sub_domain/TXT?verify\"; then\n          _sleep 20\n        else\n          _info \"Record published!\"\n          return 0\n        fi\n      done\n\n    else\n      _err \"\\n$response\"\n    fi\n\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#Usage: rm  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_mythic_beasts_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"MYTHIC BEASTS Removing record $fulldomain = $txtvalue\"\n  if ! _initAuth; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  # method path body_data\n  if _mb_rest DELETE \"$_domain/records/$_sub_domain/TXT\" \"$txtvalue\"; then\n    _info \"Record removed\"\n    return 0\n  fi\n  _err \"Remove txt record error.\"\n  return 1\n}\n\n####################  Private functions below ##################################\n\n#Possible formats:\n# _acme-challenge.www.example.com\n# _acme-challenge.example.com\n# _acme-challenge.example.co.uk\n# _acme-challenge.www.example.co.uk\n# _acme-challenge.sub1.sub2.www.example.co.uk\n# sub1.sub2.example.co.uk\n# example.com\n# example.co.uk\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  _debug \"Detect the root zone\"\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      _err \"Domain exhausted\"\n      return 1\n    fi\n\n    # Use the status errors to find the domain, continue on 403 Access denied\n    # method path body_data\n    _mb_rest GET \"$h/records\"\n    ret=\"$?\"\n    if [ \"$ret\" -eq 0 ]; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      _debug _sub_domain \"$_sub_domain\"\n      _debug _domain \"$_domain\"\n      return 0\n    elif [ \"$ret\" -eq 1 ]; then\n      return 1\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n\n    if [ \"$i\" -gt 50 ]; then\n      break\n    fi\n  done\n  _err \"Domain too long\"\n  return 1\n}\n\n_initAuth() {\n  MB_AK=\"${MB_AK:-$(_readaccountconf_mutable MB_AK)}\"\n  MB_AS=\"${MB_AS:-$(_readaccountconf_mutable MB_AS)}\"\n\n  if [ -z \"$MB_AK\" ] || [ -z \"$MB_AS\" ]; then\n    MB_AK=\"\"\n    MB_AS=\"\"\n    _err \"Please specify an OAuth2 Key & Secret\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable MB_AK \"$MB_AK\"\n  _saveaccountconf_mutable MB_AS \"$MB_AS\"\n\n  if ! _oauth2; then\n    return 1\n  fi\n\n  _info \"Checking authentication\"\n  _secure_debug access_token \"$MB_TK\"\n  _sleep 1\n\n  # GET a list of zones\n  # method path body_data\n  if ! _mb_rest GET \"\"; then\n    _err \"The token is invalid\"\n    return 1\n  fi\n  _info \"Token OK\"\n  return 0\n}\n\n# Github appears to use an outbound proxy for requests which means subsequent requests may not have the same\n# source IP. The standard Mythic Beasts OAuth2 tokens are tied to an IP, meaning github test requests fail\n# authentication. This is a work around using an undocumented MB API to obtain a token not tied to an\n# IP just for the github tests.\n_oauth2() {\n  if [ \"$GITHUB_ACTIONS\" = \"true\" ]; then\n    _oauth2_github\n  else\n    _oauth2_std\n  fi\n  return $?\n}\n\n_oauth2_std() {\n  # HTTP Basic Authentication\n  _H1=\"Authorization: Basic $(echo \"$MB_AK:$MB_AS\" | _base64)\"\n  _H2=\"Accepts: application/json\"\n  export _H1 _H2\n  body=\"grant_type=client_credentials\"\n\n  _info \"Getting OAuth2 token...\"\n  # body  url [needbase64] [POST|PUT|DELETE] [ContentType]\n  response=\"$(_post \"$body\" \"$MB_AUTH\" \"\" \"POST\" \"application/x-www-form-urlencoded\")\"\n  if _contains \"$response\" \"\\\"token_type\\\":\\\"bearer\\\"\"; then\n    MB_TK=\"$(echo \"$response\" | _egrep_o \"access_token\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d '\"')\"\n    if [ -z \"$MB_TK\" ]; then\n      _err \"Unable to get access_token\"\n      _err \"\\n$response\"\n      return 1\n    fi\n  else\n    _err \"OAuth2 token_type not Bearer\"\n    _err \"\\n$response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_oauth2_github() {\n  _H1=\"Accepts: application/json\"\n  export _H1\n  body=\"{\\\"login\\\":{\\\"handle\\\":\\\"$MB_AK\\\",\\\"pass\\\":\\\"$MB_AS\\\",\\\"floating\\\":1}}\"\n\n  _info \"Getting Floating token...\"\n  # body  url [needbase64] [POST|PUT|DELETE] [ContentType]\n  response=\"$(_post \"$body\" \"$MB_AUTH\" \"\" \"POST\" \"application/json\")\"\n  MB_TK=\"$(echo \"$response\" | _egrep_o \"\\\"token\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d '\"')\"\n  if [ -z \"$MB_TK\" ]; then\n    _err \"Unable to get token\"\n    _err \"\\n$response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n# method path body_data\n_mb_rest() {\n  # URL encoded body for single API operations\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  if [ -z \"$ep\" ]; then\n    _mb_url=\"$MB_API\"\n  else\n    _mb_url=\"$MB_API/$ep\"\n  fi\n\n  _H1=\"Authorization: Bearer $MB_TK\"\n  _H2=\"Accepts: application/json\"\n  export _H1 _H2\n  if [ \"$data\" ] || [ \"$m\" = \"POST\" ] || [ \"$m\" = \"PUT\" ] || [ \"$m\" = \"DELETE\" ]; then\n    # body  url [needbase64] [POST|PUT|DELETE] [ContentType]\n    response=\"$(_post \"data=$data\" \"$_mb_url\" \"\" \"$m\" \"application/x-www-form-urlencoded\")\"\n  else\n    response=\"$(_get \"$_mb_url\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Request error\"\n    return 1\n  fi\n\n  header=\"$(cat \"$HTTP_HEADER\")\"\n  status=\"$(echo \"$header\" | _egrep_o \"^HTTP[^ ]* .*$\" | cut -d \" \" -f 2-100 | tr -d \"\\f\\n\")\"\n  code=\"$(echo \"$status\" | _egrep_o \"^[0-9]*\")\"\n  if [ \"$code\" -ge 400 ] || _contains \"$response\" \"\\\"error\\\"\" || _contains \"$response\" \"invalid_client\"; then\n    _err \"error $status\"\n    _err \"\\n$response\"\n    _debug \"\\n$header\"\n    return 2\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_namecheap.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_namecheap_info='NameCheap.com\nSite: NameCheap.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecheap\nOptions:\n NAMECHEAP_API_KEY API Key\n NAMECHEAP_USERNAME Username\n NAMECHEAP_SOURCEIP Source IP\nIssues: github.com/acmesh-official/acme.sh/issues/2107\n'\n\n# Namecheap API\n# https://www.namecheap.com/support/api/intro.aspx\n# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.\n\n########  Public functions #####################\n\nNAMECHEAP_API=\"https://api.namecheap.com/xml.response\"\n\n#Usage: dns_namecheap_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_namecheap_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _namecheap_check_config; then\n    _err \"$error\"\n    return 1\n  fi\n\n  if ! _namecheap_set_publicip; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _debug domain \"$_domain\"\n  _debug sub_domain \"$_sub_domain\"\n\n  _set_namecheap_TXT \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_namecheap_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _namecheap_set_publicip; then\n    return 1\n  fi\n\n  if ! _namecheap_check_config; then\n    _err \"$error\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _debug domain \"$_domain\"\n  _debug sub_domain \"$_sub_domain\"\n\n  _del_namecheap_TXT \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  fulldomain=$1\n\n  if ! _get_root_by_getList \"$fulldomain\"; then\n    _debug \"Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api.\"\n    # The above \"getList\" api will only return hosts *owned* by the calling user. However, if the calling\n    # user is not the owner, but still has administrative rights, we must query the getHosts api directly.\n    # See this comment and the official namecheap response: https://disq.us/p/1q6v9x9\n    if ! _get_root_by_getHosts \"$fulldomain\"; then\n      return 1\n    fi\n  fi\n\n  return 0\n}\n\n_get_root_by_getList() {\n  domain=$1\n\n  if ! _namecheap_post \"namecheap.domains.getList\"; then\n    _err \"$error\"\n    return 1\n  fi\n\n  i=2\n  p=1\n\n  while true; do\n\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    if ! _contains \"$h\" \"\\\\.\"; then\n      #not valid\n      return 1\n    fi\n\n    if ! _contains \"$response\" \"$h\"; then\n      _debug \"$h not found\"\n    else\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_get_root_by_getHosts() {\n  i=100\n  p=99\n\n  while [ \"$p\" -ne 0 ]; do\n\n    h=$(printf \"%s\" \"$1\" | cut -d . -f \"$i\"-100)\n    if [ -n \"$h\" ]; then\n      if _contains \"$h\" \"\\\\.\"; then\n        _debug h \"$h\"\n        if _namecheap_set_tld_sld \"$h\"; then\n          _sub_domain=$(printf \"%s\" \"$1\" | cut -d . -f 1-\"$p\")\n          _domain=\"$h\"\n          return 0\n        else\n          _debug \"$h not found\"\n        fi\n      fi\n    fi\n    i=\"$p\"\n    p=$(_math \"$p\" - 1)\n  done\n  return 1\n}\n\n_namecheap_set_publicip() {\n\n  if [ -z \"$NAMECHEAP_SOURCEIP\" ]; then\n    _err \"No Source IP specified for Namecheap API.\"\n    _err \"Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP\"\n    return 1\n  else\n    _saveaccountconf NAMECHEAP_SOURCEIP \"$NAMECHEAP_SOURCEIP\"\n    _debug sourceip \"$NAMECHEAP_SOURCEIP\"\n\n    ip=$(echo \"$NAMECHEAP_SOURCEIP\" | _egrep_o '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}')\n    addr=$(echo \"$NAMECHEAP_SOURCEIP\" | _egrep_o '(http|https):\\/\\/.*')\n\n    _debug2 ip \"$ip\"\n    _debug2 addr \"$addr\"\n\n    if [ -n \"$ip\" ]; then\n      _publicip=\"$ip\"\n    elif [ -n \"$addr\" ]; then\n      _publicip=$(_get \"$addr\")\n    else\n      _err \"No Source IP specified for Namecheap API.\"\n      _err \"Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP\"\n      return 1\n    fi\n  fi\n\n  _debug publicip \"$_publicip\"\n\n  return 0\n}\n\n_namecheap_post() {\n  command=$1\n  data=\"ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}\"\n  _debug2 \"_namecheap_post data\" \"$data\"\n  response=\"$(_post \"$data\" \"$NAMECHEAP_API\" \"\" \"POST\")\"\n  _debug2 response \"$response\"\n\n  if _contains \"$response\" \"Status=\\\"ERROR\\\"\" >/dev/null; then\n    error=$(echo \"$response\" | _egrep_o \">.*<\\\\/Error>\" | cut -d '<' -f 1 | tr -d '>')\n    _err \"error $error\"\n    return 1\n  fi\n\n  return 0\n}\n\n_namecheap_parse_host() {\n  _host=$1\n  _debug _host \"$_host\"\n\n  _hostid=$(echo \"$_host\" | _egrep_o ' HostId=\"[^\"]*' | cut -d '\"' -f 2)\n  _hostname=$(echo \"$_host\" | _egrep_o ' Name=\"[^\"]*' | cut -d '\"' -f 2)\n  _hosttype=$(echo \"$_host\" | _egrep_o ' Type=\"[^\"]*' | cut -d '\"' -f 2)\n  _hostaddress=$(echo \"$_host\" | _egrep_o ' Address=\"[^\"]*' | cut -d '\"' -f 2 | _xml_decode)\n  _hostmxpref=$(echo \"$_host\" | _egrep_o ' MXPref=\"[^\"]*' | cut -d '\"' -f 2)\n  _hostttl=$(echo \"$_host\" | _egrep_o ' TTL=\"[^\"]*' | cut -d '\"' -f 2)\n\n  _debug hostid \"$_hostid\"\n  _debug hostname \"$_hostname\"\n  _debug hosttype \"$_hosttype\"\n  _debug hostaddress \"$_hostaddress\"\n  _debug hostmxpref \"$_hostmxpref\"\n  _debug hostttl \"$_hostttl\"\n}\n\n_namecheap_check_config() {\n\n  if [ -z \"$NAMECHEAP_API_KEY\" ]; then\n    _err \"No API key specified for Namecheap API.\"\n    _err \"Create your key and export it as NAMECHEAP_API_KEY\"\n    return 1\n  fi\n\n  if [ -z \"$NAMECHEAP_USERNAME\" ]; then\n    _err \"No username key specified for Namecheap API.\"\n    _err \"Create your key and export it as NAMECHEAP_USERNAME\"\n    return 1\n  fi\n\n  _saveaccountconf NAMECHEAP_API_KEY \"$NAMECHEAP_API_KEY\"\n  _saveaccountconf NAMECHEAP_USERNAME \"$NAMECHEAP_USERNAME\"\n\n  return 0\n}\n\n_set_namecheap_TXT() {\n  subdomain=$2\n  txt=$3\n\n  if ! _namecheap_set_tld_sld \"$1\"; then\n    return 1\n  fi\n\n  request=\"namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}\"\n\n  if ! _namecheap_post \"$request\"; then\n    _err \"$error\"\n    return 1\n  fi\n\n  hosts=$(echo \"$response\" | _egrep_o '<host[^>]*')\n  _debug hosts \"$hosts\"\n\n  if [ -z \"$hosts\" ]; then\n    _err \"Hosts not found\"\n    return 1\n  fi\n\n  _namecheap_reset_hostList\n\n  while read -r host; do\n    if _contains \"$host\" \"<host\"; then\n      _namecheap_parse_host \"$host\"\n      _debug2 _hostname \"_hostname\"\n      _debug2 _hosttype \"_hosttype\"\n      _debug2 _hostaddress \"_hostaddress\"\n      _debug2 _hostmxpref \"_hostmxpref\"\n      _hostaddress=\"$(printf \"%s\" \"$_hostaddress\" | _url_encode)\"\n      _debug2 \"encoded _hostaddress\" \"_hostaddress\"\n      _namecheap_add_host \"$_hostname\" \"$_hosttype\" \"$_hostaddress\" \"$_hostmxpref\" \"$_hostttl\"\n    fi\n  done <<EOT\necho \"$hosts\"\nEOT\n\n  _namecheap_add_host \"$subdomain\" \"TXT\" \"$txt\" 10 120\n\n  _debug hostrequestfinal \"$_hostrequest\"\n\n  request=\"namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}\"\n\n  if ! _namecheap_post \"$request\"; then\n    _err \"$error\"\n    return 1\n  fi\n\n  return 0\n}\n\n_del_namecheap_TXT() {\n  subdomain=$2\n  txt=$3\n\n  if ! _namecheap_set_tld_sld \"$1\"; then\n    return 1\n  fi\n\n  request=\"namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}\"\n\n  if ! _namecheap_post \"$request\"; then\n    _err \"$error\"\n    return 1\n  fi\n\n  hosts=$(echo \"$response\" | _egrep_o '<host[^>]*')\n  _debug hosts \"$hosts\"\n\n  if [ -z \"$hosts\" ]; then\n    _err \"Hosts not found\"\n    return 1\n  fi\n\n  _namecheap_reset_hostList\n\n  found=0\n\n  while read -r host; do\n    if _contains \"$host\" \"<host\"; then\n      _namecheap_parse_host \"$host\"\n      if [ \"$_hosttype\" = \"TXT\" ] && [ \"$_hostname\" = \"$subdomain\" ] && [ \"$_hostaddress\" = \"$txt\" ]; then\n        _debug \"TXT entry found\"\n        found=1\n      else\n        _hostaddress=\"$(printf \"%s\" \"$_hostaddress\" | _url_encode)\"\n        _namecheap_add_host \"$_hostname\" \"$_hosttype\" \"$_hostaddress\" \"$_hostmxpref\" \"$_hostttl\"\n      fi\n    fi\n  done <<EOT\necho \"$hosts\"\nEOT\n\n  if [ $found -eq 0 ]; then\n    _debug \"TXT entry not found\"\n    return 0\n  fi\n\n  _debug hostrequestfinal \"$_hostrequest\"\n\n  request=\"namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}\"\n\n  if ! _namecheap_post \"$request\"; then\n    _err \"$error\"\n    return 1\n  fi\n\n  return 0\n}\n\n_namecheap_reset_hostList() {\n  _hostindex=0\n  _hostrequest=\"\"\n}\n\n#Usage: _namecheap_add_host HostName RecordType Address MxPref TTL\n_namecheap_add_host() {\n  _hostindex=$(_math \"$_hostindex\" + 1)\n  _hostrequest=$(printf '%s&HostName%d=%s&RecordType%d=%s&Address%d=%s&MXPref%d=%d&TTL%d=%d' \"$_hostrequest\" \"$_hostindex\" \"$1\" \"$_hostindex\" \"$2\" \"$_hostindex\" \"$3\" \"$_hostindex\" \"$4\" \"$_hostindex\" \"$5\")\n}\n\n_namecheap_set_tld_sld() {\n  domain=$1\n  _tld=\"\"\n  _sld=\"\"\n\n  i=2\n\n  while true; do\n\n    _tld=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug tld \"$_tld\"\n\n    if [ -z \"$_tld\" ]; then\n      _debug \"invalid tld\"\n      return 1\n    fi\n\n    j=$(_math \"$i\" - 1)\n\n    _sld=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$j\")\n    _debug sld \"$_sld\"\n\n    if [ -z \"$_sld\" ]; then\n      _debug \"invalid sld\"\n      return 1\n    fi\n\n    request=\"namecheap.domains.dns.getHosts&SLD=$_sld&TLD=$_tld\"\n\n    if ! _namecheap_post \"$request\"; then\n      _debug \"sld($_sld)/tld($_tld) not found\"\n    else\n      _debug \"sld($_sld)/tld($_tld) found\"\n      return 0\n    fi\n\n    i=$(_math \"$i\" + 1)\n\n  done\n\n}\n\n_xml_decode() {\n  sed 's/&quot;/\"/g'\n}\n"
  },
  {
    "path": "dnsapi/dns_namecom.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_namecom_info='Name.com\nSite: Name.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecom\nOptions:\n Namecom_Username Username\n Namecom_Token API Token\nAuthor: @RaidenII\n'\n\n########  Public functions #####################\n\nNamecom_API=\"https://api.name.com/v4\"\n\n#Usage: dns_namecom_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_namecom_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Namecom_Username=\"${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}\"\n  Namecom_Token=\"${Namecom_Token:-$(_readaccountconf_mutable Namecom_Token)}\"\n  # First we need name.com credentials.\n  if [ -z \"$Namecom_Username\" ]; then\n    Namecom_Username=\"\"\n    _err \"Username for name.com is missing.\"\n    _err \"Please specify that in your environment variable.\"\n    return 1\n  fi\n\n  if [ -z \"$Namecom_Token\" ]; then\n    Namecom_Token=\"\"\n    _err \"API token for name.com is missing.\"\n    _err \"Please specify that in your environment variable.\"\n    return 1\n  fi\n  _debug Namecom_Username \"$Namecom_Username\"\n  _secure_debug Namecom_Token \"$Namecom_Token\"\n  # Save them in configuration.\n  _saveaccountconf_mutable Namecom_Username \"$Namecom_Username\"\n  _saveaccountconf_mutable Namecom_Token \"$Namecom_Token\"\n\n  # Login in using API\n  if ! _namecom_login; then\n    return 1\n  fi\n\n  # Find domain in domain list.\n  if ! _namecom_get_root \"$fulldomain\"; then\n    _err \"Unable to find domain specified.\"\n    return 1\n  fi\n\n  # Add TXT record.\n  _namecom_addtxt_json=\"{\\\"host\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"answer\\\":\\\"$txtvalue\\\",\\\"ttl\\\":\\\"300\\\"}\"\n  if _namecom_rest POST \"domains/$_domain/records\" \"$_namecom_addtxt_json\"; then\n    _retvalue=$(echo \"$response\" | _egrep_o \"\\\"$_sub_domain\\\"\")\n    if [ \"$_retvalue\" ]; then\n      _info \"Successfully added TXT record, ready for validation.\"\n      return 0\n    else\n      _err \"Unable to add the DNS record.\"\n      return 1\n    fi\n  fi\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_namecom_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Namecom_Username=\"${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}\"\n  Namecom_Token=\"${Namecom_Token:-$(_readaccountconf_mutable Namecom_Token)}\"\n  if ! _namecom_login; then\n    return 1\n  fi\n\n  # Find domain in domain list.\n  if ! _namecom_get_root \"$fulldomain\"; then\n    _err \"Unable to find domain specified.\"\n    return 1\n  fi\n\n  # Get the record id.\n  if _namecom_rest GET \"domains/$_domain/records\"; then\n    _record_id=$(echo \"$response\" | _egrep_o \"\\\"id\\\":[0-9]+,\\\"domainName\\\":\\\"$_domain\\\",\\\"host\\\":\\\"$_sub_domain\\\",\\\"fqdn\\\":\\\"$fulldomain.\\\",\\\"type\\\":\\\"TXT\\\",\\\"answer\\\":\\\"$txtvalue\\\"\" | cut -d \\\" -f 3 | _egrep_o [0-9]+)\n    _debug record_id \"$_record_id\"\n    if [ \"$_record_id\" ]; then\n      _info \"Successfully retrieved the record id for ACME challenge.\"\n    else\n      _err \"Unable to retrieve the record id.\"\n      return 1\n    fi\n  fi\n\n  # Remove the DNS record using record id.\n  if _namecom_rest DELETE \"domains/$_domain/records/$_record_id\"; then\n    _info \"Successfully removed the TXT record.\"\n    return 0\n  else\n    _err \"Unable to delete record id.\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n_namecom_rest() {\n  method=$1\n  param=$2\n  data=$3\n\n  export _H1=\"Authorization: Basic $_namecom_auth\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    response=\"$(_post \"$data\" \"$Namecom_API/$param\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$Namecom_API/$param\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $param\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n\n_namecom_login() {\n  # Auth string\n  # Name.com API v4 uses http basic auth to authenticate\n  # need to convert the token for http auth\n  _namecom_auth=$(printf \"%s:%s\" \"$Namecom_Username\" \"$Namecom_Token\" | _base64)\n\n  if _namecom_rest GET \"hello\"; then\n    retcode=$(echo \"$response\" | _egrep_o \"\\\"username\\\"\\:\\\"$Namecom_Username\\\"\")\n    if [ \"$retcode\" ]; then\n      _info \"Successfully logged in.\"\n    else\n      _err \"$response\"\n      _err \"Please add your ip to api whitelist\"\n      _err \"Logging in failed.\"\n      return 1\n    fi\n  fi\n}\n\n_namecom_get_root() {\n  domain=$1\n  i=2\n  p=1\n\n  if ! _namecom_rest GET \"domains\"; then\n    return 1\n  fi\n\n  # Need to exclude the last field (tld)\n  numfields=$(echo \"$domain\" | _egrep_o \"\\.\" | wc -l)\n  while [ \"$i\" -le \"$numfields\" ]; do\n    host=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug host \"$host\"\n    if [ -z \"$host\" ]; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"$host\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$host\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_namesilo.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_namesilo_info='NameSilo.com\nSite: NameSilo.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namesilo\nOptions:\n Namesilo_Key API Key\nAuthor: @meowthink\n'\n\n#Utilize API to finish dns-01 verifications.\n\nNamesilo_API=\"https://www.namesilo.com/api\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_namesilo_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$Namesilo_Key\" ]; then\n    Namesilo_Key=\"\"\n    _err \"API token for namesilo.com is missing.\"\n    _err \"Please specify that in your environment variable.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf Namesilo_Key \"$Namesilo_Key\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Unable to find domain specified.\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug txtvalue \"$txtvalue\"\n  if _namesilo_rest GET \"dnsAddRecord?version=1&type=xml&key=$Namesilo_Key&domain=$_domain&rrtype=TXT&rrhost=$_sub_domain&rrvalue=$txtvalue\"; then\n    retcode=$(printf \"%s\\n\" \"$response\" | _egrep_o \"<code>300\")\n    if [ \"$retcode\" ]; then\n      _info \"Successfully added TXT record, ready for validation.\"\n      return 0\n    else\n      _err \"Unable to add the DNS record.\"\n      return 1\n    fi\n  fi\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_namesilo_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Unable to find domain specified.\"\n    return 1\n  fi\n\n  # Get the record id.\n  if _namesilo_rest GET \"dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain\"; then\n    retcode=$(printf \"%s\\n\" \"$response\" | _egrep_o \"<code>300\")\n    if [ \"$retcode\" ]; then\n      _record_id=$(echo \"$response\" | _egrep_o \"<record_id>([^<]*)</record_id><type>TXT</type><host>$fulldomain</host>\" | _egrep_o \"<record_id>([^<]*)</record_id>\" | sed -r \"s/<record_id>([^<]*)<\\/record_id>/\\1/\" | tail -n 1)\n      _debug _record_id \"$_record_id\"\n      if [ \"$_record_id\" ]; then\n        _info \"Successfully retrieved the record id for ACME challenge.\"\n      else\n        _info \"Empty record id, it seems no such record.\"\n        return 0\n      fi\n    else\n      _err \"Unable to retrieve the record id.\"\n      return 1\n    fi\n  fi\n\n  # Remove the DNS record using record id.\n  if _namesilo_rest GET \"dnsDeleteRecord?version=1&type=xml&key=$Namesilo_Key&domain=$_domain&rrid=$_record_id\"; then\n    retcode=$(printf \"%s\\n\" \"$response\" | _egrep_o \"<code>300\")\n    if [ \"$retcode\" ]; then\n      _info \"Successfully removed the TXT record.\"\n      return 0\n    else\n      _err \"Unable to remove the DNS record.\"\n      return 1\n    fi\n  fi\n}\n\n####################  Private functions below ##################################\n\n# _acme-challenge.www.domain.com\n# returns\n#  _sub_domain=_acme-challenge.www\n#  _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n\n  if ! _namesilo_rest GET \"listDomains?version=1&type=xml&key=$Namesilo_Key\"; then\n    return 1\n  fi\n\n  # Need to exclude the last field (tld)\n  numfields=$(echo \"$domain\" | _egrep_o \"\\.\" | wc -l)\n  while [ \"$i\" -le \"$numfields\" ]; do\n    host=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug host \"$host\"\n    if [ -z \"$host\" ]; then\n      return 1\n    fi\n\n    if _contains \"$response\" \">$host</domain>\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$host\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_namesilo_rest() {\n  method=$1\n  param=$2\n  data=$3\n\n  if [ \"$method\" != \"GET\" ]; then\n    response=\"$(_post \"$data\" \"$Namesilo_API/$param\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$Namesilo_API/$param\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $param\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_nanelo.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nanelo_info='Nanelo.com\nSite: Nanelo.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nanelo\nOptions:\n NANELO_TOKEN API Token\nIssues: github.com/acmesh-official/acme.sh/issues/4519\n'\n\nNANELO_API=\"https://api.nanelo.com/v1/\"\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_nanelo_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NANELO_TOKEN=\"${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}\"\n  if [ -z \"$NANELO_TOKEN\" ]; then\n    NANELO_TOKEN=\"\"\n    _err \"You didn't configure a Nanelo API Key yet.\"\n    _err \"Please set NANELO_TOKEN and try again.\"\n    _err \"Login to Nanelo.com and go to Settings > API Keys to get a Key\"\n    return 1\n  fi\n  _saveaccountconf_mutable NANELO_TOKEN \"$NANELO_TOKEN\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding TXT record to ${fulldomain}\"\n  response=\"$(_post \"\" \"$NANELO_API$NANELO_TOKEN/dns/addrecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}\" \"\" \"\" \"\")\"\n  if _contains \"${response}\" 'success'; then\n    return 0\n  fi\n  _err \"Could not create resource record, please check the logs\"\n  _err \"${response}\"\n  return 1\n}\n\ndns_nanelo_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NANELO_TOKEN=\"${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}\"\n  if [ -z \"$NANELO_TOKEN\" ]; then\n    NANELO_TOKEN=\"\"\n    _err \"You didn't configure a Nanelo API Key yet.\"\n    _err \"Please set NANELO_TOKEN and try again.\"\n    _err \"Login to Nanelo.com and go to Settings > API Keys to get a Key\"\n    return 1\n  fi\n  _saveaccountconf_mutable NANELO_TOKEN \"$NANELO_TOKEN\"\n\n  _debug \"First, let's detect the root zone:\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Deleting resource record $fulldomain\"\n  response=\"$(_post \"\" \"$NANELO_API$NANELO_TOKEN/dns/deleterecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}\" \"\" \"\" \"\")\"\n  if _contains \"${response}\" 'success'; then\n    return 0\n  fi\n  _err \"Could not delete resource record, please check the logs\"\n  _err \"${response}\"\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n\n_get_root() {\n  fulldomain=$1\n\n  # Fetch all zones from Nanelo\n  response=\"$(_get \"$NANELO_API$NANELO_TOKEN/dns/getzones\")\" || return 1\n\n  # Extract \"zones\" array into space-separated list\n  zones=$(echo \"$response\" |\n    tr -d ' \\n' |\n    sed -n 's/.*\"zones\":\\[\\([^]]*\\)\\].*/\\1/p' |\n    tr -d '\"' |\n    tr , ' ')\n  _debug zones \"$zones\"\n\n  bestzone=\"\"\n  for z in $zones; do\n    case \"$fulldomain\" in\n    *.\"$z\" | \"$z\")\n      if [ ${#z} -gt ${#bestzone} ]; then\n        bestzone=$z\n      fi\n      ;;\n    esac\n  done\n\n  if [ -z \"$bestzone\" ]; then\n    _err \"No matching zone found for $fulldomain\"\n    return 1\n  fi\n\n  _domain=\"$bestzone\"\n  _sub_domain=$(printf \"%s\" \"$fulldomain\" | sed \"s/\\\\.$_domain\\$//\")\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_nederhost.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nederhost_info='NederHost.nl\nSite: NederHost.nl\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nederhost\nOptions:\n NederHost_Key API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2089\n'\n\nNederHost_Api=\"https://api.nederhost.nl/dns/v1\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_nederhost_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NederHost_Key=\"${NederHost_Key:-$(_readaccountconf_mutable NederHost_Key)}\"\n  if [ -z \"$NederHost_Key\" ]; then\n    NederHost_Key=\"\"\n    _err \"You didn't specify a NederHost api key.\"\n    _err \"You can get yours from https://www.nederhost.nl/mijn_nederhost\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable NederHost_Key \"$NederHost_Key\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _nederhost_rest PATCH \"zones/$_domain/records/$fulldomain/TXT\" \"[{\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":60}]\"; then\n    if _contains \"$response\" \"$fulldomain\"; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_nederhost_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NederHost_Key=\"${NederHost_Key:-$(_readaccountconf_mutable NederHost_Key)}\"\n  if [ -z \"$NederHost_Key\" ]; then\n    NederHost_Key=\"\"\n    _err \"You didn't specify a NederHost api key.\"\n    _err \"You can get yours from https://www.nederhost.nl/mijn_nederhost\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Removing txt record\"\n  _nederhost_rest DELETE \"zones/${_domain}/records/$fulldomain/TXT?content=$txtvalue\"\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    _domain=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n    _debug _domain \"$_domain\"\n    if [ -z \"$_domain\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _nederhost_rest GET \"zones/${_domain}\"; then\n      if [ \"${_code}\" = \"204\" ]; then\n        return 0\n      fi\n    else\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_nederhost_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Authorization: Bearer $NederHost_Key\"\n  export _H2=\"Content-Type: application/json\"\n\n  _debug data \"$data\"\n  response=\"$(_post \"$data\" \"$NederHost_Api/$ep\" \"\" \"$m\")\"\n\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  _debug \"http response code $_code\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_neodigit.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_neodigit_info='Neodigit.net\nSite: Neodigit.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_neodigit\nOptions:\n NEODIGIT_API_TOKEN API Token\nAuthor: Adrian Almenar\n'\n\nNEODIGIT_API_URL=\"https://api.neodigit.net/v1\"\n#\n########  Public functions #####################\n\n# Usage: dns_myapi_add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_neodigit_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NEODIGIT_API_TOKEN=\"${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}\"\n  if [ -z \"$NEODIGIT_API_TOKEN\" ]; then\n    NEODIGIT_API_TOKEN=\"\"\n    _err \"You haven't specified a Token api key.\"\n    _err \"Please create the key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable NEODIGIT_API_TOKEN \"$NEODIGIT_API_TOKEN\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _debug domain \"$_domain\"\n  _debug sub_domain \"$_sub_domain\"\n\n  _debug \"Getting txt records\"\n  _neo_rest GET \"dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain\"\n\n  _debug _code \"$_code\"\n\n  if [ \"$_code\" != \"200\" ]; then\n    _err \"error retrieving data!\"\n    return 1\n  fi\n\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  _debug domain \"$_domain\"\n  _debug sub_domain \"$_sub_domain\"\n\n  _info \"Adding record\"\n  if _neo_rest POST \"dns/zones/$_domain_id/records\" \"{\\\"record\\\":{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":60}}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"$_sub_domain\" >/dev/null; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#fulldomain txtvalue\ndns_neodigit_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NEODIGIT_API_TOKEN=\"${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}\"\n  if [ -z \"$NEODIGIT_API_TOKEN\" ]; then\n    NEODIGIT_API_TOKEN=\"\"\n    _err \"You haven't specified a Token api key.\"\n    _err \"Please create the key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable NEODIGIT_API_TOKEN \"$NEODIGIT_API_TOKEN\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _neo_rest GET \"dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain&content=$txtvalue\"\n\n  if [ \"$_code\" != \"200\" ]; then\n    _err \"error retrieving data!\"\n    return 1\n  fi\n\n  record_id=$(echo \"$response\" | _egrep_o \"\\\"id\\\":\\s*[0-9]+\" | _head_n 1 | cut -d: -f2 | cut -d, -f1)\n  _debug \"record_id\" \"$record_id\"\n  if [ -z \"$record_id\" ]; then\n    _err \"Can not get record id to remove.\"\n    return 1\n  fi\n  if ! _neo_rest DELETE \"dns/zones/$_domain_id/records/$record_id\"; then\n    _err \"Delete record error.\"\n    return 1\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=dasfdsafsadg5ythd\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _neo_rest GET \"dns/zones?name=$h\"; then\n      return 1\n    fi\n\n    _debug p \"$p\"\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"\\\"id\\\":\\s*[0-9]+\" | _head_n 1 | cut -d: -f2 | cut -d, -f1)\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_neo_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"X-TCPanel-Token: $NEODIGIT_API_TOKEN\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$NEODIGIT_API_URL/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$NEODIGIT_API_URL/$ep\")\"\n  fi\n\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_netcup.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_netcup_info='netcup.eu\nDomains: netcup.de netcup.net\nSite: netcup.eu/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_netcup\nOptions:\n NC_Apikey API Key\n NC_Apipw API Password\n NC_CID Customer Number\nAuthor: linux-insideDE\n'\n\nNC_Apikey=\"${NC_Apikey:-$(_readaccountconf_mutable NC_Apikey)}\"\nNC_Apipw=\"${NC_Apipw:-$(_readaccountconf_mutable NC_Apipw)}\"\nNC_CID=\"${NC_CID:-$(_readaccountconf_mutable NC_CID)}\"\nend=\"https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON\"\nclient=\"\"\n\ndns_netcup_add() {\n  _debug NC_Apikey \"$NC_Apikey\"\n  _login\n  if [ \"$NC_Apikey\" = \"\" ] || [ \"$NC_Apipw\" = \"\" ] || [ \"$NC_CID\" = \"\" ]; then\n    _err \"No Credentials given\"\n    return 1\n  fi\n  _saveaccountconf_mutable NC_Apikey \"$NC_Apikey\"\n  _saveaccountconf_mutable NC_Apipw \"$NC_Apipw\"\n  _saveaccountconf_mutable NC_CID \"$NC_CID\"\n  fulldomain=$1\n  txtvalue=$2\n  domain=\"\"\n  exit=$(echo \"$fulldomain\" | tr -dc '.' | wc -c)\n  exit=$(_math \"$exit\" + 1)\n  i=$exit\n\n  while\n    [ \"$exit\" -gt 0 ]\n  do\n    tmp=$(echo \"$fulldomain\" | cut -d'.' -f\"$exit\")\n    if [ \"$(_math \"$i\" - \"$exit\")\" -eq 0 ]; then\n      domain=\"$tmp\"\n    else\n      domain=\"$tmp.$domain\"\n    fi\n    if [ \"$(_math \"$i\" - \"$exit\")\" -ge 1 ]; then\n      msg=$(_post \"{\\\"action\\\": \\\"updateDnsRecords\\\", \\\"param\\\": {\\\"apikey\\\": \\\"$NC_Apikey\\\", \\\"apisessionid\\\": \\\"$sid\\\", \\\"customernumber\\\": \\\"$NC_CID\\\",\\\"clientrequestid\\\": \\\"$client\\\" , \\\"domainname\\\": \\\"$domain\\\", \\\"dnsrecordset\\\": { \\\"dnsrecords\\\": [ {\\\"id\\\": \\\"\\\", \\\"hostname\\\": \\\"$fulldomain.\\\", \\\"type\\\": \\\"TXT\\\", \\\"priority\\\": \\\"\\\", \\\"destination\\\": \\\"$txtvalue\\\", \\\"deleterecord\\\": \\\"false\\\", \\\"state\\\": \\\"yes\\\"} ]}}}\" \"$end\" \"\" \"POST\")\n      _debug \"$msg\"\n      if [ \"$(_getfield \"$msg\" \"5\" | sed 's/\"statuscode\"://g')\" != 5028 ]; then\n        if [ \"$(_getfield \"$msg\" \"4\" | sed s/\\\"status\\\":\\\"//g | sed s/\\\"//g)\" != \"success\" ]; then\n          _err \"$msg\"\n          return 1\n        else\n          break\n        fi\n      fi\n    fi\n    exit=$(_math \"$exit\" - 1)\n  done\n  logout\n}\n\ndns_netcup_rm() {\n  _login\n  fulldomain=$1\n  txtvalue=$2\n\n  domain=\"\"\n  exit=$(echo \"$fulldomain\" | tr -dc '.' | wc -c)\n  exit=$(_math \"$exit\" + 1)\n  i=$exit\n  rec=\"\"\n\n  while\n    [ \"$exit\" -gt 0 ]\n  do\n    tmp=$(echo \"$fulldomain\" | cut -d'.' -f\"$exit\")\n    if [ \"$(_math \"$i\" - \"$exit\")\" -eq 0 ]; then\n      domain=\"$tmp\"\n    else\n      domain=\"$tmp.$domain\"\n    fi\n    if [ \"$(_math \"$i\" - \"$exit\")\" -ge 1 ]; then\n      msg=$(_post \"{\\\"action\\\": \\\"infoDnsRecords\\\", \\\"param\\\": {\\\"apikey\\\": \\\"$NC_Apikey\\\", \\\"apisessionid\\\": \\\"$sid\\\", \\\"customernumber\\\": \\\"$NC_CID\\\", \\\"domainname\\\": \\\"$domain\\\"}}\" \"$end\" \"\" \"POST\")\n      rec=$(echo \"$msg\" | sed 's/\\[//g' | sed 's/\\]//g' | sed 's/{\\\"serverrequestid\\\".*\\\"dnsrecords\\\"://g' | sed 's/},{/};{/g' | sed 's/{//g' | sed 's/}//g')\n      _debug \"$msg\"\n      if [ \"$(_getfield \"$msg\" \"5\" | sed 's/\"statuscode\"://g')\" != 5028 ]; then\n        if [ \"$(_getfield \"$msg\" \"4\" | sed s/\\\"status\\\":\\\"//g | sed s/\\\"//g)\" != \"success\" ]; then\n          _err \"$msg\"\n          return 1\n        else\n          break\n        fi\n      fi\n    fi\n    exit=$(_math \"$exit\" - 1)\n  done\n\n  ida=0000\n  idv=0001\n  ids=0000000000\n  i=1\n  while\n    [ \"$i\" -ne 0 ]\n  do\n    specrec=$(_getfield \"$rec\" \"$i\" \";\")\n    idv=\"$ida\"\n    ida=$(_getfield \"$specrec\" \"1\" \",\" | sed 's/\\\"id\\\":\\\"//g' | sed 's/\\\"//g')\n    txtv=$(_getfield \"$specrec\" \"5\" \",\" | sed 's/\\\"destination\\\":\\\"//g' | sed 's/\\\"//g')\n    i=$(_math \"$i\" + 1)\n    if [ \"$txtvalue\" = \"$txtv\" ]; then\n      i=0\n      ids=\"$ida\"\n    fi\n    if [ \"$ida\" = \"$idv\" ]; then\n      i=0\n    fi\n  done\n  msg=$(_post \"{\\\"action\\\": \\\"updateDnsRecords\\\", \\\"param\\\": {\\\"apikey\\\": \\\"$NC_Apikey\\\", \\\"apisessionid\\\": \\\"$sid\\\", \\\"customernumber\\\": \\\"$NC_CID\\\",\\\"clientrequestid\\\": \\\"$client\\\" , \\\"domainname\\\": \\\"$domain\\\", \\\"dnsrecordset\\\": { \\\"dnsrecords\\\": [ {\\\"id\\\": \\\"$ids\\\", \\\"hostname\\\": \\\"$fulldomain.\\\", \\\"type\\\": \\\"TXT\\\", \\\"priority\\\": \\\"\\\", \\\"destination\\\": \\\"$txtvalue\\\", \\\"deleterecord\\\": \\\"TRUE\\\", \\\"state\\\": \\\"yes\\\"} ]}}}\" \"$end\" \"\" \"POST\")\n  _debug \"$msg\"\n  if [ \"$(_getfield \"$msg\" \"4\" | sed s/\\\"status\\\":\\\"//g | sed s/\\\"//g)\" != \"success\" ]; then\n    _err \"$msg\"\n    return 1\n  fi\n  logout\n}\n\n_login() {\n  tmp=$(_post \"{\\\"action\\\": \\\"login\\\", \\\"param\\\": {\\\"apikey\\\": \\\"$NC_Apikey\\\", \\\"apipassword\\\": \\\"$NC_Apipw\\\", \\\"customernumber\\\": \\\"$NC_CID\\\"}}\" \"$end\" \"\" \"POST\")\n  sid=$(echo \"$tmp\" | tr '{}' '\\n' | grep apisessionid | cut -d '\"' -f 4)\n  _debug \"$tmp\"\n  if [ \"$(_getfield \"$tmp\" \"4\" | sed s/\\\"status\\\":\\\"//g | sed s/\\\"//g)\" != \"success\" ]; then\n    _err \"$tmp\"\n    return 1\n  fi\n}\nlogout() {\n  tmp=$(_post \"{\\\"action\\\": \\\"logout\\\", \\\"param\\\": {\\\"apikey\\\": \\\"$NC_Apikey\\\", \\\"apisessionid\\\": \\\"$sid\\\", \\\"customernumber\\\": \\\"$NC_CID\\\"}}\" \"$end\" \"\" \"POST\")\n  _debug \"$tmp\"\n  if [ \"$(_getfield \"$tmp\" \"4\" | sed s/\\\"status\\\":\\\"//g | sed s/\\\"//g)\" != \"success\" ]; then\n    _err \"$tmp\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_netlify.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_netlify_info='Netlify.com\nSite: Netlify.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_netlify\nOptions:\n NETLIFY_ACCESS_TOKEN API Token\nIssues: github.com/acmesh-official/acme.sh/issues/3088\n'\n\nNETLIFY_HOST=\"api.netlify.com/api/v1/\"\nNETLIFY_URL=\"https://$NETLIFY_HOST\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_netlify_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NETLIFY_ACCESS_TOKEN=\"${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}\"\n\n  if [ -z \"$NETLIFY_ACCESS_TOKEN\" ]; then\n    NETLIFY_ACCESS_TOKEN=\"\"\n    _err \"Please specify your Netlify Access Token and try again.\"\n    return 1\n  else\n    _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN \"$NETLIFY_ACCESS_TOKEN\"\n  fi\n\n  _info \"Using Netlify\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  dnsRecordURI=\"dns_zones/$_domain_id/dns_records\"\n\n  body=\"{\\\"type\\\":\\\"TXT\\\", \\\"hostname\\\":\\\"$_sub_domain\\\", \\\"value\\\":\\\"$txtvalue\\\", \\\"ttl\\\":\\\"10\\\"}\"\n\n  _netlify_rest POST \"$dnsRecordURI\" \"$body\" \"$NETLIFY_ACCESS_TOKEN\"\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  if [ \"$_code\" = \"200\" ] || [ \"$_code\" = '201' ]; then\n    _info \"validation value added\"\n    return 0\n  else\n    _err \"error adding validation value ($_code)\"\n    return 1\n  fi\n\n}\n\n#Usage: dns_myapi_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n#Remove the txt record after validation.\ndns_netlify_rm() {\n  _info \"Using Netlify\"\n  txtdomain=\"$1\"\n  txt=\"$2\"\n  _debug txtdomain \"$txtdomain\"\n  _debug txt \"$txt\"\n\n  NETLIFY_ACCESS_TOKEN=\"${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}\"\n\n  if ! _get_root \"$txtdomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  dnsRecordURI=\"dns_zones/$_domain_id/dns_records\"\n\n  _netlify_rest GET \"$dnsRecordURI\" \"\" \"$NETLIFY_ACCESS_TOKEN\"\n\n  _record_id=$(echo \"$response\" | _egrep_o \"\\\"type\\\":\\\"TXT\\\",[^\\}]*\\\"value\\\":\\\"$txt\\\"\" | head -n 1 | _egrep_o \"\\\"id\\\":\\\"[^\\\"\\}]*\\\"\" | cut -d : -f 2 | tr -d \\\")\n  _debug _record_id \"$_record_id\"\n  if [ \"$_record_id\" ]; then\n    _netlify_rest DELETE \"$dnsRecordURI/$_record_id\" \"\" \"$NETLIFY_ACCESS_TOKEN\"\n    _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n    if [ \"$_code\" = \"200\" ] || [ \"$_code\" = '204' ]; then\n      _info \"validation value removed\"\n      return 0\n    else\n      _err \"error removing validation value ($_code)\"\n      return 1\n    fi\n  fi\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  accesstoken=$2\n  i=1\n  p=1\n\n  _netlify_rest GET \"dns_zones\" \"\" \"$accesstoken\"\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug2 \"Checking domain: $h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      _err \"Invalid domain\"\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"\\\"[^\\\"]*\\\",\\\"name\\\":\\\"$h\\\"\" | cut -d , -f 1 | tr -d \\\")\n      if [ \"$_domain_id\" ]; then\n        if [ \"$i\" = 1 ]; then\n          #create the record at the domain apex (@) if only the domain name was provided as --domain-alias\n          _sub_domain=\"@\"\n        else\n          _sub_domain=$(echo \"$domain\" | cut -d . -f 1-\"$p\")\n        fi\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_netlify_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  token_trimmed=$(echo \"$NETLIFY_ACCESS_TOKEN\" | tr -d '\"')\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $token_trimmed\"\n\n  : >\"$HTTP_HEADER\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$NETLIFY_URL$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$NETLIFY_URL$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_nic.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nic_info='nic.ru\nSite: nic.ru\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nic\nOptions:\n NIC_ClientID Client ID\n NIC_ClientSecret Client Secret\n NIC_Username Username\n NIC_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2547\n'\n\nNIC_Api=\"https://api.nic.ru\"\n\ndns_nic_add() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n\n  if ! _nic_get_authtoken save; then\n    _err \"get NIC auth token failed\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _service \"$_service\"\n\n  _info \"Adding record\"\n  if ! _nic_rest PUT \"services/$_service/zones/$_domain/records\" \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" ?><request><rr-list><rr><name>$_sub_domain</name><type>TXT</type><txt><string>$txtvalue</string></txt></rr></rr-list></request>\"; then\n    _err \"Add TXT record error\"\n    return 1\n  fi\n\n  if ! _nic_rest POST \"services/$_service/zones/$_domain/commit\" \"\"; then\n    return 1\n  fi\n  _info \"Added, OK\"\n}\n\ndns_nic_rm() {\n  fulldomain=\"${1}\"\n  txtvalue=\"${2}\"\n\n  if ! _nic_get_authtoken; then\n    _err \"get NIC auth token failed\"\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _service \"$_service\"\n\n  if ! _nic_rest GET \"services/$_service/zones/$_domain/records\"; then\n    _err \"Get records error\"\n    return 1\n  fi\n\n  _domain_id=$(printf \"%s\" \"$response\" | grep \"$_sub_domain\" | grep -- \"$txtvalue\" | sed -r \"s/.*<rr id=\\\"(.*)\\\".*/\\1/g\")\n\n  if ! _nic_rest DELETE \"services/$_service/zones/$_domain/records/$_domain_id\"; then\n    _err \"Delete record error\"\n    return 1\n  fi\n\n  if ! _nic_rest POST \"services/$_service/zones/$_domain/commit\" \"\"; then\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n#_nic_get_auth_elements [need2save]\n_nic_get_auth_elements() {\n  _need2save=$1\n\n  NIC_ClientID=\"${NIC_ClientID:-$(_readaccountconf_mutable NIC_ClientID)}\"\n  NIC_ClientSecret=\"${NIC_ClientSecret:-$(_readaccountconf_mutable NIC_ClientSecret)}\"\n  NIC_Username=\"${NIC_Username:-$(_readaccountconf_mutable NIC_Username)}\"\n  NIC_Password=\"${NIC_Password:-$(_readaccountconf_mutable NIC_Password)}\"\n\n  ## for backward compatibility\n  if [ -z \"$NIC_ClientID\" ] || [ -z \"$NIC_ClientSecret\" ]; then\n    NIC_Token=\"${NIC_Token:-$(_readaccountconf_mutable NIC_Token)}\"\n    _debug NIC_Token \"$NIC_Token\"\n    if [ -n \"$NIC_Token\" ]; then\n      _two_values=\"$(echo \"${NIC_Token}\" | _dbase64)\"\n      _debug _two_values \"$_two_values\"\n      NIC_ClientID=$(echo \"$_two_values\" | cut -d':' -f1)\n      NIC_ClientSecret=$(echo \"$_two_values\" | cut -d':' -f2-)\n      _debug restored_NIC_ClientID \"$NIC_ClientID\"\n      _debug restored_NIC_ClientSecret \"$NIC_ClientSecret\"\n    fi\n  fi\n\n  if [ -z \"$NIC_ClientID\" ] || [ -z \"$NIC_ClientSecret\" ] || [ -z \"$NIC_Username\" ] || [ -z \"$NIC_Password\" ]; then\n    NIC_ClientID=\"\"\n    NIC_ClientSecret=\"\"\n    NIC_Username=\"\"\n    NIC_Password=\"\"\n    _err \"You must export variables: NIC_ClientID, NIC_ClientSecret, NIC_Username and NIC_Password\"\n    return 1\n  fi\n\n  if [ \"$_need2save\" ]; then\n    _saveaccountconf_mutable NIC_ClientID \"$NIC_ClientID\"\n    _saveaccountconf_mutable NIC_ClientSecret \"$NIC_ClientSecret\"\n    _saveaccountconf_mutable NIC_Username \"$NIC_Username\"\n    _saveaccountconf_mutable NIC_Password \"$NIC_Password\"\n  fi\n\n  NIC_BasicAuth=$(printf \"%s:%s\" \"${NIC_ClientID}\" \"${NIC_ClientSecret}\" | _base64)\n  _debug NIC_BasicAuth \"$NIC_BasicAuth\"\n\n}\n\n#_nic_get_authtoken [need2save]\n_nic_get_authtoken() {\n  _need2save=$1\n\n  if ! _nic_get_auth_elements \"$_need2save\"; then\n    return 1\n  fi\n\n  _info \"Getting NIC auth token\"\n\n  export _H1=\"Authorization: Basic ${NIC_BasicAuth}\"\n  export _H2=\"Content-Type: application/x-www-form-urlencoded\"\n\n  res=$(_post \"grant_type=password&username=${NIC_Username}&password=${NIC_Password}&scope=%28GET%7CPUT%7CPOST%7CDELETE%29%3A%2Fdns-master%2F.%2B\" \"$NIC_Api/oauth/token\" \"\" \"POST\")\n  if _contains \"$res\" \"access_token\"; then\n    _auth_token=$(printf \"%s\" \"$res\" | cut -d , -f2 | tr -d \"\\\"\" | sed \"s/access_token://\")\n    _info \"Token received\"\n    _debug _auth_token \"$_auth_token\"\n    return 0\n  fi\n  return 1\n}\n\n_get_root() {\n  domain=\"$1\"\n  i=1\n  p=1\n\n  if ! _nic_rest GET \"zones\"; then\n    return 1\n  fi\n\n  _all_domains=$(printf \"%s\" \"$response\" | grep \"idn-name\" | sed -r \"s/.*idn-name=\\\"(.*)\\\" name=.*/\\1/g\")\n  _debug2 _all_domains \"$_all_domains\"\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    if _contains \"$_all_domains\" \"^$h$\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      _service=$(printf \"%s\" \"$response\" | grep -m 1 \"idn-name=\\\"$_domain\\\"\" | sed -r \"s/.*service=\\\"(.*)\\\".*$/\\1/\")\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_nic_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/xml\"\n  export _H2=\"Authorization: Bearer $_auth_token\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=$(_post \"$data\" \"$NIC_Api/dns-master/$ep\" \"\" \"$m\")\n  else\n    response=$(_get \"$NIC_Api/dns-master/$ep\")\n  fi\n\n  if _contains \"$response\" \"<errors>\"; then\n    error=$(printf \"%s\" \"$response\" | grep \"error code\" | sed -r \"s/.*<error code=.*>(.*)<\\/error>/\\1/g\")\n    _err \"Error: $error\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"<status>success</status>\"; then\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_njalla.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_njalla_info='Njalla\nSite: Njal.la\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_njalla\nOptions:\n NJALLA_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/2913\n'\n\nNJALLA_Api=\"https://njal.la/api/1/\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_njalla_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NJALLA_Token=\"${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}\"\n\n  if [ \"$NJALLA_Token\" ]; then\n    _saveaccountconf_mutable NJALLA_Token \"$NJALLA_Token\"\n  else\n    NJALLA_Token=\"\"\n    _err \"You didn't specify a Njalla api token yet.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so\n  # we can not use updating anymore.\n  #  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  #  _debug count \"$count\"\n  #  if [ \"$count\" = \"0\" ]; then\n  _info \"Adding record\"\n  if _njalla_rest \"{\\\"method\\\":\\\"add-record\\\",\\\"params\\\":{\\\"domain\\\":\\\"$_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_njalla_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  NJALLA_Token=\"${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}\"\n\n  if [ \"$NJALLA_Token\" ]; then\n    _saveaccountconf_mutable NJALLA_Token \"$NJALLA_Token\"\n  else\n    NJALLA_Token=\"\"\n    _err \"You didn't specify a Njalla api token yet.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting records for domain\"\n  if ! _njalla_rest \"{\\\"method\\\":\\\"list-records\\\",\\\"params\\\":{\\\"domain\\\":\\\"${_domain}\\\"}}\"; then\n    return 1\n  fi\n\n  if ! echo \"$response\" | tr -d \" \" | grep \"\\\"id\\\":\" >/dev/null; then\n    _err \"Error: $response\"\n    return 1\n  fi\n\n  records=$(echo \"$response\" | _egrep_o \"\\\"records\\\":\\s?\\[(.*)\\]\\}\" | _egrep_o \"\\[.*\\]\" | _egrep_o \"\\{[^\\{\\}]*\\\"id\\\":[^\\{\\}]*\\}\")\n  count=$(echo \"$records\" | wc -l)\n  _debug count \"$count\"\n\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    echo \"$records\" | while read -r record; do\n      record_name=$(echo \"$record\" | _egrep_o \"\\\"name\\\":\\s?\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \" \" | tr -d \\\")\n      record_content=$(echo \"$record\" | _egrep_o \"\\\"content\\\":\\s?\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \" \" | tr -d \\\")\n      record_id=$(echo \"$record\" | _egrep_o \"\\\"id\\\":\\s?[0-9]+\" | cut -d : -f 2 | tr -d \" \" | tr -d \\\")\n      if [ \"$_sub_domain\" = \"$record_name\" ]; then\n        if [ \"$txtvalue\" = \"$record_content\" ]; then\n          _debug \"record_id\" \"$record_id\"\n          if ! _njalla_rest \"{\\\"method\\\":\\\"remove-record\\\",\\\"params\\\":{\\\"domain\\\":\\\"${_domain}\\\",\\\"id\\\":${record_id}}}\"; then\n            _err \"Delete record error.\"\n            return 1\n          fi\n          echo \"$response\" | tr -d \" \" | grep \"\\\"result\\\"\" >/dev/null\n        fi\n      fi\n    done\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _njalla_rest \"{\\\"method\\\":\\\"get-domain\\\",\\\"params\\\":{\\\"domain\\\":\\\"${h}\\\"}}\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"$h\\\"\"; then\n      _domain_returned=$(echo \"$response\" | _egrep_o \"\\{\\\"name\\\": *\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\n      if [ \"$_domain_returned\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_njalla_rest() {\n  data=\"$1\"\n\n  token_trimmed=$(echo \"$NJALLA_Token\" | tr -d '\"')\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Authorization: Njalla $token_trimmed\"\n\n  _debug data \"$data\"\n  response=\"$(_post \"$data\" \"$NJALLA_Api\" \"\" \"POST\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $data\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_nm.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nm_info='NameMaster.de\nSite: NameMaster.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nm\nOptions:\n NM_user API Username\n NM_sha256 API Password as SHA256 hash\nAuthor: Thilo Gass <thilo.gass@gmail.com>\n'\n\n#-- dns_nm_add() - Add TXT record --------------------------------------\n# Usage: dns_nm_add _acme-challenge.subdomain.domain.com \"XyZ123...\"\n\nnamemaster_api=\"https://namemaster.de/api/api.php\"\n\ndns_nm_add() {\n  fulldomain=$1\n  txt_value=$2\n  _info \"Using DNS-01 namemaster hook\"\n\n  NM_user=\"${NM_user:-$(_readaccountconf_mutable NM_user)}\"\n  NM_sha256=\"${NM_sha256:-$(_readaccountconf_mutable NM_sha256)}\"\n  if [ -z \"$NM_user\" ] || [ -z \"$NM_sha256\" ]; then\n    NM_user=\"\"\n    NM_sha256=\"\"\n    _err \"No auth details provided. Please set user credentials using the \\$NM_user and \\$NM_sha256 environment variables.\"\n    return 1\n  fi\n  #save the api user and sha256 password to the account conf file.\n  _debug \"Save user and hash\"\n  _saveaccountconf_mutable NM_user \"$NM_user\"\n  _saveaccountconf_mutable NM_sha256 \"$NM_sha256\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\" \"$fulldomain\"\n    return 1\n  fi\n\n  _info \"die Zone lautet:\" \"$zone\"\n\n  get=\"$namemaster_api?User=$NM_user&Password=$NM_sha256&Antwort=csv&Typ=ACME&zone=$zone&hostname=$fulldomain&TXT=$txt_value&Action=Auto&Lifetime=3600\"\n\n  if ! erg=\"$(_get \"$get\")\"; then\n    _err \"error Adding $fulldomain TXT: $txt_value\"\n    return 1\n  fi\n\n  if _contains \"$erg\" \"Success\"; then\n    _info \"Success, TXT Added, OK\"\n  else\n    _err \"error Adding $fulldomain TXT: $txt_value erg: $erg\"\n    return 1\n  fi\n\n  _debug \"ok Auto $fulldomain TXT: $txt_value erg: $erg\"\n  return 0\n}\n\ndns_nm_rm() {\n\n  fulldomain=$1\n  txtvalue=$2\n  _info \"TXT enrty in $fulldomain is deleted automatically\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n}\n\n_get_root() {\n\n  domain=$1\n\n  get=\"$namemaster_api?User=$NM_user&Password=$NM_sha256&Typ=acme&hostname=$domain&Action=getzone&antwort=csv\"\n\n  if ! zone=\"$(_get \"$get\")\"; then\n    _err \"error getting Zone\"\n    return 1\n  else\n    if _contains \"$zone\" \"hostname not found\"; then\n      return 1\n    fi\n  fi\n\n}\n"
  },
  {
    "path": "dnsapi/dns_nsd.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nsd_info='NLnetLabs NSD Server\nSite: github.com/NLnetLabs/nsd\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#nsd\nOptions:\n Nsd_ZoneFile Zone File path. E.g. \"/etc/nsd/zones/example.com.zone\"\n Nsd_Command Command. E.g. \"sudo nsd-control reload\"\nIssues: github.com/acmesh-official/acme.sh/issues/2245\n'\n\n# args: fulldomain txtvalue\ndns_nsd_add() {\n  fulldomain=$1\n  txtvalue=$2\n  ttlvalue=300\n\n  Nsd_ZoneFile=\"${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}\"\n  Nsd_Command=\"${Nsd_Command:-$(_readdomainconf Nsd_Command)}\"\n\n  # Arg checks\n  if [ -z \"$Nsd_ZoneFile\" ] || [ -z \"$Nsd_Command\" ]; then\n    Nsd_ZoneFile=\"\"\n    Nsd_Command=\"\"\n    _err \"Specify ENV vars Nsd_ZoneFile and Nsd_Command\"\n    return 1\n  fi\n\n  if [ ! -f \"$Nsd_ZoneFile\" ]; then\n    Nsd_ZoneFile=\"\"\n    Nsd_Command=\"\"\n    _err \"No such file: $Nsd_ZoneFile\"\n    return 1\n  fi\n\n  _savedomainconf Nsd_ZoneFile \"$Nsd_ZoneFile\"\n  _savedomainconf Nsd_Command \"$Nsd_Command\"\n\n  echo \"$fulldomain. $ttlvalue IN TXT \\\"$txtvalue\\\"\" >>\"$Nsd_ZoneFile\"\n  _info \"Added TXT record for $fulldomain\"\n  _debug \"Running $Nsd_Command\"\n  if eval \"$Nsd_Command\"; then\n    _info \"Successfully updated the zone\"\n    return 0\n  else\n    _err \"Problem updating the zone\"\n    return 1\n  fi\n}\n\n# args: fulldomain txtvalue\ndns_nsd_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  ttlvalue=300\n\n  Nsd_ZoneFile=\"${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}\"\n  Nsd_Command=\"${Nsd_Command:-$(_readdomainconf Nsd_Command)}\"\n\n  _sed_i \"/$fulldomain. $ttlvalue IN TXT \\\"$txtvalue\\\"/d\" \"$Nsd_ZoneFile\"\n  _info \"Removed TXT record for $fulldomain\"\n  _debug \"Running $Nsd_Command\"\n  if eval \"$Nsd_Command\"; then\n    _info \"Successfully reloaded NSD \"\n    return 0\n  else\n    _err \"Problem reloading NSD\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_nsone.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nsone_info='ns1.com\nDomains: ns1.net\nSite: ns1.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nsone\nOptions:\n NS1_Key API Key\nAuthor: <dev@1e.ca>\n'\n\nNS1_Api=\"https://api.nsone.net/v1\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_nsone_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$NS1_Key\" ]; then\n    NS1_Key=\"\"\n    _err \"You didn't specify nsone dns api key yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf NS1_Key \"$NS1_Key\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _nsone_rest GET \"zones/${_domain}\"\n\n  if ! _contains \"$response\" \"\\\"records\\\":\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"domain\\\":\\\"$fulldomain\\\",[^{]*\\\"type\\\":\\\"TXT\\\"\" | wc -l | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Adding record\"\n\n    if _nsone_rest PUT \"zones/$_domain/$fulldomain/TXT\" \"{\\\"answers\\\":[{\\\"answer\\\":[\\\"$txtvalue\\\"]}],\\\"type\\\":\\\"TXT\\\",\\\"domain\\\":\\\"$fulldomain\\\",\\\"zone\\\":\\\"$_domain\\\",\\\"ttl\\\":0}\"; then\n      if _contains \"$response\" \"$fulldomain\"; then\n        _info \"Added\"\n        #todo: check if the record takes effect\n        return 0\n      else\n        _err \"Add txt record error.\"\n        return 1\n      fi\n    fi\n    _err \"Add txt record error.\"\n  else\n    _info \"Updating record\"\n    prev_txt=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"domain\\\":\\\"$fulldomain\\\",\\\"short_answers\\\":\\[\\\"[^,]*\\]\" | _head_n 1 | cut -d: -f3 | cut -d, -f1)\n    _debug \"prev_txt\" \"$prev_txt\"\n\n    _nsone_rest POST \"zones/$_domain/$fulldomain/TXT\" \"{\\\"answers\\\": [{\\\"answer\\\": [\\\"$txtvalue\\\"]},{\\\"answer\\\": $prev_txt}],\\\"type\\\": \\\"TXT\\\",\\\"domain\\\":\\\"$fulldomain\\\",\\\"zone\\\": \\\"$_domain\\\",\\\"ttl\\\":0}\"\n    if [ \"$?\" = \"0\" ] && _contains \"$response\" \"$fulldomain\"; then\n      _info \"Updated!\"\n      #todo: check if the record takes effect\n      return 0\n    fi\n    _err \"Update error\"\n    return 1\n  fi\n\n}\n\n#fulldomain\ndns_nsone_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _nsone_rest GET \"zones/${_domain}/$fulldomain/TXT\"\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"domain\\\":\\\"$fulldomain\\\",.*\\\"type\\\":\\\"TXT\\\"\" | wc -l | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    if ! _nsone_rest DELETE \"zones/${_domain}/$fulldomain/TXT\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" \"\"\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  if ! _nsone_rest GET \"zones\"; then\n    return 1\n  fi\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"zone\\\":\\\"$h\\\"\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_nsone_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"X-NSONE-Key: $NS1_Key\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$NS1_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$NS1_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_nsupdate.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nsupdate_info='nsupdate RFC 2136 DynDNS client\nSite: bind9.readthedocs.io/en/v9.18.19/manpages.html#nsupdate-dynamic-dns-update-utility\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nsupdate\nOptions:\n NSUPDATE_SERVER Server hostname. Default: \"localhost\".\n NSUPDATE_SERVER_PORT Server port. Default: \"53\".\n NSUPDATE_KEY File path to TSIG key. Default: \"\". Optional.\n NSUPDATE_ZONE Domain zone to update. Optional.\n'\n\n########  Public functions #####################\n\n#Usage: dns_nsupdate_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_nsupdate_add() {\n  fulldomain=$1\n  txtvalue=$2\n  NSUPDATE_SERVER=\"${NSUPDATE_SERVER:-$(_readaccountconf_mutable NSUPDATE_SERVER)}\"\n  NSUPDATE_SERVER_PORT=\"${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}\"\n  NSUPDATE_KEY=\"${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}\"\n  NSUPDATE_ZONE=\"${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}\"\n  NSUPDATE_OPT=\"${NSUPDATE_OPT:-$(_readaccountconf_mutable NSUPDATE_OPT)}\"\n\n  # save the dns server and key to the account conf file.\n  _saveaccountconf_mutable NSUPDATE_SERVER \"${NSUPDATE_SERVER}\"\n  _saveaccountconf_mutable NSUPDATE_SERVER_PORT \"${NSUPDATE_SERVER_PORT}\"\n  _saveaccountconf_mutable NSUPDATE_KEY \"${NSUPDATE_KEY}\"\n  _saveaccountconf_mutable NSUPDATE_ZONE \"${NSUPDATE_ZONE}\"\n  _saveaccountconf_mutable NSUPDATE_OPT \"${NSUPDATE_OPT}\"\n\n  [ -n \"${NSUPDATE_SERVER}\" ] || NSUPDATE_SERVER=\"localhost\"\n  [ -n \"${NSUPDATE_SERVER_PORT}\" ] || NSUPDATE_SERVER_PORT=53\n  [ -n \"${NSUPDATE_KEY}\" ] || NSUPDATE_KEY=\"\"\n  [ -n \"${NSUPDATE_OPT}\" ] || NSUPDATE_OPT=\"\"\n\n  NSUPDATE_SERVER_LIST=$(printf \"%s\" \"$NSUPDATE_SERVER\" | tr ',' ' ')\n\n  _info \"adding ${fulldomain}. 60 in txt \\\"${txtvalue}\\\"\"\n  [ -n \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"$DEBUG_LEVEL_1\" ] && nsdebug=\"-d\"\n  [ -n \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"$DEBUG_LEVEL_2\" ] && nsdebug=\"-D\"\n\n  for NS_SERVER in $NSUPDATE_SERVER_LIST; do\n    _info \"Updating DNS server: $NS_SERVER\"\n\n    if [ -z \"${NSUPDATE_ZONE}\" ]; then\n      #shellcheck disable=SC2086\n      if [ -z \"${NSUPDATE_KEY}\" ]; then\n        nsupdate $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nupdate add ${fulldomain}. 60 in txt \"${txtvalue}\"\nsend\nEOF\n      else\n        nsupdate -k \"${NSUPDATE_KEY}\" $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nupdate add ${fulldomain}. 60 in txt \"${txtvalue}\"\nsend\nEOF\n      fi\n    else\n      #shellcheck disable=SC2086\n      if [ -z \"${NSUPDATE_KEY}\" ]; then\n        nsupdate $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nzone ${NSUPDATE_ZONE}.\nupdate add ${fulldomain}. 60 in txt \"${txtvalue}\"\nsend\nEOF\n      else\n        nsupdate -k \"${NSUPDATE_KEY}\" $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nzone ${NSUPDATE_ZONE}.\nupdate add ${fulldomain}. 60 in txt \"${txtvalue}\"\nsend\nEOF\n      fi\n    fi\n  done\n  if [ $? -ne 0 ]; then\n    _err \"error updating domain\"\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: dns_nsupdate_rm   _acme-challenge.www.domain.com\ndns_nsupdate_rm() {\n  fulldomain=$1\n\n  NSUPDATE_SERVER=\"${NSUPDATE_SERVER:-$(_readaccountconf_mutable NSUPDATE_SERVER)}\"\n  NSUPDATE_SERVER_PORT=\"${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}\"\n  NSUPDATE_KEY=\"${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}\"\n  NSUPDATE_ZONE=\"${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}\"\n  NSUPDATE_OPT=\"${NSUPDATE_OPT:-$(_readaccountconf_mutable NSUPDATE_OPT)}\"\n\n  [ -n \"${NSUPDATE_SERVER}\" ] || NSUPDATE_SERVER=\"localhost\"\n  [ -n \"${NSUPDATE_SERVER_PORT}\" ] || NSUPDATE_SERVER_PORT=53\n  [ -n \"${NSUPDATE_KEY}\" ] || NSUPDATE_KEY=\"\"\n\n  NSUPDATE_SERVER_LIST=$(printf \"%s\" \"$NSUPDATE_SERVER\" | tr ',' ' ')\n\n  _info \"removing ${fulldomain}. txt\"\n  [ -n \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"$DEBUG_LEVEL_1\" ] && nsdebug=\"-d\"\n  [ -n \"$DEBUG\" ] && [ \"$DEBUG\" -ge \"$DEBUG_LEVEL_2\" ] && nsdebug=\"-D\"\n\n  for NS_SERVER in $NSUPDATE_SERVER_LIST; do\n    _info \"Updating DNS server: $NS_SERVER\"\n\n    if [ -z \"${NSUPDATE_ZONE}\" ]; then\n      #shellcheck disable=SC2086\n      if [ -z \"${NSUPDATE_KEY}\" ]; then\n        nsupdate $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nupdate delete ${fulldomain}. txt\nsend\nEOF\n      else\n        nsupdate -k \"${NSUPDATE_KEY}\" $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nupdate delete ${fulldomain}. txt\nsend\nEOF\n      fi\n    else\n      #shellcheck disable=SC2086\n      if [ -z \"${NSUPDATE_KEY}\" ]; then\n        nsupdate $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nzone ${NSUPDATE_ZONE}.\nupdate delete ${fulldomain}. txt\nsend\nEOF\n      else\n        nsupdate -k \"${NSUPDATE_KEY}\" $nsdebug $NSUPDATE_OPT <<EOF\nserver ${NS_SERVER}  ${NSUPDATE_SERVER_PORT}\nzone ${NSUPDATE_ZONE}.\nupdate delete ${fulldomain}. txt\nsend\nEOF\n      fi\n    fi\n  done\n  if [ $? -ne 0 ]; then\n    _err \"error updating domain\"\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_nw.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_nw_info='Nexcess.net (NocWorx)\nDomains: Thermo.io Futurehosting.com\nSite: Nexcess.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nw\nOptions:\n NW_API_TOKEN API Token\n NW_API_ENDPOINT API Endpoint. Default: \"https://portal.nexcess.net\".\nIssues: github.com/acmesh-official/acme.sh/issues/2088\nAuthor: Frank Laszlo <flaszlo@nexcess.net>\n'\n\n# Endpoints:\n#   - https://portal.nexcess.net (default)\n#   - https://core.thermo.io\n#   - https://my.futurehosting.com\n#\n#  Note: If you do not have an API token, one can be generated at one\n#        of the following URLs:\n#        - https://portal.nexcess.net/api-token\n#        - https://core.thermo.io/api-token\n#        - https://my.futurehosting.com/api-token\n\nNW_API_VERSION=\"0\"\n\n# dns_nw_add() - Add TXT record\n# Usage: dns_nw_add _acme-challenge.subdomain.domain.com \"XyZ123...\"\ndns_nw_add() {\n  host=\"${1}\"\n  txtvalue=\"${2}\"\n\n  _debug host \"${host}\"\n  _debug txtvalue \"${txtvalue}\"\n\n  if ! _check_nw_api_creds; then\n    return 1\n  fi\n\n  _info \"Using NocWorx (${NW_API_ENDPOINT})\"\n  _debug \"Calling: dns_nw_add() '${host}' '${txtvalue}'\"\n\n  _debug \"Detecting root zone\"\n  if ! _get_root \"${host}\"; then\n    _err \"Zone for domain does not exist.\"\n    return 1\n  fi\n  _debug _zone_id \"${_zone_id}\"\n  _debug _sub_domain \"${_sub_domain}\"\n  _debug _domain \"${_domain}\"\n\n  _post_data=\"{\\\"zone_id\\\": \\\"${_zone_id}\\\", \\\"type\\\": \\\"TXT\\\", \\\"host\\\": \\\"${host}\\\", \\\"target\\\": \\\"${txtvalue}\\\", \\\"ttl\\\": \\\"300\\\"}\"\n\n  if _rest POST \"dns-record\" \"${_post_data}\" && [ -n \"${response}\" ]; then\n    _record_id=$(printf \"%s\\n\" \"${response}\" | _egrep_o \"\\\"record_id\\\": *[0-9]+\" | cut -d : -f 2 | tr -d \" \" | _head_n 1)\n    _debug _record_id \"${_record_id}\"\n\n    if [ -z \"$_record_id\" ]; then\n      _err \"Error adding the TXT record.\"\n      return 1\n    fi\n\n    _info \"TXT record successfully added.\"\n    return 0\n  fi\n\n  return 1\n}\n\n# dns_nw_rm() - Remove TXT record\n# Usage: dns_nw_rm _acme-challenge.subdomain.domain.com \"XyZ123...\"\ndns_nw_rm() {\n  host=\"${1}\"\n  txtvalue=\"${2}\"\n\n  _debug host \"${host}\"\n  _debug txtvalue \"${txtvalue}\"\n\n  if ! _check_nw_api_creds; then\n    return 1\n  fi\n\n  _info \"Using NocWorx (${NW_API_ENDPOINT})\"\n  _debug \"Calling: dns_nw_rm() '${host}'\"\n\n  _debug \"Detecting root zone\"\n  if ! _get_root \"${host}\"; then\n    _err \"Zone for domain does not exist.\"\n    return 1\n  fi\n  _debug _zone_id \"${_zone_id}\"\n  _debug _sub_domain \"${_sub_domain}\"\n  _debug _domain \"${_domain}\"\n\n  _parameters=\"?zone_id=${_zone_id}\"\n\n  if _rest GET \"dns-record\" \"${_parameters}\" && [ -n \"${response}\" ]; then\n    response=\"$(echo \"${response}\" | tr -d \"\\n\" | sed 's/^\\[\\(.*\\)\\]$/\\1/' | sed -e 's/{\"record_id\":/|\"record_id\":/g' | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n    _debug response \"${response}\"\n\n    record=\"$(echo \"${response}\" | _egrep_o \"{.*\\\"host\\\": *\\\"${_sub_domain}\\\", *\\\"target\\\": *\\\"${txtvalue}\\\".*}\")\"\n    _debug record \"${record}\"\n\n    if [ \"${record}\" ]; then\n      _record_id=$(printf \"%s\\n\" \"${record}\" | _egrep_o \"\\\"record_id\\\": *[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n      if [ \"${_record_id}\" ]; then\n        _debug _record_id \"${_record_id}\"\n\n        _rest DELETE \"dns-record/${_record_id}\"\n\n        _info \"TXT record successfully deleted.\"\n        return 0\n      fi\n\n      return 1\n    fi\n\n    return 0\n  fi\n\n  return 1\n}\n\n_check_nw_api_creds() {\n  NW_API_TOKEN=\"${NW_API_TOKEN:-$(_readaccountconf_mutable NW_API_TOKEN)}\"\n  NW_API_ENDPOINT=\"${NW_API_ENDPOINT:-$(_readaccountconf_mutable NW_API_ENDPOINT)}\"\n\n  if [ -z \"${NW_API_ENDPOINT}\" ]; then\n    NW_API_ENDPOINT=\"https://portal.nexcess.net\"\n  fi\n\n  if [ -z \"${NW_API_TOKEN}\" ]; then\n    _err \"You have not defined your NW_API_TOKEN.\"\n    _err \"Please create your token and try again.\"\n    _err \"If you need to generate a new token, please visit one of the following URLs:\"\n    _err \"  - https://portal.nexcess.net/api-token\"\n    _err \"  - https://core.thermo.io/api-token\"\n    _err \"  - https://my.futurehosting.com/api-token\"\n\n    return 1\n  fi\n\n  _saveaccountconf_mutable NW_API_TOKEN \"${NW_API_TOKEN}\"\n  _saveaccountconf_mutable NW_API_ENDPOINT \"${NW_API_ENDPOINT}\"\n}\n\n_get_root() {\n  domain=\"${1}\"\n  i=2\n  p=1\n\n  if _rest GET \"dns-zone\"; then\n    response=\"$(echo \"${response}\" | tr -d \"\\n\" | sed 's/^\\[\\(.*\\)\\]$/\\1/' | sed -e 's/{\"zone_id\":/|\"zone_id\":/g' | sed 's/|/&{/g' | tr \"|\" \"\\n\")\"\n\n    _debug response \"${response}\"\n    while true; do\n      h=$(printf \"%s\" \"${domain}\" | cut -d . -f \"$i\"-100)\n      _debug h \"${h}\"\n      if [ -z \"${h}\" ]; then\n        #not valid\n        return 1\n      fi\n\n      hostedzone=\"$(echo \"${response}\" | _egrep_o \"{.*\\\"domain\\\": *\\\"${h}\\\".*}\")\"\n      if [ \"${hostedzone}\" ]; then\n        _zone_id=$(printf \"%s\\n\" \"${hostedzone}\" | _egrep_o \"\\\"zone_id\\\": *[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n        if [ \"${_zone_id}\" ]; then\n          _sub_domain=$(printf \"%s\" \"${domain}\" | cut -d . -f 1-\"${p}\")\n          _domain=\"${h}\"\n          return 0\n        fi\n        return 1\n      fi\n      p=$i\n      i=$(_math \"${i}\" + 1)\n    done\n  fi\n  return 1\n}\n\n_rest() {\n  method=\"${1}\"\n  ep=\"/${2}\"\n  data=\"${3}\"\n\n  _debug method \"${method}\"\n  _debug ep \"${ep}\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Api-Version: ${NW_API_VERSION}\"\n  export _H4=\"User-Agent: NW-ACME-CLIENT\"\n  export _H5=\"Authorization: Bearer ${NW_API_TOKEN}\"\n\n  if [ \"${method}\" != \"GET\" ]; then\n    _debug data \"${data}\"\n    response=\"$(_post \"${data}\" \"${NW_API_ENDPOINT}${ep}\" \"\" \"${method}\")\"\n  else\n    response=\"$(_get \"${NW_API_ENDPOINT}${ep}${data}\")\"\n  fi\n\n  if [ \"${?}\" != \"0\" ]; then\n    _err \"error ${ep}\"\n    return 1\n  fi\n  _debug2 response \"${response}\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_oci.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_oci_info='Oracle Cloud Infrastructure (OCI)\n If OCI CLI configuration file ~/.oci/config has a DEFAULT profile then it will be used.\nSite: Cloud.Oracle.com\nDocs: github.com/acmesh-official/acme.sh/wiki/How-to-use-Oracle-Cloud-Infrastructure-DNS\nOptions:\n OCI_CLI_TENANCY OCID of tenancy that contains the target DNS zone. Optional.\n OCI_CLI_USER OCID of user with permission to add/remove records from zones. Optional.\n OCI_CLI_REGION Should point to the tenancy home region. Optional.\n OCI_CLI_KEY_FILE Path to private API signing key file in PEM format. Optional.\n OCI_CLI_KEY The private API signing key in PEM format. Optional.\nIssues: github.com/acmesh-official/acme.sh/issues/3540\nAuthor: Avi Miller <me@dje.li>\n'\n\n# Copyright (c) 2021, Oracle and/or its affiliates\n#\n# The plugin will automatically use the default profile from an OCI SDK and CLI\n# configuration file, if it exists.\n#\n# Alternatively, set the following environment variables:\n# - OCI_CLI_TENANCY : OCID of tenancy that contains the target DNS zone\n# - OCI_CLI_USER    : OCID of user with permission to add/remove records from zones\n# - OCI_CLI_REGION  : Should point to the tenancy home region\n#\n# One of the following two variables is required:\n# - OCI_CLI_KEY_FILE: Path to private API signing key file in PEM format; or\n# - OCI_CLI_KEY     : The private API signing key in PEM format\n#\n# NOTE: using an encrypted private key that needs a passphrase is not supported.\n#\n\ndns_oci_add() {\n  _fqdn=\"$1\"\n  _rdata=\"$2\"\n\n  if _get_oci_zone; then\n\n    _add_record_body=\"{\\\"items\\\":[{\\\"domain\\\":\\\"${_sub_domain}.${_domain}\\\",\\\"rdata\\\":\\\"$_rdata\\\",\\\"rtype\\\":\\\"TXT\\\",\\\"ttl\\\": 30,\\\"operation\\\":\\\"ADD\\\"}]}\"\n    response=$(_signed_request \"PATCH\" \"/20180115/zones/${_domain}/records\" \"$_add_record_body\")\n    if [ \"$response\" ]; then\n      _info \"Success: added TXT record for ${_sub_domain}.${_domain}.\"\n    else\n      _err \"Error: failed to add TXT record for ${_sub_domain}.${_domain}.\"\n      _err \"Check that the user has permission to add records to this zone.\"\n      return 1\n    fi\n\n  else\n    return 1\n  fi\n\n}\n\ndns_oci_rm() {\n  _fqdn=\"$1\"\n  _rdata=\"$2\"\n\n  if _get_oci_zone; then\n\n    _remove_record_body=\"{\\\"items\\\":[{\\\"domain\\\":\\\"${_sub_domain}.${_domain}\\\",\\\"rdata\\\":\\\"$_rdata\\\",\\\"rtype\\\":\\\"TXT\\\",\\\"operation\\\":\\\"REMOVE\\\"}]}\"\n    response=$(_signed_request \"PATCH\" \"/20180115/zones/${_domain}/records\" \"$_remove_record_body\")\n    if [ \"$response\" ]; then\n      _info \"Success: removed TXT record for ${_sub_domain}.${_domain}.\"\n    else\n      _err \"Error: failed to remove TXT record for ${_sub_domain}.${_domain}.\"\n      _err \"Check that the user has permission to remove records from this zone.\"\n      return 1\n    fi\n\n  else\n    return 1\n  fi\n\n}\n\n####################  Private functions below ##################################\n_get_oci_zone() {\n\n  if ! _oci_config; then\n    return 1\n  fi\n\n  if ! _get_zone \"$_fqdn\"; then\n    _err \"Error: DNS Zone not found for $_fqdn in $OCI_CLI_TENANCY\"\n    return 1\n  fi\n\n  return 0\n\n}\n\n_oci_config() {\n\n  _DEFAULT_OCI_CLI_CONFIG_FILE=\"$HOME/.oci/config\"\n  OCI_CLI_CONFIG_FILE=\"${OCI_CLI_CONFIG_FILE:-$(_readaccountconf_mutable OCI_CLI_CONFIG_FILE)}\"\n\n  if [ -z \"$OCI_CLI_CONFIG_FILE\" ]; then\n    OCI_CLI_CONFIG_FILE=\"$_DEFAULT_OCI_CLI_CONFIG_FILE\"\n  fi\n\n  if [ \"$_DEFAULT_OCI_CLI_CONFIG_FILE\" != \"$OCI_CLI_CONFIG_FILE\" ]; then\n    _saveaccountconf_mutable OCI_CLI_CONFIG_FILE \"$OCI_CLI_CONFIG_FILE\"\n  else\n    _clearaccountconf_mutable OCI_CLI_CONFIG_FILE\n  fi\n\n  _DEFAULT_OCI_CLI_PROFILE=\"DEFAULT\"\n  OCI_CLI_PROFILE=\"${OCI_CLI_PROFILE:-$(_readaccountconf_mutable OCI_CLI_PROFILE)}\"\n  if [ \"$_DEFAULT_OCI_CLI_PROFILE\" != \"$OCI_CLI_PROFILE\" ]; then\n    _saveaccountconf_mutable OCI_CLI_PROFILE \"$OCI_CLI_PROFILE\"\n  else\n    OCI_CLI_PROFILE=\"$_DEFAULT_OCI_CLI_PROFILE\"\n    _clearaccountconf_mutable OCI_CLI_PROFILE\n  fi\n\n  OCI_CLI_TENANCY=\"${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}\"\n  if [ \"$OCI_CLI_TENANCY\" ]; then\n    _saveaccountconf_mutable OCI_CLI_TENANCY \"$OCI_CLI_TENANCY\"\n  elif [ -f \"$OCI_CLI_CONFIG_FILE\" ]; then\n    _debug \"Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE\"\n    OCI_CLI_TENANCY=\"${OCI_CLI_TENANCY:-$(_readini \"$OCI_CLI_CONFIG_FILE\" tenancy \"$OCI_CLI_PROFILE\")}\"\n  fi\n\n  if [ -z \"$OCI_CLI_TENANCY\" ]; then\n    _err \"Error: unable to read OCI_CLI_TENANCY from config file or environment variable.\"\n    return 1\n  fi\n\n  OCI_CLI_USER=\"${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}\"\n  if [ \"$OCI_CLI_USER\" ]; then\n    _saveaccountconf_mutable OCI_CLI_USER \"$OCI_CLI_USER\"\n  elif [ -f \"$OCI_CLI_CONFIG_FILE\" ]; then\n    _debug \"Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE\"\n    OCI_CLI_USER=\"${OCI_CLI_USER:-$(_readini \"$OCI_CLI_CONFIG_FILE\" user \"$OCI_CLI_PROFILE\")}\"\n  fi\n  if [ -z \"$OCI_CLI_USER\" ]; then\n    _err \"Error: unable to read OCI_CLI_USER from config file or environment variable.\"\n    return 1\n  fi\n\n  OCI_CLI_REGION=\"${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}\"\n  if [ \"$OCI_CLI_REGION\" ]; then\n    _saveaccountconf_mutable OCI_CLI_REGION \"$OCI_CLI_REGION\"\n  elif [ -f \"$OCI_CLI_CONFIG_FILE\" ]; then\n    _debug \"Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE\"\n    OCI_CLI_REGION=\"${OCI_CLI_REGION:-$(_readini \"$OCI_CLI_CONFIG_FILE\" region \"$OCI_CLI_PROFILE\")}\"\n  fi\n  if [ -z \"$OCI_CLI_REGION\" ]; then\n    _err \"Error: unable to read OCI_CLI_REGION from config file or environment variable.\"\n    return 1\n  fi\n\n  OCI_CLI_KEY=\"${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}\"\n  if [ -z \"$OCI_CLI_KEY\" ]; then\n    _clearaccountconf_mutable OCI_CLI_KEY\n    OCI_CLI_KEY_FILE=\"${OCI_CLI_KEY_FILE:-$(_readini \"$OCI_CLI_CONFIG_FILE\" key_file \"$OCI_CLI_PROFILE\")}\"\n    if [ \"$OCI_CLI_KEY_FILE\" ] && [ -f \"$OCI_CLI_KEY_FILE\" ]; then\n      _debug \"Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE\"\n      OCI_CLI_KEY=$(_base64 <\"$OCI_CLI_KEY_FILE\")\n      _saveaccountconf_mutable OCI_CLI_KEY \"$OCI_CLI_KEY\"\n    fi\n  else\n    _saveaccountconf_mutable OCI_CLI_KEY \"$OCI_CLI_KEY\"\n  fi\n\n  if [ -z \"$OCI_CLI_KEY_FILE\" ] && [ -z \"$OCI_CLI_KEY\" ]; then\n    _err \"Error: unable to find key file path in OCI config file or OCI_CLI_KEY_FILE.\"\n    _err \"Error: unable to load private API signing key from OCI_CLI_KEY.\"\n    return 1\n  fi\n\n  if [ \"$(printf \"%s\\n\" \"$OCI_CLI_KEY\" | wc -l)\" -eq 1 ]; then\n    OCI_CLI_KEY=$(printf \"%s\" \"$OCI_CLI_KEY\" | _dbase64)\n  fi\n\n  return 0\n\n}\n\n# _get_zone(): retrieves the Zone name and OCID\n#\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_ociid=ocid1.dns-zone.oc1..\n_get_zone() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      # not valid\n      return 1\n    fi\n\n    _domain_id=$(_signed_request \"GET\" \"/20180115/zones/$h\" \"\" \"id\")\n    if [ \"$_domain_id\" ]; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n\n      _debug _domain_id \"$_domain_id\"\n      _debug _sub_domain \"$_sub_domain\"\n      _debug _domain \"$_domain\"\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n\n}\n\n#Usage: privatekey\n#Output MD5 fingerprint\n_fingerprint() {\n\n  pkey=\"$1\"\n  if [ -z \"$pkey\" ]; then\n    _usage \"Usage: _fingerprint privkey\"\n    return 1\n  fi\n\n  printf \"%s\" \"$pkey\" | ${ACME_OPENSSL_BIN:-openssl} rsa -pubout -outform DER 2>/dev/null | ${ACME_OPENSSL_BIN:-openssl} md5 -c | cut -d = -f 2 | tr -d ' '\n\n}\n\n_signed_request() {\n\n  _sig_method=\"$1\"\n  _sig_target=\"$2\"\n  _sig_body=\"$3\"\n  _return_field=\"$4\"\n\n  _key_fingerprint=$(_fingerprint \"$OCI_CLI_KEY\")\n  _sig_host=\"dns.$OCI_CLI_REGION.oraclecloud.com\"\n  _sig_keyId=\"$OCI_CLI_TENANCY/$OCI_CLI_USER/$_key_fingerprint\"\n  _sig_alg=\"rsa-sha256\"\n  _sig_version=\"1\"\n  _sig_now=\"$(LC_ALL=C \\date -u \"+%a, %d %h %Y %H:%M:%S GMT\")\"\n\n  _request_method=$(printf %s \"$_sig_method\" | _lower_case)\n  _curl_method=$(printf %s \"$_sig_method\" | _upper_case)\n\n  _request_target=\"(request-target): $_request_method $_sig_target\"\n  _date_header=\"date: $_sig_now\"\n  _host_header=\"host: $_sig_host\"\n\n  _string_to_sign=\"$_request_target\\n$_date_header\\n$_host_header\"\n  _sig_headers=\"(request-target) date host\"\n\n  if [ \"$_sig_body\" ]; then\n    _secure_debug3 _sig_body \"$_sig_body\"\n    _sig_body_sha256=\"x-content-sha256: $(printf %s \"$_sig_body\" | _digest sha256)\"\n    _sig_body_type=\"content-type: application/json\"\n    _sig_body_length=\"content-length: ${#_sig_body}\"\n    _string_to_sign=\"$_string_to_sign\\n$_sig_body_sha256\\n$_sig_body_type\\n$_sig_body_length\"\n    _sig_headers=\"$_sig_headers x-content-sha256 content-type content-length\"\n  fi\n\n  _tmp_file=$(_mktemp)\n  if [ -f \"$_tmp_file\" ]; then\n    printf '%s' \"$OCI_CLI_KEY\" >\"$_tmp_file\"\n    _signature=$(printf '%b' \"$_string_to_sign\" | _sign \"$_tmp_file\" sha256 | tr -d '\\r\\n')\n    rm -f \"$_tmp_file\"\n  fi\n\n  _signed_header=\"Authorization: Signature version=\\\"$_sig_version\\\",keyId=\\\"$_sig_keyId\\\",algorithm=\\\"$_sig_alg\\\",headers=\\\"$_sig_headers\\\",signature=\\\"$_signature\\\"\"\n  _secure_debug3 _signed_header \"$_signed_header\"\n\n  if [ \"$_curl_method\" = \"GET\" ]; then\n    export _H1=\"$_date_header\"\n    export _H2=\"$_signed_header\"\n    _response=\"$(_get \"https://${_sig_host}${_sig_target}\")\"\n  elif [ \"$_curl_method\" = \"PATCH\" ]; then\n    export _H1=\"$_date_header\"\n    # shellcheck disable=SC2090\n    export _H2=\"$_sig_body_sha256\"\n    export _H3=\"$_sig_body_type\"\n    export _H4=\"$_sig_body_length\"\n    export _H5=\"$_signed_header\"\n    _response=\"$(_post \"$_sig_body\" \"https://${_sig_host}${_sig_target}\" \"\" \"PATCH\")\"\n  else\n    _err \"Unable to process method: $_curl_method.\"\n  fi\n\n  _ret=\"$?\"\n  if [ \"$_return_field\" ]; then\n    _response=\"$(echo \"$_response\" | sed 's/\\\\\\\"//g'))\"\n    _return=$(echo \"${_response}\" | _egrep_o \"\\\"$_return_field\\\"\\\\s*:\\\\s*\\\"[^\\\"]*\\\"\" | _head_n 1 | cut -d : -f 2 | tr -d \"\\\"\")\n  else\n    _return=\"$_response\"\n  fi\n\n  printf \"%s\" \"$_return\"\n  return $_ret\n\n}\n\n# file  key  [section]\n_readini() {\n  _file=\"$1\"\n  _key=\"$2\"\n  _section=\"${3:-DEFAULT}\"\n\n  _start_n=$(grep -n '\\['\"$_section\"']' \"$_file\" | cut -d : -f 1)\n  _debug3 _start_n \"$_start_n\"\n  if [ -z \"$_start_n\" ]; then\n    _err \"Can not find section: $_section\"\n    return 1\n  fi\n\n  _start_nn=$(_math \"$_start_n\" + 1)\n  _debug3 \"_start_nn\" \"$_start_nn\"\n\n  _left=\"$(sed -n \"${_start_nn},99999p\" \"$_file\")\"\n  _debug3 _left \"$_left\"\n  _end=\"$(echo \"$_left\" | grep -n \"^\\[\" | _head_n 1)\"\n  _debug3 \"_end\" \"$_end\"\n  if [ \"$_end\" ]; then\n    _end_n=$(echo \"$_end\" | cut -d : -f 1)\n    _debug3 \"_end_n\" \"$_end_n\"\n    _seg_n=$(echo \"$_left\" | sed -n \"1,${_end_n}p\")\n  else\n    _seg_n=\"$_left\"\n  fi\n\n  _debug3 \"_seg_n\" \"$_seg_n\"\n  _lineini=\"$(echo \"$_seg_n\" | grep \"^ *$_key *= *\")\"\n  _inivalue=\"$(printf \"%b\" \"$(eval \"echo $_lineini | sed \\\"s/^ *${_key} *= *//g\\\"\")\")\"\n  _debug2 _inivalue \"$_inivalue\"\n  echo \"$_inivalue\"\n\n}\n"
  },
  {
    "path": "dnsapi/dns_omglol.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_omglol_info='omg.lol\nSite: omg.lol\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol\nOptions:\n OMG_ApiKey - API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account\n OMG_Address - Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard\nIssues: github.com/acmesh-official/acme.sh/issues/5299\nAuthor: @Kholin <kholin+acme.omglolapi@omg.lol>\n'\n\n# See API Docs https://api.omg.lol/\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_omglol_add() {\n  fulldomain=$1\n  txtvalue=$2\n  OMG_ApiKey=\"${OMG_ApiKey:-$(_readaccountconf_mutable OMG_ApiKey)}\"\n  OMG_Address=\"${OMG_Address:-$(_readaccountconf_mutable OMG_Address)}\"\n\n  # As omg.lol includes a leading @ for their addresses, pre-strip this before save\n  OMG_Address=\"$(echo \"$OMG_Address\" | tr -d '@')\"\n\n  _saveaccountconf_mutable OMG_ApiKey \"$OMG_ApiKey\"\n  _saveaccountconf_mutable OMG_Address \"$OMG_Address\"\n\n  _info \"Using omg.lol.\"\n  _debug \"Function\" \"dns_omglol_add()\"\n  _debug \"Full Domain Name\" \"$fulldomain\"\n  _debug \"txt Record Value\" \"$txtvalue\"\n  _secure_debug \"omg.lol API key\" \"$OMG_ApiKey\"\n  _debug \"omg.lol Address\" \"$OMG_Address\"\n\n  omg_validate \"$OMG_ApiKey\" \"$OMG_Address\" \"$fulldomain\"\n  if [ 1 = $? ]; then\n    return 1\n  fi\n\n  dnsName=$(_getDnsRecordName \"$fulldomain\" \"$OMG_Address\")\n  authHeader=\"$(_createAuthHeader \"$OMG_ApiKey\")\"\n\n  _debug2 \"dns_omglol_add(): Address\" \"$dnsName\"\n\n  omg_add \"$OMG_Address\" \"$authHeader\" \"$dnsName\" \"$txtvalue\"\n\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_omglol_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  OMG_ApiKey=\"${OMG_ApiKey:-$(_readaccountconf_mutable OMG_ApiKey)}\"\n  OMG_Address=\"${OMG_Address:-$(_readaccountconf_mutable OMG_Address)}\"\n\n  # As omg.lol includes a leading @ for their addresses, strip this in case provided\n  OMG_Address=\"$(echo \"$OMG_Address\" | tr -d '@')\"\n\n  _info \"Using omg.lol\"\n  _debug \"Function\" \"dns_omglol_rm()\"\n  _debug \"Full Domain Name\" \"$fulldomain\"\n  _debug \"txt Record Value\" \"$txtvalue\"\n  _secure_debug \"omg.lol API key\" \"$OMG_ApiKey\"\n  _debug \"omg.lol Address\" \"$OMG_Address\"\n\n  omg_validate \"$OMG_ApiKey\" \"$OMG_Address\" \"$fulldomain\"\n  if [ 1 = $? ]; then\n    return 1\n  fi\n\n  dnsName=$(_getDnsRecordName \"$fulldomain\" \"$OMG_Address\")\n  authHeader=\"$(_createAuthHeader \"$OMG_ApiKey\")\"\n\n  omg_delete \"$OMG_Address\" \"$authHeader\" \"$dnsName\" \"$txtvalue\"\n}\n\n####################  Private functions below ##################################\n# Check that the minimum requirements are present.  Close ungracefully if not\nomg_validate() {\n  omg_apikey=$1\n  omg_address=$2\n  fulldomain=$3\n\n  _debug2 \"Function\" \"dns_validate()\"\n  _secure_debug2 \"omg.lol API key\" \"$omg_apikey\"\n  _debug2 \"omg.lol Address\" \"$omg_address\"\n  _debug2 \"Full Domain Name\" \"$fulldomain\"\n\n  if [ \"\" = \"$omg_address\" ]; then\n    _err \"omg.lol base address not provided.  Exiting\"\n    return 1\n  fi\n\n  if [ \"\" = \"$omg_apikey\" ]; then\n    _err \"omg.lol API key not provided.  Exiting\"\n    return 1\n  fi\n\n  _endswith \"$fulldomain\" \"omg.lol\"\n  if [ 1 = $? ]; then\n    _err \"Domain name requested is not under omg.lol\"\n    return 1\n  fi\n\n  _endswith \"$fulldomain\" \"$omg_address.omg.lol\"\n  if [ 1 = $? ]; then\n    _err \"Domain name is not a subdomain of provided omg.lol address $omg_address\"\n    return 1\n  fi\n\n  omg_testconnect \"$omg_apikey\" \"$omg_address\"\n  if [ 1 = $? ]; then\n    _err \"Authentication to omg.lol for address $omg_address using provided API key failed\"\n    return 1\n  fi\n\n  _debug \"Required environment parameters are all present and validated\"\n}\n\n# Validate that the address and API key are both correct and associated to each other\nomg_testconnect() {\n  omg_apikey=$1\n  omg_address=$2\n\n  _debug2 \"Function\" \"omg_testconnect\"\n  _secure_debug2 \"omg.lol API key\" \"$omg_apikey\"\n  _debug2 \"omg.lol Address\" \"$omg_address\"\n\n  authheader=\"$(_createAuthHeader \"$omg_apikey\")\"\n  export _H1=\"$authheader\"\n  endpoint=\"https://api.omg.lol/address/$omg_address/info\"\n  _debug2 \"Endpoint for validation\" \"$endpoint\"\n\n  response=$(_get \"$endpoint\" \"\" 30)\n\n  _jsonResponseCheck \"$response\" \"status_code\" 200\n  if [ 1 = $? ]; then\n    _debug2 \"Failed to query omg.lol for $omg_address with provided API key\"\n    _secure_debug2 \"API Key\" \"omg_apikey\"\n    _secure_debug3 \"Raw response\" \"$response\"\n    return 1\n  fi\n}\n\n# Add (or modify) an entry for a new ACME query\nomg_add() {\n  address=$1\n  authHeader=$2\n  dnsName=$3\n  txtvalue=$4\n\n  _info \"Creating DNS entry for $dnsName\"\n  _debug2 \"omg_add()\"\n  _debug2 \"omg.lol Address: \" \"$address\"\n  _secure_debug2 \"omg.lol authorization header: \" \"$authHeader\"\n  _debug2 \"Full Domain name:\" \"$dnsName.$address.omg.lol\"\n  _debug2 \"TXT value to set:\" \"$txtvalue\"\n\n  export _H1=\"$authHeader\"\n\n  endpoint=\"https://api.omg.lol/address/$address/dns\"\n  _debug2 \"Endpoint\" \"$endpoint\"\n\n  payload='{\"type\": \"TXT\", \"name\":\"'\"$dnsName\"'\", \"data\":\"'\"$txtvalue\"'\", \"ttl\":30}'\n  _debug2 \"Payload\" \"$payload\"\n\n  response=$(_post \"$payload\" \"$endpoint\" \"\" \"POST\" \"application/json\")\n\n  omg_validate_add \"$response\" \"$dnsName.$address\" \"$txtvalue\"\n}\n\nomg_validate_add() {\n  response=$1\n  name=$2\n  content=$3\n\n  _debug \"Validating DNS record addition\"\n  _debug2 \"omg_validate_add()\"\n  _debug2 \"Response\" \"$response\"\n  _debug2 \"DNS Name\" \"$name\"\n  _debug2 \"DNS value\" \"$content\"\n\n  _jsonResponseCheck \"$response\" \"success\" \"true\"\n  if [ \"1\" = \"$?\" ]; then\n    _err \"Response did not report success\"\n    return 1\n  fi\n\n  _jsonResponseCheck \"$response\" \"message\" \"Your DNS record was created successfully.\"\n  if [ \"1\" = \"$?\" ]; then\n    _err \"Response message did not indicate DNS record was successfully created\"\n    return 1\n  fi\n\n  _jsonResponseCheck \"$response\" \"name\" \"$name\"\n  if [ \"1\" = \"$?\" ]; then\n    _err \"Response DNS Name did not match the response received\"\n    return 1\n  fi\n\n  _jsonResponseCheck \"$response\" \"content\" \"$content\"\n  if [ \"1\" = \"$?\" ]; then\n    _err \"Response DNS Name did not match the response received\"\n    return 1\n  fi\n\n  _info \"Record Created successfully\"\n  return 0\n}\n\nomg_getRecords() {\n  address=$1\n  authHeader=$2\n  dnsName=$3\n  txtValue=$4\n\n  _debug2 \"omg_getRecords()\"\n  _debug2 \"omg.lol Address: \" \"$address\"\n  _secure_debug2 \"omg.lol Auth Header: \" \"$authHeader\"\n  _debug2 \"omg.lol DNS name:\" \"$dnsName\"\n  _debug2 \"txt Value\" \"$txtValue\"\n\n  export _H1=\"$authHeader\"\n\n  endpoint=\"https://api.omg.lol/address/$address/dns\"\n  _debug2 \"Endpoint\" \"$endpoint\"\n\n  payload=$(_get \"$endpoint\")\n\n  _debug2 \"Received Payload:\" \"$payload\"\n\n  # Reformat the JSON to be more parseable\n  recordID=$(echo \"$payload\" | _stripWhitespace)\n  recordID=$(echo \"$recordID\" | _exposeJsonArray)\n\n  # Now find the one with the right value, and caputre its ID\n  recordID=$(echo \"$recordID\" | grep -- \"$txtValue\" | grep -i -- \"$dnsName.$address\")\n  _getJsonElement \"$recordID\" \"id\"\n}\n\nomg_delete() {\n  address=$1\n  authHeader=$2\n  dnsName=$3\n  txtValue=$4\n\n  _info \"Deleting DNS entry for $dnsName with value $txtValue\"\n  _debug2 \"omg_delete()\"\n  _debug2 \"omg.lol Address: \" \"$address\"\n  _secure_debug2 \"omg.lol Auth Header: \" \"$authHeader\"\n  _debug2 \"Full Domain name:\" \"$dnsName.$address.omg.lol\"\n  _debug2 \"txt Value\" \"$txtValue\"\n\n  record=$(omg_getRecords \"$address\" \"$authHeader\" \"$dnsName\" \"$txtvalue\")\n  if [ \"\" = \"$record\" ]; then\n    _err \"DNS record $address not found!\"\n    return 1\n  fi\n\n  endpoint=\"https://api.omg.lol/address/$address/dns/$record\"\n  _debug2 \"Endpoint\" \"$endpoint\"\n\n  export _H1=\"$authHeader\"\n  output=$(_post \"\" \"$endpoint\" \"\" \"DELETE\")\n\n  _debug2 \"Response\" \"$output\"\n\n  omg_validate_delete \"$output\"\n}\n\n# Validate the response on request to delete.\n# Confirm status is success and message indicates deletion was successful.\n# Input: Response - HTTP response received from delete request\nomg_validate_delete() {\n  response=$1\n\n  _info \"Validating DNS record deletion\"\n  _debug2 \"omg_validate_delete()\"\n  _debug2 \"Response\" \"$response\"\n\n  _jsonResponseCheck \"$output\" \"success\" \"true\"\n  if [ \"1\" = \"$?\" ]; then\n    _err \"Response did not report success\"\n    return 1\n  fi\n\n  _jsonResponseCheck \"$output\" \"message\" \"OK, your DNS record has been deleted.\"\n  if [ \"1\" = \"$?\" ]; then\n    _err \"Response message did not indicate DNS record was successfully deleted\"\n    return 1\n  fi\n\n  _info \"Record deleted successfully\"\n  return 0\n}\n\n########## Utility Functions #####################################\n# All utility functions only log at debug3\n_jsonResponseCheck() {\n  response=$1\n  field=$2\n  correct=$3\n\n  correct=$(echo \"$correct\" | _lower_case)\n\n  _debug3 \"jsonResponseCheck()\"\n  _debug3 \"Response to parse\" \"$response\"\n  _debug3 \"Field to get response from\" \"$field\"\n  _debug3 \"What is the correct response\" \"$correct\"\n\n  responseValue=$(_jsonGetLastResponse \"$response\" \"$field\")\n\n  if [ \"$responseValue\" != \"$correct\" ]; then\n    _debug3 \"Expected: $correct\"\n    _debug3 \"Actual: $responseValue\"\n    return 1\n  else\n    _debug3 \"Matched: $responseValue\"\n  fi\n  return 0\n}\n\n_jsonGetLastResponse() {\n  response=$1\n  field=$2\n\n  _debug3 \"jsonGetLastResponse()\"\n  _debug3 \"Response provided\" \"$response\"\n  _debug3 \"Field to get responses for\" \"$field\"\n\n  responseValue=$(echo \"$response\" | grep -- \"\\\"$field\\\"\" | cut -f2 -d\":\")\n\n  _debug3 \"Response lines found:\" \"$responseValue\"\n\n  responseValue=$(echo \"$responseValue\" | sed 's/^ //g' | sed 's/^\"//g' | sed 's/\\\\\"//g')\n  responseValue=$(echo \"$responseValue\" | sed 's/,$//g' | sed 's/\"$//g')\n  responseValue=$(echo \"$responseValue\" | _lower_case)\n\n  _debug3 \"Responses found\" \"$responseValue\"\n  _debug3 \"Response Selected\" \"$(echo \"$responseValue\" | tail -1)\"\n\n  echo \"$responseValue\" | tail -1\n}\n\n_stripWhitespace() {\n  tr -d '\\n' | tr -d '\\r' | tr -d '\\t' | sed -r 's/ +/ /g' | sed 's/\\\\\"//g'\n}\n\n_exposeJsonArray() {\n  sed -r 's/.*\\[//g' | tr '}' '|' | tr '{' '|' | sed 's/|, |/|/g' | tr '|' '\\n'\n}\n\n_getJsonElement() {\n  content=$1\n  field=$2\n\n  _debug3 \"_getJsonElement()\"\n  _debug3 \"Input JSON element\" \"$content\"\n  _debug3 \"JSON element to isolate\" \"$field\"\n\n  # With a single JSON entry to parse, convert commas to newlines puts each element on\n  # its own line - which then allows us to just grep teh name, remove the key, and\n  # isolate the value\n  output=$(echo \"$content\" | tr ',' '\\n' | grep -- \"\\\"$field\\\":\" | sed 's/.*: //g')\n\n  _debug3 \"String before unquoting: $output\"\n\n  _unquoteString \"$output\"\n}\n\n_createAuthHeader() {\n  apikey=$1\n\n  _debug3 \"_createAuthHeader()\"\n  _secure_debug3 \"Provided API Key\" \"$apikey\"\n\n  authheader=\"Authorization: Bearer $apikey\"\n  _secure_debug3 \"Authorization Header\" \"$authheader\"\n  echo \"$authheader\"\n}\n\n_getDnsRecordName() {\n  fqdn=$1\n  address=$2\n\n  _debug3 \"_getDnsRecordName()\"\n  _debug3 \"FQDN\" \"$fqdn\"\n  _debug3 \"omg.lol Address\" \"$address\"\n\n  echo \"$fqdn\" | sed 's/\\.omg\\.lol//g' | sed 's/\\.'\"$address\"'$//g'\n}\n\n_unquoteString() {\n  output=$1\n  quotes=0\n\n  _debug3 \"_unquoteString()\"\n  _debug3 \"Possibly quoted string\" \"$output\"\n\n  _startswith \"$output\" \"\\\"\"\n  if [ $? ]; then\n    quotes=$((quotes + 1))\n  fi\n\n  _endswith \"$output\" \"\\\"\"\n  if [ $? ]; then\n    quotes=$((quotes + 1))\n  fi\n\n  _debug3 \"Original String: $output\"\n  _debug3 \"Quotes found: $quotes\"\n\n  if [ $((quotes)) -gt 1 ]; then\n    output=$(echo \"$output\" | sed 's/^\"//g' | sed 's/\"$//g')\n    _debug3 \"Quotes removed: $output\"\n  fi\n\n  echo \"$output\"\n}\n"
  },
  {
    "path": "dnsapi/dns_one.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_one_info='one.com\nSite: one.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_one\nOptions:\n ONECOM_User Username\n ONECOM_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2103\n'\n\ndns_one_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _dns_one_login; then\n    _err \"login failed\"\n    return 1\n  fi\n\n  _debug \"detect the root domain\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"root domain not found\"\n    return 1\n  fi\n\n  subdomain=\"${_sub_domain}\"\n  maindomain=${_domain}\n\n  _debug subdomain \"$subdomain\"\n  _debug maindomain \"$maindomain\"\n\n  #Check if the TXT exists\n  _dns_one_getrecord \"TXT\" \"$subdomain\" \"$txtvalue\"\n  if [ -n \"$id\" ]; then\n    _info \"$(__green \"Txt record with the same value found. Skip adding.\")\"\n    return 0\n  fi\n\n  _dns_one_addrecord \"TXT\" \"$subdomain\" \"$txtvalue\"\n  if [ -z \"$id\" ]; then\n    _err \"Add TXT record error.\"\n    return 1\n  else\n    _info \"$(__green \"Added, OK ($id)\")\"\n    return 0\n  fi\n}\n\ndns_one_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _dns_one_login; then\n    _err \"login failed\"\n    return 1\n  fi\n\n  _debug \"detect the root domain\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"root domain not found\"\n    return 1\n  fi\n\n  subdomain=\"${_sub_domain}\"\n  maindomain=${_domain}\n\n  _debug subdomain \"$subdomain\"\n  _debug maindomain \"$maindomain\"\n\n  #Check if the TXT exists\n  _dns_one_getrecord \"TXT\" \"$subdomain\" \"$txtvalue\"\n  if [ -z \"$id\" ]; then\n    _err \"Txt record not found.\"\n    return 1\n  fi\n\n  # delete entry\n  if _dns_one_delrecord \"$id\"; then\n    _info \"$(__green Removed, OK)\"\n    return 0\n  else\n    _err \"Removing txt record error.\"\n    return 1\n  fi\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=\"$1\"\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    response=\"$(_get \"https://www.one.com/admin/api/domains/$h/dns/custom_records\")\"\n\n    if ! _contains \"$response\" \"CRMRST_000302\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  _err \"Unable to parse this domain\"\n  return 1\n}\n\n_dns_one_login() {\n\n  # get credentials\n  ONECOM_User=\"${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}\"\n  ONECOM_Password=\"${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}\"\n  if [ -z \"$ONECOM_User\" ] || [ -z \"$ONECOM_Password\" ]; then\n    ONECOM_User=\"\"\n    ONECOM_Password=\"\"\n    _err \"You didn't specify a one.com username and password yet.\"\n    _err \"Please create the key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable ONECOM_User \"$ONECOM_User\"\n  _saveaccountconf_mutable ONECOM_Password \"$ONECOM_Password\"\n\n  # Login with user and password\n  postdata=\"loginDomain=true\"\n  postdata=\"$postdata&displayUsername=$ONECOM_User\"\n  postdata=\"$postdata&username=$ONECOM_User\"\n  postdata=\"$postdata&targetDomain=\"\n  postdata=\"$postdata&password1=$ONECOM_Password\"\n  postdata=\"$postdata&loginTarget=\"\n  #_debug postdata \"$postdata\"\n\n  response=\"$(_post \"$postdata\" \"https://www.one.com/admin/login.do\" \"\" \"POST\" \"application/x-www-form-urlencoded\")\"\n  #_debug response \"$response\"\n\n  # Get SessionID\n  JSESSIONID=\"$(grep \"OneSIDCrmAdmin\" \"$HTTP_HEADER\" | grep \"^[Ss]et-[Cc]ookie:\" | _head_n 1 | _egrep_o 'OneSIDCrmAdmin=[^;]*;' | tr -d ';')\"\n  _debug jsessionid \"$JSESSIONID\"\n\n  if [ -z \"$JSESSIONID\" ]; then\n    _err \"error sessionid cookie not found\"\n    return 1\n  fi\n\n  export _H1=\"Cookie: ${JSESSIONID}\"\n\n  return 0\n}\n\n_dns_one_getrecord() {\n  type=\"$1\"\n  name=\"$2\"\n  value=\"$3\"\n  if [ -z \"$type\" ]; then\n    type=\"TXT\"\n  fi\n  if [ -z \"$name\" ]; then\n    _err \"Record name is empty.\"\n    return 1\n  fi\n\n  response=\"$(_get \"https://www.one.com/admin/api/domains/$maindomain/dns/custom_records\")\"\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  _debug response \"$response\"\n\n  if [ -z \"${value}\" ]; then\n    id=$(printf -- \"%s\" \"$response\" | sed -n \"s/.*{\\\"type\\\":\\\"dns_custom_records\\\",\\\"id\\\":\\\"\\([^\\\"]*\\)\\\",\\\"attributes\\\":{\\\"prefix\\\":\\\"${name}\\\",\\\"type\\\":\\\"${type}\\\",\\\"content\\\":\\\"[^\\\"]*\\\",\\\"priority\\\":0,\\\"ttl\\\":600}.*/\\1/p\")\n    response=$(printf -- \"%s\" \"$response\" | sed -n \"s/.*{\\\"type\\\":\\\"dns_custom_records\\\",\\\"id\\\":\\\"[^\\\"]*\\\",\\\"attributes\\\":{\\\"prefix\\\":\\\"${name}\\\",\\\"type\\\":\\\"${type}\\\",\\\"content\\\":\\\"\\([^\\\"]*\\)\\\",\\\"priority\\\":0,\\\"ttl\\\":600}.*/\\1/p\")\n  else\n    id=$(printf -- \"%s\" \"$response\" | sed -n \"s/.*{\\\"type\\\":\\\"dns_custom_records\\\",\\\"id\\\":\\\"\\([^\\\"]*\\)\\\",\\\"attributes\\\":{\\\"prefix\\\":\\\"${name}\\\",\\\"type\\\":\\\"${type}\\\",\\\"content\\\":\\\"${value}\\\",\\\"priority\\\":0,\\\"ttl\\\":600}.*/\\1/p\")\n  fi\n  if [ -z \"$id\" ]; then\n    return 1\n  fi\n  return 0\n}\n\n_dns_one_addrecord() {\n  type=\"$1\"\n  name=\"$2\"\n  value=\"$3\"\n  if [ -z \"$type\" ]; then\n    type=\"TXT\"\n  fi\n  if [ -z \"$name\" ]; then\n    _err \"Record name is empty.\"\n    return 1\n  fi\n\n  postdata=\"{\\\"type\\\":\\\"dns_custom_records\\\",\\\"attributes\\\":{\\\"priority\\\":0,\\\"ttl\\\":600,\\\"type\\\":\\\"${type}\\\",\\\"prefix\\\":\\\"${name}\\\",\\\"content\\\":\\\"${value}\\\"}}\"\n  _debug postdata \"$postdata\"\n  response=\"$(_post \"$postdata\" \"https://www.one.com/admin/api/domains/$maindomain/dns/custom_records\" \"\" \"POST\" \"application/json\")\"\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  _debug response \"$response\"\n\n  id=$(echo \"$response\" | sed -n \"s/{\\\"result\\\":{\\\"data\\\":{\\\"type\\\":\\\"dns_custom_records\\\",\\\"id\\\":\\\"\\([^\\\"]*\\)\\\",\\\"attributes\\\":{\\\"prefix\\\":\\\"$subdomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"priority\\\":0,\\\"ttl\\\":600}}},\\\"metadata\\\":null}/\\1/p\")\n\n  if [ -z \"$id\" ]; then\n    return 1\n  else\n    return 0\n  fi\n}\n\n_dns_one_delrecord() {\n  id=\"$1\"\n  if [ -z \"$id\" ]; then\n    return 1\n  fi\n\n  response=\"$(_post \"\" \"https://www.one.com/admin/api/domains/$maindomain/dns/custom_records/$id\" \"\" \"DELETE\" \"application/json\")\"\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n  _debug response \"$response\"\n\n  if [ \"$response\" = '{\"result\":null,\"metadata\":null}' ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_online.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_online_info='online.net\nDomains: scaleway.com\nSite: online.net\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_online\nOptions:\n ONLINE_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2093\n'\n\n# Online API\n# https://console.online.net/en/api/\n\n########  Public functions #####################\n\nONLINE_API=\"https://api.online.net/api/v1\"\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_online_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _online_check_config; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _real_dns_version \"$_real_dns_version\"\n\n  _info \"Creating temporary zone version\"\n  _online_create_temporary_zone_version\n  _info \"Enabling temporary zone version\"\n  _online_enable_zone \"$_temporary_dns_version\"\n\n  _info \"Adding record\"\n  _online_create_TXT_record \"$_real_dns_version\" \"$_sub_domain\" \"$txtvalue\"\n  _info \"Disabling temporary version\"\n  _online_enable_zone \"$_real_dns_version\"\n  _info \"Destroying temporary version\"\n  _online_destroy_zone \"$_temporary_dns_version\"\n\n  _info \"Record added.\"\n  return 0\n}\n\n#fulldomain\ndns_online_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _online_check_config; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug _real_dns_version \"$_real_dns_version\"\n\n  _debug \"Getting txt records\"\n  if ! _online_rest GET \"domain/$_domain/version/active\"; then\n    return 1\n  fi\n\n  rid=$(echo \"$response\" | _egrep_o \"\\\"id\\\":[0-9]+,\\\"name\\\":\\\"$_sub_domain\\\",\\\"data\\\":\\\"\\\\\\u0022$txtvalue\\\\\\u0022\\\"\" | cut -d ':' -f 2 | cut -d ',' -f 1)\n  _debug rid \"$rid\"\n  if [ -z \"$rid\" ]; then\n    return 1\n  fi\n\n  _info \"Creating temporary zone version\"\n  _online_create_temporary_zone_version\n  _info \"Enabling temporary zone version\"\n  _online_enable_zone \"$_temporary_dns_version\"\n\n  _info \"Removing DNS record\"\n  _online_rest DELETE \"domain/$_domain/version/$_real_dns_version/zone/$rid\"\n  _info \"Disabling temporary version\"\n  _online_enable_zone \"$_real_dns_version\"\n  _info \"Destroying temporary version\"\n  _online_destroy_zone \"$_temporary_dns_version\"\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_online_check_config() {\n  ONLINE_API_KEY=\"${ONLINE_API_KEY:-$(_readaccountconf_mutable ONLINE_API_KEY)}\"\n  if [ -z \"$ONLINE_API_KEY\" ]; then\n    _err \"No API key specified for Online API.\"\n    _err \"Create your key and export it as ONLINE_API_KEY\"\n    return 1\n  fi\n  if ! _online_rest GET \"domain/\"; then\n    _err \"Invalid API key specified for Online API.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable ONLINE_API_KEY \"$ONLINE_API_KEY\"\n\n  return 0\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _online_rest GET \"domain/$h/version/active\"\n\n    if ! _contains \"$response\" \"Domain not found\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      _real_dns_version=$(echo \"$response\" | _egrep_o '\"uuid_ref\":.*' | cut -d ':' -f 2 | cut -d '\"' -f 2)\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  _err \"Unable to retrive DNS zone matching this domain\"\n  return 1\n}\n\n# this function create a temporary zone version\n# as online.net does not allow updating an active version\n_online_create_temporary_zone_version() {\n\n  _online_rest POST \"domain/$_domain/version\" \"name=acme.sh\"\n  if [ \"$?\" != \"0\" ]; then\n    return 1\n  fi\n\n  _temporary_dns_version=$(echo \"$response\" | _egrep_o '\"uuid_ref\":.*' | cut -d ':' -f 2 | cut -d '\"' -f 2)\n\n  # Creating a dummy record in this temporary version, because online.net doesn't accept enabling an empty version\n  _online_create_TXT_record \"$_temporary_dns_version\" \"dummy.acme.sh\" \"dummy\"\n\n  return 0\n}\n\n_online_destroy_zone() {\n  version_id=$1\n  _online_rest DELETE \"domain/$_domain/version/$version_id\"\n\n  if [ \"$?\" != \"0\" ]; then\n    return 1\n  fi\n  return 0\n}\n\n_online_enable_zone() {\n  version_id=$1\n  _online_rest PATCH \"domain/$_domain/version/$version_id/enable\"\n\n  if [ \"$?\" != \"0\" ]; then\n    return 1\n  fi\n  return 0\n}\n\n_online_create_TXT_record() {\n  version=$1\n  txt_name=$2\n  txt_value=$3\n\n  _online_rest POST \"domain/$_domain/version/$version/zone\" \"type=TXT&name=$txt_name&data=%22$txt_value%22&ttl=60&priority=0\"\n\n  # Note : the normal, expected response SHOULD be \"Unknown method\".\n  # this happens because the API HTTP response contains a Location: header, that redirect\n  # to an unknown online.net endpoint.\n  if [ \"$?\" != \"0\" ] || _contains \"$response\" \"Unknown method\" || _contains \"$response\" \"\\$ref\"; then\n    return 0\n  else\n    _err \"error $response\"\n    return 1\n  fi\n}\n\n_online_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n  _online_url=\"$ONLINE_API/$ep\"\n  _debug2 _online_url \"$_online_url\"\n  export _H1=\"Authorization: Bearer $ONLINE_API_KEY\"\n  export _H2=\"X-Pretty-JSON: 1\"\n  if [ \"$data\" ] || [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$_online_url\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$_online_url\")\"\n  fi\n  if [ \"$?\" != \"0\" ] || _contains \"$response\" \"invalid_grant\" || _contains \"$response\" \"Method not allowed\"; then\n    _err \"error $response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_openprovider.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_openprovider_info='OpenProvider.eu\nSite: OpenProvider.eu\nDomains: OpenProvider.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_openprovider\nOptions:\n OPENPROVIDER_USER Username\n OPENPROVIDER_PASSWORDHASH Password hash\nIssues: github.com/acmesh-official/acme.sh/issues/2104\nAuthor: Sylvia van Os\n'\n\nOPENPROVIDER_API=\"https://api.openprovider.eu/\"\n#OPENPROVIDER_API=\"https://api.cte.openprovider.eu/\" # Test API\n\n########  Public functions #####################\n\n#Usage: dns_openprovider_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_openprovider_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  OPENPROVIDER_USER=\"${OPENPROVIDER_USER:-$(_readaccountconf_mutable OPENPROVIDER_USER)}\"\n  OPENPROVIDER_PASSWORDHASH=\"${OPENPROVIDER_PASSWORDHASH:-$(_readaccountconf_mutable OPENPROVIDER_PASSWORDHASH)}\"\n\n  if [ -z \"$OPENPROVIDER_USER\" ] || [ -z \"$OPENPROVIDER_PASSWORDHASH\" ]; then\n    _err \"You didn't specify the openprovider user and/or password hash.\"\n    return 1\n  fi\n\n  # save the username and password to the account conf file.\n  _saveaccountconf_mutable OPENPROVIDER_USER \"$OPENPROVIDER_USER\"\n  _saveaccountconf_mutable OPENPROVIDER_PASSWORDHASH \"$OPENPROVIDER_PASSWORDHASH\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain_name \"$_domain_name\"\n  _debug _domain_extension \"$_domain_extension\"\n\n  _debug \"Getting current records\"\n  existing_items=\"\"\n  results_retrieved=0\n  while true; do\n    _openprovider_request \"$(printf '<searchZoneRecordDnsRequest><name>%s.%s</name><offset>%s</offset></searchZoneRecordDnsRequest>' \"$_domain_name\" \"$_domain_extension\" \"$results_retrieved\")\"\n\n    items=\"$response\"\n    while true; do\n      item=\"$(echo \"$items\" | _egrep_o '<openXML>.*<\\/openXML>' | sed -n 's/.*\\(<item>.*<\\/item>\\).*/\\1/p')\"\n      _debug existing_items \"$existing_items\"\n      _debug results_retrieved \"$results_retrieved\"\n      _debug item \"$item\"\n\n      if [ -z \"$item\" ]; then\n        break\n      fi\n\n      tmpitem=\"$(echo \"$item\" | sed 's/\\*/\\\\*/g')\"\n      items=\"$(echo \"$items\" | sed \"s|${tmpitem}||\")\"\n\n      results_retrieved=\"$(_math \"$results_retrieved\" + 1)\"\n      new_item=\"$(echo \"$item\" | sed -n 's/.*<item>.*\\(<name>\\(.*\\)\\.'\"$_domain_name\"'\\.'\"$_domain_extension\"'<\\/name>.*\\(<type>.*<\\/type>\\).*\\(<value>.*<\\/value>\\).*\\(<prio>.*<\\/prio>\\).*\\(<ttl>.*<\\/ttl>\\)\\).*<\\/item>.*/<item><name>\\2<\\/name>\\3\\4\\5\\6<\\/item>/p')\"\n      if [ -z \"$new_item\" ]; then\n        # Domain apex\n        new_item=\"$(echo \"$item\" | sed -n 's/.*<item>.*\\(<name>\\(.*\\)'\"$_domain_name\"'\\.'\"$_domain_extension\"'<\\/name>.*\\(<type>.*<\\/type>\\).*\\(<value>.*<\\/value>\\).*\\(<prio>.*<\\/prio>\\).*\\(<ttl>.*<\\/ttl>\\)\\).*<\\/item>.*/<item><name>\\2<\\/name>\\3\\4\\5\\6<\\/item>/p')\"\n      fi\n\n      if [ -z \"$(echo \"$new_item\" | _egrep_o \".*<type>(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\\/type>.*\")\" ]; then\n        _debug \"not an allowed record type, skipping\" \"$new_item\"\n        continue\n      fi\n\n      existing_items=\"$existing_items$new_item\"\n    done\n\n    total=\"$(echo \"$response\" | _egrep_o '<total>.*?<\\/total>' | sed -n 's/.*<total>\\(.*\\)<\\/total>.*/\\1/p')\"\n\n    _debug total \"$total\"\n    if [ \"$results_retrieved\" -eq \"$total\" ]; then\n      break\n    fi\n  done\n\n  _debug \"Creating acme record\"\n  acme_record=\"$(echo \"$fulldomain\" | sed -e \"s/.$_domain_name.$_domain_extension$//\")\"\n  _openprovider_request \"$(printf '<modifyZoneDnsRequest><domain><name>%s</name><extension>%s</extension></domain><type>master</type><records><array>%s<item><name>%s</name><type>TXT</type><value>%s</value><ttl>600</ttl></item></array></records></modifyZoneDnsRequest>' \"$_domain_name\" \"$_domain_extension\" \"$existing_items\" \"$acme_record\" \"$txtvalue\")\"\n\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_openprovider_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  OPENPROVIDER_USER=\"${OPENPROVIDER_USER:-$(_readaccountconf_mutable OPENPROVIDER_USER)}\"\n  OPENPROVIDER_PASSWORDHASH=\"${OPENPROVIDER_PASSWORDHASH:-$(_readaccountconf_mutable OPENPROVIDER_PASSWORDHASH)}\"\n\n  if [ -z \"$OPENPROVIDER_USER\" ] || [ -z \"$OPENPROVIDER_PASSWORDHASH\" ]; then\n    _err \"You didn't specify the openprovider user and/or password hash.\"\n    return 1\n  fi\n\n  # save the username and password to the account conf file.\n  _saveaccountconf_mutable OPENPROVIDER_USER \"$OPENPROVIDER_USER\"\n  _saveaccountconf_mutable OPENPROVIDER_PASSWORDHASH \"$OPENPROVIDER_PASSWORDHASH\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain_name \"$_domain_name\"\n  _debug _domain_extension \"$_domain_extension\"\n\n  _debug \"Getting current records\"\n  existing_items=\"\"\n  results_retrieved=0\n  while true; do\n    _openprovider_request \"$(printf '<searchZoneRecordDnsRequest><name>%s.%s</name><offset>%s</offset></searchZoneRecordDnsRequest>' \"$_domain_name\" \"$_domain_extension\" \"$results_retrieved\")\"\n\n    # Remove acme records from items\n    items=\"$response\"\n    while true; do\n      item=\"$(echo \"$items\" | _egrep_o '<openXML>.*<\\/openXML>' | sed -n 's/.*\\(<item>.*<\\/item>\\).*/\\1/p')\"\n      _debug existing_items \"$existing_items\"\n      _debug results_retrieved \"$results_retrieved\"\n      _debug item \"$item\"\n\n      if [ -z \"$item\" ]; then\n        break\n      fi\n\n      tmpitem=\"$(echo \"$item\" | sed 's/\\*/\\\\*/g')\"\n      items=\"$(echo \"$items\" | sed \"s|${tmpitem}||\")\"\n\n      results_retrieved=\"$(_math \"$results_retrieved\" + 1)\"\n      if ! echo \"$item\" | grep -v \"$fulldomain\"; then\n        _debug \"acme record, skipping\" \"$item\"\n        continue\n      fi\n\n      new_item=\"$(echo \"$item\" | sed -n 's/.*<item>.*\\(<name>\\(.*\\)\\.'\"$_domain_name\"'\\.'\"$_domain_extension\"'<\\/name>.*\\(<type>.*<\\/type>\\).*\\(<value>.*<\\/value>\\).*\\(<prio>.*<\\/prio>\\).*\\(<ttl>.*<\\/ttl>\\)\\).*<\\/item>.*/<item><name>\\2<\\/name>\\3\\4\\5\\6<\\/item>/p')\"\n\n      if [ -z \"$new_item\" ]; then\n        # domain apex\n        new_item=\"$(echo \"$item\" | sed -n 's/.*<item>.*\\(<name>\\(.*\\)'\"$_domain_name\"'\\.'\"$_domain_extension\"'<\\/name>.*\\(<type>.*<\\/type>\\).*\\(<value>.*<\\/value>\\).*\\(<prio>.*<\\/prio>\\).*\\(<ttl>.*<\\/ttl>\\)\\).*<\\/item>.*/<item><name>\\2<\\/name>\\3\\4\\5\\6<\\/item>/p')\"\n      fi\n\n      if [ -z \"$(echo \"$new_item\" | _egrep_o \".*<type>(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\\/type>.*\")\" ]; then\n        _debug \"not an allowed record type, skipping\" \"$new_item\"\n        continue\n      fi\n\n      existing_items=\"$existing_items$new_item\"\n    done\n\n    total=\"$(echo \"$response\" | _egrep_o '<total>.*?<\\/total>' | sed -n 's/.*<total>\\(.*\\)<\\/total>.*/\\1/p')\"\n\n    _debug total \"$total\"\n\n    if [ \"$results_retrieved\" -eq \"$total\" ]; then\n      break\n    fi\n  done\n\n  _debug \"Removing acme record\"\n  _openprovider_request \"$(printf '<modifyZoneDnsRequest><domain><name>%s</name><extension>%s</extension></domain><type>master</type><records><array>%s</array></records></modifyZoneDnsRequest>' \"$_domain_name\" \"$_domain_extension\" \"$existing_items\")\"\n\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _domain_name=domain\n# _domain_extension=com\n_get_root() {\n  domain=$1\n  i=2\n\n  results_retrieved=0\n  while true; do\n    h=$(echo \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _openprovider_request \"$(printf '<searchDomainRequest><domainNamePattern>%s</domainNamePattern><offset>%s</offset></searchDomainRequest>' \"$(echo \"$h\" | cut -d . -f 1)\" \"$results_retrieved\")\"\n\n    items=\"$response\"\n    while true; do\n      item=\"$(echo \"$items\" | _egrep_o '<openXML>.*<\\/openXML>' | sed -n 's/.*\\(<domain>.*<\\/domain>\\).*/\\1/p')\"\n      _debug existing_items \"$existing_items\"\n      _debug results_retrieved \"$results_retrieved\"\n      _debug item \"$item\"\n\n      if [ -z \"$item\" ]; then\n        break\n      fi\n\n      tmpitem=\"$(echo \"$item\" | sed 's/\\*/\\\\*/g')\"\n      items=\"$(echo \"$items\" | sed \"s|${tmpitem}||\")\"\n\n      results_retrieved=\"$(_math \"$results_retrieved\" + 1)\"\n\n      _domain_name=\"$(echo \"$item\" | sed -n 's/.*<domain>.*<name>\\(.*\\)<\\/name>.*<\\/domain>.*/\\1/p')\"\n      _domain_extension=\"$(echo \"$item\" | sed -n 's/.*<domain>.*<extension>\\(.*\\)<\\/extension>.*<\\/domain>.*/\\1/p')\"\n      _debug _domain_name \"$_domain_name\"\n      _debug _domain_extension \"$_domain_extension\"\n      if [ \"$_domain_name.$_domain_extension\" = \"$h\" ]; then\n        return 0\n      fi\n    done\n\n    total=\"$(echo \"$response\" | _egrep_o '<total>.*?<\\/total>' | sed -n 's/.*<total>\\(.*\\)<\\/total>.*/\\1/p')\"\n\n    _debug total \"$total\"\n\n    if [ \"$results_retrieved\" -eq \"$total\" ]; then\n      results_retrieved=0\n      i=\"$(_math \"$i\" + 1)\"\n    fi\n  done\n  return 1\n}\n\n_openprovider_request() {\n  request_xml=$1\n\n  xml_prefix='<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n  xml_content=$(printf '<openXML><credentials><username>%s</username><hash>%s</hash></credentials>%s</openXML>' \"$OPENPROVIDER_USER\" \"$OPENPROVIDER_PASSWORDHASH\" \"$request_xml\")\n  response=\"$(_post \"$(echo \"$xml_prefix$xml_content\" | tr -d '\\n')\" \"$OPENPROVIDER_API\" \"\" \"POST\" \"application/xml\")\"\n  _debug response \"$response\"\n  if ! _contains \"$response\" \"<openXML><reply><code>0</code>.*</reply></openXML>\"; then\n    _err \"API request failed.\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_openprovider_rest.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_openprovider_rest_info='OpenProvider (REST)\nDomains: OpenProvider.com\nSite: OpenProvider.eu\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openprovider_rest\nOptions:\n OPENPROVIDER_REST_USERNAME Openprovider Account Username\n OPENPROVIDER_REST_PASSWORD Openprovider Account Password\nIssues: github.com/acmesh-official/acme.sh/issues/6122\nAuthor: Lambiek12\n'\n\nOPENPROVIDER_API_URL=\"https://api.openprovider.eu/v1beta\"\n\n########  Public functions #####################\n\n# Usage: add  _acme-challenge.www.domain.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_openprovider_rest_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _openprovider_prepare_credentials || return 1\n\n  _debug \"Try fetch OpenProvider DNS zone details\"\n  if ! _get_dns_zone \"$fulldomain\"; then\n    _err \"DNS zone not found within configured OpenProvider account.\"\n    return 1\n  fi\n\n  if [ -n \"$_domain_id\" ]; then\n    addzonerecordrequestparameters=\"dns/zones/$_domain_name\"\n    addzonerecordrequestbody=\"{\\\"id\\\":$_domain_id,\\\"name\\\":\\\"$_domain_name\\\",\\\"records\\\":{\\\"add\\\":[{\\\"name\\\":\\\"$_sub_domain\\\",\\\"ttl\\\":900,\\\"type\\\":\\\"TXT\\\",\\\"value\\\":\\\"$txtvalue\\\"}]}}\"\n\n    if _openprovider_rest PUT \"$addzonerecordrequestparameters\" \"$addzonerecordrequestbody\"; then\n      if _contains \"$response\" \"\\\"success\\\":true\"; then\n        return 0\n      elif _contains \"$response\" \"\\\"Duplicate record\\\"\"; then\n        _debug \"Record already existed\"\n        return 0\n      else\n        _err \"Adding TXT record failed due to errors.\"\n        return 1\n      fi\n    fi\n  fi\n\n  _err \"Adding TXT record failed due to errors.\"\n  return 1\n}\n\n# Usage: rm  _acme-challenge.www.domain.com  \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to remove the txt record after validation\ndns_openprovider_rest_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _openprovider_prepare_credentials || return 1\n\n  _debug \"Try fetch OpenProvider DNS zone details\"\n  if ! _get_dns_zone \"$fulldomain\"; then\n    _err \"DNS zone not found within configured OpenProvider account.\"\n    return 1\n  fi\n\n  if [ -n \"$_domain_id\" ]; then\n    removezonerecordrequestparameters=\"dns/zones/$_domain_name\"\n    removezonerecordrequestbody=\"{\\\"id\\\":$_domain_id,\\\"name\\\":\\\"$_domain_name\\\",\\\"records\\\":{\\\"remove\\\":[{\\\"name\\\":\\\"$_sub_domain\\\",\\\"ttl\\\":900,\\\"type\\\":\\\"TXT\\\",\\\"value\\\":\\\"\\\\\\\"$txtvalue\\\\\\\"\\\"}]}}\"\n\n    if _openprovider_rest PUT \"$removezonerecordrequestparameters\" \"$removezonerecordrequestbody\"; then\n      if _contains \"$response\" \"\\\"success\\\":true\"; then\n        return 0\n      else\n        _err \"Removing TXT record failed due to errors.\"\n        return 1\n      fi\n    fi\n  fi\n\n  _err \"Removing TXT record failed due to errors.\"\n  return 1\n}\n\n####################  OpenProvider API common functions  ####################\n_openprovider_prepare_credentials() {\n  OPENPROVIDER_REST_USERNAME=\"${OPENPROVIDER_REST_USERNAME:-$(_readaccountconf_mutable OPENPROVIDER_REST_USERNAME)}\"\n  OPENPROVIDER_REST_PASSWORD=\"${OPENPROVIDER_REST_PASSWORD:-$(_readaccountconf_mutable OPENPROVIDER_REST_PASSWORD)}\"\n\n  if [ -z \"$OPENPROVIDER_REST_USERNAME\" ] || [ -z \"$OPENPROVIDER_REST_PASSWORD\" ]; then\n    OPENPROVIDER_REST_USERNAME=\"\"\n    OPENPROVIDER_REST_PASSWORD=\"\"\n    _err \"You didn't specify the Openprovider username or password yet.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable OPENPROVIDER_REST_USERNAME \"$OPENPROVIDER_REST_USERNAME\"\n  _saveaccountconf_mutable OPENPROVIDER_REST_PASSWORD \"$OPENPROVIDER_REST_PASSWORD\"\n}\n\n_openprovider_rest() {\n  httpmethod=$1\n  queryparameters=$2\n  requestbody=$3\n\n  _openprovider_rest_login\n  if [ -z \"$openproviderauthtoken\" ]; then\n    _err \"Unable to fetch authentication token from Openprovider API.\"\n    return 1\n  fi\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Authorization: Bearer $openproviderauthtoken\"\n\n  if [ \"$httpmethod\" != \"GET\" ]; then\n    response=\"$(_post \"$requestbody\" \"$OPENPROVIDER_API_URL/$queryparameters\" \"\" \"$httpmethod\")\"\n  else\n    response=\"$(_get \"$OPENPROVIDER_API_URL/$queryparameters\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"No valid parameters supplied for Openprovider API: Error $queryparameters\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n\n  return 0\n}\n\n_openprovider_rest_login() {\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Accept: application/json\"\n\n  loginrequesturl=\"$OPENPROVIDER_API_URL/auth/login\"\n  loginrequestbody=\"{\\\"ip\\\":\\\"0.0.0.0\\\",\\\"password\\\":\\\"$OPENPROVIDER_REST_PASSWORD\\\",\\\"username\\\":\\\"$OPENPROVIDER_REST_USERNAME\\\"}\"\n  loginresponse=\"$(_post \"$loginrequestbody\" \"$loginrequesturl\" \"\" \"POST\")\"\n\n  openproviderauthtoken=\"$(printf \"%s\\n\" \"$loginresponse\" | _egrep_o '\"token\" *: *\"[^\"]*' | _head_n 1 | sed 's#^\"token\" *: *\"##')\"\n\n  export openproviderauthtoken\n}\n\n####################  Private functions ##################################\n\n# Usage: _get_dns_zone _acme-challenge.www.domain.com\n# Returns:\n# _domain_id=123456789\n# _domain_name=domain.com\n# _sub_domain=_acme-challenge.www\n_get_dns_zone() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      # Empty value not allowed\n      return 1\n    fi\n\n    if ! _openprovider_rest GET \"dns/zones/$h\" \"\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _domain_id=\"$(printf \"%s\\n\" \"$response\" | _egrep_o '\"id\" *: *[^,]*' | _head_n 1 | sed 's#^\"id\" *: *##')\"\n      _debug _domain_id \"$_domain_id\"\n\n      _domain_name=\"$h\"\n      _debug _domain_name \"$_domain_name\"\n\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _debug _sub_domain \"$_sub_domain\"\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_openstack.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_openstack_info='OpenStack Designate API\n Depends on OpenStackClient and python-desginateclient.\n You will require Keystone V3 credentials loaded into your environment,\n which could be either password or v3 application credential type.\nSite: docs.openstack.org/api-ref/dns/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openstack\nOptions:\n OS_AUTH_URL Auth URL. E.g. \"https://keystone.example.com:5000/\"\n OS_USERNAME Username\n OS_PASSWORD Password\n OS_PROJECT_NAME Project name\n OS_PROJECT_DOMAIN_NAME Project domain name. E.g. \"Default\"\n OS_USER_DOMAIN_NAME User domain name. E.g. \"Default\"\nIssues: github.com/acmesh-official/acme.sh/issues/3054\nAuthor: Andy Botting <andy@andybotting.com>\n'\n\n########  Public functions #####################\n\n# Usage: dns_openstack_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_openstack_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _dns_openstack_credentials || return $?\n  _dns_openstack_check_setup || return $?\n  _dns_openstack_find_zone || return $?\n  _dns_openstack_get_recordset || return $?\n  _debug _recordset_id \"$_recordset_id\"\n  if [ -n \"$_recordset_id\" ]; then\n    _dns_openstack_get_records || return $?\n    _debug _records \"$_records\"\n  fi\n  _dns_openstack_create_recordset || return $?\n}\n\n# Usage: dns_openstack_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Remove the txt record after validation.\ndns_openstack_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _dns_openstack_credentials || return $?\n  _dns_openstack_check_setup || return $?\n  _dns_openstack_find_zone || return $?\n  _dns_openstack_get_recordset || return $?\n  _debug _recordset_id \"$_recordset_id\"\n  if [ -n \"$_recordset_id\" ]; then\n    _dns_openstack_get_records || return $?\n    _debug _records \"$_records\"\n  fi\n  _dns_openstack_delete_recordset || return $?\n}\n\n####################  Private functions below ##################################\n\n_dns_openstack_create_recordset() {\n\n  if [ -z \"$_recordset_id\" ]; then\n    _info \"Creating a new recordset\"\n    if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record=\"$txtvalue\" \"$_zone_id\" \"$fulldomain.\"); then\n      _err \"No recordset ID found after create\"\n      return 1\n    fi\n  else\n    _info \"Updating existing recordset\"\n    # Build new list of --record=<rec> args for update\n    _record_args=\"--record=$txtvalue\"\n    for _rec in $_records; do\n      _record_args=\"$_record_args --record=$_rec\"\n    done\n    # shellcheck disable=SC2086\n    if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args \"$_zone_id\" \"$fulldomain.\"); then\n      _err \"Recordset update failed\"\n      return 1\n    fi\n  fi\n\n  _max_retries=60\n  _sleep_sec=5\n  _retry_times=0\n  while [ \"$_retry_times\" -lt \"$_max_retries\" ]; do\n    _retry_times=$(_math \"$_retry_times\" + 1)\n    _debug3 _retry_times \"$_retry_times\"\n\n    _record_status=$(openstack recordset show -c status -f value \"$_zone_id\" \"$_recordset_id\")\n    _info \"Recordset status is $_record_status\"\n    if [ \"$_record_status\" = \"ACTIVE\" ]; then\n      return 0\n    elif [ \"$_record_status\" = \"ERROR\" ]; then\n      return 1\n    else\n      _sleep $_sleep_sec\n    fi\n  done\n\n  _err \"Recordset failed to become ACTIVE\"\n  return 1\n}\n\n_dns_openstack_delete_recordset() {\n\n  if [ \"$_records\" = \"$txtvalue\" ]; then\n    _info \"Only one record found, deleting recordset\"\n    if ! openstack recordset delete \"$_zone_id\" \"$fulldomain.\" >/dev/null; then\n      _err \"Failed to delete recordset\"\n      return 1\n    fi\n  else\n    _info \"Found existing records, updating recordset\"\n    # Build new list of --record=<rec> args for update\n    _record_args=\"\"\n    for _rec in $_records; do\n      if [ \"$_rec\" = \"$txtvalue\" ]; then\n        continue\n      fi\n      _record_args=\"$_record_args --record=$_rec\"\n    done\n    # shellcheck disable=SC2086\n    if ! openstack recordset set -c id -f value $_record_args \"$_zone_id\" \"$fulldomain.\" >/dev/null; then\n      _err \"Recordset update failed\"\n      return 1\n    fi\n  fi\n}\n\n_dns_openstack_get_root() {\n  # Take the full fqdn and strip away pieces until we get an exact zone name\n  # match. For example, _acme-challenge.something.domain.com might need to go\n  # into something.domain.com or domain.com\n  _zone_name=$1\n  _zone_list=$2\n  while [ \"$_zone_name\" != \"\" ]; do\n    _zone_name=\"$(echo \"$_zone_name\" | sed 's/[^.]*\\.*//')\"\n    echo \"$_zone_list\" | while read -r id name; do\n      if _startswith \"$_zone_name.\" \"$name\"; then\n        echo \"$id\"\n      fi\n    done\n  done | _head_n 1\n}\n\n_dns_openstack_find_zone() {\n  if ! _zone_list=\"$(openstack zone list -c id -c name -f value)\"; then\n    _err \"Can't list zones. Check your OpenStack credentials\"\n    return 1\n  fi\n  _debug _zone_list \"$_zone_list\"\n\n  if ! _zone_id=\"$(_dns_openstack_get_root \"$fulldomain\" \"$_zone_list\")\"; then\n    _err \"Can't find a matching zone. Check your OpenStack credentials\"\n    return 1\n  fi\n  _debug _zone_id \"$_zone_id\"\n}\n\n_dns_openstack_get_records() {\n  if ! _records=$(openstack recordset show -c records -f value \"$_zone_id\" \"$fulldomain.\"); then\n    _err \"Failed to get records\"\n    return 1\n  fi\n  return 0\n}\n\n_dns_openstack_get_recordset() {\n  if ! _recordset_id=$(openstack recordset list -c id -f value --name \"$fulldomain.\" \"$_zone_id\"); then\n    _err \"Failed to get recordset\"\n    return 1\n  fi\n  return 0\n}\n\n_dns_openstack_check_setup() {\n  if ! _exists openstack; then\n    _err \"OpenStack client not found\"\n    return 1\n  fi\n}\n\n_dns_openstack_credentials() {\n  _debug \"Check OpenStack credentials\"\n\n  # If we have OS_AUTH_URL already set in the environment, then assume we want\n  # to use those, otherwise use stored credentials\n  if [ -n \"$OS_AUTH_URL\" ]; then\n    _debug \"OS_AUTH_URL env var found, using environment\"\n  else\n    _debug \"OS_AUTH_URL not found, loading stored credentials\"\n    OS_AUTH_URL=\"${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}\"\n    OS_IDENTITY_API_VERSION=\"${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}\"\n    OS_AUTH_TYPE=\"${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}\"\n    OS_APPLICATION_CREDENTIAL_ID=\"${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}\"\n    OS_APPLICATION_CREDENTIAL_SECRET=\"${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}\"\n    OS_USERNAME=\"${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}\"\n    OS_PASSWORD=\"${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}\"\n    OS_PROJECT_NAME=\"${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}\"\n    OS_PROJECT_ID=\"${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}\"\n    OS_USER_DOMAIN_NAME=\"${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}\"\n    OS_USER_DOMAIN_ID=\"${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}\"\n    OS_PROJECT_DOMAIN_NAME=\"${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}\"\n    OS_PROJECT_DOMAIN_ID=\"${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}\"\n  fi\n\n  # Check each var and either save or clear it depending on whether its set.\n  # The helps us clear out old vars in the case where a user may want\n  # to switch between password and app creds\n  _debug \"OS_AUTH_URL\" \"$OS_AUTH_URL\"\n  if [ -n \"$OS_AUTH_URL\" ]; then\n    export OS_AUTH_URL\n    _saveaccountconf_mutable OS_AUTH_URL \"$OS_AUTH_URL\"\n  else\n    unset OS_AUTH_URL\n    _clearaccountconf SAVED_OS_AUTH_URL\n  fi\n\n  _debug \"OS_IDENTITY_API_VERSION\" \"$OS_IDENTITY_API_VERSION\"\n  if [ -n \"$OS_IDENTITY_API_VERSION\" ]; then\n    export OS_IDENTITY_API_VERSION\n    _saveaccountconf_mutable OS_IDENTITY_API_VERSION \"$OS_IDENTITY_API_VERSION\"\n  else\n    unset OS_IDENTITY_API_VERSION\n    _clearaccountconf SAVED_OS_IDENTITY_API_VERSION\n  fi\n\n  _debug \"OS_AUTH_TYPE\" \"$OS_AUTH_TYPE\"\n  if [ -n \"$OS_AUTH_TYPE\" ]; then\n    export OS_AUTH_TYPE\n    _saveaccountconf_mutable OS_AUTH_TYPE \"$OS_AUTH_TYPE\"\n  else\n    unset OS_AUTH_TYPE\n    _clearaccountconf SAVED_OS_AUTH_TYPE\n  fi\n\n  _debug \"OS_APPLICATION_CREDENTIAL_ID\" \"$OS_APPLICATION_CREDENTIAL_ID\"\n  if [ -n \"$OS_APPLICATION_CREDENTIAL_ID\" ]; then\n    export OS_APPLICATION_CREDENTIAL_ID\n    _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID \"$OS_APPLICATION_CREDENTIAL_ID\"\n  else\n    unset OS_APPLICATION_CREDENTIAL_ID\n    _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID\n  fi\n\n  _secure_debug \"OS_APPLICATION_CREDENTIAL_SECRET\" \"$OS_APPLICATION_CREDENTIAL_SECRET\"\n  if [ -n \"$OS_APPLICATION_CREDENTIAL_SECRET\" ]; then\n    export OS_APPLICATION_CREDENTIAL_SECRET\n    _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET \"$OS_APPLICATION_CREDENTIAL_SECRET\"\n  else\n    unset OS_APPLICATION_CREDENTIAL_SECRET\n    _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET\n  fi\n\n  _debug \"OS_USERNAME\" \"$OS_USERNAME\"\n  if [ -n \"$OS_USERNAME\" ]; then\n    export OS_USERNAME\n    _saveaccountconf_mutable OS_USERNAME \"$OS_USERNAME\"\n  else\n    unset OS_USERNAME\n    _clearaccountconf SAVED_OS_USERNAME\n  fi\n\n  _secure_debug \"OS_PASSWORD\" \"$OS_PASSWORD\"\n  if [ -n \"$OS_PASSWORD\" ]; then\n    export OS_PASSWORD\n    _saveaccountconf_mutable OS_PASSWORD \"$OS_PASSWORD\"\n  else\n    unset OS_PASSWORD\n    _clearaccountconf SAVED_OS_PASSWORD\n  fi\n\n  _debug \"OS_PROJECT_NAME\" \"$OS_PROJECT_NAME\"\n  if [ -n \"$OS_PROJECT_NAME\" ]; then\n    export OS_PROJECT_NAME\n    _saveaccountconf_mutable OS_PROJECT_NAME \"$OS_PROJECT_NAME\"\n  else\n    unset OS_PROJECT_NAME\n    _clearaccountconf SAVED_OS_PROJECT_NAME\n  fi\n\n  _debug \"OS_PROJECT_ID\" \"$OS_PROJECT_ID\"\n  if [ -n \"$OS_PROJECT_ID\" ]; then\n    export OS_PROJECT_ID\n    _saveaccountconf_mutable OS_PROJECT_ID \"$OS_PROJECT_ID\"\n  else\n    unset OS_PROJECT_ID\n    _clearaccountconf SAVED_OS_PROJECT_ID\n  fi\n\n  _debug \"OS_USER_DOMAIN_NAME\" \"$OS_USER_DOMAIN_NAME\"\n  if [ -n \"$OS_USER_DOMAIN_NAME\" ]; then\n    export OS_USER_DOMAIN_NAME\n    _saveaccountconf_mutable OS_USER_DOMAIN_NAME \"$OS_USER_DOMAIN_NAME\"\n  else\n    unset OS_USER_DOMAIN_NAME\n    _clearaccountconf SAVED_OS_USER_DOMAIN_NAME\n  fi\n\n  _debug \"OS_USER_DOMAIN_ID\" \"$OS_USER_DOMAIN_ID\"\n  if [ -n \"$OS_USER_DOMAIN_ID\" ]; then\n    export OS_USER_DOMAIN_ID\n    _saveaccountconf_mutable OS_USER_DOMAIN_ID \"$OS_USER_DOMAIN_ID\"\n  else\n    unset OS_USER_DOMAIN_ID\n    _clearaccountconf SAVED_OS_USER_DOMAIN_ID\n  fi\n\n  _debug \"OS_PROJECT_DOMAIN_NAME\" \"$OS_PROJECT_DOMAIN_NAME\"\n  if [ -n \"$OS_PROJECT_DOMAIN_NAME\" ]; then\n    export OS_PROJECT_DOMAIN_NAME\n    _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME \"$OS_PROJECT_DOMAIN_NAME\"\n  else\n    unset OS_PROJECT_DOMAIN_NAME\n    _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME\n  fi\n\n  _debug \"OS_PROJECT_DOMAIN_ID\" \"$OS_PROJECT_DOMAIN_ID\"\n  if [ -n \"$OS_PROJECT_DOMAIN_ID\" ]; then\n    export OS_PROJECT_DOMAIN_ID\n    _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID \"$OS_PROJECT_DOMAIN_ID\"\n  else\n    unset OS_PROJECT_DOMAIN_ID\n    _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID\n  fi\n\n  if [ \"$OS_AUTH_TYPE\" = \"v3applicationcredential\" ]; then\n    # Application Credential auth\n    if [ -z \"$OS_APPLICATION_CREDENTIAL_ID\" ] || [ -z \"$OS_APPLICATION_CREDENTIAL_SECRET\" ]; then\n      _err \"When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID\"\n      _err \"and OS_APPLICATION_CREDENTIAL_SECRET must be set.\"\n      _err \"Please check your credentials and try again.\"\n      return 1\n    fi\n  else\n    # Password auth\n    if [ -z \"$OS_USERNAME\" ] || [ -z \"$OS_PASSWORD\" ]; then\n      _err \"OpenStack username or password not found.\"\n      _err \"Please check your credentials and try again.\"\n      return 1\n    fi\n\n    if [ -z \"$OS_PROJECT_NAME\" ] && [ -z \"$OS_PROJECT_ID\" ]; then\n      _err \"When using password authentication, OS_PROJECT_NAME or\"\n      _err \"OS_PROJECT_ID must be set.\"\n      _err \"Please check your credentials and try again.\"\n      return 1\n    fi\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_opnsense.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_opnsense_info='OPNsense Server\nSite: docs.opnsense.org/development/api.html\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_opnsense\nOptions:\n OPNs_Host Server Hostname. E.g. \"opnsense.example.com\"\n OPNs_Port Port. Default: \"443\".\n OPNs_Key API Key\n OPNs_Token API Token\n OPNs_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept\nIssues: github.com/acmesh-official/acme.sh/issues/2480\n'\n\n########  Public functions #####################\n#Usage: add _acme-challenge.www.domain.com \"123456789ABCDEF0000000000000000000000000000000000000\"\n#fulldomain\n#txtvalue\nOPNs_DefaultPort=443\nOPNs_DefaultApi_Insecure=0\n\ndns_opnsense_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _opns_check_auth || return 1\n\n  if ! set_record \"$fulldomain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n#fulldomain\ndns_opnsense_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _opns_check_auth || return 1\n\n  if ! rm_record \"$fulldomain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n  return 0\n}\n\nset_record() {\n  fulldomain=$1\n  new_challenge=$2\n  _info \"Adding record $fulldomain with challenge: $new_challenge\"\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain \"$_domain\"\n  _debug _host \"$_host\"\n  _debug _domainid \"$_domainid\"\n  _return_str=\"\"\n  _record_string=\"\"\n  _build_record_string \"$_domainid\" \"$_host\" \"$new_challenge\"\n  _uuid=\"\"\n  if _existingchallenge \"$_domain\" \"$_host\" \"$new_challenge\"; then\n    # Update\n    if _opns_rest \"POST\" \"/record/setRecord/${_uuid}\" \"$_record_string\"; then\n      _return_str=\"$response\"\n    else\n      return 1\n    fi\n\n  else\n    #create\n    if _opns_rest \"POST\" \"/record/addRecord\" \"$_record_string\"; then\n      _return_str=\"$response\"\n    else\n      return 1\n    fi\n  fi\n\n  if echo \"$_return_str\" | _egrep_o \"\\\"result\\\":\\\"saved\\\"\" >/dev/null; then\n    _opns_rest \"POST\" \"/service/reconfigure\" \"{}\"\n    _debug \"Record created\"\n  else\n    _err \"Error creating record $_record_string\"\n    return 1\n  fi\n\n  return 0\n}\n\nrm_record() {\n  fulldomain=$1\n  new_challenge=\"$2\"\n  _info \"Remove record $fulldomain with challenge: $new_challenge\"\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain \"$_domain\"\n  _debug _host \"$_host\"\n  _debug _domainid \"$_domainid\"\n  _uuid=\"\"\n  if _existingchallenge \"$_domain\" \"$_host\" \"$new_challenge\"; then\n    # Delete\n    if _opns_rest \"POST\" \"/record/delRecord/${_uuid}\" \"\\{\\}\"; then\n      if echo \"$response\" | _egrep_o \"\\\"result\\\":\\\"deleted\\\"\" >/dev/null; then\n        _debug \"Record deleted\"\n        _opns_rest \"POST\" \"/service/reconfigure\" \"{}\"\n        _debug \"Service reconfigured\"\n      else\n        _err \"Error deleting record $_host from domain $fulldomain\"\n        return 1\n      fi\n    else\n      _err \"Error requesting deletion of record $_host from domain $fulldomain\"\n      return 1\n    fi\n  else\n    _info \"Record not found, nothing to remove\"\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _domainid=domid\n#_domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  if _opns_rest \"GET\" \"/domain/searchPrimaryDomain\"; then\n    _domain_response=\"$response\"\n  else\n    return 1\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    _debug h \"$h\"\n    lines=$(echo \"$_domain_response\" | sed 's/{/\\n/g')\n    for line in $lines; do\n      id=$(echo \"$line\" | _egrep_o \"\\\"uuid\\\":\\\"[a-z0-9\\-]*\\\",\\\"enabled\\\":\\\"1\\\",\\\"type\\\":\\\"primary\\\",.*\\\"domainname\\\":\\\"${h}\\\"\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n      if [ -n \"$id\" ]; then\n        _debug id \"$id\"\n        _host=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"${h}\"\n        _domainid=\"${id}\"\n        return 0\n      fi\n    done\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  _debug \"$domain not found\"\n\n  return 1\n}\n\n_opns_rest() {\n  method=$1\n  ep=$2\n  data=$3\n  #Percent encode user and token\n  key=$(echo \"$OPNs_Key\" | tr -d \"\\n\\r\" | _url_encode)\n  token=$(echo \"$OPNs_Token\" | tr -d \"\\n\\r\" | _url_encode)\n\n  opnsense_url=\"https://${key}:${token}@${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}\"\n  export _H1=\"Content-Type: application/json\"\n  _debug2 \"Try to call api: https://${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}\"\n  if [ ! \"$method\" = \"GET\" ]; then\n    _debug data \"$data\"\n    export _H1=\"Content-Type: application/json\"\n    response=\"$(_post \"$data\" \"$opnsense_url\" \"\" \"$method\")\"\n  else\n    export _H1=\"\"\n    response=\"$(_get \"$opnsense_url\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n\n  return 0\n}\n\n_build_record_string() {\n  _record_string=\"{\\\"record\\\":{\\\"enabled\\\":\\\"1\\\",\\\"domain\\\":\\\"$1\\\",\\\"name\\\":\\\"$2\\\",\\\"type\\\":\\\"TXT\\\",\\\"value\\\":\\\"$3\\\"}}\"\n}\n\n_existingchallenge() {\n  if _opns_rest \"GET\" \"/record/searchRecord\"; then\n    _record_response=\"$response\"\n  else\n    return 1\n  fi\n  _uuid=\"\"\n  _uuid=$(echo \"$_record_response\" | _egrep_o \"\\\"uuid\\\":\\\"[a-z0-9\\-]*\\\",\\\"enabled\\\":\\\"[01]\\\",\\\"domain\\\":\\\"[a-z0-9\\-]*\\\",\\\"%domain\\\":\\\"$1\\\",\\\"name\\\":\\\"$2\\\",\\\"type\\\":\\\"TXT\\\",\\\"value\\\":\\\"$3\\\"\" | cut -d ':' -f 2 | cut -d '\"' -f 2)\n\n  if [ -n \"$_uuid\" ]; then\n    _debug uuid \"$_uuid\"\n    return 0\n  fi\n  _debug \"${2}.${1} record not found\"\n\n  return 1\n}\n\n_opns_check_auth() {\n  OPNs_Host=\"${OPNs_Host:-$(_readaccountconf_mutable OPNs_Host)}\"\n  OPNs_Port=\"${OPNs_Port:-$(_readaccountconf_mutable OPNs_Port)}\"\n  OPNs_Key=\"${OPNs_Key:-$(_readaccountconf_mutable OPNs_Key)}\"\n  OPNs_Token=\"${OPNs_Token:-$(_readaccountconf_mutable OPNs_Token)}\"\n  OPNs_Api_Insecure=\"${OPNs_Api_Insecure:-$(_readaccountconf_mutable OPNs_Api_Insecure)}\"\n\n  if [ -z \"$OPNs_Host\" ]; then\n    _err \"You don't specify OPNsense address.\"\n    return 1\n  else\n    _saveaccountconf_mutable OPNs_Host \"$OPNs_Host\"\n  fi\n\n  if ! printf '%s' \"$OPNs_Port\" | grep '^[0-9]*$' >/dev/null; then\n    _err 'OPNs_Port specified but not numeric value'\n    return 1\n  elif [ -z \"$OPNs_Port\" ]; then\n    _info \"OPNSense port not specified. Defaulting to using port $OPNs_DefaultPort\"\n  else\n    _saveaccountconf_mutable OPNs_Port \"$OPNs_Port\"\n  fi\n\n  if ! printf '%s' \"$OPNs_Api_Insecure\" | grep '^[01]$' >/dev/null; then\n    _err 'OPNs_Api_Insecure specified but not 0/1 value'\n    return 1\n  elif [ -n \"$OPNs_Api_Insecure\" ]; then\n    _saveaccountconf_mutable OPNs_Api_Insecure \"$OPNs_Api_Insecure\"\n  fi\n  export HTTPS_INSECURE=\"${OPNs_Api_Insecure:-$OPNs_DefaultApi_Insecure}\"\n\n  if [ -z \"$OPNs_Key\" ]; then\n    _err \"you have not specified your OPNsense api key id.\"\n    _err \"Please set OPNs_Key and try again.\"\n    return 1\n  else\n    _saveaccountconf_mutable OPNs_Key \"$OPNs_Key\"\n  fi\n\n  if [ -z \"$OPNs_Token\" ]; then\n    _err \"you have not specified your OPNsense token.\"\n    _err \"Please create OPNs_Token and try again.\"\n    return 1\n  else\n    _saveaccountconf_mutable OPNs_Token \"$OPNs_Token\"\n  fi\n\n  if ! _opns_rest \"GET\" \"/general/get\"; then\n    _err \"Call to OPNsense API interface failed. Unable to access OPNsense API.\"\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_opusdns.sh",
    "content": "#!/usr/bin/env sh\n\n# shellcheck disable=SC2034\ndns_opusdns_info='OpusDNS.com\nSite: OpusDNS.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_opusdns\nOptions:\n OPUSDNS_API_Key API Key. Can be created at https://dashboard.opusdns.com/settings/api-keys\n OPUSDNS_API_Endpoint API Endpoint URL. Default \"https://api.opusdns.com\". Optional.\n OPUSDNS_TTL TTL for DNS challenge records in seconds. Default \"60\". Optional.\nIssues: github.com/acmesh-official/acme.sh/issues/XXXX\nAuthor: OpusDNS Team <https://github.com/opusdns>\n'\n\nOPUSDNS_API_Endpoint_Default=\"https://api.opusdns.com\"\nOPUSDNS_TTL_Default=60\n\n######## Public functions ###########\n\n# Add DNS TXT record\ndns_opusdns_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using OpusDNS DNS API\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _opusdns_init; then\n    return 1\n  fi\n\n  if ! _get_zone \"$fulldomain\"; then\n    return 1\n  fi\n\n  _info \"Zone: $_zone, Record: $_record_name\"\n\n  if ! _opusdns_api PATCH \"/v1/dns/$_zone/records\" \"{\\\"ops\\\":[{\\\"op\\\":\\\"upsert\\\",\\\"record\\\":{\\\"name\\\":\\\"$_record_name\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":$OPUSDNS_TTL,\\\"rdata\\\":\\\"\\\\\\\"$txtvalue\\\\\\\"\\\"}}]}\"; then\n    _err \"Failed to add TXT record\"\n    return 1\n  fi\n\n  _info \"TXT record added successfully\"\n  return 0\n}\n\n# Remove DNS TXT record\ndns_opusdns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Removing OpusDNS DNS record\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  if ! _opusdns_init; then\n    return 1\n  fi\n\n  if ! _get_zone \"$fulldomain\"; then\n    _err \"Zone not found, cleanup skipped\"\n    return 0\n  fi\n\n  _info \"Zone: $_zone, Record: $_record_name\"\n\n  if ! _opusdns_api PATCH \"/v1/dns/$_zone/records\" \"{\\\"ops\\\":[{\\\"op\\\":\\\"remove\\\",\\\"record\\\":{\\\"name\\\":\\\"$_record_name\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":$OPUSDNS_TTL,\\\"rdata\\\":\\\"\\\\\\\"$txtvalue\\\\\\\"\\\"}}]}\"; then\n    _err \"Warning: Failed to remove TXT record\"\n    return 0\n  fi\n\n  _info \"TXT record removed successfully\"\n  return 0\n}\n\n######## Private functions ###########\n\n# Initialize and validate configuration\n_opusdns_init() {\n  OPUSDNS_API_Key=\"${OPUSDNS_API_Key:-$(_readaccountconf_mutable OPUSDNS_API_Key)}\"\n  OPUSDNS_API_Endpoint=\"${OPUSDNS_API_Endpoint:-$(_readaccountconf_mutable OPUSDNS_API_Endpoint)}\"\n  OPUSDNS_TTL=\"${OPUSDNS_TTL:-$(_readaccountconf_mutable OPUSDNS_TTL)}\"\n\n  if [ -z \"$OPUSDNS_API_Key\" ]; then\n    _err \"OPUSDNS_API_Key not set\"\n    return 1\n  fi\n\n  [ -z \"$OPUSDNS_API_Endpoint\" ] && OPUSDNS_API_Endpoint=\"$OPUSDNS_API_Endpoint_Default\"\n  [ -z \"$OPUSDNS_TTL\" ] && OPUSDNS_TTL=\"$OPUSDNS_TTL_Default\"\n\n  _saveaccountconf_mutable OPUSDNS_API_Key \"$OPUSDNS_API_Key\"\n  _saveaccountconf_mutable OPUSDNS_API_Endpoint \"$OPUSDNS_API_Endpoint\"\n  _saveaccountconf_mutable OPUSDNS_TTL \"$OPUSDNS_TTL\"\n\n  _debug \"Endpoint: $OPUSDNS_API_Endpoint\"\n  return 0\n}\n\n# Make API request\n# Usage: _opusdns_api METHOD PATH [DATA]\n_opusdns_api() {\n  method=$1\n  path=$2\n  data=$3\n\n  export _H1=\"X-Api-Key: $OPUSDNS_API_Key\"\n  export _H2=\"Content-Type: application/json\"\n\n  url=\"$OPUSDNS_API_Endpoint$path\"\n  _debug2 \"API: $method $url\"\n  [ -n \"$data\" ] && _debug2 \"Data: $data\"\n\n  if [ -n \"$data\" ]; then\n    response=$(_post \"$data\" \"$url\" \"\" \"$method\")\n  else\n    response=$(_get \"$url\")\n  fi\n\n  if [ $? -ne 0 ]; then\n    _err \"API request failed\"\n    _debug \"Response: $response\"\n    return 1\n  fi\n\n  _debug2 \"Response: $response\"\n  return 0\n}\n\n# Detect zone from FQDN\n# Sets: _zone, _record_name\n_get_zone() {\n  domain=$(echo \"$1\" | sed 's/\\.$//')\n  _debug \"Finding zone for: $domain\"\n\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    if [ -z \"$h\" ]; then\n      _err \"No valid zone found for: $domain\"\n      return 1\n    fi\n\n    _debug \"Trying: $h\"\n    if _opusdns_api GET \"/v1/dns/$h\" && _contains \"$response\" '\"dnssec_status\"'; then\n      _zone=\"$h\"\n      _record_name=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      [ -z \"$_record_name\" ] && _record_name=\"@\"\n      return 0\n    fi\n\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n}\n"
  },
  {
    "path": "dnsapi/dns_ovh.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ovh_info='OVH.com\nDomains: kimsufi.com soyoustart.com\nSite: OVH.com\nDocs: github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api\nOptions:\n OVH_END_POINT Endpoint. \"ovh-eu\", \"ovh-us\", \"ovh-ca\", \"kimsufi-eu\", \"kimsufi-ca\", \"soyoustart-eu\", \"soyoustart-ca\" or raw URL. Default: \"ovh-eu\".\n OVH_AK Application Key\n OVH_AS Application Secret\n OVH_CK Consumer Key\n'\n\n#OVH_END_POINT=ovh-eu\n\n#'ovh-eu'\nOVH_EU='https://eu.api.ovh.com/1.0'\n\n#'ovh-us'\nOVH_US='https://api.us.ovhcloud.com/1.0'\n\n#'ovh-ca':\nOVH_CA='https://ca.api.ovh.com/1.0'\n\n#'kimsufi-eu'\nKSF_EU='https://eu.api.kimsufi.com/1.0'\n\n#'kimsufi-ca'\nKSF_CA='https://ca.api.kimsufi.com/1.0'\n\n#'soyoustart-eu'\nSYS_EU='https://eu.api.soyoustart.com/1.0'\n\n#'soyoustart-ca'\nSYS_CA='https://ca.api.soyoustart.com/1.0'\n\nwiki=\"https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api\"\n\novh_success=\"https://github.com/acmesh-official/acme.sh/wiki/OVH-Success\"\n\n_ovh_get_api() {\n  _ogaep=\"$1\"\n\n  case \"${_ogaep}\" in\n\n  ovh-eu | ovheu)\n    printf \"%s\" $OVH_EU\n    return\n    ;;\n  ovh-us | ovhus)\n    printf \"%s\" $OVH_US\n    return\n    ;;\n  ovh-ca | ovhca)\n    printf \"%s\" $OVH_CA\n    return\n    ;;\n  kimsufi-eu | kimsufieu)\n    printf \"%s\" $KSF_EU\n    return\n    ;;\n  kimsufi-ca | kimsufica)\n    printf \"%s\" $KSF_CA\n    return\n    ;;\n  soyoustart-eu | soyoustarteu)\n    printf \"%s\" $SYS_EU\n    return\n    ;;\n  soyoustart-ca | soyoustartca)\n    printf \"%s\" $SYS_CA\n    return\n    ;;\n  # raw API url starts with https://\n  https*)\n    printf \"%s\" \"$1\"\n    return\n    ;;\n\n  *)\n\n    _err \"Unknown endpoint : $1\"\n    return 1\n    ;;\n  esac\n}\n\n_initAuth() {\n  OVH_AK=\"${OVH_AK:-$(_readaccountconf_mutable OVH_AK)}\"\n  OVH_AS=\"${OVH_AS:-$(_readaccountconf_mutable OVH_AS)}\"\n\n  if [ -z \"$OVH_AK\" ] || [ -z \"$OVH_AS\" ]; then\n    OVH_AK=\"\"\n    OVH_AS=\"\"\n    _err \"You don't specify OVH application key and application secret yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  if [ \"$OVH_AK\" != \"$(_readaccountconf OVH_AK)\" ]; then\n    _info \"It seems that your ovh key is changed, let's clear consumer key first.\"\n    _clearaccountconf_mutable OVH_CK\n  fi\n  _saveaccountconf_mutable OVH_AK \"$OVH_AK\"\n  _saveaccountconf_mutable OVH_AS \"$OVH_AS\"\n\n  OVH_END_POINT=\"${OVH_END_POINT:-$(_readaccountconf_mutable OVH_END_POINT)}\"\n  if [ -z \"$OVH_END_POINT\" ]; then\n    OVH_END_POINT=\"ovh-eu\"\n  fi\n  _info \"Using OVH endpoint: $OVH_END_POINT\"\n  if [ \"$OVH_END_POINT\" != \"ovh-eu\" ]; then\n    _saveaccountconf_mutable OVH_END_POINT \"$OVH_END_POINT\"\n  fi\n\n  OVH_API=\"$(_ovh_get_api \"$OVH_END_POINT\")\"\n  _debug OVH_API \"$OVH_API\"\n\n  OVH_CK=\"${OVH_CK:-$(_readaccountconf_mutable OVH_CK)}\"\n  if [ -z \"$OVH_CK\" ]; then\n    _info \"OVH consumer key is empty, Let's get one:\"\n    if ! _ovh_authentication; then\n      _err \"Can not get consumer key.\"\n    fi\n    #return and wait for retry.\n    return 1\n  fi\n  _saveaccountconf_mutable OVH_CK \"$OVH_CK\"\n\n  _info \"Checking authentication\"\n\n  if ! _ovh_rest GET \"domain\" || _contains \"$response\" \"INVALID_CREDENTIAL\" || _contains \"$response\" \"NOT_CREDENTIAL\"; then\n    _err \"The consumer key is invalid: $OVH_CK\"\n    _err \"Please retry to create a new one.\"\n    _clearaccountconf_mutable OVH_CK\n    return 1\n  fi\n  _info \"Consumer key is ok.\"\n  return 0\n}\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_ovh_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _initAuth; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _ovh_rest POST \"domain/zone/$_domain/record\" \"{\\\"fieldType\\\":\\\"TXT\\\",\\\"subDomain\\\":\\\"$_sub_domain\\\",\\\"target\\\":\\\"$txtvalue\\\",\\\"ttl\\\":60}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _ovh_rest POST \"domain/zone/$_domain/refresh\"\n      _debug \"Refresh:$response\"\n      _info \"Added, sleep 10 seconds.\"\n      _sleep 10\n      return 0\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain\ndns_ovh_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _initAuth; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug \"Getting txt records\"\n  if ! _ovh_rest GET \"domain/zone/$_domain/record?fieldType=TXT&subDomain=$_sub_domain\"; then\n    return 1\n  fi\n\n  for rid in $(echo \"$response\" | tr '][,' '   '); do\n    _debug rid \"$rid\"\n    if ! _ovh_rest GET \"domain/zone/$_domain/record/$rid\"; then\n      return 1\n    fi\n    if _contains \"$response\" \"$txtvalue\"; then\n      _debug \"Found txt id:$rid\"\n      if ! _ovh_rest DELETE \"domain/zone/$_domain/record/$rid\"; then\n        return 1\n      fi\n      _ovh_rest POST \"domain/zone/$_domain/refresh\"\n      _debug \"Refresh:$response\"\n      return 0\n    fi\n  done\n\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_ovh_authentication() {\n\n  _H1=\"X-Ovh-Application: $OVH_AK\"\n  _H2=\"Content-type: application/json\"\n  _H3=\"\"\n  _H4=\"\"\n\n  _ovhdata='{\"accessRules\": [{\"method\": \"GET\",\"path\": \"/auth/time\"},{\"method\": \"GET\",\"path\": \"/domain\"},{\"method\": \"GET\",\"path\": \"/domain/zone/*\"},{\"method\": \"GET\",\"path\": \"/domain/zone/*/record\"},{\"method\": \"POST\",\"path\": \"/domain/zone/*/record\"},{\"method\": \"POST\",\"path\": \"/domain/zone/*/refresh\"},{\"method\": \"PUT\",\"path\": \"/domain/zone/*/record/*\"},{\"method\": \"DELETE\",\"path\": \"/domain/zone/*/record/*\"}],\"redirection\":\"'$ovh_success'\"}'\n\n  response=\"$(_post \"$_ovhdata\" \"$OVH_API/auth/credential\")\"\n  _debug3 response \"$response\"\n  validationUrl=\"$(echo \"$response\" | _egrep_o \"validationUrl\\\":\\\"[^\\\"]*\\\"\" | _egrep_o \"http.*\\\"\" | tr -d '\"')\"\n  if [ -z \"$validationUrl\" ]; then\n    _err \"Unable to get validationUrl\"\n    return 1\n  fi\n  _debug validationUrl \"$validationUrl\"\n\n  consumerKey=\"$(echo \"$response\" | _egrep_o \"consumerKey\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d '\"')\"\n  if [ -z \"$consumerKey\" ]; then\n    _err \"Unable to get consumerKey\"\n    return 1\n  fi\n  _secure_debug consumerKey \"$consumerKey\"\n\n  OVH_CK=\"$consumerKey\"\n  _saveaccountconf_mutable OVH_CK \"$OVH_CK\"\n  _info \"Please open this link to do authentication: $(__green \"$validationUrl\")\"\n\n  _info \"Here is a guide for you: $(__green \"$wiki\")\"\n  _info \"Please retry after the authentication is done.\"\n\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _ovh_rest GET \"domain/zone/$h\"; then\n      return 1\n    fi\n\n    if ! _contains \"$response\" \"This service does not exist\" >/dev/null &&\n      ! _contains \"$response\" \"This call has not been granted\" >/dev/null &&\n      ! _contains \"$response\" \"NOT_GRANTED_CALL\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_ovh_timestamp() {\n  _H1=\"\"\n  _H2=\"\"\n  _H3=\"\"\n  _H4=\"\"\n  _H5=\"\"\n  _get \"$OVH_API/auth/time\" \"\" 30\n}\n\n_ovh_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  _ovh_url=\"$OVH_API/$ep\"\n  _debug2 _ovh_url \"$_ovh_url\"\n  _ovh_t=\"$(_ovh_timestamp)\"\n  _debug2 _ovh_t \"$_ovh_t\"\n  _ovh_p=\"$OVH_AS+$OVH_CK+$m+$_ovh_url+$data+$_ovh_t\"\n  _secure_debug _ovh_p \"$_ovh_p\"\n  _ovh_hex=\"$(printf \"%s\" \"$_ovh_p\" | _digest sha1 hex)\"\n  _debug2 _ovh_hex \"$_ovh_hex\"\n\n  export _H1=\"X-Ovh-Application: $OVH_AK\"\n  export _H2=\"X-Ovh-Signature: \\$1\\$$_ovh_hex\"\n  _debug2 _H2 \"$_H2\"\n  export _H3=\"X-Ovh-Timestamp: $_ovh_t\"\n  export _H4=\"X-Ovh-Consumer: $OVH_CK\"\n  export _H5=\"Content-Type: application/json;charset=utf-8\"\n  if [ \"$data\" ] || [ \"$m\" = \"POST\" ] || [ \"$m\" = \"PUT\" ] || [ \"$m\" = \"DELETE\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$_ovh_url\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$_ovh_url\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ] || _contains \"$response\" \"INVALID_CREDENTIAL\"; then\n    _err \"error $response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_pdns.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_pdns_info='PowerDNS Server API\nSite: PowerDNS.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_pdns\nOptions:\n PDNS_Url API URL. E.g. \"http://ns.example.com:8081\"\n PDNS_ServerId Server ID. E.g. \"localhost\"\n PDNS_Token API Token\n PDNS_Ttl Domain TTL. Default: \"60\".\n'\n\nDEFAULT_PDNS_TTL=60\n\n########  Public functions #####################\n#Usage: add _acme-challenge.www.domain.com \"123456789ABCDEF0000000000000000000000000000000000000\"\n#fulldomain\n#txtvalue\ndns_pdns_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  PDNS_Url=\"${PDNS_Url:-$(_readaccountconf_mutable PDNS_Url)}\"\n  PDNS_ServerId=\"${PDNS_ServerId:-$(_readaccountconf_mutable PDNS_ServerId)}\"\n  PDNS_Token=\"${PDNS_Token:-$(_readaccountconf_mutable PDNS_Token)}\"\n  PDNS_Ttl=\"${PDNS_Ttl:-$(_readaccountconf_mutable PDNS_Ttl)}\"\n\n  if [ -z \"$PDNS_Url\" ]; then\n    PDNS_Url=\"\"\n    _err \"You don't specify PowerDNS address.\"\n    _err \"Please set PDNS_Url and try again.\"\n    return 1\n  fi\n\n  if [ -z \"$PDNS_ServerId\" ]; then\n    PDNS_ServerId=\"\"\n    _err \"You don't specify PowerDNS server id.\"\n    _err \"Please set you PDNS_ServerId and try again.\"\n    return 1\n  fi\n\n  if [ -z \"$PDNS_Token\" ]; then\n    PDNS_Token=\"\"\n    _err \"You don't specify PowerDNS token.\"\n    _err \"Please create you PDNS_Token and try again.\"\n    return 1\n  fi\n\n  if [ -z \"$PDNS_Ttl\" ]; then\n    PDNS_Ttl=\"$DEFAULT_PDNS_TTL\"\n  fi\n\n  #save the api addr and key to the account conf file.\n  _saveaccountconf_mutable PDNS_Url \"$PDNS_Url\"\n  _saveaccountconf_mutable PDNS_ServerId \"$PDNS_ServerId\"\n  _saveaccountconf_mutable PDNS_Token \"$PDNS_Token\"\n\n  if [ \"$PDNS_Ttl\" != \"$DEFAULT_PDNS_TTL\" ]; then\n    _saveaccountconf_mutable PDNS_Ttl \"$PDNS_Ttl\"\n  fi\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain \"$_domain\"\n\n  if ! set_record \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n#fulldomain\ndns_pdns_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  PDNS_Url=\"${PDNS_Url:-$(_readaccountconf_mutable PDNS_Url)}\"\n  PDNS_ServerId=\"${PDNS_ServerId:-$(_readaccountconf_mutable PDNS_ServerId)}\"\n  PDNS_Token=\"${PDNS_Token:-$(_readaccountconf_mutable PDNS_Token)}\"\n  PDNS_Ttl=\"${PDNS_Ttl:-$(_readaccountconf_mutable PDNS_Ttl)}\"\n\n  if [ -z \"$PDNS_Ttl\" ]; then\n    PDNS_Ttl=\"$DEFAULT_PDNS_TTL\"\n  fi\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _domain \"$_domain\"\n\n  if ! rm_record \"$_domain\" \"$fulldomain\" \"$txtvalue\"; then\n    return 1\n  fi\n\n  return 0\n}\n\nset_record() {\n  _info \"Adding record\"\n  root=$1\n  full=$2\n  new_challenge=$3\n\n  _record_string=\"\"\n  _build_record_string \"$new_challenge\"\n  _list_existingchallenges\n  for oldchallenge in $_existing_challenges; do\n    _build_record_string \"$oldchallenge\"\n  done\n\n  if ! _pdns_rest \"PATCH\" \"/api/v1/servers/$PDNS_ServerId/zones/$root\" \"{\\\"rrsets\\\": [{\\\"changetype\\\": \\\"REPLACE\\\", \\\"name\\\": \\\"$full.\\\", \\\"type\\\": \\\"TXT\\\", \\\"ttl\\\": $PDNS_Ttl, \\\"records\\\": [$_record_string]}]}\" \"application/json\"; then\n    _err \"Set txt record error.\"\n    return 1\n  fi\n\n  if ! notify_slaves \"$root\"; then\n    return 1\n  fi\n\n  return 0\n}\n\nrm_record() {\n  _info \"Remove record\"\n  root=$1\n  full=$2\n  txtvalue=$3\n\n  #Enumerate existing acme challenges\n  _list_existingchallenges\n\n  if _contains \"$_existing_challenges\" \"$txtvalue\"; then\n    #Delete all challenges (PowerDNS API does not allow to delete content)\n    if ! _pdns_rest \"PATCH\" \"/api/v1/servers/$PDNS_ServerId/zones/$root\" \"{\\\"rrsets\\\": [{\\\"changetype\\\": \\\"DELETE\\\", \\\"name\\\": \\\"$full.\\\", \\\"type\\\": \\\"TXT\\\"}]}\" \"application/json\"; then\n      _err \"Delete txt record error.\"\n      return 1\n    fi\n    _record_string=\"\"\n    #If the only existing challenge was the challenge to delete: nothing to do\n    if ! [ \"$_existing_challenges\" = \"$txtvalue\" ]; then\n      for oldchallenge in $_existing_challenges; do\n        #Build up the challenges to re-add, ommitting the one what should be deleted\n        if ! [ \"$oldchallenge\" = \"$txtvalue\" ]; then\n          _build_record_string \"$oldchallenge\"\n        fi\n      done\n      #Recreate the existing challenges\n      if ! _pdns_rest \"PATCH\" \"/api/v1/servers/$PDNS_ServerId/zones/$root\" \"{\\\"rrsets\\\": [{\\\"changetype\\\": \\\"REPLACE\\\", \\\"name\\\": \\\"$full.\\\", \\\"type\\\": \\\"TXT\\\", \\\"ttl\\\": $PDNS_Ttl, \\\"records\\\": [$_record_string]}]}\" \"application/json\"; then\n        _err \"Set txt record error.\"\n        return 1\n      fi\n    fi\n    if ! notify_slaves \"$root\"; then\n      return 1\n    fi\n  else\n    _info \"Record not found, nothing to remove\"\n  fi\n\n  return 0\n}\n\nnotify_slaves() {\n  root=$1\n\n  if ! _pdns_rest \"PUT\" \"/api/v1/servers/$PDNS_ServerId/zones/$root/notify\"; then\n    _err \"Notify slaves error.\"\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n\n  if _pdns_rest \"GET\" \"/api/v1/servers/$PDNS_ServerId/zones\"; then\n    _zones_response=$(echo \"$response\" | _normalizeJson)\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    if _contains \"$_zones_response\" \"\\\"name\\\":\\\"$h.\\\"\"; then\n      _domain=\"$h.\"\n      if [ -z \"$h\" ]; then\n        _domain=\"=2E\"\n      fi\n      return 0\n    fi\n\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n  _debug \"$domain not found\"\n\n  return 1\n}\n\n_pdns_rest() {\n  method=$1\n  ep=$2\n  data=$3\n  ct=$4\n\n  export _H1=\"X-API-Key: $PDNS_Token\"\n\n  if [ ! \"$method\" = \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$PDNS_Url$ep\" \"\" \"$method\" \"$ct\")\"\n  else\n    response=\"$(_get \"$PDNS_Url$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n\n  return 0\n}\n\n_build_record_string() {\n  _record_string=\"${_record_string:+${_record_string}, }{\\\"content\\\": \\\"\\\\\\\"${1}\\\\\\\"\\\", \\\"disabled\\\": false}\"\n}\n\n_list_existingchallenges() {\n  _pdns_rest \"GET\" \"/api/v1/servers/$PDNS_ServerId/zones/$root\"\n  _existing_challenges=$(echo \"$response\" | _normalizeJson | _egrep_o \"\\\"name\\\":\\\"${fulldomain}[^]]*}\" | _egrep_o 'content\\\":\\\"\\\\\"[^\\\\]*' | sed -n 's/^content\":\"\\\\\"//p')\n}\n"
  },
  {
    "path": "dnsapi/dns_pleskxml.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_pleskxml_info='Plesk Server API\nSite: Plesk.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_pleskxml\nOptions:\n pleskxml_uri Plesk server API URL. E.g. \"https://your-plesk-server.net:8443/enterprise/control/agent.php\"\n pleskxml_user Username\n pleskxml_pass Password\nIssues: github.com/acmesh-official/acme.sh/issues/2577\nAuthor: @Stilez, @romanlum\n'\n\n##  Plesk XML API described at:\n##  https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709\n##  and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784\n\n##  Note: a DNS ID with host = empty string is OK for this API, see\n##  https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798\n##  For example, to add a TXT record to DNS alias domain \"acme-alias.com\" would be a valid Plesk action.\n##  So this API module can handle such a request, if needed.\n\n##  For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records.\n\n##  The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user\n\n####################  INTERNAL VARIABLES + NEWLINE + API TEMPLATES ##################################\n\npleskxml_init_checks_done=0\n\n# Variable containing bare newline - not a style issue\n# shellcheck disable=SC1004\nNEWLINE='\\\n'\n\npleskxml_tplt_get_domains=\"<packet><webspace><get><filter/><dataset><gen_info/></dataset></get></webspace></packet>\"\n# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh\n# Also used to test credentials and URI.\n# No params.\n\npleskxml_tplt_get_additional_domains=\"<packet><site><get><filter/><dataset><gen_info/></dataset></get></site></packet>\"\n# Get a list of additional domains that PLESK can manage, so we can check root domain + host for acme.sh\n# No params.\n\npleskxml_tplt_get_dns_records=\"<packet><dns><get_rec><filter><site-id>%s</site-id></filter></get_rec></dns></packet>\"\n# Get all DNS records for a Plesk domain ID.\n# PARAM = Plesk domain id to query\n\npleskxml_tplt_add_txt_record=\"<packet><dns><add_rec><site-id>%s</site-id><type>TXT</type><host>%s</host><value>%s</value></add_rec></dns></packet>\"\n# Add a TXT record to a domain.\n# PARAMS = (1) Plesk internal domain ID, (2) \"hostname\" for the new record, eg '_acme_challenge', (3) TXT record value\n\npleskxml_tplt_rmv_dns_record=\"<packet><dns><del_rec><filter><id>%s</id></filter></del_rec></dns></packet>\"\n# Delete a specific TXT record from a domain.\n# PARAM = the Plesk internal ID for the DNS record to be deleted\n\n####################  Public functions ##################################\n\n#Usage: dns_pleskxml_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_pleskxml_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'...\"\n\n  # Get credentials if not already checked, and confirm we can log in to Plesk XML API\n  if ! _credential_check; then\n    return 1\n  fi\n\n  # Get root and subdomain details, and Plesk domain ID\n  if ! _pleskxml_get_root_domain \"$fulldomain\"; then\n    return 1\n  fi\n\n  _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record'\n\n  # printf using template in a variable - not a style issue\n  # shellcheck disable=SC2059\n  request=\"$(printf \"$pleskxml_tplt_add_txt_record\" \"$root_domain_id\" \"$sub_domain_name\" \"$txtvalue\")\"\n  if ! _call_api \"$request\"; then\n    return 1\n  fi\n\n  # OK, we should have added a TXT record. Let's check and return success if so.\n  # All that should be left in the result, is one section, containing <result><status>ok</status><id>NEW_DNS_RECORD_ID</id></result>\n\n  results=\"$(_api_response_split \"$pleskxml_prettyprint_result\" 'result' '<status>')\"\n\n  if ! _value \"$results\" | grep '<status>ok</status>' | grep '<id>[0-9]\\{1,\\}</id>' >/dev/null; then\n    # Error - doesn't contain expected string. Something's wrong.\n    _err 'Error when calling Plesk XML API.'\n    _err 'The result did not contain the expected <id>XXXXX</id> section, or contained other values as well.'\n    _err 'This is unexpected: something has gone wrong.'\n    _err 'The full response was:'\n    _err \"$pleskxml_prettyprint_result\"\n    return 1\n  fi\n\n  recid=\"$(_value \"$results\" | grep '<id>[0-9]\\{1,\\}</id>' | sed 's/^.*<id>\\([0-9]\\{1,\\}\\)<\\/id>.*$/\\1/')\"\n\n  _info \"Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add().\"\n\n  return 0\n}\n\n#Usage: dns_pleskxml_rm   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_pleskxml_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'...\"\n\n  # Get credentials if not already checked, and confirm we can log in to Plesk XML API\n  if ! _credential_check; then\n    return 1\n  fi\n\n  # Get root and subdomain details, and Plesk domain ID\n  if ! _pleskxml_get_root_domain \"$fulldomain\"; then\n    return 1\n  fi\n\n  _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs'\n\n  # printf using template in a variable - not a style issue\n  # shellcheck disable=SC2059\n  request=\"$(printf \"$pleskxml_tplt_get_dns_records\" \"$root_domain_id\")\"\n  if ! _call_api \"$request\"; then\n    return 1\n  fi\n\n  # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have)\n  # Also strip out spaces between tags, redundant <data> and </data> group tags and any <self-closing/> tags\n  reclist=\"$(\n    _api_response_split \"$pleskxml_prettyprint_result\" 'result' '<status>ok</status>' |\n      sed 's# \\{1,\\}<\\([a-zA-Z]\\)#<\\1#g;s#</\\{0,1\\}data>##g;s#<[a-z][^/<>]*/>##g' |\n      grep \"<site-id>${root_domain_id}</site-id>\" |\n      grep '<id>[0-9]\\{1,\\}</id>' |\n      grep '<type>TXT</type>'\n  )\"\n\n  if [ -z \"$reclist\" ]; then\n    _err \"No TXT records found for root domain $fulldomain (Plesk domain ID ${root_domain_id}). Exiting.\"\n    return 1\n  fi\n\n  _debug \"Got list of DNS TXT records for root Plesk domain ID ${root_domain_id} of root domain $fulldomain:\"\n  _debug \"$reclist\"\n\n  # Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value\n  recid=\"$(\n    _value \"$reclist\" |\n      grep -i \"<host>${fulldomain}.</host>\" |\n      grep \"<value>${txtvalue}</value>\" |\n      sed 's/^.*<id>\\([0-9]\\{1,\\}\\)<\\/id>.*$/\\1/'\n  )\"\n\n  _debug \"Got id from line: $recid\"\n\n  if ! _value \"$recid\" | grep '^[0-9]\\{1,\\}$' >/dev/null; then\n    _err \"DNS records for root domain '${fulldomain}.' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'\"\n    _err \"Cannot delete TXT record. Exiting.\"\n    return 1\n  fi\n\n  _debug \"Found Plesk record ID for target text string '${txtvalue}': ID=${recid}\"\n  _debug 'Calling Plesk XML API to remove TXT record'\n\n  # printf using template in a variable - not a style issue\n  # shellcheck disable=SC2059\n  request=\"$(printf \"$pleskxml_tplt_rmv_dns_record\" \"$recid\")\"\n  if ! _call_api \"$request\"; then\n    return 1\n  fi\n\n  # OK, we should have removed a TXT record. Let's check and return success if so.\n  # All that should be left in the result, is one section, containing <result><status>ok</status><id>PLESK_DELETED_DNS_RECORD_ID</id></result>\n\n  results=\"$(_api_response_split \"$pleskxml_prettyprint_result\" 'result' '<status>')\"\n\n  if ! _value \"$results\" | grep '<status>ok</status>' | grep '<id>[0-9]\\{1,\\}</id>' >/dev/null; then\n    # Error - doesn't contain expected string. Something's wrong.\n    _err 'Error when calling Plesk XML API.'\n    _err 'The result did not contain the expected <id>XXXXX</id> section, or contained other values as well.'\n    _err 'This is unexpected: something has gone wrong.'\n    _err 'The full response was:'\n    _err \"$pleskxml_prettyprint_result\"\n    return 1\n  fi\n\n  _info \"Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm().\"\n  return 0\n}\n\n####################  Private functions below (utility functions) ##################################\n\n# Outputs value of a variable without additional newlines etc\n_value() {\n  printf '%s' \"$1\"\n}\n\n# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between\n# $1, $2 = where to cut\n# $3 = FQDN\n_valuecut() {\n  printf '%s' \"$3\" | cut -d . -f \"${1}-${2}\"\n}\n\n# Counts '.' present in a domain name or other string\n# $1 = domain name\n_countdots() {\n  _value \"$1\" | tr -dc '.' | wc -c | sed 's/ //g'\n}\n\n# Cleans up an API response, splits it \"one line per item in the response\" and greps for a string that in the context, identifies \"useful\" lines\n# $1 - result string from API\n# $2 - plain text tag to resplit on (usually \"result\" or \"domain\"). NOT REGEX\n# $3 - basic regex to recognise useful return lines\n# note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment.\n#       Last line could change to <sed -n '/.../p'> instead, with suitable escaping of ['\"/$],\n#       if future Plesk XML API changes ever require extended regex\n_api_response_split() {\n  printf '%s' \"$1\" |\n    sed 's/^ +//;s/ +$//' |\n    tr -d '\\n\\r' |\n    sed \"s/<\\/\\{0,1\\}$2>/${NEWLINE}/g\" |\n    grep \"$3\"\n}\n\n####################  Private functions below (DNS functions) ##################################\n\n# Calls Plesk XML API, and checks results for obvious issues\n_call_api() {\n  request=\"$1\"\n  errtext=''\n\n  _debug 'Entered _call_api(). Calling Plesk XML API with request:'\n  _debug \"'$request'\"\n\n  export _H1=\"HTTP_AUTH_LOGIN: $pleskxml_user\"\n  export _H2=\"HTTP_AUTH_PASSWD: $pleskxml_pass\"\n  export _H3=\"content-Type: text/xml\"\n  export _H4=\"HTTP_PRETTY_PRINT: true\"\n  pleskxml_prettyprint_result=\"$(_post \"${request}\" \"$pleskxml_uri\" \"\" \"POST\")\"\n  pleskxml_retcode=\"$?\"\n  _debug 'The responses from the Plesk XML server were:'\n  _debug \"retcode=$pleskxml_retcode. Literal response:\"\n  _debug \"'$pleskxml_prettyprint_result'\"\n\n  # Detect any <status> that isn't \"ok\". None of the used calls should fail if the API is working correctly.\n  # Also detect if there simply aren't any status lines (null result?) and report that, as well.\n  # Remove <data></data> structure from result string, since it might contain <status> values that are related to the status of the domain and not to the API request\n\n  statuslines_count_total=\"$(echo \"$pleskxml_prettyprint_result\" | sed '/<data>/,/<\\/data>/d' | grep -c '^ *<status>[^<]*</status> *$')\"\n  statuslines_count_okay=\"$(echo \"$pleskxml_prettyprint_result\" | sed '/<data>/,/<\\/data>/d' | grep -c '^ *<status>ok</status> *$')\"\n  _debug \"statuslines_count_total=$statuslines_count_total.\"\n  _debug \"statuslines_count_okay=$statuslines_count_okay.\"\n\n  if [ -z \"$statuslines_count_total\" ]; then\n\n    # We have no status lines at all. Results are empty\n    errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.'\n\n  elif [ \"$statuslines_count_okay\" -ne \"$statuslines_count_total\" ]; then\n\n    # We have some status lines that aren't \"ok\". Any available details are in API response fields \"status\" \"errcode\" and \"errtext\"\n    # Workaround for basic regex:\n    #   - filter output to keep only lines like this: \"SPACES<TAG>text</TAG>SPACES\" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok)\n    #   - then edit the 3 \"useful\" error tokens individually and remove closing tags on all lines\n    #   - then filter again to remove all lines not edited (which will be the lines not starting A-Z)\n    errtext=\"$(\n      _value \"$pleskxml_prettyprint_result\" |\n        grep '^ *<[a-z]\\{1,\\}>[^<]*<\\/[a-z]\\{1,\\}> *$' |\n        sed 's/^ *<status>/Status:     /;s/^ *<errcode>/Error code: /;s/^ *<errtext>/Error text: /;s/<\\/.*$//' |\n        grep '^[A-Z]'\n    )\"\n\n  fi\n\n  if [ \"$pleskxml_retcode\" -ne 0 ] || [ \"$errtext\" != \"\" ]; then\n    # Call failed, for reasons either in the retcode or the response text...\n\n    if [ \"$pleskxml_retcode\" -eq 0 ]; then\n      _err \"The POST request was successfully sent to the Plesk server.\"\n    else\n      _err \"The return code for the POST request was $pleskxml_retcode (non-zero = failure in submitting request to server).\"\n    fi\n\n    if [ \"$errtext\" != \"\" ]; then\n      _err 'The error responses received from the Plesk server were:'\n      _err \"$errtext\"\n    else\n      _err \"No additional error messages were received back from the Plesk server\"\n    fi\n\n    _err \"The Plesk XML API call failed.\"\n    return 1\n\n  fi\n\n  _debug \"Leaving _call_api(). Successful call.\"\n\n  return 0\n}\n\n# Startup checks (credentials, URI)\n_credential_check() {\n  _debug \"Checking Plesk XML API login credentials and URI...\"\n\n  if [ \"$pleskxml_init_checks_done\" -eq 1 ]; then\n    _debug \"Initial checks already done, no need to repeat. Skipped.\"\n    return 0\n  fi\n\n  pleskxml_user=\"${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}\"\n  pleskxml_pass=\"${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}\"\n  pleskxml_uri=\"${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}\"\n\n  if [ -z \"$pleskxml_user\" ] || [ -z \"$pleskxml_pass\" ] || [ -z \"$pleskxml_uri\" ]; then\n    pleskxml_user=\"\"\n    pleskxml_pass=\"\"\n    pleskxml_uri=\"\"\n    _err \"You didn't specify one or more of the Plesk XML API username, password, or URI.\"\n    _err \"Please create these and try again.\"\n    _err \"Instructions are in the 'dns_pleskxml' plugin source code or in the acme.sh documentation.\"\n    return 1\n  fi\n\n  # Test the API is usable, by trying to read the list of managed domains...\n  _call_api \"$pleskxml_tplt_get_domains\"\n  if [ \"$pleskxml_retcode\" -ne 0 ]; then\n    _err 'Failed to access Plesk XML API.'\n    _err \"Please check your login credentials and Plesk URI, and that the URI is reachable, and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable pleskxml_uri \"$pleskxml_uri\"\n  _saveaccountconf_mutable pleskxml_user \"$pleskxml_user\"\n  _saveaccountconf_mutable pleskxml_pass \"$pleskxml_pass\"\n\n  _debug \"Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use.\"\n\n  pleskxml_init_checks_done=1\n\n  return 0\n}\n\n# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any.\n\n# IMPORTANT NOTE:  a result with host = empty string is OK for this API, see\n# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798\n# See notes at top of this file\n\n_pleskxml_get_root_domain() {\n  original_full_domain_name=\"$1\"\n\n  _debug \"Identifying DNS root domain for '$original_full_domain_name' that is managed by the Plesk account.\"\n\n  # test if the domain as provided is valid for splitting.\n\n  if [ \"$(_countdots \"$original_full_domain_name\")\" -eq 0 ]; then\n    _err \"Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record.\"\n    return 1\n  fi\n\n  _debug \"Querying Plesk server for list of managed domains...\"\n\n  _call_api \"$pleskxml_tplt_get_domains\"\n  if [ \"$pleskxml_retcode\" -ne 0 ]; then\n    return 1\n  fi\n\n  # Generate a crude list of domains known to this Plesk account based on subscriptions.\n  # We convert <ascii-name> tags to <name> so it'll flag on a hit with either <name> or <ascii-name> fields,\n  # for non-Western character sets.\n  # Output will be one line per known domain, containing 2 <name> tages and a single <id> tag\n  # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.\n\n  output=\"$(_api_response_split \"$pleskxml_prettyprint_result\" 'result' '<status>ok</status>' | sed 's/<ascii-name>/<name>/g;s/<\\/ascii-name>/<\\/name>/g' | grep '<name>' | grep '<id>')\"\n  debug_output=\"$(printf \"%s\" \"$output\" | sed -n 's:.*<name>\\(.*\\)</name>.*:\\1:p')\"\n\n  _debug 'Domains managed by Plesk server are:'\n  _debug \"$debug_output\"\n\n  _debug \"Querying Plesk server for list of additional managed domains...\"\n\n  _call_api \"$pleskxml_tplt_get_additional_domains\"\n  if [ \"$pleskxml_retcode\" -ne 0 ]; then\n    return 1\n  fi\n\n  # Generate a crude list of additional domains known to this Plesk account based on sites.\n  # We convert <ascii-name> tags to <name> so it'll flag on a hit with either <name> or <ascii-name> fields,\n  # for non-Western character sets.\n  # Output will be one line per known domain, containing 2 <name> tages and a single <id> tag\n  # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.\n\n  output_additional=\"$(_api_response_split \"$pleskxml_prettyprint_result\" 'result' '<status>ok</status>' | sed 's/<ascii-name>/<name>/g;s/<\\/ascii-name>/<\\/name>/g' | grep '<name>' | grep '<id>')\"\n  debug_additional=\"$(printf \"%s\" \"$output_additional\" | sed -n 's:.*<name>\\(.*\\)</name>.*:\\1:p')\"\n\n  _debug 'Additional domains managed by Plesk server are:'\n  _debug \"$debug_additional\"\n\n  # Concate the two outputs together.\n\n  output=\"$(printf \"%s\" \"$output $NEWLINE $output_additional\")\"\n  debug_output=\"$(printf \"%s\" \"$output\" | sed -n 's:.*<name>\\(.*\\)</name>.*:\\1:p')\"\n\n  _debug 'Domains (including additional) managed by Plesk server are:'\n  _debug \"$debug_output\"\n\n  # loop and test if domain, or any parent domain, is managed by Plesk\n  # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain\n\n  root_domain_name=\"$original_full_domain_name\"\n\n  while true; do\n\n    _debug \"Checking if '$root_domain_name' is managed by the Plesk server...\"\n\n    root_domain_id=\"$(_value \"$output\" | grep \"<name>$root_domain_name</name>\" | _head_n 1 | sed 's/^.*<id>\\([0-9]\\{1,\\}\\)<\\/id>.*$/\\1/')\"\n\n    if [ -n \"$root_domain_id\" ]; then\n      # Found a match\n      # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT.\n      # SO WE HANDLE IT AND DON'T PREVENT IT\n      sub_domain_name=\"$(_value \"$original_full_domain_name\" | sed \"s/\\.\\{0,1\\}${root_domain_name}\"'$//')\"\n      _info \"Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning.\"\n      return 0\n    fi\n\n    # No match, try next parent up (if any)...\n\n    root_domain_name=\"$(_valuecut 2 1000 \"$root_domain_name\")\"\n\n    if [ \"$(_countdots \"$root_domain_name\")\" -eq 0 ]; then\n      _debug \"No match, and next parent would be a TLD...\"\n      _err \"Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk.\"\n      _err \"Are you sure that this domain is managed by this Plesk server?\"\n      return 1\n    fi\n\n    _debug \"No match, trying next parent up...\"\n\n  done\n}\n"
  },
  {
    "path": "dnsapi/dns_pointhq.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_pointhq_info='pointhq.com PointDNS\nSite: pointhq.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_pointhq\nOptions:\n PointHQ_Key API Key\n PointHQ_Email Email\nIssues: github.com/acmesh-official/acme.sh/issues/2060\n'\n\nPointHQ_Api=\"https://api.pointhq.com\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_pointhq_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  PointHQ_Key=\"${PointHQ_Key:-$(_readaccountconf_mutable PointHQ_Key)}\"\n  PointHQ_Email=\"${PointHQ_Email:-$(_readaccountconf_mutable PointHQ_Email)}\"\n  if [ -z \"$PointHQ_Key\" ] || [ -z \"$PointHQ_Email\" ]; then\n    PointHQ_Key=\"\"\n    PointHQ_Email=\"\"\n    _err \"You didn't specify a PointHQ API key and email yet.\"\n    _err \"Please create the key and try again.\"\n    return 1\n  fi\n\n  if ! _contains \"$PointHQ_Email\" \"@\"; then\n    _err \"It seems that the PointHQ_Email=$PointHQ_Email is not a valid email address.\"\n    _err \"Please check and retry.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable PointHQ_Key \"$PointHQ_Key\"\n  _saveaccountconf_mutable PointHQ_Email \"$PointHQ_Email\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _pointhq_rest POST \"zones/$_domain/records\" \"{\\\"zone_record\\\": {\\\"name\\\":\\\"$_sub_domain\\\",\\\"record_type\\\":\\\"TXT\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":3600}}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"$fulldomain\" >/dev/null; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#fulldomain txtvalue\ndns_pointhq_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  PointHQ_Key=\"${PointHQ_Key:-$(_readaccountconf_mutable PointHQ_Key)}\"\n  PointHQ_Email=\"${PointHQ_Email:-$(_readaccountconf_mutable PointHQ_Email)}\"\n  if [ -z \"$PointHQ_Key\" ] || [ -z \"$PointHQ_Email\" ]; then\n    PointHQ_Key=\"\"\n    PointHQ_Email=\"\"\n    _err \"You didn't specify a PointHQ API key and email yet.\"\n    _err \"Please create the key and try again.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _pointhq_rest GET \"zones/${_domain}/records?record_type=TXT&name=$_sub_domain\"\n\n  if ! printf \"%s\" \"$response\" | grep \"^\\[\" >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n\n  if [ \"$response\" = \"[]\" ]; then\n    _info \"No records to remove.\"\n  else\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":[^,]*\" | cut -d : -f 2 | tr -d \\\" | head -n 1)\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _pointhq_rest DELETE \"zones/$_domain/records/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" '\"status\":\"OK\"'\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _pointhq_rest GET \"zones\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_pointhq_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  _pointhq_auth=$(printf \"%s:%s\" \"$PointHQ_Email\" \"$PointHQ_Key\" | _base64)\n\n  export _H1=\"Authorization: Basic $_pointhq_auth\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Accept: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$PointHQ_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$PointHQ_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_porkbun.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_porkbun_info='Porkbun.com\nSite: Porkbun.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_porkbun\nOptions:\n PORKBUN_API_KEY API Key\n PORKBUN_SECRET_API_KEY API Secret\nIssues: github.com/acmesh-official/acme.sh/issues/3450\n'\n\nPORKBUN_Api=\"https://api.porkbun.com/api/json/v3\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_porkbun_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  PORKBUN_API_KEY=\"${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}\"\n  PORKBUN_SECRET_API_KEY=\"${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}\"\n\n  if [ -z \"$PORKBUN_API_KEY\" ] || [ -z \"$PORKBUN_SECRET_API_KEY\" ]; then\n    PORKBUN_API_KEY=''\n    PORKBUN_SECRET_API_KEY=''\n    _err \"You didn't specify a Porkbun api key and secret api key yet.\"\n    _err \"You can get yours from here https://porkbun.com/account/api.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable PORKBUN_API_KEY \"$PORKBUN_API_KEY\"\n  _saveaccountconf_mutable PORKBUN_SECRET_API_KEY \"$PORKBUN_SECRET_API_KEY\"\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so\n  # we can not use updating anymore.\n  #  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  #  _debug count \"$count\"\n  #  if [ \"$count\" = \"0\" ]; then\n  _info \"Adding record\"\n  if _porkbun_rest POST \"dns/create/$_domain\" \"{\\\"name\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"; then\n    if _contains \"$response\" '\\\"status\\\":\"SUCCESS\"'; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"The record already exists\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error. ($response)\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_porkbun_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  PORKBUN_API_KEY=\"${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}\"\n  PORKBUN_SECRET_API_KEY=\"${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}\"\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  count=$(echo \"$response\" | _egrep_o \"\\\"count\\\": *[^,]*\" | cut -d : -f 2 | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(echo \"$response\" | tr '{' '\\n' | grep -- \"$txtvalue\" | cut -d, -f1 | cut -d: -f2 | tr -d \\\")\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _porkbun_rest POST \"dns/delete/$_domain/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    echo \"$response\" | tr -d \" \" | grep '\"status\":\"SUCCESS\"' >/dev/null\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    if _porkbun_rest POST \"dns/retrieve/$h\"; then\n      if _contains \"$response\" \"\\\"status\\\":\\\"SUCCESS\\\"\"; then\n        _domain=$h\n        _sub_domain=\"$(echo \"$fulldomain\" | sed \"s/\\\\.$_domain\\$//\")\"\n        return 0\n      else\n        _debug \"Go to next level of $_domain\"\n      fi\n    else\n      _debug \"Go to next level of $_domain\"\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_porkbun_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  api_key_trimmed=$(echo \"$PORKBUN_API_KEY\" | tr -d '\"')\n  secret_api_key_trimmed=$(echo \"$PORKBUN_SECRET_API_KEY\" | tr -d '\"')\n\n  test -z \"$data\" && data=\"{\" || data=\"$(echo \"$data\" | cut -d'}' -f1),\"\n  data=\"$data\\\"apikey\\\":\\\"$api_key_trimmed\\\",\\\"secretapikey\\\":\\\"$secret_api_key_trimmed\\\"}\"\n\n  export _H1=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$PORKBUN_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$PORKBUN_Api/$ep\")\"\n  fi\n\n  _sleep 3 # prevent rate limit\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_qc.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_qc_info='QUIC.cloud\nSite: quic.cloud\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_qc\nOptions:\n QC_API_KEY QC API Key\n QC_API_EMAIL Your account email\n'\n\nQC_Api=\"https://api.quic.cloud/v2\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_qc_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"Enter dns_qc_add fulldomain: $fulldomain, txtvalue: $txtvalue\"\n  QC_API_KEY=\"${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}\"\n  QC_API_EMAIL=\"${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}\"\n\n  if [ \"$QC_API_KEY\" ]; then\n    _saveaccountconf_mutable QC_API_KEY \"$QC_API_KEY\"\n  else\n    _err \"You didn't specify a QUIC.cloud api key as QC_API_KEY.\"\n    _err \"You can get yours from here https://my.quic.cloud/up/api.\"\n    return 1\n  fi\n\n  if ! _contains \"$QC_API_EMAIL\" \"@\"; then\n    _err \"It seems that the QC_API_EMAIL=$QC_API_EMAIL is not a valid email address.\"\n    _err \"Please check and retry.\"\n    return 1\n  fi\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable QC_API_EMAIL \"$QC_API_EMAIL\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain during add\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _qc_rest GET \"zones/${_domain_id}/records\"\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"success\\\":true >/dev/null; then\n    _err \"Error failed response from QC GET: $response\"\n    return 1\n  fi\n\n  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so\n  # we can not use updating anymore.\n  #  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  #  _debug count \"$count\"\n  #  if [ \"$count\" = \"0\" ]; then\n  _info \"Adding txt record\"\n  if _qc_rest POST \"zones/$_domain_id/records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":1800}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added txt record, OK\"\n      return 0\n    elif _contains \"$response\" \"Same record already exists\"; then\n      _info \"txt record already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error: $response\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error: POST failed: $response\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_qc_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"Enter dns_qc_rm fulldomain: $fulldomain, txtvalue: $txtvalue\"\n  QC_API_KEY=\"${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}\"\n  QC_API_EMAIL=\"${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain during rm\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _qc_rest GET \"zones/${_domain_id}/records\"\n\n  if ! echo \"$response\" | tr -d \" \" | grep \\\"success\\\":true >/dev/null; then\n    _err \"Error rm GET response: $response\"\n    return 1\n  fi\n\n  _debug \"Pre-jq response:\" \"$response\"\n  # Do not use jq or subsequent code\n  #response=$(echo \"$response\" | jq \".result[] | select(.id) | select(.content == \\\"$txtvalue\\\") | select(.type == \\\"TXT\\\")\")\n  #_debug \"get txt response\" \"$response\"\n  #if [ \"${response}\" = \"\" ]; then\n  #  _info \"Don't need to remove txt records.\"\n  #  return 0\n  #fi\n  #record_id=$(echo \"$response\" | grep \\\"id\\\" | awk -F ' ' '{print $2}' | sed 's/,$//')\n  #_debug \"txt record_id\" \"$record_id\"\n  #Instead of jq\n  array=$(echo \"$response\" | grep -o '\\[[^]]*\\]' | sed 's/^\\[\\(.*\\)\\]$/\\1/')\n  if [ -z \"$array\" ]; then\n    _err \"Expected array in QC response: $response\"\n    return 1\n  fi\n  # Temporary file to hold matched content (one per line)\n  tmpfile=$(_mktemp)\n  echo \"$array\" | grep -o '{[^}]*}' | sed 's/^{//;s/}$//' >\"$tmpfile\"\n  record_id=\"\"\n\n  while IFS= read -r obj || [ -n \"$obj\" ]; do\n    if echo \"$obj\" | grep -q '\"TXT\"' && echo \"$obj\" | grep -q '\"id\"' && echo \"$obj\" | grep -q \"$txtvalue\"; then\n      _debug \"response includes\" \"$obj\"\n      record_id=$(echo \"$obj\" | sed 's/^\\\"id\\\":\\([0-9]\\+\\).*/\\1/')\n      break\n    fi\n  done <\"$tmpfile\"\n\n  rm \"$tmpfile\"\n\n  if [ -z \"$record_id\" ]; then\n    _info \"TXT record, or $txtvalue not found, nothing to remove\"\n    return 0\n  fi\n\n  #End of jq replacement\n  if ! _qc_rest DELETE \"zones/$_domain_id/records/$record_id\"; then\n    _info \"Delete txt record error.\"\n    return 1\n  fi\n\n  _info \"TXT Record ID: $record_id successfully deleted\"\n  return 0\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  h=$(printf \"%s\" \"$domain\" | cut -d . -f2-)\n  _debug h \"$h\"\n  if [ -z \"$h\" ]; then\n    _err \"$h ($domain) is an invalid domain\"\n    return 1\n  fi\n\n  if ! _qc_rest GET \"zones\"; then\n    _err \"qc_rest failed\"\n    return 1\n  fi\n\n  if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" || _contains \"$response\" \"\\\"name\\\":\\\"$h.\\\"\"; then\n    _domain_id=$h\n    if [ \"$_domain_id\" ]; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    _err \"Empty domain_id $h\"\n    return 1\n  fi\n  _err \"Missing domain_id $h\"\n  return 1\n}\n\n_qc_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  email_trimmed=$(echo \"$QC_API_EMAIL\" | tr -d '\"')\n  token_trimmed=$(echo \"$QC_API_KEY\" | tr -d '\"')\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"X-Auth-Email: $email_trimmed\"\n  export _H3=\"X-Auth-Key: $token_trimmed\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$QC_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$QC_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_rackcorp.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_rackcorp_info='RackCorp.com\nSite: RackCorp.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rackcorp\nOptions:\n RACKCORP_APIUUID API UUID. See Portal: ADMINISTRATION -> API\n RACKCORP_APISECRET API Secret\nIssues: github.com/acmesh-official/acme.sh/issues/3351\nAuthor: Stephen Dendtler <sdendtler@rackcorp.com>\n'\n\nRACKCORP_API_ENDPOINT=\"https://api.rackcorp.net/api/rest/v2.4/json.php\"\n\n########  Public functions #####################\n\ndns_rackcorp_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _debug fulldomain=\"$fulldomain\"\n  _debug txtvalue=\"$txtvalue\"\n\n  if ! _rackcorp_validate; then\n    return 1\n  fi\n\n  _debug \"Searching for root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _lookup \"$_lookup\"\n  _debug _domain \"$_domain\"\n\n  _info \"Creating TXT record.\"\n\n  if ! _rackcorp_api dns.record.create \"\\\"name\\\":\\\"$_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"lookup\\\":\\\"$_lookup\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":300\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_rackcorp_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug fulldomain=\"$fulldomain\"\n  _debug txtvalue=\"$txtvalue\"\n\n  if ! _rackcorp_validate; then\n    return 1\n  fi\n\n  _debug \"Searching for root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _lookup \"$_lookup\"\n  _debug _domain \"$_domain\"\n\n  _info \"Creating TXT record.\"\n\n  if ! _rackcorp_api dns.record.delete \"\\\"name\\\":\\\"$_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"lookup\\\":\\\"$_lookup\\\",\\\"data\\\":\\\"$txtvalue\\\"\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.domain.com\n#returns\n# _lookup=_acme-challenge\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  if ! _rackcorp_api dns.domain.getall \"\\\"name\\\":\\\"$domain\\\"\"; then\n    return 1\n  fi\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug searchhost \"$h\"\n    if [ -z \"$h\" ]; then\n      _err \"Could not find domain for record $domain in RackCorp using the provided credentials\"\n      #not valid\n      return 1\n    fi\n\n    _rackcorp_api dns.domain.getall \"\\\"exactName\\\":\\\"$h\\\"\"\n\n    if _contains \"$response\" \"\\\"matches\\\":1\"; then\n      if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n        _lookup=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"$h\"\n        return 0\n      fi\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_rackcorp_validate() {\n  RACKCORP_APIUUID=\"${RACKCORP_APIUUID:-$(_readaccountconf_mutable RACKCORP_APIUUID)}\"\n  if [ -z \"$RACKCORP_APIUUID\" ]; then\n    RACKCORP_APIUUID=\"\"\n    _err \"You require a RackCorp API UUID (export RACKCORP_APIUUID=\\\"<api uuid>\\\")\"\n    _err \"Please login to the portal and create an API key and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable RACKCORP_APIUUID \"$RACKCORP_APIUUID\"\n\n  RACKCORP_APISECRET=\"${RACKCORP_APISECRET:-$(_readaccountconf_mutable RACKCORP_APISECRET)}\"\n  if [ -z \"$RACKCORP_APISECRET\" ]; then\n    RACKCORP_APISECRET=\"\"\n    _err \"You require a RackCorp API secret (export RACKCORP_APISECRET=\\\"<api secret>\\\")\"\n    _err \"Please login to the portal and create an API key and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable RACKCORP_APISECRET \"$RACKCORP_APISECRET\"\n\n  return 0\n}\n_rackcorp_api() {\n  _rackcorpcmd=$1\n  _rackcorpinputdata=$2\n  _debug cmd \"$_rackcorpcmd $_rackcorpinputdata\"\n\n  export _H1=\"Accept: application/json\"\n  response=\"$(_post \"{\\\"APIUUID\\\":\\\"$RACKCORP_APIUUID\\\",\\\"APISECRET\\\":\\\"$RACKCORP_APISECRET\\\",\\\"cmd\\\":\\\"$_rackcorpcmd\\\",$_rackcorpinputdata}\" \"$RACKCORP_API_ENDPOINT\" \"\" \"POST\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  if _contains \"$response\" \"\\\"code\\\":\\\"OK\\\"\"; then\n    _debug code \"OK\"\n  else\n    _debug code \"FAILED\"\n    response=\"\"\n    return 1\n  fi\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_rackspace.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_rackspace_info='RackSpace.com\nSite: RackSpace.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_rackspace\nOptions:\n RACKSPACE_Apikey API Key\n RACKSPACE_Username Username\nIssues: github.com/acmesh-official/acme.sh/issues/2091\n'\n\nRACKSPACE_Endpoint=\"https://dns.api.rackspacecloud.com/v1.0\"\n\n# 20210923 - RS changed the fields in the API response; fix sed\n# 20190213 - The name & id fields swapped in the API response; fix sed\n# 20190101 - Duplicating file for new pull request to dev branch\n# Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297\n\n########  Public functions #####################\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_rackspace_add() {\n  fulldomain=\"$1\"\n  _debug fulldomain=\"$fulldomain\"\n  txtvalue=\"$2\"\n  _debug txtvalue=\"$txtvalue\"\n  _rackspace_check_auth || return 1\n  _rackspace_check_rootzone || return 1\n  _info \"Creating TXT record.\"\n  if ! _rackspace_rest POST \"$RACKSPACE_Tenant/domains/$_domain_id/records\" \"{\\\"records\\\":[{\\\"name\\\":\\\"$fulldomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":300}]}\"; then\n    return 1\n  fi\n  _debug2 response \"$response\"\n  if ! _contains \"$response\" \"$txtvalue\" >/dev/null; then\n    _err \"Could not add TXT record.\"\n    return 1\n  fi\n  return 0\n}\n\n#fulldomain txtvalue\ndns_rackspace_rm() {\n  fulldomain=$1\n  _debug fulldomain=\"$fulldomain\"\n  txtvalue=$2\n  _debug txtvalue=\"$txtvalue\"\n  _rackspace_check_auth || return 1\n  _rackspace_check_rootzone || return 1\n  _info \"Checking for TXT record.\"\n  if ! _get_recordid \"$_domain_id\" \"$fulldomain\" \"$txtvalue\"; then\n    _err \"Could not get TXT record id.\"\n    return 1\n  fi\n  if [ \"$_dns_record_id\" = \"\" ]; then\n    _err \"TXT record not found.\"\n    return 1\n  fi\n  _info \"Removing TXT record.\"\n  if ! _delete_txt_record \"$_domain_id\" \"$_dns_record_id\"; then\n    _err \"Could not remove TXT record $_dns_record_id.\"\n  fi\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root_zone() {\n  domain=\"$1\"\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    if ! _rackspace_rest GET \"$RACKSPACE_Tenant/domains/search?name=$h\"; then\n      return 1\n    fi\n    _debug2 response \"$response\"\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      # Response looks like:\n      #   {\"id\":\"12345\",\"accountId\":\"1111111\",\"name\": \"example.com\",\"ttl\":3600,\"emailAddress\": ... <and so on>\n      _domain_id=$(echo \"$response\" | sed -n \"s/^.*\\\"id\\\":\\\"\\([^,]*\\)\\\",\\\"accountId\\\":\\\"[0-9]*\\\",\\\"name\\\":\\\"$h\\\",.*/\\1/p\")\n      _debug2 domain_id \"$_domain_id\"\n      if [ -n \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_get_recordid() {\n  domainid=\"$1\"\n  fulldomain=\"$2\"\n  txtvalue=\"$3\"\n  if ! _rackspace_rest GET \"$RACKSPACE_Tenant/domains/$domainid/records?name=$fulldomain&type=TXT\"; then\n    return 1\n  fi\n  _debug response \"$response\"\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _dns_record_id=0\n    return 0\n  fi\n  _dns_record_id=$(echo \"$response\" | tr '{' \"\\n\" | grep \"\\\"data\\\":\\\"$txtvalue\\\"\" | sed -n 's/^.*\"id\":\"\\([^\"]*\\)\".*/\\1/p')\n  _debug _dns_record_id \"$_dns_record_id\"\n  return 0\n}\n\n_delete_txt_record() {\n  domainid=\"$1\"\n  _dns_record_id=\"$2\"\n  if ! _rackspace_rest DELETE \"$RACKSPACE_Tenant/domains/$domainid/records?id=$_dns_record_id\"; then\n    return 1\n  fi\n  _debug response \"$response\"\n  if ! _contains \"$response\" \"RUNNING\"; then\n    return 1\n  fi\n  return 0\n}\n\n_rackspace_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  _debug ep \"$ep\"\n  export _H1=\"Accept: application/json\"\n  export _H2=\"X-Auth-Token: $RACKSPACE_Token\"\n  export _H3=\"X-Project-Id: $RACKSPACE_Tenant\"\n  export _H4=\"Content-Type: application/json\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$RACKSPACE_Endpoint/$ep\" \"\" \"$m\")\"\n    retcode=$?\n  else\n    _info \"Getting $RACKSPACE_Endpoint/$ep\"\n    response=\"$(_get \"$RACKSPACE_Endpoint/$ep\")\"\n    retcode=$?\n  fi\n\n  if [ \"$retcode\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_rackspace_authorization() {\n  export _H1=\"Content-Type: application/json\"\n  data=\"{\\\"auth\\\":{\\\"RAX-KSKEY:apiKeyCredentials\\\":{\\\"username\\\":\\\"$RACKSPACE_Username\\\",\\\"apiKey\\\":\\\"$RACKSPACE_Apikey\\\"}}}\"\n  _debug data \"$data\"\n  response=\"$(_post \"$data\" \"https://identity.api.rackspacecloud.com/v2.0/tokens\" \"\" \"POST\")\"\n  retcode=$?\n  _debug2 response \"$response\"\n  if [ \"$retcode\" != \"0\" ]; then\n    _err \"Authentication failed.\"\n    return 1\n  fi\n  if _contains \"$response\" \"token\"; then\n    RACKSPACE_Token=\"$(echo \"$response\" | _normalizeJson | sed -n 's/^.*\"token\":{.*,\"id\":\"\\([^\"]*\\)\",\".*/\\1/p')\"\n    RACKSPACE_Tenant=\"$(echo \"$response\" | _normalizeJson | sed -n 's/^.*\"token\":{.*,\"id\":\"\\([^\"]*\\)\"}.*/\\1/p')\"\n    _debug RACKSPACE_Token \"$RACKSPACE_Token\"\n    _debug RACKSPACE_Tenant \"$RACKSPACE_Tenant\"\n  fi\n  return 0\n}\n\n_rackspace_check_auth() {\n  # retrieve the rackspace creds\n  RACKSPACE_Username=\"${RACKSPACE_Username:-$(_readaccountconf_mutable RACKSPACE_Username)}\"\n  RACKSPACE_Apikey=\"${RACKSPACE_Apikey:-$(_readaccountconf_mutable RACKSPACE_Apikey)}\"\n  # check their vals for null\n  if [ -z \"$RACKSPACE_Username\" ] || [ -z \"$RACKSPACE_Apikey\" ]; then\n    RACKSPACE_Username=\"\"\n    RACKSPACE_Apikey=\"\"\n    _err \"You didn't specify a Rackspace username and api key.\"\n    _err \"Please set those values and try again.\"\n    return 1\n  fi\n  # save the username and api key to the account conf file.\n  _saveaccountconf_mutable RACKSPACE_Username \"$RACKSPACE_Username\"\n  _saveaccountconf_mutable RACKSPACE_Apikey \"$RACKSPACE_Apikey\"\n  if [ -z \"$RACKSPACE_Token\" ]; then\n    _info \"Getting authorization token.\"\n    if ! _rackspace_authorization; then\n      _err \"Can not get token.\"\n    fi\n  fi\n}\n\n_rackspace_check_rootzone() {\n  _debug \"First detect the root zone\"\n  if ! _get_root_zone \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n}\n"
  },
  {
    "path": "dnsapi/dns_rage4.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_rage4_info='rage4.com\nSite: rage4.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_rage4\nOptions:\n RAGE4_TOKEN API Key\n RAGE4_USERNAME Username\nIssues: github.com/acmesh-official/acme.sh/issues/4306\n'\n\nRAGE4_Api=\"https://rage4.com/rapi/\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_rage4_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  unquotedtxtvalue=$(echo \"$txtvalue\" | tr -d \\\")\n\n  RAGE4_USERNAME=\"${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}\"\n  RAGE4_TOKEN=\"${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}\"\n\n  if [ -z \"$RAGE4_USERNAME\" ] || [ -z \"$RAGE4_TOKEN\" ]; then\n    RAGE4_USERNAME=\"\"\n    RAGE4_TOKEN=\"\"\n    _err \"You didn't specify a Rage4 api token and username yet.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable RAGE4_USERNAME \"$RAGE4_USERNAME\"\n  _saveaccountconf_mutable RAGE4_TOKEN \"$RAGE4_TOKEN\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n\n  _rage4_rest \"createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1\"\n\n  # Response after adding a TXT record should be something like this:\n  # {\"status\":true,\"id\":28160443,\"error\":null}\n  if ! _contains \"$response\" '\"error\":null' >/dev/null; then\n    _err \"Error while adding TXT record: '$response'\"\n    return 1\n  fi\n\n  return 0\n}\n\n#fulldomain txtvalue\ndns_rage4_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  RAGE4_USERNAME=\"${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}\"\n  RAGE4_TOKEN=\"${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n\n  _debug \"Getting txt records\"\n  _rage4_rest \"getrecords/?id=${_domain_id}\"\n\n  _record_id=$(echo \"$response\" | tr '{' '\\n' | grep '\"TXT\"' | grep \"\\\"$txtvalue\" | sed -rn 's/.*\"id\":([[:digit:]]+),.*/\\1/p')\n  if [ -z \"$_record_id\" ]; then\n    _err \"error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'.\"\n    return 1\n  fi\n\n  _rage4_rest \"deleterecord/?id=${_record_id}\"\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n\n  if ! _rage4_rest \"getdomains\"; then\n    return 1\n  fi\n  _debug _get_root_domain \"$domain\"\n\n  for line in $(echo \"$response\" | tr '}' '\\n'); do\n    __domain=$(echo \"$line\" | sed -rn 's/.*\"name\":\"([^\"]*)\",.*/\\1/p')\n    __domain_id=$(echo \"$line\" | sed -rn 's/.*\"id\":([^,]*),.*/\\1/p')\n    if [ \"$domain\" != \"${domain%\"$__domain\"*}\" ]; then\n      _domain_id=\"$__domain_id\"\n      break\n    fi\n  done\n\n  if [ -z \"$_domain_id\" ]; then\n    return 1\n  fi\n\n  return 0\n}\n\n_rage4_rest() {\n  ep=\"$1\"\n  _debug \"$ep\"\n\n  username_trimmed=$(echo \"$RAGE4_USERNAME\" | tr -d '\"')\n  token_trimmed=$(echo \"$RAGE4_TOKEN\" | tr -d '\"')\n  auth=$(printf '%s:%s' \"$username_trimmed\" \"$token_trimmed\" | _base64)\n\n  export _H1=\"Authorization: Basic $auth\"\n\n  response=\"$(_get \"$RAGE4_Api$ep\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_rcode0.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_rcode0_info='Rcode0 rcodezero.at\nSite: rcodezero.at\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rcode0\nOptions:\n RCODE0_URL API URL. E.g. \"https://my.rcodezero.at\"\n RCODE0_API_TOKEN API Token\n RCODE0_TTL TTL. Default: \"60\".\nIssues: github.com/acmesh-official/acme.sh/issues/2490\n'\n\n#Rcode0 API Integration\n#https://my.rcodezero.at/api-doc\n#\n# log into https://my.rcodezero.at/enableapi and  get your ACME API Token (the ACME API token has limited\n# access to the REST calls needed for acme.sh only)\n\nDEFAULT_RCODE0_URL=\"https://my.rcodezero.at\"\nDEFAULT_RCODE0_TTL=60\n\n########  Public functions #####################\n#Usage: add _acme-challenge.www.domain.com \"123456789ABCDEF0000000000000000000000000000000000000\"\n#fulldomain\n#txtvalue\ndns_rcode0_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  RCODE0_API_TOKEN=\"${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}\"\n  RCODE0_URL=\"${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}\"\n  RCODE0_TTL=\"${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}\"\n\n  if [ -z \"$RCODE0_URL\" ]; then\n    RCODE0_URL=\"$DEFAULT_RCODE0_URL\"\n  fi\n\n  if [ -z \"$RCODE0_API_TOKEN\" ]; then\n    RCODE0_API_TOKEN=\"\"\n    _err \"Missing Rcode0 ACME API Token.\"\n    _err \"Please login and create your token at httsp://my.rcodezero.at/enableapi and try again.\"\n    return 1\n  fi\n\n  if [ -z \"$RCODE0_TTL\" ]; then\n    RCODE0_TTL=\"$DEFAULT_RCODE0_TTL\"\n  fi\n\n  #save the token to the account conf file.\n  _saveaccountconf_mutable RCODE0_API_TOKEN \"$RCODE0_API_TOKEN\"\n\n  if [ \"$RCODE0_URL\" != \"$DEFAULT_RCODE0_URL\" ]; then\n    _saveaccountconf_mutable RCODE0_URL \"$RCODE0_URL\"\n  fi\n\n  if [ \"$RCODE0_TTL\" != \"$DEFAULT_RCODE0_TTL\" ]; then\n    _saveaccountconf_mutable RCODE0_TTL \"$RCODE0_TTL\"\n  fi\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"No 'MASTER' zone for $fulldomain found at RcodeZero Anycast.\"\n    return 1\n  fi\n  _debug _domain \"$_domain\"\n\n  _debug \"Adding record\"\n\n  _record_string=\"\"\n  _build_record_string \"$txtvalue\"\n  _list_existingchallenges\n  for oldchallenge in $_existing_challenges; do\n    _build_record_string \"$oldchallenge\"\n  done\n\n  _debug \"Challenges: $_existing_challenges\"\n\n  if [ -z \"$_existing_challenges\" ]; then\n    if ! _rcode0_rest \"PATCH\" \"/api/v1/acme/zones/$_domain/rrsets\" \"[{\\\"changetype\\\": \\\"add\\\", \\\"name\\\": \\\"$fulldomain.\\\", \\\"type\\\": \\\"TXT\\\", \\\"ttl\\\": $RCODE0_TTL, \\\"records\\\": [$_record_string]}]\"; then\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  else\n    # try update in case a records exists (need for wildcard certs)\n    if ! _rcode0_rest \"PATCH\" \"/api/v1/acme/zones/$_domain/rrsets\" \"[{\\\"changetype\\\": \\\"update\\\", \\\"name\\\": \\\"$fulldomain.\\\", \\\"type\\\": \\\"TXT\\\", \\\"ttl\\\": $RCODE0_TTL, \\\"records\\\": [$_record_string]}]\"; then\n      _err \"Set txt record error.\"\n      return 1\n    fi\n  fi\n\n  return 0\n}\n\n#fulldomain txtvalue\ndns_rcode0_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  RCODE0_API_TOKEN=\"${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}\"\n  RCODE0_URL=\"${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}\"\n  RCODE0_TTL=\"${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}\"\n\n  if [ -z \"$RCODE0_URL\" ]; then\n    RCODE0_URL=\"$DEFAULT_RCODE0_URL\"\n  fi\n\n  if [ -z \"$RCODE0_API_TOKEN\" ]; then\n    RCODE0_API_TOKEN=\"\"\n    _err \"Missing Rcode0 API Token.\"\n    _err \"Please login and create your token at httsp://my.rcodezero.at/enableapi and try again.\"\n    return 1\n  fi\n\n  #save the api addr and key to the account conf file.\n  _saveaccountconf_mutable RCODE0_URL \"$RCODE0_URL\"\n  _saveaccountconf_mutable RCODE0_API_TOKEN \"$RCODE0_API_TOKEN\"\n\n  if [ \"$RCODE0_TTL\" != \"$DEFAULT_RCODE0_TTL\" ]; then\n    _saveaccountconf_mutable RCODE0_TTL \"$RCODE0_TTL\"\n  fi\n\n  if [ -z \"$RCODE0_TTL\" ]; then\n    RCODE0_TTL=\"$DEFAULT_RCODE0_TTL\"\n  fi\n\n  _debug \"Detect root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug \"Remove record\"\n\n  #Enumerate existing acme challenges\n  _list_existingchallenges\n\n  if _contains \"$_existing_challenges\" \"$txtvalue\"; then\n    #Delete all challenges (PowerDNS API does not allow to delete content)\n    if ! _rcode0_rest \"PATCH\" \"/api/v1/acme/zones/$_domain/rrsets\" \"[{\\\"changetype\\\": \\\"delete\\\", \\\"name\\\": \\\"$fulldomain.\\\", \\\"type\\\": \\\"TXT\\\"}]\"; then\n      _err \"Delete txt record error.\"\n      return 1\n    fi\n    _record_string=\"\"\n    #If the only existing challenge was the challenge to delete: nothing to do\n    if ! [ \"$_existing_challenges\" = \"$txtvalue\" ]; then\n      for oldchallenge in $_existing_challenges; do\n        #Build up the challenges to re-add, ommitting the one what should be deleted\n        if ! [ \"$oldchallenge\" = \"$txtvalue\" ]; then\n          _build_record_string \"$oldchallenge\"\n        fi\n      done\n      #Recreate the existing challenges\n      if ! _rcode0_rest \"PATCH\" \"/api/v1/acme/zones/$_domain/rrsets\" \"[{\\\"changetype\\\": \\\"update\\\", \\\"name\\\": \\\"$fulldomain.\\\", \\\"type\\\": \\\"TXT\\\", \\\"ttl\\\": $RCODE0_TTL, \\\"records\\\": [$_record_string]}]\"; then\n        _err \"Set txt record error.\"\n        return 1\n      fi\n    fi\n  else\n    _info \"Record not found, nothing to remove\"\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    _debug \"try to find: $h\"\n    if _rcode0_rest \"GET\" \"/api/v1/acme/zones/$h\"; then\n      if [ \"$response\" = \"[\\\"found\\\"]\" ]; then\n        _domain=\"$h\"\n        if [ -z \"$h\" ]; then\n          _domain=\"=2E\"\n        fi\n        return 0\n      elif [ \"$response\" = \"[\\\"not a master domain\\\"]\" ]; then\n        return 1\n      fi\n    fi\n\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n  _debug \"no matching domain for $domain found\"\n\n  return 1\n}\n\n_rcode0_rest() {\n  method=$1\n  ep=$2\n  data=$3\n\n  export _H1=\"Authorization: Bearer $RCODE0_API_TOKEN\"\n\n  if [ ! \"$method\" = \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$RCODE0_URL$ep\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$RCODE0_URL$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n\n  return 0\n}\n\n_build_record_string() {\n  _record_string=\"${_record_string:+${_record_string}, }{\\\"content\\\": \\\"\\\\\\\"${1}\\\\\\\"\\\", \\\"disabled\\\": false}\"\n}\n\n_list_existingchallenges() {\n  _rcode0_rest \"GET\" \"/api/v1/acme/zones/$_domain/rrsets\"\n  _existing_challenges=$(echo \"$response\" | _normalizeJson | _egrep_o \"\\\"name\\\":\\\"${fulldomain}[^]]*}\" | _egrep_o 'content\\\":\\\"\\\\\"[^\\\\]*' | sed -n 's/^content\":\"\\\\\"//p')\n  _debug2 \"$_existing_challenges\"\n}\n"
  },
  {
    "path": "dnsapi/dns_regru.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_regru_info='reg.ru\nSite: reg.ru\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_regru\nOptions:\n REGRU_API_Username Username\n REGRU_API_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/2336\n'\n\nREGRU_API_URL=\"https://api.reg.ru/api/regru2\"\n\n########  Public functions #####################\n\ndns_regru_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  REGRU_API_Username=\"${REGRU_API_Username:-$(_readaccountconf_mutable REGRU_API_Username)}\"\n  REGRU_API_Password=\"${REGRU_API_Password:-$(_readaccountconf_mutable REGRU_API_Password)}\"\n  if [ -z \"$REGRU_API_Username\" ] || [ -z \"$REGRU_API_Password\" ]; then\n    REGRU_API_Username=\"\"\n    REGRU_API_Password=\"\"\n    _err \"You don't specify regru password or username.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable REGRU_API_Username \"$REGRU_API_Username\"\n  _saveaccountconf_mutable REGRU_API_Password \"$REGRU_API_Password\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain \"$_domain\"\n\n  _subdomain=$(echo \"$fulldomain\" | sed -r \"s/.$_domain//\")\n  _debug _subdomain \"$_subdomain\"\n\n  _info \"Adding TXT record to ${fulldomain}\"\n  _regru_rest POST \"zone/add_txt\" \"input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json\"\n\n  if ! _contains \"${response}\" 'error'; then\n    return 0\n  fi\n  _err \"Could not create resource record, check logs\"\n  _err \"${response}\"\n  return 1\n}\n\ndns_regru_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  REGRU_API_Username=\"${REGRU_API_Username:-$(_readaccountconf_mutable REGRU_API_Username)}\"\n  REGRU_API_Password=\"${REGRU_API_Password:-$(_readaccountconf_mutable REGRU_API_Password)}\"\n  if [ -z \"$REGRU_API_Username\" ] || [ -z \"$REGRU_API_Password\" ]; then\n    REGRU_API_Username=\"\"\n    REGRU_API_Password=\"\"\n    _err \"You don't specify regru password or username.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain \"$_domain\"\n\n  _subdomain=$(echo \"$fulldomain\" | sed -r \"s/.$_domain//\")\n  _debug _subdomain \"$_subdomain\"\n\n  _info \"Deleting resource record $fulldomain\"\n  _regru_rest POST \"zone/remove_record\" \"input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json\"\n\n  if ! _contains \"${response}\" 'error'; then\n    return 0\n  fi\n  _err \"Could not delete resource record, check logs\"\n  _err \"${response}\"\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _domain=domain.com\n_get_root() {\n  domain=$1\n\n  _regru_rest POST \"service/get_list\" \"username=${REGRU_API_Username}&password=${REGRU_API_Password}&output_format=xml&servtype=domain\"\n  domains_list=$(echo \"${response}\" | grep dname | sed -r \"s/.*dname=\\\"([^\\\"]+)\\\".*/\\\\1/g\")\n\n  for ITEM in ${domains_list}; do\n    IDN_ITEM=${ITEM}\n    case \"${domain}\" in\n    *${IDN_ITEM}*)\n      _domain=\"$(_idn \"${ITEM}\")\"\n      _debug _domain \"${_domain}\"\n      return 0\n      ;;\n    esac\n  done\n\n  return 1\n}\n\n#returns\n# response\n_regru_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/x-www-form-urlencoded\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$REGRU_API_URL/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$REGRU_API_URL/$ep?$data\")\"\n  fi\n\n  _debug response \"${response}\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_scaleway.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_scaleway_info='ScaleWay.com\nSite: ScaleWay.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_scaleway\nOptions:\n SCALEWAY_API_TOKEN API Token\nIssues: github.com/acmesh-official/acme.sh/issues/3295\n'\n\n# Scaleway API\n# https://developers.scaleway.com/en/products/domain/dns/api/\n\n########  Public functions #####################\n\nSCALEWAY_API=\"https://api.scaleway.com/domain/v2beta1\"\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_scaleway_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _scaleway_check_config; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  _scaleway_create_TXT_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n  if _contains \"$response\" \"records\"; then\n    return 0\n  else\n    _err error \"$response\"\n    return 1\n  fi\n\n}\n\ndns_scaleway_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _scaleway_check_config; then\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Deleting record\"\n  _scaleway_delete_TXT_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"\n  if _contains \"$response\" \"records\"; then\n    return 0\n  else\n    _err error \"$response\"\n    return 1\n  fi\n\n}\n\n####################  Private functions below ##################################\n\n_scaleway_check_config() {\n  SCALEWAY_API_TOKEN=\"${SCALEWAY_API_TOKEN:-$(_readaccountconf_mutable SCALEWAY_API_TOKEN)}\"\n  if [ -z \"$SCALEWAY_API_TOKEN\" ]; then\n    _err \"No API key specified for Scaleway API.\"\n    _err \"Create your key and export it as SCALEWAY_API_TOKEN\"\n    return 1\n  fi\n  if ! _scaleway_rest GET \"dns-zones\"; then\n    _err \"Invalid API key specified for Scaleway API.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable SCALEWAY_API_TOKEN \"$SCALEWAY_API_TOKEN\"\n\n  return 0\n}\n\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _scaleway_rest GET \"dns-zones/$h/records\"\n\n    if ! _contains \"$response\" \"subdomain not found\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  _err \"Unable to retrive DNS zone matching this domain\"\n  return 1\n}\n\n# this function add a TXT record\n_scaleway_create_TXT_record() {\n  txt_zone=$1\n  txt_name=$2\n  txt_value=$3\n\n  _scaleway_rest PATCH \"dns-zones/$txt_zone/records\" \"{\\\"return_all_records\\\":false,\\\"changes\\\":[{\\\"add\\\":{\\\"records\\\":[{\\\"name\\\":\\\"$txt_name\\\",\\\"data\\\":\\\"$txt_value\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":60}]}}]}\"\n\n  if _contains \"$response\" \"records\"; then\n    return 0\n  else\n    _err \"error1 $response\"\n    return 1\n  fi\n}\n\n# this function delete a TXT record based on name and content\n_scaleway_delete_TXT_record() {\n  txt_zone=$1\n  txt_name=$2\n  txt_value=$3\n\n  _scaleway_rest PATCH \"dns-zones/$txt_zone/records\" \"{\\\"return_all_records\\\":false,\\\"changes\\\":[{\\\"delete\\\":{\\\"id_fields\\\":{\\\"name\\\":\\\"$txt_name\\\",\\\"data\\\":\\\"$txt_value\\\",\\\"type\\\":\\\"TXT\\\"}}}]}\"\n\n  if _contains \"$response\" \"records\"; then\n    return 0\n  else\n    _err \"error2 $response\"\n    return 1\n  fi\n}\n\n_scaleway_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n  _scaleway_url=\"$SCALEWAY_API/$ep\"\n  _debug2 _scaleway_url \"$_scaleway_url\"\n  export _H1=\"x-auth-token: $SCALEWAY_API_TOKEN\"\n  export _H2=\"Accept: application/json\"\n  export _H3=\"Content-Type: application/json\"\n\n  if [ \"$data\" ] || [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$_scaleway_url\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$_scaleway_url\")\"\n  fi\n  if [ \"$?\" != \"0\" ] || _contains \"$response\" \"denied_authentication\" || _contains \"$response\" \"Method not allowed\" || _contains \"$response\" \"json parse error: unexpected EOF\"; then\n    _err \"error $response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_schlundtech.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_schlundtech_info='SchlundTech.de\nSite: SchlundTech.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_schlundtech\nOptions:\n SCHLUNDTECH_USER Username\n SCHLUNDTECH_PASSWORD Password\nIssues: github.com/acmesh-official/acme.sh/issues/2246\nAuthor: @mod242\n'\n\nSCHLUNDTECH_API=\"https://gateway.schlundtech.de\"\n\n# Arguments:\n#   txtdomain\n#   txt\ndns_schlundtech_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  SCHLUNDTECH_USER=\"${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}\"\n  SCHLUNDTECH_PASSWORD=\"${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}\"\n\n  if [ -z \"$SCHLUNDTECH_USER\" ] || [ -z \"$SCHLUNDTECH_PASSWORD\" ]; then\n    _err \"You didn't specify schlundtech user and password.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable SCHLUNDTECH_USER \"$SCHLUNDTECH_USER\"\n  _saveaccountconf_mutable SCHLUNDTECH_PASSWORD \"$SCHLUNDTECH_PASSWORD\"\n\n  _debug \"First detect the root zone\"\n\n  if ! _get_autodns_zone \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _zone \"$_zone\"\n  _debug _system_ns \"$_system_ns\"\n\n  _info \"Adding TXT record\"\n\n  autodns_response=\"$(_autodns_zone_update \"$_zone\" \"$_sub_domain\" \"$txtvalue\" \"$_system_ns\")\"\n\n  if [ \"$?\" -eq \"0\" ]; then\n    _info \"Added, OK\"\n    return 0\n  fi\n\n  return 1\n}\n\n# Arguments:\n#   txtdomain\n#   txt\ndns_schlundtech_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  SCHLUNDTECH_USER=\"${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}\"\n  SCHLUNDTECH_PASSWORD=\"${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}\"\n\n  if [ -z \"$SCHLUNDTECH_USER\" ] || [ -z \"$SCHLUNDTECH_PASSWORD\" ]; then\n    _err \"You didn't specify schlundtech user and password.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n\n  if ! _get_autodns_zone \"$fulldomain\"; then\n    _err \"zone not found\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _zone \"$_zone\"\n  _debug _system_ns \"$_system_ns\"\n\n  _info \"Delete TXT record\"\n\n  autodns_response=\"$(_autodns_zone_cleanup \"$_zone\" \"$_sub_domain\" \"$txtvalue\" \"$_system_ns\")\"\n\n  if [ \"$?\" -eq \"0\" ]; then\n    _info \"Deleted, OK\"\n    return 0\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n\n# Arguments:\n#   fulldomain\n# Returns:\n#   _sub_domain=_acme-challenge.www\n#   _zone=domain.com\n#   _system_ns\n_get_autodns_zone() {\n  domain=\"$1\"\n\n  i=2\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n\n    if [ -z \"$h\" ]; then\n      # not valid\n      return 1\n    fi\n\n    autodns_response=\"$(_autodns_zone_inquire \"$h\")\"\n\n    if [ \"$?\" -ne \"0\" ]; then\n      _err \"invalid domain\"\n      return 1\n    fi\n\n    if _contains \"$autodns_response\" \"<summary>1</summary>\" >/dev/null; then\n      _zone=\"$(echo \"$autodns_response\" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)\"\n      _system_ns=\"$(echo \"$autodns_response\" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)\"\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_build_request_auth_xml() {\n  printf \"<auth>\n    <user>%s</user>\n    <password>%s</password>\n    <context>10</context>\n  </auth>\" \"$SCHLUNDTECH_USER\" \"$SCHLUNDTECH_PASSWORD\"\n}\n\n# Arguments:\n#   zone\n_build_zone_inquire_xml() {\n  printf \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\n  <request>\n    %s\n    <task>\n      <code>0205</code>\n      <view>\n        <children>1</children>\n        <limit>1</limit>\n      </view>\n      <where>\n        <key>name</key>\n        <operator>eq</operator>\n        <value>%s</value>\n      </where>\n    </task>\n  </request>\" \"$(_build_request_auth_xml)\" \"$1\"\n}\n\n# Arguments:\n#   zone\n#   subdomain\n#   txtvalue\n#   system_ns\n_build_zone_update_xml() {\n  printf \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\n  <request>\n    %s\n    <task>\n      <code>0202001</code>\n      <default>\n        <rr_add>\n          <name>%s</name>\n          <ttl>600</ttl>\n          <type>TXT</type>\n          <value>%s</value>\n        </rr_add>\n      </default>\n      <zone>\n        <name>%s</name>\n        <system_ns>%s</system_ns>\n      </zone>\n    </task>\n  </request>\" \"$(_build_request_auth_xml)\" \"$2\" \"$3\" \"$1\" \"$4\"\n}\n\n# Arguments:\n#   zone\n_autodns_zone_inquire() {\n  request_data=\"$(_build_zone_inquire_xml \"$1\")\"\n  autodns_response=\"$(_autodns_api_call \"$request_data\")\"\n  ret=\"$?\"\n\n  printf \"%s\" \"$autodns_response\"\n  return \"$ret\"\n}\n\n# Arguments:\n#   zone\n#   subdomain\n#   txtvalue\n#   system_ns\n_autodns_zone_update() {\n  request_data=\"$(_build_zone_update_xml \"$1\" \"$2\" \"$3\" \"$4\")\"\n  autodns_response=\"$(_autodns_api_call \"$request_data\")\"\n  ret=\"$?\"\n\n  printf \"%s\" \"$autodns_response\"\n  return \"$ret\"\n}\n\n# Arguments:\n#   zone\n#   subdomain\n#   txtvalue\n#   system_ns\n_autodns_zone_cleanup() {\n  request_data=\"$(_build_zone_update_xml \"$1\" \"$2\" \"$3\" \"$4\")\"\n  # replace 'rr_add>' with 'rr_rem>' in request_data\n  request_data=\"$(printf -- \"%s\" \"$request_data\" | sed 's/rr_add>/rr_rem>/g')\"\n  autodns_response=\"$(_autodns_api_call \"$request_data\")\"\n  ret=\"$?\"\n\n  printf \"%s\" \"$autodns_response\"\n  return \"$ret\"\n}\n\n# Arguments:\n#   request_data\n_autodns_api_call() {\n  request_data=\"$1\"\n\n  _debug request_data \"$request_data\"\n\n  autodns_response=\"$(_post \"$request_data\" \"$SCHLUNDTECH_API\")\"\n  ret=\"$?\"\n\n  _debug autodns_response \"$autodns_response\"\n\n  if [ \"$ret\" -ne \"0\" ]; then\n    _err \"error\"\n    return 1\n  fi\n\n  if _contains \"$autodns_response\" \"<type>success</type>\" >/dev/null; then\n    _info \"success\"\n    printf \"%s\" \"$autodns_response\"\n    return 0\n  fi\n\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_selectel.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_selectel_info='Selectel.com\nDomains: Selectel.ru\nSite: Selectel.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel\nOptions: For old API version v1 (deprecated)\n   SL_Ver API version. Use \"v1\".\n   SL_Key API Key\nOptionsAlt: For the current API version v2\n   SL_Ver API version. Use \"v2\".\n   SL_Login_ID Account ID\n   SL_Project_Name Project name\n   SL_Login_Name Service user name\n   SL_Pswd Service user password\n   SL_Expire Token lifetime. In minutes (0-1440). Default \"1400\"\nIssues: github.com/acmesh-official/acme.sh/issues/5126\n'\n\nSL_Api=\"https://api.selectel.ru/domains\"\nauth_uri=\"https://cloud.api.selcloud.ru/identity/v3/auth/tokens\"\n_sl_sep='#'\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_selectel_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _sl_init_vars; then\n    return 1\n  fi\n  _debug2 SL_Ver \"$SL_Ver\"\n  _debug2 SL_Expire \"$SL_Expire\"\n  _debug2 SL_Login_Name \"$SL_Login_Name\"\n  _debug2 SL_Login_ID \"$SL_Login_ID\"\n  _debug2 SL_Project_Name \"$SL_Project_Name\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if [ \"$SL_Ver\" = \"v2\" ]; then\n    _ext_srv1=\"/zones/\"\n    _ext_srv2=\"/rrset/\"\n    _text_tmp=$(echo \"$txtvalue\" | sed -En \"s/[\\\"]*([^\\\"]*)/\\1/p\")\n    _text_tmp='\\\"'$_text_tmp'\\\"'\n    _data=\"{\\\"type\\\": \\\"TXT\\\", \\\"ttl\\\": 60, \\\"name\\\": \\\"${fulldomain}.\\\", \\\"records\\\": [{\\\"content\\\":\\\"$_text_tmp\\\"}]}\"\n  elif [ \"$SL_Ver\" = \"v1\" ]; then\n    _ext_srv1=\"/\"\n    _ext_srv2=\"/records/\"\n    _data=\"{\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":60,\\\"name\\\":\\\"$fulldomain\\\",\\\"content\\\":\\\"$txtvalue\\\"}\"\n  else\n    _err \"Error. Unsupported version API $SL_Ver\"\n    return 1\n  fi\n  _ext_uri=\"${_ext_srv1}$_domain_id${_ext_srv2}\"\n  _debug _ext_uri \"$_ext_uri\"\n  _debug _data \"$_data\"\n\n  if _sl_rest POST \"$_ext_uri\" \"$_data\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    fi\n    if _contains \"$response\" \"already_exists\"; then\n      # record TXT with $fulldomain already exists\n      if [ \"$SL_Ver\" = \"v2\" ]; then\n        # It is necessary to add one more content to the comments\n        # read all records rrset\n        _debug \"Getting txt records\"\n        _sl_rest GET \"${_ext_uri}\"\n        # There is already a $txtvalue value, no need to add it\n        if _contains \"$response\" \"$txtvalue\"; then\n          _info \"Added, OK\"\n          _info \"Txt record ${fulldomain} with value ${txtvalue} already exists\"\n          return 0\n        fi\n        # group \\1 - full record rrset; group \\2 - records attribute value, exactly {\"content\":\"\\\"value1\\\"\"},{\"content\":\"\\\"value2\\\"\"}\",...\n        _record_seg=\"$(echo \"$response\" | sed -En \"s/.*(\\{\\\"id\\\"[^}]*${fulldomain}[^}]*records[^}]*\\[(\\{[^]]*\\})\\][^}]*}).*/\\1/p\")\"\n        _record_array=\"$(echo \"$response\" | sed -En \"s/.*(\\{\\\"id\\\"[^}]*${fulldomain}[^}]*records[^}]*\\[(\\{[^]]*\\})\\][^}]*}).*/\\2/p\")\"\n        # record id\n        _record_id=\"$(echo \"$_record_seg\" | tr \",\" \"\\n\" | tr \"}\" \"\\n\" | tr -d \" \" | grep \"\\\"id\\\"\" | cut -d : -f 2 | tr -d \"\\\"\")\"\n        # preparing _data\n        _tmp_str=\"${_record_array},{\\\"content\\\":\\\"${_text_tmp}\\\"}\"\n        _data=\"{\\\"ttl\\\": 60, \\\"records\\\": [${_tmp_str}]}\"\n        _debug2 _record_seg \"$_record_seg\"\n        _debug2 _record_array \"$_record_array\"\n        _debug2 _record_array \"$_record_id\"\n        _debug \"New data for record\" \"$_data\"\n        if _sl_rest PATCH \"${_ext_uri}${_record_id}\" \"$_data\"; then\n          _info \"Added, OK\"\n          return 0\n        fi\n      elif [ \"$SL_Ver\" = \"v1\" ]; then\n        _info \"Added, OK\"\n        return 0\n      fi\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n}\n\n#fulldomain txtvalue\ndns_selectel_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _sl_init_vars \"nosave\"; then\n    return 1\n  fi\n  _debug2 SL_Ver \"$SL_Ver\"\n  _debug2 SL_Expire \"$SL_Expire\"\n  _debug2 SL_Login_Name \"$SL_Login_Name\"\n  _debug2 SL_Login_ID \"$SL_Login_ID\"\n  _debug2 SL_Project_Name \"$SL_Project_Name\"\n  #\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  #\n  if [ \"$SL_Ver\" = \"v2\" ]; then\n    _ext_srv1=\"/zones/\"\n    _ext_srv2=\"/rrset/\"\n  elif [ \"$SL_Ver\" = \"v1\" ]; then\n    _ext_srv1=\"/\"\n    _ext_srv2=\"/records/\"\n  else\n    _err \"Error. Unsupported version API $SL_Ver\"\n    return 1\n  fi\n  #\n  _debug \"Getting txt records\"\n  _ext_uri=\"${_ext_srv1}$_domain_id${_ext_srv2}\"\n  _debug _ext_uri \"$_ext_uri\"\n  _sl_rest GET \"${_ext_uri}\"\n  #\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _err \"Txt record not found\"\n    return 1\n  fi\n  #\n  if [ \"$SL_Ver\" = \"v2\" ]; then\n    _record_seg=\"$(echo \"$response\" | sed -En \"s/.*(\\{\\\"id\\\"[^}]*records[^[]*(\\[(\\{[^]]*${txtvalue}[^]]*)\\])[^}]*}).*/\\1/gp\")\"\n    _record_arr=\"$(echo \"$response\" | sed -En \"s/.*(\\{\\\"id\\\"[^}]*records[^[]*(\\[(\\{[^]]*${txtvalue}[^]]*)\\])[^}]*}).*/\\3/p\")\"\n  elif [ \"$SL_Ver\" = \"v1\" ]; then\n    _record_seg=\"$(echo \"$response\" | _egrep_o \"[^{]*\\\"content\\\" *: *\\\"$txtvalue\\\"[^}]*}\")\"\n  else\n    _err \"Error. Unsupported version API $SL_Ver\"\n    return 1\n  fi\n  _debug2 \"_record_seg\" \"$_record_seg\"\n  if [ -z \"$_record_seg\" ]; then\n    _err \"can not find _record_seg\"\n    return 1\n  fi\n  # record id\n  # the following lines change the algorithm for deleting records with the value $txtvalue\n  # if you use the 1st line, then all such records are deleted at once\n  # if you use the 2nd line, then only the first entry from them is deleted\n  #_record_id=\"$(echo \"$_record_seg\" | tr \",\" \"\\n\" | tr \"}\" \"\\n\" | tr -d \" \" | grep \"\\\"id\\\"\" | cut -d : -f 2 | tr -d \"\\\"\")\"\n  _record_id=\"$(echo \"$_record_seg\" | tr \",\" \"\\n\" | tr \"}\" \"\\n\" | tr -d \" \" | grep \"\\\"id\\\"\" | cut -d : -f 2 | tr -d \"\\\"\" | sed '1!d')\"\n  if [ -z \"$_record_id\" ]; then\n    _err \"can not find _record_id\"\n    return 1\n  fi\n  _debug2 \"_record_id\" \"$_record_id\"\n  # delete all record type TXT with text $txtvalue\n  if [ \"$SL_Ver\" = \"v2\" ]; then\n    # actual\n    _new_arr=\"$(echo \"$_record_seg\" | sed -En \"s/.*(\\{\\\"id\\\"[^}]*records[^[]*(\\[(\\{[^]]*${txtvalue}[^]]*)\\])[^}]*}).*/\\3/gp\" | sed -En \"s/(\\},\\{)/}\\n{/gp\" | sed \"/${txtvalue}/d\" | sed \":a;N;s/\\n/,/;ta\")\"\n    # uri record for DEL or PATCH\n    _del_uri=\"${_ext_uri}${_record_id}\"\n    _debug _del_uri \"$_del_uri\"\n    if [ -z \"$_new_arr\" ]; then\n      # remove record\n      if ! _sl_rest DELETE \"${_del_uri}\"; then\n        _err \"Delete record error: ${_del_uri}.\"\n      else\n        info \"Delete record success: ${_del_uri}.\"\n      fi\n    else\n      # update a record by removing one element in content\n      _data=\"{\\\"ttl\\\": 60, \\\"records\\\": [${_new_arr}]}\"\n      _debug2 _data \"$_data\"\n      # REST API PATCH call\n      if _sl_rest PATCH \"${_del_uri}\" \"$_data\"; then\n        _info \"Patched, OK: ${_del_uri}\"\n      else\n        _err \"Patched record error: ${_del_uri}.\"\n      fi\n    fi\n  else\n    # legacy\n    for _one_id in $_record_id; do\n      _del_uri=\"${_ext_uri}${_one_id}\"\n      _debug _del_uri \"$_del_uri\"\n      if ! _sl_rest DELETE \"${_del_uri}\"; then\n        _err \"Delete record error: ${_del_uri}.\"\n      else\n        info \"Delete record success: ${_del_uri}.\"\n      fi\n    done\n  fi\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n\n  if [ \"$SL_Ver\" = 'v1' ]; then\n    # version API 1\n    if ! _sl_rest GET \"/\"; then\n      return 1\n    fi\n    i=2\n    p=1\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        return 1\n      fi\n      if _contains \"$response\" \"\\\"name\\\" *: *\\\"$h\\\",\"; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        _debug \"Getting domain id for $h\"\n        if ! _sl_rest GET \"/$h\"; then\n          _err \"Error read records of all domains $SL_Ver\"\n          return 1\n        fi\n        _domain_id=\"$(echo \"$response\" | tr \",\" \"\\n\" | tr \"}\" \"\\n\" | tr -d \" \" | grep \"\\\"id\\\":\" | cut -d : -f 2)\"\n        return 0\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n    _err \"Error read records of all domains $SL_Ver\"\n    return 1\n  elif [ \"$SL_Ver\" = \"v2\" ]; then\n    # version API 2\n    _ext_uri='/zones/'\n    domain=\"${domain}.\"\n    _debug \"domain:: \" \"$domain\"\n    # read records of all domains\n    if ! _sl_rest GET \"$_ext_uri\"; then\n      _err \"Error read records of all domains $SL_Ver\"\n      return 1\n    fi\n    i=1\n    p=1\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        _err \"The domain was not found among the registered ones\"\n        return 1\n      fi\n      _domain_record=$(echo \"$response\" | sed -En \"s/.*(\\{[^}]*id[^}]*\\\"name\\\" *: *\\\"$h\\\"[^}]*}).*/\\1/p\")\n      _debug \"_domain_record:: \" \"$_domain_record\"\n      if [ -n \"$_domain_record\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        _debug \"Getting domain id for $h\"\n        _domain_id=$(echo \"$_domain_record\" | sed -En \"s/\\{[^}]*\\\"id\\\" *: *\\\"([^\\\"]*)\\\"[^}]*\\}/\\1/p\")\n        return 0\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n    _err \"Error read records of all domains $SL_Ver\"\n    return 1\n  else\n    _err \"Error. Unsupported version API $SL_Ver\"\n    return 1\n  fi\n}\n\n#################################################################\n# use: method add_url body\n_sl_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n\n  _token=$(_get_auth_token)\n  if [ -z \"$_token\" ]; then\n    _err \"BAD key or token $ep\"\n    return 1\n  fi\n  if [ \"$SL_Ver\" = v2 ]; then\n    _h1_name=\"X-Auth-Token\"\n  else\n    _h1_name='X-Token'\n  fi\n  export _H1=\"${_h1_name}: ${_token}\"\n  export _H2=\"Content-Type: application/json\"\n  _debug2 \"Full URI: \" \"$SL_Api/${SL_Ver}${ep}\"\n  _debug2 \"_H1:\" \"$_H1\"\n  _debug2 \"_H2:\" \"$_H2\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$SL_Api/${SL_Ver}${ep}\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$SL_Api/${SL_Ver}${ep}\")\"\n  fi\n  # shellcheck disable=SC2181\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_get_auth_token() {\n  if [ \"$SL_Ver\" = 'v1' ]; then\n    # token for v1\n    _debug \"Token v1\"\n    _token_keystone=$SL_Key\n  elif [ \"$SL_Ver\" = 'v2' ]; then\n    # token for v2. Get a token for calling the API\n    _debug \"Keystone Token v2\"\n    token_v2=$(_readaccountconf_mutable SL_Token_V2)\n    if [ -n \"$token_v2\" ]; then\n      # The structure with the token was considered. Let's check its validity\n      # field 1 - SL_Login_Name\n      # field 2 - token keystone\n      # field 3 - SL_Login_ID\n      # field 4 - SL_Project_Name\n      # field 5 - Receipt time\n      # separator - '$_sl_sep'\n      _login_name=$(_getfield \"$token_v2\" 1 \"$_sl_sep\")\n      _token_keystone=$(_getfield \"$token_v2\" 2 \"$_sl_sep\")\n      _project_name=$(_getfield \"$token_v2\" 4 \"$_sl_sep\")\n      _receipt_time=$(_getfield \"$token_v2\" 5 \"$_sl_sep\")\n      _login_id=$(_getfield \"$token_v2\" 3 \"$_sl_sep\")\n      _debug2 _login_name \"$_login_name\"\n      _debug2 _login_id \"$_login_id\"\n      _debug2 _project_name \"$_project_name\"\n      # check the validity of the token for the user and the project and its lifetime\n      _dt_diff_minute=$((($(date +%s) - _receipt_time) / 60))\n      _debug2 _dt_diff_minute \"$_dt_diff_minute\"\n      [ \"$_dt_diff_minute\" -gt \"$SL_Expire\" ] && unset _token_keystone\n      if [ \"$_project_name\" != \"$SL_Project_Name\" ] || [ \"$_login_name\" != \"$SL_Login_Name\" ] || [ \"$_login_id\" != \"$SL_Login_ID\" ]; then\n        unset _token_keystone\n      fi\n      _debug \"Get exists token\"\n    fi\n    if [ -z \"$_token_keystone\" ]; then\n      # the previous token is incorrect or was not received, get a new one\n      _debug \"Update (get new) token\"\n      _data_auth=\"{\\\"auth\\\":{\\\"identity\\\":{\\\"methods\\\":[\\\"password\\\"],\\\"password\\\":{\\\"user\\\":{\\\"name\\\":\\\"${SL_Login_Name}\\\",\\\"domain\\\":{\\\"name\\\":\\\"${SL_Login_ID}\\\"},\\\"password\\\":\\\"${SL_Pswd}\\\"}}},\\\"scope\\\":{\\\"project\\\":{\\\"name\\\":\\\"${SL_Project_Name}\\\",\\\"domain\\\":{\\\"name\\\":\\\"${SL_Login_ID}\\\"}}}}}\"\n      export _H1=\"Content-Type: application/json\"\n      _result=$(_post \"$_data_auth\" \"$auth_uri\")\n      _token_keystone=$(grep 'x-subject-token' \"$HTTP_HEADER\" | sed -nE \"s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\\r*)/\\1/p\")\n      _dt_curr=$(date +%s)\n      SL_Token_V2=\"${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}\"\n      _saveaccountconf_mutable SL_Token_V2 \"$SL_Token_V2\"\n    fi\n  else\n    # token set empty for unsupported version API\n    _token_keystone=\"\"\n  fi\n  printf -- \"%s\" \"$_token_keystone\"\n}\n\n#################################################################\n# use: [non_save]\n_sl_init_vars() {\n  _non_save=\"${1}\"\n  _debug2 _non_save \"$_non_save\"\n\n  _debug \"First init variables\"\n  # version API\n  SL_Ver=\"${SL_Ver:-$(_readaccountconf_mutable SL_Ver)}\"\n  if [ -z \"$SL_Ver\" ]; then\n    SL_Ver=\"v1\"\n  fi\n  if ! [ \"$SL_Ver\" = \"v1\" ] && ! [ \"$SL_Ver\" = \"v2\" ]; then\n    _err \"You don't specify selectel.ru API version.\"\n    _err \"Please define specify API version.\"\n  fi\n  _debug2 SL_Ver \"$SL_Ver\"\n  if [ \"$SL_Ver\" = \"v1\" ]; then\n    # token\n    SL_Key=\"${SL_Key:-$(_readaccountconf_mutable SL_Key)}\"\n\n    if [ -z \"$SL_Key\" ]; then\n      SL_Key=\"\"\n      _err \"You don't specify selectel.ru api key yet.\"\n      _err \"Please create you key and try again.\"\n      return 1\n    fi\n    #save the api key to the account conf file.\n    if [ -z \"$_non_save\" ]; then\n      _saveaccountconf_mutable SL_Key \"$SL_Key\"\n    fi\n  elif [ \"$SL_Ver\" = \"v2\" ]; then\n    # time expire token\n    SL_Expire=\"${SL_Expire:-$(_readaccountconf_mutable SL_Expire)}\"\n    if [ -z \"$SL_Expire\" ]; then\n      SL_Expire=1400 # 23h 20 min\n    fi\n    if [ -z \"$_non_save\" ]; then\n      _saveaccountconf_mutable SL_Expire \"$SL_Expire\"\n    fi\n    # login service user\n    SL_Login_Name=\"${SL_Login_Name:-$(_readaccountconf_mutable SL_Login_Name)}\"\n    if [ -z \"$SL_Login_Name\" ]; then\n      SL_Login_Name=''\n      _err \"You did not specify the selectel.ru API service user name.\"\n      _err \"Please provide a service user name and try again.\"\n      return 1\n    fi\n    if [ -z \"$_non_save\" ]; then\n      _saveaccountconf_mutable SL_Login_Name \"$SL_Login_Name\"\n    fi\n    # user ID\n    SL_Login_ID=\"${SL_Login_ID:-$(_readaccountconf_mutable SL_Login_ID)}\"\n    if [ -z \"$SL_Login_ID\" ]; then\n      SL_Login_ID=''\n      _err \"You did not specify the selectel.ru API user ID.\"\n      _err \"Please provide a user ID and try again.\"\n      return 1\n    fi\n    if [ -z \"$_non_save\" ]; then\n      _saveaccountconf_mutable SL_Login_ID \"$SL_Login_ID\"\n    fi\n    # project name\n    SL_Project_Name=\"${SL_Project_Name:-$(_readaccountconf_mutable SL_Project_Name)}\"\n    if [ -z \"$SL_Project_Name\" ]; then\n      SL_Project_Name=''\n      _err \"You did not specify the project name.\"\n      _err \"Please provide a project name and try again.\"\n      return 1\n    fi\n    if [ -z \"$_non_save\" ]; then\n      _saveaccountconf_mutable SL_Project_Name \"$SL_Project_Name\"\n    fi\n    # service user password\n    SL_Pswd=\"${SL_Pswd:-$(_readaccountconf_mutable SL_Pswd)}\"\n    if [ -z \"$SL_Pswd\" ]; then\n      SL_Pswd=''\n      _err \"You did not specify the service user password.\"\n      _err \"Please provide a service user password and try again.\"\n      return 1\n    fi\n    if [ -z \"$_non_save\" ]; then\n      _saveaccountconf_mutable SL_Pswd \"$SL_Pswd\" \"12345678\"\n    fi\n  else\n    SL_Ver=\"\"\n    _err \"You also specified the wrong version of the selectel.ru API.\"\n    _err \"Please provide the correct API version and try again.\"\n    return 1\n  fi\n  if [ -z \"$_non_save\" ]; then\n    _saveaccountconf_mutable SL_Ver \"$SL_Ver\"\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_selfhost.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_selfhost_info='SelfHost.de\nSite: SelfHost.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_selfhost\nOptions:\n SELFHOSTDNS_USERNAME Username\n SELFHOSTDNS_PASSWORD Password\n SELFHOSTDNS_MAP Subdomain name\nIssues: github.com/acmesh-official/acme.sh/issues/4291\nAuthor: Marvin Edeler\n'\n\ndns_selfhost_add() {\n  fulldomain=$1\n  txt=$2\n  _info \"Calling acme-dns on selfhost\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txt\"\n\n  SELFHOSTDNS_UPDATE_URL=\"https://selfhost.de/cgi-bin/api.pl\"\n\n  # Get values, but don't save until we successfully validated\n  SELFHOSTDNS_USERNAME=\"${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}\"\n  SELFHOSTDNS_PASSWORD=\"${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}\"\n  # These values are domain dependent, so read them from there\n  SELFHOSTDNS_MAP=\"${SELFHOSTDNS_MAP:-$(_readdomainconf SELFHOSTDNS_MAP)}\"\n  # Selfhost api can't dynamically add TXT record,\n  # so we have to store the last used RID of the domain to support a second RID for wildcard domains\n  # (format: 'fulldomainA:lastRid fulldomainB:lastRid ...')\n  SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(_readdomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL)\n\n  if [ -z \"${SELFHOSTDNS_USERNAME:-}\" ] || [ -z \"${SELFHOSTDNS_PASSWORD:-}\" ]; then\n    _err \"SELFHOSTDNS_USERNAME and SELFHOSTDNS_PASSWORD must be set\"\n    return 1\n  fi\n\n  # get the domain entry from SELFHOSTDNS_MAP\n  # only match full domains (at the beginning of the string or with a leading whitespace),\n  # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com\n  # if the domain is defined multiple times only the last occurance will be matched\n  mapEntry=$(echo \"$SELFHOSTDNS_MAP\" | sed -n -E \"s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\\2\\3\\4/p\")\n  _debug2 mapEntry \"$mapEntry\"\n  if test -z \"$mapEntry\"; then\n    _err \"SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID\"\n    return 1\n  fi\n\n  # get the RIDs from the map entry\n  rid1=$(echo \"$mapEntry\" | cut -d: -f2)\n  rid2=$(echo \"$mapEntry\" | cut -d: -f3)\n\n  # read last used rid domain\n  lastUsedRidForDomainEntry=$(echo \"$SELFHOSTDNS_MAP_LAST_USED_INTERNAL\" | sed -n -E \"s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\\2/p\")\n  _debug2 lastUsedRidForDomainEntry \"$lastUsedRidForDomainEntry\"\n  lastUsedRidForDomain=$(echo \"$lastUsedRidForDomainEntry\" | cut -d: -f2)\n\n  rid=\"$rid1\"\n  if [ \"$lastUsedRidForDomain\" = \"$rid\" ] && ! test -z \"$rid2\"; then\n    rid=\"$rid2\"\n  fi\n\n  _info \"Trying to add $txt on selfhost for rid: $rid\"\n\n  data=\"?username=$SELFHOSTDNS_USERNAME&password=$SELFHOSTDNS_PASSWORD&rid=$rid&content=$txt\"\n  response=\"$(_get \"$SELFHOSTDNS_UPDATE_URL$data\")\"\n\n  if ! echo \"$response\" | grep \"200 OK\" >/dev/null; then\n    _err \"Invalid response of acme-dns for selfhost\"\n    return 1\n  fi\n\n  # write last used rid domain\n  newLastUsedRidForDomainEntry=\"$fulldomain:$rid\"\n  if ! test -z \"$lastUsedRidForDomainEntry\"; then\n    # replace last used rid entry for domain\n    SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(echo \"$SELFHOSTDNS_MAP_LAST_USED_INTERNAL\" | sed -n -E \"s/$lastUsedRidForDomainEntry/$newLastUsedRidForDomainEntry/p\")\n  else\n    # add last used rid entry for domain\n    if test -z \"$SELFHOSTDNS_MAP_LAST_USED_INTERNAL\"; then\n      SELFHOSTDNS_MAP_LAST_USED_INTERNAL=\"$newLastUsedRidForDomainEntry\"\n    else\n      SELFHOSTDNS_MAP_LAST_USED_INTERNAL=\"$SELFHOSTDNS_MAP_LAST_USED_INTERNAL $newLastUsedRidForDomainEntry\"\n    fi\n  fi\n\n  # Now that we know the values are good, save them\n  _saveaccountconf_mutable SELFHOSTDNS_USERNAME \"$SELFHOSTDNS_USERNAME\"\n  _saveaccountconf_mutable SELFHOSTDNS_PASSWORD \"$SELFHOSTDNS_PASSWORD\"\n  # These values are domain dependent, so store them there\n  _savedomainconf SELFHOSTDNS_MAP \"$SELFHOSTDNS_MAP\"\n  _savedomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL \"$SELFHOSTDNS_MAP_LAST_USED_INTERNAL\"\n}\n\ndns_selfhost_rm() {\n  fulldomain=$1\n  txt=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txt\"\n  _info \"Creating and removing of records is not supported by selfhost API, will not delete anything.\"\n}\n"
  },
  {
    "path": "dnsapi/dns_servercow.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_servercow_info='ServerCow.de\nSite: ServerCow.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_servercow\nOptions:\n SERVERCOW_API_Username Username\n SERVERCOW_API_Password Password\nIssues: github.com/jhartlep/servercow-dns-api/issues\nAuthor: Jens Hartlep\n'\n\nSERVERCOW_API=\"https://api.servercow.de/dns/v1/domains\"\n\n# Usage dns_servercow_add _acme-challenge.www.domain.com \"abcdefghijklmnopqrstuvwxyz\"\ndns_servercow_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using servercow\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  SERVERCOW_API_Username=\"${SERVERCOW_API_Username:-$(_readaccountconf_mutable SERVERCOW_API_Username)}\"\n  SERVERCOW_API_Password=\"${SERVERCOW_API_Password:-$(_readaccountconf_mutable SERVERCOW_API_Password)}\"\n  if [ -z \"$SERVERCOW_API_Username\" ] || [ -z \"$SERVERCOW_API_Password\" ]; then\n    SERVERCOW_API_Username=\"\"\n    SERVERCOW_API_Password=\"\"\n    _err \"You don't specify servercow api username and password yet.\"\n    _err \"Please create your username and password and try again.\"\n    return 1\n  fi\n\n  # save the credentials to the account conf file\n  _saveaccountconf_mutable SERVERCOW_API_Username \"$SERVERCOW_API_Username\"\n  _saveaccountconf_mutable SERVERCOW_API_Password \"$SERVERCOW_API_Password\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # check whether a txt record already exists for the subdomain\n  if printf -- \"%s\" \"$response\" | grep \"{\\\"name\\\":\\\"$_sub_domain\\\",\\\"ttl\\\":20,\\\"type\\\":\\\"TXT\\\"\" >/dev/null; then\n    _info \"A txt record with the same name already exists.\"\n    # trim the string on the left\n    txtvalue_old=${response#*{\\\"name\\\":\\\"\"$_sub_domain\"\\\",\\\"ttl\\\":20,\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"}\n    # trim the string on the right\n    txtvalue_old=${txtvalue_old%%\\\"*}\n\n    _debug txtvalue_old \"$txtvalue_old\"\n\n    _info \"Add the new txtvalue to the existing txt record.\"\n    if _servercow_api POST \"$_domain\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\",\\\"content\\\":[\\\"$txtvalue\\\",\\\"$txtvalue_old\\\"],\\\"ttl\\\":20}\"; then\n      if printf -- \"%s\" \"$response\" | grep \"ok\" >/dev/null; then\n        _info \"Added additional txtvalue, OK\"\n        return 0\n      else\n        _err \"add txt record error.\"\n        return 1\n      fi\n    fi\n    _err \"add txt record error.\"\n    return 1\n  else\n    _info \"There is no txt record with the name yet.\"\n    if _servercow_api POST \"$_domain\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":20}\"; then\n      if printf -- \"%s\" \"$response\" | grep \"ok\" >/dev/null; then\n        _info \"Added, OK\"\n        return 0\n      else\n        _err \"add txt record error.\"\n        return 1\n      fi\n    fi\n    _err \"add txt record error.\"\n    return 1\n  fi\n\n}\n\n# Usage fulldomain txtvalue\n# Remove the txt record after validation\ndns_servercow_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using servercow\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$fulldomain\"\n\n  SERVERCOW_API_Username=\"${SERVERCOW_API_Username:-$(_readaccountconf_mutable SERVERCOW_API_Username)}\"\n  SERVERCOW_API_Password=\"${SERVERCOW_API_Password:-$(_readaccountconf_mutable SERVERCOW_API_Password)}\"\n  if [ -z \"$SERVERCOW_API_Username\" ] || [ -z \"$SERVERCOW_API_Password\" ]; then\n    SERVERCOW_API_Username=\"\"\n    SERVERCOW_API_Password=\"\"\n    _err \"You don't specify servercow api username and password yet.\"\n    _err \"Please create your username and password and try again.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if _servercow_api DELETE \"$_domain\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\"}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"ok\" >/dev/null; then\n      _info \"Deleted, OK\"\n      _contains \"$response\" '\"message\":\"ok\"'\n    else\n      _err \"delete txt record error.\"\n      return 1\n    fi\n  fi\n\n}\n\n####################  Private functions below ##################################\n\n# _acme-challenge.www.domain.com\n# returns\n#  _sub_domain=_acme-challenge.www\n#  _domain=domain.com\n_get_root() {\n  fulldomain=$1\n  i=2\n  p=1\n\n  while true; do\n    _domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f \"$i\"-100)\n\n    _debug _domain \"$_domain\"\n    if [ -z \"$_domain\" ]; then\n      # not valid\n      return 1\n    fi\n\n    if ! _servercow_api GET \"$_domain\"; then\n      return 1\n    fi\n\n    if ! _contains \"$response\" '\"error\":\"no such domain in user context\"' >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$fulldomain\" | cut -d . -f 1-\"$p\")\n      if [ -z \"$_sub_domain\" ]; then\n        # not valid\n        return 1\n      fi\n\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_servercow_api() {\n  method=$1\n  domain=$2\n  data=\"$3\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"X-Auth-Username: $SERVERCOW_API_Username\"\n  export _H3=\"X-Auth-Password: $SERVERCOW_API_Password\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$SERVERCOW_API/$domain\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$SERVERCOW_API/$domain\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $domain\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_simply.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_simply_info='Simply.com\nSite: Simply.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_simply\nOptions:\n SIMPLY_AccountName Account name\n SIMPLY_ApiKey API Key\n'\n\n#SIMPLY_Api=\"https://api.simply.com/2/\"\nSIMPLY_Api_Default=\"https://api.simply.com/2\"\n\n#This is used for determining success of REST call\nSIMPLY_SUCCESS_CODE='\"status\":200'\n\n########  Public functions #####################\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_simply_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _simply_load_config; then\n    return 1\n  fi\n\n  _simply_save_config\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n\n  if ! _simply_add_record \"$_domain\" \"$_sub_domain\" \"$txtvalue\"; then\n    _err \"Could not add DNS record\"\n    return 1\n  fi\n  return 0\n}\n\ndns_simply_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _simply_load_config; then\n    return 1\n  fi\n\n  _simply_save_config\n\n  _debug \"Find the DNS zone\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _info \"Getting all existing records\"\n\n  if ! _simply_get_all_records \"$_domain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  records=$(echo \"$response\" | tr '{' \"\\n\" | grep 'record_id\\|type\\|data\\|\\name' | sed 's/\\\"record_id/;\\\"record_id/' | tr \"\\n\" ' ' | tr -d ' ' | tr ';' ' ')\n\n  nr_of_deleted_records=0\n  _info \"Fetching txt record\"\n\n  for record in $records; do\n    _debug record \"$record\"\n\n    record_data=$(echo \"$record\" | sed -n \"s/.*\\\"data\\\":\\\"\\([^\\\"]*\\)\\\".*/\\1/p\")\n    record_type=$(echo \"$record\" | sed -n \"s/.*\\\"type\\\":\\\"\\([^\\\"]*\\)\\\".*/\\1/p\")\n\n    _debug2 record_data \"$record_data\"\n    _debug2 record_type \"$record_type\"\n\n    if [ \"$record_data\" = \"$txtvalue\" ] && [ \"$record_type\" = \"TXT\" ]; then\n\n      record_id=$(echo \"$record\" | cut -d \",\" -f 1 | grep \"record_id\" | cut -d \":\" -f 2)\n\n      _info \"Deleting record $record\"\n      _debug2 record_id \"$record_id\"\n\n      if [ \"$record_id\" -gt 0 ]; then\n\n        if ! _simply_delete_record \"$_domain\" \"$_sub_domain\" \"$record_id\"; then\n          _err \"Record with id $record_id could not be deleted\"\n          return 1\n        fi\n\n        nr_of_deleted_records=1\n        break\n      else\n        _err \"Fetching record_id could not be done, this should not happen, exiting function. Failing record is $record\"\n        break\n      fi\n    fi\n\n  done\n\n  if [ \"$nr_of_deleted_records\" -eq 0 ]; then\n    _err \"No record deleted, the DNS record needs to be removed manually.\"\n  else\n    _info \"Deleted $nr_of_deleted_records record\"\n  fi\n\n  return 0\n}\n\n####################  Private functions below ##################################\n\n_simply_load_config() {\n  SIMPLY_Api=\"${SIMPLY_Api:-$(_readaccountconf_mutable SIMPLY_Api)}\"\n  SIMPLY_AccountName=\"${SIMPLY_AccountName:-$(_readaccountconf_mutable SIMPLY_AccountName)}\"\n  SIMPLY_ApiKey=\"${SIMPLY_ApiKey:-$(_readaccountconf_mutable SIMPLY_ApiKey)}\"\n\n  if [ -z \"$SIMPLY_Api\" ]; then\n    SIMPLY_Api=\"$SIMPLY_Api_Default\"\n  fi\n\n  if [ -z \"$SIMPLY_AccountName\" ] || [ -z \"$SIMPLY_ApiKey\" ]; then\n    SIMPLY_AccountName=\"\"\n    SIMPLY_ApiKey=\"\"\n\n    _err \"A valid Simply API account and apikey not provided.\"\n    _err \"Please provide a valid API user and try again.\"\n\n    return 1\n  fi\n\n  return 0\n}\n\n_simply_save_config() {\n  if [ \"$SIMPLY_Api\" != \"$SIMPLY_Api_Default\" ]; then\n    _saveaccountconf_mutable SIMPLY_Api \"$SIMPLY_Api\"\n  fi\n  _saveaccountconf_mutable SIMPLY_AccountName \"$SIMPLY_AccountName\"\n  _saveaccountconf_mutable SIMPLY_ApiKey \"$SIMPLY_ApiKey\"\n}\n\n_simply_get_all_records() {\n  domain=$1\n\n  if ! _simply_rest GET \"my/products/$domain/dns/records/\"; then\n    return 1\n  fi\n\n  return 0\n}\n\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _simply_rest GET \"my/products/$h/dns/\"; then\n      return 1\n    fi\n\n    if ! _contains \"$response\" \"$SIMPLY_SUCCESS_CODE\"; then\n      _debug \"$h not found\"\n    else\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_simply_add_record() {\n  domain=$1\n  sub_domain=$2\n  txtval=$3\n\n  data=\"{\\\"name\\\": \\\"$sub_domain\\\", \\\"type\\\":\\\"TXT\\\", \\\"data\\\": \\\"$txtval\\\", \\\"priority\\\":0, \\\"ttl\\\": 3600}\"\n\n  if ! _simply_rest POST \"my/products/$domain/dns/records/\" \"$data\"; then\n    _err \"Adding record not successfull!\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$SIMPLY_SUCCESS_CODE\"; then\n    _err \"Call to API not sucessfull, see below message for more details\"\n    _err \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n\n_simply_delete_record() {\n  domain=$1\n  sub_domain=$2\n  record_id=$3\n\n  _debug record_id \"Delete record with id $record_id\"\n\n  if ! _simply_rest DELETE \"my/products/$domain/dns/records/$record_id/\"; then\n    _err \"Deleting record not successfull!\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$SIMPLY_SUCCESS_CODE\"; then\n    _err \"Call to API not sucessfull, see below message for more details\"\n    _err \"$response\"\n    return 1\n  fi\n\n  return 0\n}\n\n_simply_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug2 data \"$data\"\n  _debug2 ep \"$ep\"\n  _debug2 m \"$m\"\n\n  basicauth=$(printf \"%s:%s\" \"$SIMPLY_AccountName\" \"$SIMPLY_ApiKey\" | _base64)\n\n  if [ \"$basicauth\" ]; then\n    export _H1=\"Authorization: Basic $basicauth\"\n  fi\n\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    response=\"$(_post \"$data\" \"$SIMPLY_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$SIMPLY_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n\n  _debug2 response \"$response\"\n\n  if _contains \"$response\" \"Invalid account authorization\"; then\n    _err \"It seems that your api key or accountnumber is not correct.\"\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_sotoon.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_sotoon_info='Sotoon.ir\nSite: Sotoon.ir\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sotoon\nOptions:\n Sotoon_Token API Token\n Sotoon_WorkspaceUUID Workspace UUID\nIssues: github.com/acmesh-official/acme.sh/issues/6656\nAuthor: Erfan Gholizade\n'\n\nSOTOON_API_URL=\"https://api.sotoon.ir/delivery/v2.1/global\"\n\n########  Public functions #####################\n\n#Adding the txt record for validation.\n#Usage: dns_sotoon_add   fulldomain   TXT_record\n#Usage: dns_sotoon_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_sotoon_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info_sotoon \"Using Sotoon\"\n\n  Sotoon_Token=\"${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}\"\n  Sotoon_WorkspaceUUID=\"${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}\"\n\n  if [ -z \"$Sotoon_Token\" ]; then\n    _err_sotoon \"You didn't specify \\\"Sotoon_Token\\\" token yet.\"\n    _err_sotoon \"You can get yours from here https://ocean.sotoon.ir/profile/tokens\"\n    return 1\n  fi\n  if [ -z \"$Sotoon_WorkspaceUUID\" ]; then\n    _err_sotoon \"You didn't specify \\\"Sotoon_WorkspaceUUID\\\" Workspace UUID yet.\"\n    _err_sotoon \"You can get yours from here https://ocean.sotoon.ir/profile/workspaces\"\n    return 1\n  fi\n\n  #save the info to the account conf file.\n  _saveaccountconf_mutable Sotoon_Token \"$Sotoon_Token\"\n  _saveaccountconf_mutable Sotoon_WorkspaceUUID \"$Sotoon_WorkspaceUUID\"\n\n  _debug_sotoon \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err_sotoon \"invalid domain\"\n    return 1\n  fi\n\n  _info_sotoon \"Adding record\"\n\n  _debug_sotoon _domain_id \"$_domain_id\"\n  _debug_sotoon _sub_domain \"$_sub_domain\"\n  _debug_sotoon _domain \"$_domain\"\n\n  # First, GET the current domain zone to check for existing TXT records\n  # This is needed for wildcard certs which require multiple TXT values\n  _info_sotoon \"Checking for existing TXT records\"\n  if ! _sotoon_rest GET \"$_domain_id\"; then\n    _err_sotoon \"Failed to get domain zone\"\n    return 1\n  fi\n\n  # Check if there are existing TXT records for this subdomain\n  _existing_txt=\"\"\n  if _contains \"$response\" \"\\\"$_sub_domain\\\"\"; then\n    _debug_sotoon \"Found existing records for $_sub_domain\"\n    # Extract existing TXT values from the response\n    # The format is: \"_acme-challenge\":[{\"TXT\":\"value1\",\"type\":\"TXT\",\"ttl\":10},{\"TXT\":\"value2\",...}]\n    _existing_txt=$(echo \"$response\" | _egrep_o \"\\\"$_sub_domain\\\":\\[[^]]*\\]\" | sed \"s/\\\"$_sub_domain\\\"://\")\n    _debug_sotoon \"Existing TXT records: $_existing_txt\"\n  fi\n\n  # Build the new record entry\n  _new_record=\"{\\\"TXT\\\":\\\"$txtvalue\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":120}\"\n\n  # If there are existing records, append to them; otherwise create new array\n  if [ -n \"$_existing_txt\" ] && [ \"$_existing_txt\" != \"[]\" ] && [ \"$_existing_txt\" != \"null\" ]; then\n    # Check if this exact TXT value already exists (avoid duplicates)\n    if _contains \"$_existing_txt\" \"\\\"$txtvalue\\\"\"; then\n      _info_sotoon \"TXT record already exists, skipping\"\n      return 0\n    fi\n    # Remove the closing bracket and append new record\n    _combined_records=\"$(echo \"$_existing_txt\" | sed 's/]$//'),$_new_record]\"\n    _debug_sotoon \"Combined records: $_combined_records\"\n  else\n    # No existing records, create new array\n    _combined_records=\"[$_new_record]\"\n  fi\n\n  # Prepare the DNS record data in Kubernetes CRD format\n  _dns_record=\"{\\\"spec\\\":{\\\"records\\\":{\\\"$_sub_domain\\\":$_combined_records}}}\"\n\n  _debug_sotoon \"DNS record payload: $_dns_record\"\n\n  # Use PATCH to update/add the record to the domain zone\n  _info_sotoon \"Updating domain zone $_domain_id with TXT record\"\n  if _sotoon_rest PATCH \"$_domain_id\" \"$_dns_record\"; then\n    if _contains \"$response\" \"$txtvalue\" || _contains \"$response\" \"\\\"$_sub_domain\\\"\"; then\n      _info_sotoon \"Added, OK\"\n      return 0\n    else\n      _debug_sotoon \"Response: $response\"\n      _err_sotoon \"Add txt record error.\"\n      return 1\n    fi\n  fi\n\n  _err_sotoon \"Add txt record error.\"\n  return 1\n}\n\n#Remove the txt record after validation.\n#Usage: dns_sotoon_rm   fulldomain   TXT_record\n#Usage: dns_sotoon_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_sotoon_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info_sotoon \"Using Sotoon\"\n  _debug_sotoon fulldomain \"$fulldomain\"\n  _debug_sotoon txtvalue \"$txtvalue\"\n\n  Sotoon_Token=\"${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}\"\n  Sotoon_WorkspaceUUID=\"${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}\"\n\n  _debug_sotoon \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err_sotoon \"invalid domain\"\n    return 1\n  fi\n  _debug_sotoon _domain_id \"$_domain_id\"\n  _debug_sotoon _sub_domain \"$_sub_domain\"\n  _debug_sotoon _domain \"$_domain\"\n\n  _info_sotoon \"Removing TXT record\"\n\n  # First, GET the current domain zone to check for existing TXT records\n  if ! _sotoon_rest GET \"$_domain_id\"; then\n    _err_sotoon \"Failed to get domain zone\"\n    return 1\n  fi\n\n  # Check if there are existing TXT records for this subdomain\n  _existing_txt=\"\"\n  if _contains \"$response\" \"\\\"$_sub_domain\\\"\"; then\n    _debug_sotoon \"Found existing records for $_sub_domain\"\n    _existing_txt=$(echo \"$response\" | _egrep_o \"\\\"$_sub_domain\\\":\\[[^]]*\\]\" | sed \"s/\\\"$_sub_domain\\\"://\")\n    _debug_sotoon \"Existing TXT records: $_existing_txt\"\n  fi\n\n  # If no existing records, nothing to remove\n  if [ -z \"$_existing_txt\" ] || [ \"$_existing_txt\" = \"[]\" ] || [ \"$_existing_txt\" = \"null\" ]; then\n    _info_sotoon \"No TXT records found, nothing to remove\"\n    return 0\n  fi\n\n  # Remove the specific TXT value from the array\n  # This handles the case where there are multiple TXT values (wildcard certs)\n  _remaining_records=$(echo \"$_existing_txt\" | sed \"s/{\\\"TXT\\\":\\\"$txtvalue\\\"[^}]*},*//g\" | sed 's/,]/]/g' | sed 's/\\[,/[/g')\n  _debug_sotoon \"Remaining records after removal: $_remaining_records\"\n\n  # If no records remain, set to null to remove the subdomain entirely\n  if [ \"$_remaining_records\" = \"[]\" ] || [ -z \"$_remaining_records\" ]; then\n    _dns_record=\"{\\\"spec\\\":{\\\"records\\\":{\\\"$_sub_domain\\\":null}}}\"\n  else\n    _dns_record=\"{\\\"spec\\\":{\\\"records\\\":{\\\"$_sub_domain\\\":$_remaining_records}}}\"\n  fi\n\n  _debug_sotoon \"Remove record payload: $_dns_record\"\n\n  # Use PATCH to remove the record from the domain zone\n  if _sotoon_rest PATCH \"$_domain_id\" \"$_dns_record\"; then\n    _info_sotoon \"Record removed, OK\"\n    return 0\n  else\n    _debug_sotoon \"Response: $response\"\n    _err_sotoon \"Error removing record\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  _debug_sotoon \"Getting root domain for: $domain\"\n  _debug_sotoon \"Sotoon WorkspaceUUID: $Sotoon_WorkspaceUUID\"\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug_sotoon \"Checking domain part: $h\"\n\n    if [ -z \"$h\" ]; then\n      #not valid\n      _err_sotoon \"Could not find valid domain\"\n      return 1\n    fi\n\n    _debug_sotoon \"Fetching domain zones from Sotoon API\"\n    if ! _sotoon_rest GET \"\"; then\n      _err_sotoon \"Failed to get domain zones from Sotoon API\"\n      _err_sotoon \"Please check your Sotoon_Token, Sotoon_WorkspaceUUID\"\n      return 1\n    fi\n\n    _debug2_sotoon \"API Response: $response\"\n\n    # Check if the response contains our domain\n    # Sotoon API uses Kubernetes CRD format with spec.origin for domain matching\n    if _contains \"$response\" \"\\\"origin\\\":\\\"$h\\\"\"; then\n      _debug_sotoon \"Found domain by origin: $h\"\n\n      # In Kubernetes CRD format, the metadata.name is the resource identifier\n      # The name can be either:\n      # 1. Same as origin\n      # 2. Origin with dots replaced by hyphens\n      # We check both patterns in the response to determine which one exists\n\n      # Convert origin to hyphenated version for checking\n      _h_hyphenated=$(echo \"$h\" | tr '.' '-')\n\n      # Check if the hyphenated name exists in the response\n      if _contains \"$response\" \"\\\"name\\\":\\\"$_h_hyphenated\\\"\"; then\n        _domain_id=\"$_h_hyphenated\"\n        _debug_sotoon \"Found domain ID (hyphenated): $_domain_id\"\n      # Check if the origin itself is used as name\n      elif _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n        _domain_id=\"$h\"\n        _debug_sotoon \"Found domain ID (same as origin): $_domain_id\"\n      else\n        # Fallback: use the hyphenated version (more common)\n        _domain_id=\"$_h_hyphenated\"\n        _debug_sotoon \"Using hyphenated domain ID as fallback: $_domain_id\"\n      fi\n\n      if [ -n \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        _debug_sotoon \"Domain ID (metadata.name): $_domain_id\"\n        _debug_sotoon \"Sub domain: $_sub_domain\"\n        _debug_sotoon \"Domain (origin): $_domain\"\n        return 0\n      fi\n      _err_sotoon \"Found domain $h but could not extract domain ID\"\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_sotoon_rest() {\n  mtd=\"$1\"\n  resource_id=\"$2\"\n  data=\"$3\"\n\n  token_trimmed=$(echo \"$Sotoon_Token\" | tr -d '\"')\n\n  # Construct the API endpoint\n  _api_path=\"$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/domainzones\"\n\n  if [ -n \"$resource_id\" ]; then\n    _api_path=\"$_api_path/$resource_id\"\n  fi\n\n  _debug_sotoon \"API Path: $_api_path\"\n  _debug_sotoon \"Method: $mtd\"\n\n  # Set authorization header - Sotoon API uses Bearer token\n  export _H1=\"Authorization: Bearer $token_trimmed\"\n\n  if [ \"$mtd\" = \"GET\" ]; then\n    # GET request\n    _debug_sotoon \"GET\" \"$_api_path\"\n    response=\"$(_get \"$_api_path\")\"\n  elif [ \"$mtd\" = \"PATCH\" ]; then\n    # PATCH Request\n    export _H2=\"Content-Type: application/merge-patch+json\"\n    _debug_sotoon data \"$data\"\n    response=\"$(_post \"$data\" \"$_api_path\" \"\" \"$mtd\")\"\n  else\n    _err_sotoon \"Unknown method: $mtd\"\n    return 1\n  fi\n\n  _debug2_sotoon response \"$response\"\n  return 0\n}\n\n#Wrappers for logging\n_info_sotoon() {\n  _info \"[Sotoon]\" \"$@\"\n}\n\n_err_sotoon() {\n  _err \"[Sotoon]\" \"$@\"\n}\n\n_debug_sotoon() {\n  _debug \"[Sotoon]\" \"$@\"\n}\n\n_debug2_sotoon() {\n  _debug2 \"[Sotoon]\" \"$@\"\n}\n"
  },
  {
    "path": "dnsapi/dns_spaceship.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_spaceship_info='Spaceship.com\nSite: Spaceship.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_spaceship\nOptions:\n SPACESHIP_API_KEY API Key\n SPACESHIP_API_SECRET API Secret\n SPACESHIP_ROOT_DOMAIN Root domain. Manually specify the root domain if auto-detection fails. Optional.\nIssues: github.com/acmesh-official/acme.sh/issues/6304\nAuthor: Meow <@Meo597>\n'\n\n# Spaceship API\n# https://docs.spaceship.dev/\n\n########  Public functions #####################\n\nSPACESHIP_API_BASE=\"https://spaceship.dev/api/v1\"\n\n# Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n# Used to add txt record\ndns_spaceship_add() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Adding TXT record for $fulldomain with value $txtvalue\"\n\n  # Initialize API credentials and headers\n  if ! _spaceship_init; then\n    return 1\n  fi\n\n  # Detect root zone\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  # Extract subdomain part relative to root domain\n  subdomain=$(echo \"$fulldomain\" | sed \"s/\\.$_domain$//\")\n  if [ \"$subdomain\" = \"$fulldomain\" ]; then\n    _err \"Failed to extract subdomain from $fulldomain relative to root domain $_domain\"\n    return 1\n  fi\n  _debug \"Extracted subdomain: $subdomain for root domain: $_domain\"\n\n  # Escape txtvalue to prevent JSON injection (e.g., quotes in txtvalue)\n  escaped_txtvalue=$(echo \"$txtvalue\" | sed 's/\"/\\\\\"/g')\n\n  # Prepare payload and URL for adding TXT record\n  # Note: 'name' in payload uses subdomain (e.g., _acme-challenge.sub) as required by Spaceship API\n  payload=\"{\\\"force\\\": true, \\\"items\\\": [{\\\"type\\\": \\\"TXT\\\", \\\"name\\\": \\\"$subdomain\\\", \\\"value\\\": \\\"$escaped_txtvalue\\\", \\\"ttl\\\": 600}]}\"\n  url=\"$SPACESHIP_API_BASE/dns/records/$_domain\"\n\n  # Send API request\n  if _spaceship_api_request \"PUT\" \"$url\" \"$payload\"; then\n    _info \"Successfully added TXT record for $fulldomain\"\n    return 0\n  else\n    _err \"Failed to add TXT record. If the domain $_domain is incorrect, set SPACESHIP_ROOT_DOMAIN to the correct root domain.\"\n    return 1\n  fi\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_spaceship_rm() {\n  fulldomain=\"$1\"\n  txtvalue=\"$2\"\n\n  _info \"Removing TXT record for $fulldomain with value $txtvalue\"\n\n  # Initialize API credentials and headers\n  if ! _spaceship_init; then\n    return 1\n  fi\n\n  # Detect root zone\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  # Extract subdomain part relative to root domain\n  subdomain=$(echo \"$fulldomain\" | sed \"s/\\.$_domain$//\")\n  if [ \"$subdomain\" = \"$fulldomain\" ]; then\n    _err \"Failed to extract subdomain from $fulldomain relative to root domain $_domain\"\n    return 1\n  fi\n  _debug \"Extracted subdomain: $subdomain for root domain: $_domain\"\n\n  # Escape txtvalue to prevent JSON injection\n  escaped_txtvalue=$(echo \"$txtvalue\" | sed 's/\"/\\\\\"/g')\n\n  # Prepare payload and URL for deleting TXT record\n  # Note: 'name' in payload uses subdomain (e.g., _acme-challenge.sub) as required by Spaceship API\n  payload=\"[{\\\"type\\\": \\\"TXT\\\", \\\"name\\\": \\\"$subdomain\\\", \\\"value\\\": \\\"$escaped_txtvalue\\\"}]\"\n  url=\"$SPACESHIP_API_BASE/dns/records/$_domain\"\n\n  # Send API request\n  if _spaceship_api_request \"DELETE\" \"$url\" \"$payload\"; then\n    _info \"Successfully deleted TXT record for $fulldomain\"\n    return 0\n  else\n    _err \"Failed to delete TXT record. If the domain $_domain is incorrect, set SPACESHIP_ROOT_DOMAIN to the correct root domain.\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n_spaceship_init() {\n  SPACESHIP_API_KEY=\"${SPACESHIP_API_KEY:-$(_readaccountconf_mutable SPACESHIP_API_KEY)}\"\n  SPACESHIP_API_SECRET=\"${SPACESHIP_API_SECRET:-$(_readaccountconf_mutable SPACESHIP_API_SECRET)}\"\n\n  if [ -z \"$SPACESHIP_API_KEY\" ] || [ -z \"$SPACESHIP_API_SECRET\" ]; then\n    _err \"Spaceship API credentials are not set. Please set SPACESHIP_API_KEY and SPACESHIP_API_SECRET.\"\n    _err \"Ensure \\\"$LE_CONFIG_HOME\\\" directory has restricted permissions (chmod 700 \\\"$LE_CONFIG_HOME\\\") to protect credentials.\"\n    return 1\n  fi\n\n  # Save credentials to account config for future renewals\n  _saveaccountconf_mutable SPACESHIP_API_KEY \"$SPACESHIP_API_KEY\"\n  _saveaccountconf_mutable SPACESHIP_API_SECRET \"$SPACESHIP_API_SECRET\"\n\n  # Set common headers for API requests\n  export _H1=\"X-API-Key: $SPACESHIP_API_KEY\"\n  export _H2=\"X-API-Secret: $SPACESHIP_API_SECRET\"\n  export _H3=\"Content-Type: application/json\"\n  return 0\n}\n\n_get_root() {\n  domain=\"$1\"\n\n  # Check manual override\n  SPACESHIP_ROOT_DOMAIN=\"${SPACESHIP_ROOT_DOMAIN:-$(_readdomainconf SPACESHIP_ROOT_DOMAIN)}\"\n  if [ -n \"$SPACESHIP_ROOT_DOMAIN\" ]; then\n    _domain=\"$SPACESHIP_ROOT_DOMAIN\"\n    _debug \"Using manually specified or saved root domain: $_domain\"\n    _savedomainconf SPACESHIP_ROOT_DOMAIN \"$SPACESHIP_ROOT_DOMAIN\"\n    return 0\n  fi\n\n  _debug \"Detecting root zone for '$domain'\"\n\n  i=1\n  p=1\n  while true; do\n    _cutdomain=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    _debug \"Attempt i=$i: Checking if '$_cutdomain' is root zone (cut ret=$?)\"\n\n    if [ -z \"$_cutdomain\" ]; then\n      _debug \"Cut resulted in empty string, root zone not found.\"\n      break\n    fi\n\n    # Call the API to check if this _cutdomain is a manageable zone\n    if _spaceship_api_request \"GET\" \"$SPACESHIP_API_BASE/dns/records/$_cutdomain?take=1&skip=0\"; then\n      # API call succeeded (HTTP 200 OK for GET /dns/records)\n      _domain=\"$_cutdomain\"\n      _debug \"Root zone found: '$_domain'\"\n\n      # Save the detected root domain\n      _savedomainconf SPACESHIP_ROOT_DOMAIN \"$_domain\"\n      _info \"Root domain '$_domain' saved to configuration for future use.\"\n\n      return 0\n    fi\n\n    _debug \"API check failed for '$_cutdomain'. Continuing search.\"\n\n    p=$i\n    i=$((i + 1))\n  done\n\n  _err \"Could not detect root zone for '$domain'. Please set SPACESHIP_ROOT_DOMAIN manually.\"\n  return 1\n}\n\n_spaceship_api_request() {\n  method=\"$1\"\n  url=\"$2\"\n  payload=\"$3\"\n\n  _debug2 \"Sending $method request to $url with payload $payload\"\n  if [ \"$method\" = \"GET\" ]; then\n    response=\"$(_get \"$url\")\"\n  else\n    response=\"$(_post \"$payload\" \"$url\" \"\" \"$method\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"API request failed. Response: $response\"\n    return 1\n  fi\n\n  _debug2 \"API response body: $response\"\n\n  if [ \"$method\" = \"GET\" ]; then\n    if _contains \"$(_head_n 1 <\"$HTTP_HEADER\")\" '200'; then\n      return 0\n    fi\n  else\n    if _contains \"$(_head_n 1 <\"$HTTP_HEADER\")\" '204'; then\n      return 0\n    fi\n  fi\n\n  _debug2 \"API response header: $HTTP_HEADER\"\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_technitium.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_technitium_info='Technitium DNS Server\nSite: Technitium.com/dns/\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_technitium\nOptions:\n Technitium_Server Server Address\n Technitium_Token API Token\n Technitium_Expiry_Ttl Number of seconds before DNS server auto-deletes the acme record\nIssues: github.com/acmesh-official/acme.sh/issues/6116\nAuthor: Henning Reich <acmesh@qupfer.de>\n'\n\ndns_technitium_add() {\n  _info \"add txt Record using Technitium\"\n  _Technitium_account\n  fulldomain=$1\n  txtvalue=$2\n  expiryTtl=${Technitium_Expirty_Ttl:-$(_readaccountconf_mutable Technitium_Expiry_Ttl)}\n  expiryTtl=${expiryTtl:-0}\n\n  response=\"$(_get \"$Technitium_Server/api/zones/records/add?token=$Technitium_Token&domain=$fulldomain&type=TXT&text=${txtvalue}&expiryTtl=$expiryTtl\")\"\n  if _contains \"$response\" '\"status\":\"ok\"'; then\n    return 0\n  fi\n  _err \"Could not add txt record.\"\n  return 1\n}\n\ndns_technitium_rm() {\n  _info \"remove txt record using Technitium\"\n  _Technitium_account\n  fulldomain=$1\n  txtvalue=$2\n  expiryTtl=${Technitium_Expirty_Ttl:-$(_readaccountconf_mutable Technitium_Expiry_Ttl)}\n  expiryTtl=${expiryTtl:-0}\n\n  if [ \"$expiryTtl\" -ne 0 ]; then\n    _info \"DNS record is configured to be auto-removed after $expiryTtl seconds. Remove operation is bypassed.\"\n    return 0\n  fi\n\n  response=\"$(_get \"$Technitium_Server/api/zones/records/delete?token=$Technitium_Token&domain=$fulldomain&type=TXT&text=${txtvalue}\")\"\n  if _contains \"$response\" '\"status\":\"ok\"'; then\n    return 0\n  fi\n  _err \"Could not remove txt record\"\n  return 1\n}\n\n####################  Private functions below ##################################\n\n_Technitium_account() {\n  Technitium_Server=\"${Technitium_Server:-$(_readaccountconf_mutable Technitium_Server)}\"\n  Technitium_Token=\"${Technitium_Token:-$(_readaccountconf_mutable Technitium_Token)}\"\n  if [ -z \"$Technitium_Server\" ] || [ -z \"$Technitium_Token\" ]; then\n    Technitium_Server=\"\"\n    Technitium_Token=\"\"\n    _err \"You don't specify Technitium Server and Token yet.\"\n    _err \"Please create your Token and add server address and try again.\"\n    return 1\n  fi\n\n  #save the credentials to the account conf file.\n  _saveaccountconf_mutable Technitium_Server \"$Technitium_Server\"\n  _saveaccountconf_mutable Technitium_Token \"$Technitium_Token\"\n}\n"
  },
  {
    "path": "dnsapi/dns_tele3.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_tele3_info='tele3.cz\nSite: tele3.cz\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#tele3\nOptions:\n TELE3_Key API Key\n TELE3_Secret API Secret\nAuthor: Roman Blizik <@par-pa>\n'\n\nTELE3_API=\"https://www.tele3.cz/acme/\"\n\n########  Public functions  #####################\n\ndns_tele3_add() {\n  _info \"Using TELE3 DNS\"\n  data=\"\\\"ope\\\":\\\"add\\\", \\\"domain\\\":\\\"$1\\\", \\\"value\\\":\\\"$2\\\"\"\n  if ! _tele3_call; then\n    _err \"Publish zone failed\"\n    return 1\n  fi\n\n  _info \"Zone published\"\n}\n\ndns_tele3_rm() {\n  _info \"Using TELE3 DNS\"\n  data=\"\\\"ope\\\":\\\"rm\\\", \\\"domain\\\":\\\"$1\\\", \\\"value\\\":\\\"$2\\\"\"\n  if ! _tele3_call; then\n    _err \"delete TXT record failed\"\n    return 1\n  fi\n\n  _info \"TXT record successfully deleted\"\n}\n\n####################  Private functions below  ##################################\n\n_tele3_init() {\n  TELE3_Key=\"${TELE3_Key:-$(_readaccountconf_mutable TELE3_Key)}\"\n  TELE3_Secret=\"${TELE3_Secret:-$(_readaccountconf_mutable TELE3_Secret)}\"\n  if [ -z \"$TELE3_Key\" ] || [ -z \"$TELE3_Secret\" ]; then\n    TELE3_Key=\"\"\n    TELE3_Secret=\"\"\n    _err \"You must export variables: TELE3_Key and TELE3_Secret\"\n    return 1\n  fi\n\n  #save the config variables to the account conf file.\n  _saveaccountconf_mutable TELE3_Key \"$TELE3_Key\"\n  _saveaccountconf_mutable TELE3_Secret \"$TELE3_Secret\"\n}\n\n_tele3_call() {\n  _tele3_init\n  data=\"{\\\"key\\\":\\\"$TELE3_Key\\\", \\\"secret\\\":\\\"$TELE3_Secret\\\", $data}\"\n\n  _debug data \"$data\"\n\n  response=\"$(_post \"$data\" \"$TELE3_API\" \"\" \"POST\")\"\n  _debug response \"$response\"\n\n  if [ \"$response\" != \"success\" ]; then\n    _err \"$response\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_tencent.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_tencent_info='Tencent.com\nSite: cloud.Tencent.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_tencent\nOptions:\n Tencent_SecretId Secret ID\n Tencent_SecretKey Secret Key\nIssues: github.com/acmesh-official/acme.sh/issues/4781\n'\nTencent_API=\"https://dnspod.tencentcloudapi.com\"\n\n#Usage: dns_tencent_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_tencent_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  Tencent_SecretId=\"${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}\"\n  Tencent_SecretKey=\"${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}\"\n  if [ -z \"$Tencent_SecretId\" ] || [ -z \"$Tencent_SecretKey\" ]; then\n    Tencent_SecretId=\"\"\n    Tencent_SecretKey=\"\"\n    _err \"You don't specify tencent api SecretId and SecretKey yet.\"\n    return 1\n  fi\n\n  #save the api SecretId and SecretKey to the account conf file.\n  _saveaccountconf_mutable Tencent_SecretId \"$Tencent_SecretId\"\n  _saveaccountconf_mutable Tencent_SecretKey \"$Tencent_SecretKey\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  _debug \"Add record\"\n  _add_record_query \"$_domain\" \"$_sub_domain\" \"$txtvalue\" && _tencent_rest \"CreateRecord\"\n}\n\ndns_tencent_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  Tencent_SecretId=\"${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}\"\n  Tencent_SecretKey=\"${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  _debug \"Get record list\"\n  attempt=1\n  max_attempts=5\n  while [ -z \"$record_id\" ] && [ \"$attempt\" -le $max_attempts ]; do\n    _check_exist_query \"$_domain\" \"$_sub_domain\" \"$txtvalue\" && _tencent_rest \"DescribeRecordFilterList\"\n    record_id=\"$(echo \"$response\" | _egrep_o \"\\\"RecordId\\\":\\s*[0-9]+\" | _egrep_o \"[0-9]+\")\"\n    _debug2 record_id \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _debug \"Due to TencentCloud API synchronization delay, record not found, waiting 10 seconds and retrying\"\n      _sleep 10\n      attempt=$(_math \"$attempt + 1\")\n    fi\n  done\n\n  record_id=\"$(echo \"$response\" | _egrep_o \"\\\"RecordId\\\":\\s*[0-9]+\" | _egrep_o \"[0-9]+\")\"\n  _debug2 record_id \"$record_id\"\n\n  if [ -z \"$record_id\" ]; then\n    _debug \"record not found after $max_attempts attempts, skip\"\n  else\n    _debug \"Delete record\"\n    _delete_record_query \"$record_id\" && _tencent_rest \"DeleteRecord\"\n  fi\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _describe_records_query \"$h\" \"@\"\n    if ! _tencent_rest \"DescribeRecordList\" \"ignore\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"TotalCount\\\":\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _debug _sub_domain \"$_sub_domain\"\n      _domain=\"$h\"\n      _debug _domain \"$_domain\"\n      return 0\n    fi\n    p=\"$i\"\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_tencent_rest() {\n  action=$1\n  service=\"dnspod\"\n  payload=\"${query}\"\n  timestamp=$(date -u +%s)\n\n  token=$(tencent_signature_v3 $service \"$action\" \"$payload\" \"$timestamp\")\n  version=\"2021-03-23\"\n\n  if ! response=\"$(tencent_api_request $service $version \"$action\" \"$payload\" \"$timestamp\")\"; then\n    _err \"Error <$1>\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  if [ -z \"$2\" ]; then\n    message=\"$(echo \"$response\" | _egrep_o \"\\\"Message\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\")\"\n    if [ \"$message\" ]; then\n      _err \"$message\"\n      return 1\n    fi\n  fi\n}\n\n_add_record_query() {\n  query=\"{\\\"Domain\\\":\\\"$1\\\",\\\"SubDomain\\\":\\\"$2\\\",\\\"RecordType\\\":\\\"TXT\\\",\\\"RecordLineId\\\":\\\"0\\\",\\\"RecordLine\\\":\\\"0\\\",\\\"Value\\\":\\\"$3\\\",\\\"TTL\\\":600}\"\n}\n\n_describe_records_query() {\n  query=\"{\\\"Domain\\\":\\\"$1\\\",\\\"Limit\\\":3000}\"\n}\n\n_delete_record_query() {\n  query=\"{\\\"Domain\\\":\\\"$_domain\\\",\\\"RecordId\\\":$1}\"\n}\n\n_check_exist_query() {\n  _domain=\"$1\"\n  _subdomain=\"$2\"\n  _value=\"$3\"\n  query=\"{\\\"Domain\\\":\\\"$_domain\\\",\\\"SubDomain\\\":\\\"$_subdomain\\\",\\\"RecordValue\\\":\\\"$_value\\\"}\"\n}\n\n# shell client for tencent cloud api v3 | @author: rehiy\n\ntencent_sha256() {\n  printf %b \"$@\" | _digest sha256 hex\n}\n\ntencent_hmac_sha256() {\n  k=$1\n  shift\n  hex_key=$(printf %b \"$k\" | _hex_dump | tr -d ' ')\n  printf %b \"$@\" | _hmac sha256 \"$hex_key\" hex\n}\n\ntencent_hmac_sha256_hexkey() {\n  k=$1\n  shift\n  printf %b \"$@\" | _hmac sha256 \"$k\" hex\n}\n\ntencent_signature_v3() {\n  service=$1\n  action=$(echo \"$2\" | _lower_case)\n  payload=${3:-'{}'}\n  timestamp=${4:-$(date +%s)}\n\n  domain=\"$service.tencentcloudapi.com\"\n  secretId=${Tencent_SecretId:-'tencent-cloud-secret-id'}\n  secretKey=${Tencent_SecretKey:-'tencent-cloud-secret-key'}\n\n  algorithm='TC3-HMAC-SHA256'\n  date=$(date -u -d \"@$timestamp\" +%Y-%m-%d 2>/dev/null)\n  [ -z \"$date\" ] && date=$(date -u -r \"$timestamp\" +%Y-%m-%d)\n\n  canonicalUri='/'\n  canonicalQuery=''\n  canonicalHeaders=\"content-type:application/json\\nhost:$domain\\nx-tc-action:$action\\n\"\n\n  signedHeaders='content-type;host;x-tc-action'\n  canonicalRequest=\"POST\\n$canonicalUri\\n$canonicalQuery\\n$canonicalHeaders\\n$signedHeaders\\n$(tencent_sha256 \"$payload\")\"\n\n  credentialScope=\"$date/$service/tc3_request\"\n  stringToSign=\"$algorithm\\n$timestamp\\n$credentialScope\\n$(tencent_sha256 \"$canonicalRequest\")\"\n\n  secretDate=$(tencent_hmac_sha256 \"TC3$secretKey\" \"$date\")\n  secretService=$(tencent_hmac_sha256_hexkey \"$secretDate\" \"$service\")\n  secretSigning=$(tencent_hmac_sha256_hexkey \"$secretService\" 'tc3_request')\n  signature=$(tencent_hmac_sha256_hexkey \"$secretSigning\" \"$stringToSign\")\n\n  echo \"$algorithm Credential=$secretId/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature\"\n}\n\ntencent_api_request() {\n  service=$1\n  version=$2\n  action=$3\n  payload=${4:-'{}'}\n  timestamp=${5:-$(date +%s)}\n\n  token=$(tencent_signature_v3 \"$service\" \"$action\" \"$payload\" \"$timestamp\")\n\n  _H1=\"Content-Type: application/json\"\n  _H2=\"Authorization: $token\"\n  _H3=\"X-TC-Version: $version\"\n  _H4=\"X-TC-Timestamp: $timestamp\"\n  _H5=\"X-TC-Action: $action\"\n\n  _post \"$payload\" \"$Tencent_API\" \"\" \"POST\" \"application/json\"\n}\n"
  },
  {
    "path": "dnsapi/dns_timeweb.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_timeweb_info='Timeweb.Cloud\nSite: Timeweb.Cloud\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_timeweb\nOptions:\n TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys\nIssues: github.com/acmesh-official/acme.sh/issues/5140\nAuthor: Nikolay Pronchev <@nikolaypronchev>\n'\n\nTW_Api=\"https://api.timeweb.cloud/api/v1\"\n\n################  Public functions ################\n\n# Adds an ACME DNS-01 challenge DNS TXT record via the Timeweb Cloud API.\n#\n# Param1: The ACME DNS-01 challenge FQDN.\n# Param2: The value of the ACME DNS-01 challenge TXT record.\n#\n# Example: dns_timeweb_add \"_acme-challenge.sub.domain.com\" \"D-52Wm...4uYM\"\ndns_timeweb_add() {\n  _debug \"$(__green \"Timeweb DNS API\"): \\\"dns_timeweb_add\\\" started.\"\n\n  _timeweb_set_acme_fqdn \"$1\" || return 1\n  _timeweb_set_acme_txt \"$2\" || return 1\n  _timeweb_check_token || return 1\n  _timeweb_split_acme_fqdn || return 1\n  _timeweb_dns_txt_add || return 1\n\n  _debug \"$(__green \"Timeweb DNS API\"): \\\"dns_timeweb_add\\\" finished.\"\n}\n\n# Removes a DNS TXT record via the Timeweb Cloud API.\n#\n# Param1: The ACME DNS-01 challenge FQDN.\n# Param2: The value of the ACME DNS-01 challenge TXT record.\n#\n# Example: dns_timeweb_rm \"_acme-challenge.sub.domain.com\" \"D-52Wm...4uYM\"\ndns_timeweb_rm() {\n  _debug \"$(__green \"Timeweb DNS API\"): \\\"dns_timeweb_rm\\\" started.\"\n\n  _timeweb_set_acme_fqdn \"$1\" || return 1\n  _timeweb_set_acme_txt \"$2\" || return 1\n  _timeweb_check_token || return 1\n  _timeweb_split_acme_fqdn || return 1\n  _timeweb_get_dns_txt || return 1\n  _timeweb_dns_txt_remove || return 1\n\n  _debug \"$(__green \"Timeweb DNS API\"): \\\"dns_timeweb_rm\\\" finished.\"\n}\n\n################  Private functions ################\n\n# Checks and sets the ACME DNS-01 challenge FQDN.\n#\n# Param1: The ACME DNS-01 challenge FQDN.\n#\n# Example: _timeweb_set_acme_fqdn \"_acme-challenge.sub.domain.com\"\n#\n# Sets the \"Acme_Fqdn\" variable (_acme-challenge.sub.domain.com)\n_timeweb_set_acme_fqdn() {\n  Acme_Fqdn=$1\n  _debug \"Setting ACME DNS-01 challenge FQDN \\\"$Acme_Fqdn\\\".\"\n  [ -z \"$Acme_Fqdn\" ] && {\n    _err \"ACME DNS-01 challenge FQDN is empty.\"\n    return 1\n  }\n  return 0\n}\n\n# Checks and sets the value of the ACME DNS-01 challenge TXT record.\n#\n# Param1: Value of the ACME DNS-01 challenge TXT record.\n#\n# Example: _timeweb_set_acme_txt \"D-52Wm...4uYM\"\n#\n# Sets the \"Acme_Txt\" variable to the provided value (D-52Wm...4uYM)\n_timeweb_set_acme_txt() {\n  Acme_Txt=$1\n  _debug \"Setting the value of the ACME DNS-01 challenge TXT record to \\\"$Acme_Txt\\\".\"\n  [ -z \"$Acme_Txt\" ] && {\n    _err \"ACME DNS-01 challenge TXT record value is empty.\"\n    return 1\n  }\n  return 0\n}\n\n# Checks if the Timeweb Cloud API JWT token is present (refer to the script description).\n# Adds or updates the token in the acme.sh account configuration.\n_timeweb_check_token() {\n  _debug \"Checking for the presence of the Timeweb Cloud API JWT token.\"\n\n  TW_Token=\"${TW_Token:-$(_readaccountconf_mutable TW_Token)}\"\n\n  [ -z \"$TW_Token\" ] && {\n    _err \"Timeweb Cloud API JWT token was not found.\"\n    return 1\n  }\n\n  _saveaccountconf_mutable TW_Token \"$TW_Token\"\n}\n\n# Divides the ACME DNS-01 challenge FQDN into its main domain and subdomain components.\n_timeweb_split_acme_fqdn() {\n  _debug \"Trying to divide \\\"$Acme_Fqdn\\\" into its main domain and subdomain components.\"\n\n  TW_Page_Limit=100\n  TW_Page_Offset=0\n  TW_Domains_Returned=\"\"\n\n  while [ -z \"$TW_Domains_Returned\" ] || [ \"$TW_Domains_Returned\" -ge \"$TW_Page_Limit\" ]; do\n\n    _timeweb_list_domains \"$TW_Page_Limit\" \"$TW_Page_Offset\" || return 1\n\n    # Remove the 'subdomains' subarray to prevent confusion with FQDNs.\n\n    TW_Domains=$(\n      echo \"$TW_Domains\" |\n        sed 's/\"subdomains\":\\[[^]]*]//g'\n    )\n\n    [ -z \"$TW_Domains\" ] && {\n      _err \"Failed to parse the list of domains.\"\n      return 1\n    }\n\n    while\n      TW_Domain=$(\n        echo \"$TW_Domains\" |\n          sed -n 's/.*{[^{]*\"fqdn\":\"\\([^\"]*\\)\"[^}]*}.*/\\1/p'\n      )\n\n      [ -n \"$TW_Domain\" ] && {\n        _timeweb_is_main_domain \"$TW_Domain\" && return 0\n\n        TW_Domains=$(\n          echo \"$TW_Domains\" |\n            sed 's/{\\([^{]*\"fqdn\":\"'\"$TW_Domain\"'\"[^}]*\\)}//'\n        )\n        continue\n      }\n    do :; done\n\n    TW_Page_Offset=$(_math \"$TW_Page_Offset\" + \"$TW_Page_Limit\")\n  done\n\n  _err \"Failed to divide \\\"$Acme_Fqdn\\\" into its main domain and subdomain components.\"\n  return 1\n}\n\n# Searches for a previously added DNS TXT record.\n#\n# Sets the \"TW_Dns_Txt_Id\" variable.\n_timeweb_get_dns_txt() {\n  _debug \"Trying to locate a DNS TXT record with the value \\\"$Acme_Txt\\\".\"\n\n  TW_Page_Limit=100\n  TW_Page_Offset=0\n  TW_Dns_Records_Returned=\"\"\n\n  while [ -z \"$TW_Dns_Records_Returned\" ] || [ \"$TW_Dns_Records_Returned\" -ge \"$TW_Page_Limit\" ]; do\n\n    _timeweb_list_dns_records \"$TW_Page_Limit\" \"$TW_Page_Offset\" || return 1\n\n    while\n      Dns_Record=$(\n        echo \"$TW_Dns_Records\" |\n          sed -n 's/.*{\\([^{]*{[^{]*'\"$Acme_Txt\"'[^}]*}[^}]*\\)}.*/\\1/p'\n      )\n\n      [ -n \"$Dns_Record\" ] && {\n        _timeweb_is_added_txt \"$Dns_Record\" && return 0\n\n        TW_Dns_Records=$(\n          echo \"$TW_Dns_Records\" |\n            sed 's/{\\([^{]*{[^{]*'\"$Acme_Txt\"'[^}]*}[^}]*\\)}//'\n        )\n        continue\n      }\n    do :; done\n\n    TW_Page_Offset=$(_math \"$TW_Page_Offset\" + \"$TW_Page_Limit\")\n  done\n\n  _err \"DNS TXT record was not found.\"\n  return 1\n}\n\n# Lists domains via the Timeweb Cloud API.\n#\n# Param 1: Limit for listed domains.\n# Param 2: Offset for domains list.\n#\n# Sets the \"TW_Domains\" variable.\n# Sets the \"TW_Domains_Returned\" variable.\n_timeweb_list_domains() {\n  _debug \"Listing domains via Timeweb Cloud API. Limit: $1, offset: $2.\"\n\n  export _H1=\"Authorization: Bearer $TW_Token\"\n\n  if ! TW_Domains=$(_get \"$TW_Api/domains?limit=$1&offset=$2\"); then\n    _err \"The request to the Timeweb Cloud API failed.\"\n    return 1\n  fi\n\n  [ -z \"$TW_Domains\" ] && {\n    _err \"Empty response from the Timeweb Cloud API.\"\n    return 1\n  }\n\n  TW_Domains_Returned=$(\n    echo \"$TW_Domains\" |\n      sed 's/.*\"meta\":{\"total\":\\([0-9]*\\)[^0-9].*/\\1/'\n  )\n\n  [ -z \"$TW_Domains_Returned\" ] && {\n    _err \"Failed to extract the total count of domains.\"\n    return 1\n  }\n\n  [ \"$TW_Domains_Returned\" -eq \"0\" ] && {\n    _err \"Domains are missing.\"\n    return 1\n  }\n\n  _debug \"Domains returned by Timeweb Cloud API: $TW_Domains_Returned.\"\n}\n\n# Lists domain DNS records via the Timeweb Cloud API.\n#\n# Param 1: Limit for listed DNS records.\n# Param 2: Offset for DNS records list.\n#\n# Sets the \"TW_Dns_Records\" variable.\n# Sets the \"TW_Dns_Records_Returned\" variable.\n_timeweb_list_dns_records() {\n  _debug \"Listing domain DNS records via the Timeweb Cloud API. Limit: $1, offset: $2.\"\n\n  export _H1=\"Authorization: Bearer $TW_Token\"\n\n  if ! TW_Dns_Records=$(_get \"$TW_Api/domains/$TW_Main_Domain/dns-records?limit=$1&offset=$2\"); then\n    _err \"The request to the Timeweb Cloud API failed.\"\n    return 1\n  fi\n\n  [ -z \"$TW_Dns_Records\" ] && {\n    _err \"Empty response from the Timeweb Cloud API.\"\n    return 1\n  }\n\n  TW_Dns_Records_Returned=$(\n    echo \"$TW_Dns_Records\" |\n      sed 's/.*\"meta\":{\"total\":\\([0-9]*\\)[^0-9].*/\\1/'\n  )\n\n  [ -z \"$TW_Dns_Records_Returned\" ] && {\n    _err \"Failed to extract the total count of DNS records.\"\n    return 1\n  }\n\n  [ \"$TW_Dns_Records_Returned\" -eq \"0\" ] && {\n    _err \"DNS records are missing.\"\n    return 1\n  }\n\n  _debug \"DNS records returned by Timeweb Cloud API: $TW_Dns_Records_Returned.\"\n}\n\n# Verifies whether the domain is the primary domain for the ACME DNS-01 challenge FQDN.\n# The requirement is that the provided domain is the top-level domain\n# for the ACME DNS-01 challenge FQDN.\n#\n# Param 1: Domain object returned by Timeweb Cloud API.\n#\n# Sets the \"TW_Main_Domain\" variable (e.g. \"_acme-challenge.s1.domain.co.uk\" → \"domain.co.uk\").\n# Sets the \"TW_Subdomains\" variable (e.g. \"_acme-challenge.s1.domain.co.uk\" → \"_acme-challenge.s1\").\n_timeweb_is_main_domain() {\n  _debug \"Checking if \\\"$1\\\" is the main domain of the ACME DNS-01 challenge FQDN.\"\n\n  [ -z \"$1\" ] && {\n    _debug \"Failed to extract FQDN. Skipping domain.\"\n    return 1\n  }\n\n  ! echo \".$Acme_Fqdn\" | grep -qi \"\\.$1$\" && {\n    _debug \"Domain does not match the ACME DNS-01 challenge FQDN. Skipping domain.\"\n    return 1\n  }\n\n  TW_Main_Domain=$1\n  TW_Subdomains=$(\n    echo \"$Acme_Fqdn\" |\n      sed \"s/\\.*.\\{${#1}\\}$//\"\n  )\n\n  _debug \"Matched domain. ACME DNS-01 challenge FQDN  split as [$TW_Subdomains].[$TW_Main_Domain].\"\n  return 0\n}\n\n# Verifies whether a DNS record was previously added based on the following criteria:\n# - The value matches the ACME DNS-01 challenge TXT record value;\n# - The record type is TXT;\n# - The subdomain matches the ACME DNS-01 challenge FQDN.\n#\n# Param 1: DNS record object returned by Timeweb Cloud API.\n#\n# Sets the \"TW_Dns_Txt_Id\" variable.\n_timeweb_is_added_txt() {\n  _debug \"Checking if \\\"$1\\\" is a previously added DNS TXT record.\"\n\n  echo \"$1\" | grep -qv '\"type\":\"TXT\"' && {\n    _debug \"Not a TXT record. Skipping the record.\"\n    return 1\n  }\n\n  if [ -n \"$TW_Subdomains\" ]; then\n    echo \"$1\" | grep -qvi \"\\\"subdomain\\\":\\\"$TW_Subdomains\\\"\" && {\n      _debug \"Subdomains do not match. Skipping the record.\"\n      return 1\n    }\n  else\n    echo \"$1\" | grep -q '\"subdomain\\\":\"..*\"' && {\n      _debug \"Subdomains do not match. Skipping the record.\"\n      return 1\n    }\n  fi\n\n  TW_Dns_Txt_Id=$(\n    echo \"$1\" |\n      sed 's/.*\"id\":\\([0-9]*\\)[^0-9].*/\\1/'\n  )\n\n  [ -z \"$TW_Dns_Txt_Id\" ] && {\n    _debug \"Failed to extract the DNS record ID. Skipping the record.\"\n    return 1\n  }\n\n  _debug \"Matching DNS TXT record ID is \\\"$TW_Dns_Txt_Id\\\".\"\n  return 0\n}\n\n# Adds a DNS TXT record via the Timeweb Cloud API.\n_timeweb_dns_txt_add() {\n  _debug \"Adding a new DNS TXT record via the Timeweb Cloud API.\"\n\n  export _H1=\"Authorization: Bearer $TW_Token\"\n  export _H2=\"Content-Type: application/json\"\n\n  if ! TW_Response=$(\n    _post \"{\n      \\\"subdomain\\\":\\\"$TW_Subdomains\\\",\n      \\\"type\\\":\\\"TXT\\\",\n      \\\"value\\\":\\\"$Acme_Txt\\\"\n    }\" \\\n      \"$TW_Api/domains/$TW_Main_Domain/dns-records\"\n  ); then\n    _err \"The request to the Timeweb Cloud API failed.\"\n    return 1\n  fi\n\n  [ -z \"$TW_Response\" ] && {\n    _err \"An unexpected empty response was received from the Timeweb Cloud API.\"\n    return 1\n  }\n\n  TW_Dns_Txt_Id=$(\n    echo \"$TW_Response\" |\n      sed 's/.*\"id\":\\([0-9]*\\)[^0-9].*/\\1/'\n  )\n\n  [ -z \"$TW_Dns_Txt_Id\" ] && {\n    _err \"Failed to extract the DNS TXT Record ID.\"\n    return 1\n  }\n\n  _debug \"DNS TXT record has been added. ID: \\\"$TW_Dns_Txt_Id\\\".\"\n}\n\n# Removes a DNS record via the Timeweb Cloud API.\n_timeweb_dns_txt_remove() {\n  _debug \"Removing DNS record via the Timeweb Cloud API.\"\n\n  export _H1=\"Authorization: Bearer $TW_Token\"\n\n  if ! TW_Response=$(\n    _post \\\n      \"\" \\\n      \"$TW_Api/domains/$TW_Main_Domain/dns-records/$TW_Dns_Txt_Id\" \\\n      \"\" \\\n      \"DELETE\"\n  ); then\n    _err \"The request to the Timeweb Cloud API failed.\"\n    return 1\n  fi\n\n  [ -n \"$TW_Response\" ] && {\n    _err \"Received an unexpected response body from the Timeweb Cloud API.\"\n    return 1\n  }\n\n  _debug \"DNS TXT record with ID \\\"$TW_Dns_Txt_Id\\\" has been removed.\"\n}\n"
  },
  {
    "path": "dnsapi/dns_transip.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_transip_info='TransIP.nl\nSite: TransIP.nl\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_transip\nOptions:\n TRANSIP_Username Username\n TRANSIP_Key_File Private key file path\nIssues: github.com/acmesh-official/acme.sh/issues/2949\n'\n\nTRANSIP_Api_Url=\"https://api.transip.nl/v6\"\nTRANSIP_Token_Read_Only=\"false\"\nTRANSIP_Token_Expiration=\"30 minutes\"\n# You can't reuse a label token, so we leave this empty normally\nTRANSIP_Token_Label=\"\"\n\n########  Public functions #####################\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_transip_add() {\n  fulldomain=\"$1\"\n  _debug fulldomain=\"$fulldomain\"\n  txtvalue=\"$2\"\n  _debug txtvalue=\"$txtvalue\"\n  _transip_setup \"$fulldomain\" || return 1\n  _info \"Creating TXT record.\"\n  if ! _transip_rest POST \"domains/$_domain/dns\" \"{\\\"dnsEntry\\\":{\\\"name\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"expire\\\":60}}\"; then\n    _err \"Could not add TXT record.\"\n    return 1\n  fi\n  return 0\n}\n\ndns_transip_rm() {\n  fulldomain=$1\n  _debug fulldomain=\"$fulldomain\"\n  txtvalue=$2\n  _debug txtvalue=\"$txtvalue\"\n  _transip_setup \"$fulldomain\" || return 1\n  _info \"Removing TXT record.\"\n  if ! _transip_rest DELETE \"domains/$_domain/dns\" \"{\\\"dnsEntry\\\":{\\\"name\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"expire\\\":60}}\"; then\n    _err \"Could not remove TXT record $_sub_domain for $domain\"\n    return 1\n  fi\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=\"$1\"\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n    _domain=\"$h\"\n\n    if _transip_rest GET \"domains/$h/dns\" && _contains \"$response\" \"dnsEntries\"; then\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  _err \"Unable to parse this domain\"\n  return 1\n}\n\n_transip_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  _debug ep \"$ep\"\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Authorization: Bearer $_token\"\n  export _H4=\"Content-Type: application/json\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$TRANSIP_Api_Url/$ep\" \"\" \"$m\")\"\n    retcode=$?\n  else\n    response=\"$(_get \"$TRANSIP_Api_Url/$ep\")\"\n    retcode=$?\n  fi\n\n  if [ \"$retcode\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_transip_get_token() {\n  nonce=$(echo \"TRANSIP$(_time)\" | _digest sha1 hex | cut -c 1-32)\n  _debug nonce \"$nonce\"\n\n  # make IP whitelisting configurable\n  TRANSIP_Token_Global_Key=\"${TRANSIP_Token_Global_Key:-$(_readaccountconf_mutable TRANSIP_Token_Global_Key)}\"\n  _saveaccountconf_mutable TRANSIP_Token_Global_Key \"$TRANSIP_Token_Global_Key\"\n\n  data=\"{\\\"login\\\":\\\"${TRANSIP_Username}\\\",\\\"nonce\\\":\\\"${nonce}\\\",\\\"read_only\\\":\\\"${TRANSIP_Token_Read_Only}\\\",\\\"expiration_time\\\":\\\"${TRANSIP_Token_Expiration}\\\",\\\"label\\\":\\\"${TRANSIP_Token_Label}\\\",\\\"global_key\\\":\\\"${TRANSIP_Token_Global_Key:-false}\\\"}\"\n  _debug data \"$data\"\n\n  #_signature=$(printf \"%s\" \"$data\" | openssl dgst -sha512 -sign \"$TRANSIP_Key_File\" | _base64)\n  _signature=$(printf \"%s\" \"$data\" | _sign \"$TRANSIP_Key_File\" \"sha512\")\n  _debug2 _signature \"$_signature\"\n\n  export _H1=\"Signature: $_signature\"\n  export _H2=\"Content-Type: application/json\"\n\n  response=\"$(_post \"$data\" \"$TRANSIP_Api_Url/auth\" \"\" \"POST\")\"\n  retcode=$?\n  _debug2 response \"$response\"\n  if [ \"$retcode\" != \"0\" ]; then\n    _err \"Authentication failed.\"\n    return 1\n  fi\n  if _contains \"$response\" \"token\"; then\n    _token=\"$(echo \"$response\" | _normalizeJson | sed -n 's/^{\"token\":\"\\(.*\\)\"}/\\1/p')\"\n    _debug _token \"$_token\"\n    return 0\n  fi\n  return 1\n}\n\n_transip_setup() {\n  fulldomain=$1\n\n  # retrieve the transip creds\n  TRANSIP_Username=\"${TRANSIP_Username:-$(_readaccountconf_mutable TRANSIP_Username)}\"\n  TRANSIP_Key_File=\"${TRANSIP_Key_File:-$(_readaccountconf_mutable TRANSIP_Key_File)}\"\n  # check their vals for null\n  if [ -z \"$TRANSIP_Username\" ] || [ -z \"$TRANSIP_Key_File\" ]; then\n    TRANSIP_Username=\"\"\n    TRANSIP_Key_File=\"\"\n    _err \"You didn't specify a TransIP username and api key file location\"\n    _err \"Please set those values and try again.\"\n    return 1\n  fi\n  # save the username and api key to the account conf file.\n  _saveaccountconf_mutable TRANSIP_Username \"$TRANSIP_Username\"\n  _saveaccountconf_mutable TRANSIP_Key_File \"$TRANSIP_Key_File\"\n\n  # download key file if it's an URL\n  if _startswith \"$TRANSIP_Key_File\" \"http\"; then\n    _debug \"download transip key file\"\n    TRANSIP_Key_URL=$TRANSIP_Key_File\n    TRANSIP_Key_File=\"$(_mktemp)\"\n    chmod 600 \"$TRANSIP_Key_File\"\n    if ! _get \"$TRANSIP_Key_URL\" >\"$TRANSIP_Key_File\"; then\n      _err \"Error getting key file from : $TRANSIP_Key_URL\"\n      return 1\n    fi\n  fi\n\n  if [ -f \"$TRANSIP_Key_File\" ]; then\n    if ! grep \"BEGIN PRIVATE KEY\" \"$TRANSIP_Key_File\" >/dev/null 2>&1; then\n      _err \"Key file doesn't seem to be a valid key: ${TRANSIP_Key_File}\"\n      return 1\n    fi\n  else\n    _err \"Can't read private key file: ${TRANSIP_Key_File}\"\n    return 1\n  fi\n\n  if [ -z \"$_token\" ]; then\n    if ! _transip_get_token; then\n      _err \"Can not get token.\"\n      return 1\n    fi\n  fi\n\n  if [ -n \"${TRANSIP_Key_URL}\" ]; then\n    _debug \"delete transip key file\"\n    rm \"${TRANSIP_Key_File}\"\n    TRANSIP_Key_File=$TRANSIP_Key_URL\n  fi\n\n  _get_root \"$fulldomain\" || return 1\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_udr.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_udr_info='united-domains Reselling\nSite: ud-reselling.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_udr\nOptions:\n UDR_USER Username\n UDR_PASS Password\nIssues: github.com/acmesh-official/acme.sh/issues/3923\nAuthor: Andreas Scherer <@andischerer>\n'\n\nUDR_API=\"https://api.domainreselling.de/api/call.cgi\"\nUDR_TTL=\"30\"\n\n########  Public functions #####################\n\n#Usage: add _acme-challenge.www.domain.com \"some_long_string_of_characters_go_here_from_lets_encrypt\"\ndns_udr_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  UDR_USER=\"${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}\"\n  UDR_PASS=\"${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}\"\n  if [ -z \"$UDR_USER\" ] || [ -z \"$UDR_PASS\" ]; then\n    UDR_USER=\"\"\n    UDR_PASS=\"\"\n    _err \"You didn't specify an UD-Reselling username and password yet\"\n    return 1\n  fi\n  # save the username and password to the account conf file.\n  _saveaccountconf_mutable UDR_USER \"$UDR_USER\"\n  _saveaccountconf_mutable UDR_PASS \"$UDR_PASS\"\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _dnszone \"${_dnszone}\"\n\n  _debug \"Getting txt records\"\n  if ! _udr_rest \"QueryDNSZoneRRList\" \"dnszone=${_dnszone}\"; then\n    return 1\n  fi\n\n  rr=\"${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}\"\n  _debug resource_record \"${rr}\"\n  if _contains \"$response\" \"$rr\" >/dev/null; then\n    _err \"Error, it would appear that this record already exists. Please review existing TXT records for this domain.\"\n    return 1\n  fi\n\n  _info \"Adding record\"\n  if ! _udr_rest \"UpdateDNSZone\" \"dnszone=${_dnszone}&addrr0=${rr}\"; then\n    _err \"Adding the record did not succeed, please verify/check.\"\n    return 1\n  fi\n\n  _info \"Added, OK\"\n  return 0\n}\n\ndns_udr_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  UDR_USER=\"${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}\"\n  UDR_PASS=\"${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}\"\n  if [ -z \"$UDR_USER\" ] || [ -z \"$UDR_PASS\" ]; then\n    UDR_USER=\"\"\n    UDR_PASS=\"\"\n    _err \"You didn't specify an UD-Reselling username and password yet\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _dnszone \"${_dnszone}\"\n\n  _debug \"Getting txt records\"\n  if ! _udr_rest \"QueryDNSZoneRRList\" \"dnszone=${_dnszone}\"; then\n    return 1\n  fi\n\n  rr=\"${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}\"\n  _debug resource_record \"${rr}\"\n  if _contains \"$response\" \"$rr\" >/dev/null; then\n    if ! _udr_rest \"UpdateDNSZone\" \"dnszone=${_dnszone}&delrr0=${rr}\"; then\n      _err \"Deleting the record did not succeed, please verify/check.\"\n      return 1\n    fi\n    _info \"Removed, OK\"\n    return 0\n  else\n    _info \"Text record is not present, will not delete anything.\"\n    return 0\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n\n  if ! _udr_rest \"QueryDNSZoneList\" \"\"; then\n    return 1\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"${response}\" \"${h}.\" >/dev/null; then\n      _dnszone=$(echo \"$response\" | _egrep_o \"${h}\")\n      if [ \"$_dnszone\" ]; then\n        return 0\n      fi\n      return 1\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_udr_rest() {\n  if [ -n \"$2\" ]; then\n    data=\"command=$1&$2\"\n  else\n    data=\"command=$1\"\n  fi\n\n  _debug data \"${data}\"\n  response=\"$(_post \"${data}\" \"${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}\" \"\" \"POST\")\"\n\n  _code=$(echo \"$response\" | _egrep_o \"code = ([0-9]+)\" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  _description=$(echo \"$response\" | _egrep_o \"description = .*\" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n\n  _debug response_code \"$_code\"\n  _debug response_description \"$_description\"\n\n  if [ ! \"$_code\" = \"200\" ]; then\n    _err \"DNS-API-Error: $_description\"\n    return 1\n  fi\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_ultra.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_ultra_info='UltraDNS.com\nSite: UltraDNS.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ultra\nOptions:\n ULTRA_USR Username\n ULTRA_PWD Password\nIssues: github.com/acmesh-official/acme.sh/issues/2118\n'\n\nULTRA_API=\"https://api.ultradns.com/v3/\"\nULTRA_AUTH_API=\"https://api.ultradns.com/v2/\"\n\n#Usage: add _acme-challenge.www.domain.com \"some_long_string_of_characters_go_here_from_lets_encrypt\"\ndns_ultra_add() {\n  fulldomain=$1\n  txtvalue=$2\n  export txtvalue\n  ULTRA_USR=\"${ULTRA_USR:-$(_readaccountconf_mutable ULTRA_USR)}\"\n  ULTRA_PWD=\"${ULTRA_PWD:-$(_readaccountconf_mutable ULTRA_PWD)}\"\n  if [ -z \"$ULTRA_USR\" ] || [ -z \"$ULTRA_PWD\" ]; then\n    ULTRA_USR=\"\"\n    ULTRA_PWD=\"\"\n    _err \"You didn't specify an UltraDNS username and password yet\"\n    return 1\n  fi\n  # save the username and password to the account conf file.\n  _saveaccountconf_mutable ULTRA_USR \"$ULTRA_USR\"\n  _saveaccountconf_mutable ULTRA_PWD \"$ULTRA_PWD\"\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"${_domain_id}\"\n  _debug _sub_domain \"${_sub_domain}\"\n  _debug _domain \"${_domain}\"\n  _debug \"Getting txt records\"\n  _ultra_rest GET \"zones/${_domain_id}/rrsets/TXT?q=value:${fulldomain}\"\n  if printf \"%s\" \"$response\" | grep \\\"totalCount\\\" >/dev/null; then\n    _err \"Error, it would appear that this record already exists. Please review existing TXT records for this domain.\"\n    return 1\n  fi\n\n  _info \"Adding record\"\n  if _ultra_rest POST \"zones/$_domain_id/rrsets/TXT/${_sub_domain}\" '{\"ttl\":300,\"rdata\":[\"'\"${txtvalue}\"'\"]}'; then\n    if _contains \"$response\" \"Successful\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"Resource Record of type 16 with these attributes already exists\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n\n}\n\ndns_ultra_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  export txtvalue\n  ULTRA_USR=\"${ULTRA_USR:-$(_readaccountconf_mutable ULTRA_USR)}\"\n  ULTRA_PWD=\"${ULTRA_PWD:-$(_readaccountconf_mutable ULTRA_PWD)}\"\n  if [ -z \"$ULTRA_USR\" ] || [ -z \"$ULTRA_PWD\" ]; then\n    ULTRA_USR=\"\"\n    ULTRA_PWD=\"\"\n    _err \"You didn't specify an UltraDNS username and password yet\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"${_domain_id}\"\n  _debug _sub_domain \"${_sub_domain}\"\n  _debug _domain \"${domain}\"\n\n  _debug \"Getting TXT records\"\n  _ultra_rest GET \"zones/${_domain_id}/rrsets?q=kind:RECORDS+owner:${_sub_domain}\"\n\n  if ! printf \"%s\" \"$response\" | grep \\\"resultInfo\\\" >/dev/null; then\n    _err \"There was an error in obtaining the resource records for ${_domain_id}\"\n    return 1\n  fi\n\n  count=$(echo \"$response\" | _egrep_o \"\\\"returnedCount\\\":[^,]*\" | cut -d: -f2 | cut -d'}' -f1)\n  _debug count \"${count}\"\n  if [ \"${count}\" = \"\" ]; then\n    _info \"Text record is not present, will not delete anything.\"\n  else\n    if ! _ultra_rest DELETE \"zones/$_domain_id/rrsets/TXT/${_sub_domain}\" '{\"ttl\":300,\"rdata\":[\"'\"${txtvalue}\"'\"]}'; then\n      _err \"Deleting the record did not succeed, please verify/check.\"\n      return 1\n    fi\n    _contains \"$response\" \"\"\n  fi\n\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    _debug response \"$response\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    if ! _ultra_rest GET \"zones\"; then\n      return 1\n    fi\n    if _contains \"${response}\" \"${h}.\" >/dev/null; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"${h}\" | head -1)\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"${h}\"\n        _debug sub_domain \"${_sub_domain}\"\n        _debug domain \"${_domain}\"\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_ultra_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n  if [ -z \"$AUTH_TOKEN\" ]; then\n    _ultra_login\n  fi\n  _debug TOKEN \"$AUTH_TOKEN\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $AUTH_TOKEN\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$ULTRA_API$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$ULTRA_API$ep\")\"\n  fi\n}\n\n_ultra_login() {\n  export _H1=\"\"\n  export _H2=\"\"\n  AUTH_TOKEN=$(_post \"grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}\" \"${ULTRA_AUTH_API}authorization/token\" | cut -d, -f3 | cut -d\\\" -f4)\n  export AUTH_TOKEN\n}\n"
  },
  {
    "path": "dnsapi/dns_unoeuro.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_unoeuro_info='unoeuro.com\n Deprecated. The unoeuro.com is now simply.com\nSite: unoeuro.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_unoeuro\nOptions:\n UNO_Key API Key\n UNO_User Username\n'\n\nUno_Api=\"https://api.simply.com/1\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_unoeuro_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  UNO_Key=\"${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}\"\n  UNO_User=\"${UNO_User:-$(_readaccountconf_mutable UNO_User)}\"\n  if [ -z \"$UNO_Key\" ] || [ -z \"$UNO_User\" ]; then\n    UNO_Key=\"\"\n    UNO_User=\"\"\n    _err \"You haven't specified a UnoEuro api key and account yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable UNO_Key \"$UNO_Key\"\n  _saveaccountconf_mutable UNO_User \"$UNO_User\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _uno_rest GET \"my/products/$h/dns/records\"\n\n  if ! _contains \"$response\" \"\\\"status\\\": 200\" >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n  _info \"Adding record\"\n\n  if _uno_rest POST \"my/products/$h/dns/records\" \"{\\\"name\\\":\\\"$fulldomain\\\",\\\"type\\\":\\\"TXT\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120,\\\"priority\\\":0}\"; then\n    if _contains \"$response\" \"\\\"status\\\": 200\" >/dev/null; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n}\n\n#fulldomain txtvalue\ndns_unoeuro_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  UNO_Key=\"${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}\"\n  UNO_User=\"${UNO_User:-$(_readaccountconf_mutable UNO_User)}\"\n  if [ -z \"$UNO_Key\" ] || [ -z \"$UNO_User\" ]; then\n    UNO_Key=\"\"\n    UNO_User=\"\"\n    _err \"You haven't specified a UnoEuro api key and account yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  if ! _contains \"$UNO_User\" \"UE\"; then\n    _err \"It seems that the UNO_User=$UNO_User is not a valid username.\"\n    _err \"Please check and retry.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _uno_rest GET \"my/products/$h/dns/records\"\n\n  if ! _contains \"$response\" \"\\\"status\\\": 200\"; then\n    _err \"Error\"\n    return 1\n  fi\n\n  if ! _contains \"$response\" \"$_sub_domain\"; then\n    _info \"Don't need to remove.\"\n  else\n    for record_line_number in $(echo \"$response\" | grep -n \"$_sub_domain\" | cut -d : -f 1); do\n      record_line_number=$(_math \"$record_line_number\" - 1)\n      _debug \"record_line_number\" \"$record_line_number\"\n      record_id=$(echo \"$response\" | _head_n \"$record_line_number\" | _tail_n 1 1 | _egrep_o \"[0-9]{1,}\")\n      _debug \"record_id\" \"$record_id\"\n\n      if [ -z \"$record_id\" ]; then\n        _err \"Can not get record id to remove.\"\n        return 1\n      fi\n\n      if ! _uno_rest DELETE \"my/products/$h/dns/records/$record_id\"; then\n        _err \"Delete record error.\"\n        return 1\n      fi\n      _contains \"$response\" \"\\\"status\\\": 200\"\n    done\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _uno_rest GET \"my/products/$h/dns/records\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"status\\\": 200\"; then\n      _domain_id=$h\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_uno_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$Uno_Api/$UNO_User/$UNO_Key/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$Uno_Api/$UNO_User/$UNO_Key/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_variomedia.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_variomedia_info='variomedia.de\nSite: variomedia.de\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_variomedia\nOptions:\n VARIOMEDIA_API_TOKEN API Token\nIssues: github.com/acmesh-official/acme.sh/issues/2564\n'\n\nVARIOMEDIA_API=\"https://api.variomedia.de\"\n\n######## Public functions #####################\n\n#Usage: add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_variomedia_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  VARIOMEDIA_API_TOKEN=\"${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}\"\n  if test -z \"$VARIOMEDIA_API_TOKEN\"; then\n    VARIOMEDIA_API_TOKEN=\"\"\n    _err 'VARIOMEDIA_API_TOKEN was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable VARIOMEDIA_API_TOKEN \"$VARIOMEDIA_API_TOKEN\"\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  if ! _variomedia_rest POST \"dns-records\" \"{\\\"data\\\": {\\\"type\\\": \\\"dns-record\\\", \\\"attributes\\\": {\\\"record_type\\\": \\\"TXT\\\", \\\"name\\\": \\\"$_sub_domain\\\", \\\"domain\\\": \\\"$_domain\\\", \\\"data\\\": \\\"$txtvalue\\\", \\\"ttl\\\":300}}}\"; then\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 _response \"$response\"\n  return 0\n}\n\n#fulldomain txtvalue\ndns_variomedia_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  VARIOMEDIA_API_TOKEN=\"${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}\"\n  if test -z \"$VARIOMEDIA_API_TOKEN\"; then\n    VARIOMEDIA_API_TOKEN=\"\"\n    _err 'VARIOMEDIA_API_TOKEN was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable VARIOMEDIA_API_TOKEN \"$VARIOMEDIA_API_TOKEN\"\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug 'Getting txt records'\n\n  if ! _variomedia_rest GET \"dns-records?filter[domain]=$_domain\"; then\n    _err 'Error'\n    return 1\n  fi\n\n  _record_id=\"$(echo \"$response\" | sed -E 's/,\"tags\":\\[[^]]*\\]//g' | cut -d '[' -f3 | cut -d']' -f1 | sed 's/},[ \\t]*{/\\},§\\{/g' | tr § '\\n' | grep -i \"$_sub_domain\" | grep -- \"$txtvalue\" | sed 's/^{//;s/}[,]?$//' | tr , '\\n' | tr -d '\\\"' | grep ^id | cut -d : -f2 | tr -d ' ')\"\n  _debug _record_id \"$_record_id\"\n  if [ \"$_record_id\" ]; then\n    _info \"Successfully retrieved the record id for ACME challenge.\"\n  else\n    _info \"Empty record id, it seems no such record.\"\n    return 0\n  fi\n\n  if ! _variomedia_rest DELETE \"/dns-records/$_record_id\"; then\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 _response \"$response\"\n  return 0\n}\n\n#################### Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    if ! _variomedia_rest GET \"domains/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"id\\\":\\\"$h\\\"\"; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d '.' -f 1-\"$p\")\n      _domain=\"$h\"\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_variomedia_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Authorization: token $VARIOMEDIA_API_TOKEN\"\n  export _H2=\"Content-Type: application/vnd.api+json\"\n  export _H3=\"Accept: application/vnd.variomedia.v1+json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$VARIOMEDIA_API/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$VARIOMEDIA_API/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $ep\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_veesp.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_veesp_info='veesp.com\nSite: veesp.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_veesp\nOptions:\n VEESP_User Username\n VEESP_Password Password\nIssues: github.com/acmesh-official/acme.sh/issues/3712\nAuthor: <stepan@plyask.in>\n'\n\nVEESP_Api=\"https://secure.veesp.com/api\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_veesp_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  VEESP_Password=\"${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}\"\n  VEESP_User=\"${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}\"\n  VEESP_auth=$(printf \"%s\" \"$VEESP_User:$VEESP_Password\" | _base64)\n\n  if [ -z \"$VEESP_Password\" ] || [ -z \"$VEESP_User\" ]; then\n    VEESP_Password=\"\"\n    VEESP_User=\"\"\n    _err \"You don't specify veesp api key and email yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable VEESP_Password \"$VEESP_Password\"\n  _saveaccountconf_mutable VEESP_User \"$VEESP_User\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if VEESP_rest POST \"service/$_service_id/dns/$_domain_id/records\" \"{\\\"name\\\":\\\"$fulldomain\\\",\\\"ttl\\\":1,\\\"priority\\\":0,\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\"$txtvalue\\\"}\"; then\n    if _contains \"$response\" \"\\\"success\\\":true\"; then\n      _info \"Added\"\n      #todo: check if the record takes effect\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n}\n\n# Usage: fulldomain txtvalue\n# Used to remove the txt record after validation\ndns_veesp_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  VEESP_Password=\"${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}\"\n  VEESP_User=\"${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}\"\n  VEESP_auth=$(printf \"%s\" \"$VEESP_User:$VEESP_Password\" | _base64)\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  VEESP_rest GET \"service/$_service_id/dns/$_domain_id\"\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\".\\\"$txtvalue.\\\"\\\"\" | wc -l | tr -d \" \")\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"{\\\"id\\\":[^}]*\\\"type\\\":\\\"TXT\\\",\\\"content\\\":\\\".\\\"$txtvalue.\\\"\\\"\" | cut -d\\\" -f4)\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! VEESP_rest DELETE \"service/$_service_id/dns/$_domain_id/records/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    _contains \"$response\" \"\\\"success\\\":true\"\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n  if ! VEESP_rest GET \"dns\"; then\n    return 1\n  fi\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _domain_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"domain_id\\\":[^,]*,\\\"name\\\":\\\"$h\\\"\" | cut -d : -f 2 | cut -d , -f 1 | cut -d '\"' -f 2)\n      _debug _domain_id \"$_domain_id\"\n      _service_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$h\\\",\\\"service_id\\\":[^}]*\" | cut -d : -f 3 | cut -d '\"' -f 2)\n      _debug _service_id \"$_service_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=\"$h\"\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\nVEESP_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Authorization: Basic $VEESP_auth\"\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    export _H3=\"Content-Type: application/json\"\n    response=\"$(_post \"$data\" \"$VEESP_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$VEESP_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_vercel.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_vercel_info='Vercel.com\nSite: Vercel.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vercel\nOptions:\n VERCEL_TOKEN API Token\n'\n\n# This is your API token which can be acquired on the account page.\n# https://vercel.com/account/tokens\n\nVERCEL_API=\"https://api.vercel.com\"\n\n#Usage: add _acme-challenge.www.domain.com \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_vercel_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  VERCEL_TOKEN=\"${VERCEL_TOKEN:-$(_readaccountconf_mutable VERCEL_TOKEN)}\"\n\n  if [ -z \"$VERCEL_TOKEN\" ]; then\n    VERCEL_TOKEN=\"\"\n    _err \"You have not set the Vercel API token yet.\"\n    _err \"Please visit https://vercel.com/account/tokens to generate it.\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable VERCEL_TOKEN \"$VERCEL_TOKEN\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _info \"Adding record\"\n  if _vercel_rest POST \"v2/domains/$_domain/records\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"value\\\":\\\"$txtvalue\\\"}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"\\\"uid\\\":\\\"\" >/dev/null; then\n      _info \"Added\"\n      return 0\n    else\n      _err \"Unexpected response while adding text record.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n}\n\ndns_vercel_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _vercel_rest GET \"v2/domains/$_domain/records\"\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$_sub_domain\\\",[^{]*\\\"type\\\":\\\"TXT\\\"\" | wc -l | tr -d \" \")\n\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    _record_id=$(printf \"%s\" \"$response\" | _egrep_o \"\\\"id\\\":[^,]*,\\\"slug\\\":\\\"[^,]*\\\",\\\"name\\\":\\\"$_sub_domain\\\",[^{]*\\\"type\\\":\\\"TXT\\\",\\\"value\\\":\\\"$txtvalue\\\"\" | cut -d: -f2 | cut -d, -f1 | tr -d '\"')\n\n    if [ \"$_record_id\" ]; then\n      echo \"$_record_id\" | while read -r item; do\n        if _vercel_rest DELETE \"v2/domains/$_domain/records/$item\"; then\n          _info \"removed record\" \"$item\"\n          return 0\n        else\n          _err \"failed to remove record\" \"$item\"\n          return 1\n        fi\n      done\n    fi\n  fi\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n_get_root() {\n  domain=\"$1\"\n  ep=\"$2\"\n  i=1\n  p=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _vercel_rest GET \"v4/domains/$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\" >/dev/null; then\n      _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n      _domain=$h\n      return 0\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_vercel_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  path=\"$VERCEL_API/$ep\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $VERCEL_TOKEN\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _secure_debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"$path\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$path\")\"\n  fi\n  _ret=\"$?\"\n  _code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\\\r\\\\n\")\"\n  _debug \"http response code $_code\"\n  _secure_debug2 response \"$response\"\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n\n  response=\"$(printf \"%s\" \"$response\" | _normalizeJson)\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_virakcloud.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_virakcloud_info='VirakCloud DNS API\nSite: VirakCloud.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_virakcloud\nOptions:\n VIRAKCLOUD_API_TOKEN VirakCloud API Bearer Token\n'\n\nVIRAKCLOUD_API_URL=\"https://public-api.virakcloud.com/dns\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\n#Used to add txt record\ndns_virakcloud_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  VIRAKCLOUD_API_TOKEN=\"${VIRAKCLOUD_API_TOKEN:-$(_readaccountconf_mutable VIRAKCLOUD_API_TOKEN)}\"\n\n  if [ -z \"$VIRAKCLOUD_API_TOKEN\" ]; then\n    _err \"You haven't configured your VirakCloud API token yet.\"\n    _err \"Please set VIRAKCLOUD_API_TOKEN environment variable or run:\"\n    _err \"  export VIRAKCLOUD_API_TOKEN=\\\"your-api-token\\\"\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable VIRAKCLOUD_API_TOKEN \"$VIRAKCLOUD_API_TOKEN\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    http_code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n    if [ \"$http_code\" = \"401\" ]; then\n      return 1\n    fi\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug _domain \"$_domain\"\n  _debug fulldomain \"$fulldomain\"\n\n  _info \"Adding TXT record\"\n\n  if _virakcloud_rest POST \"domains/${_domain}/records\" \"{\\\"record\\\":\\\"${fulldomain}\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":3600,\\\"content\\\":\\\"${txtvalue}\\\"}\"; then\n    if echo \"$response\" | grep -q \"success\" || echo \"$response\" | grep -q \"\\\"data\\\"\"; then\n      _info \"Added, OK\"\n      return 0\n    elif echo \"$response\" | grep -q \"already exists\" || echo \"$response\" | grep -q \"duplicate\"; then\n      _info \"Record already exists, OK\"\n      return 0\n    else\n      _err \"Add TXT record error.\"\n      _err \"Response: $response\"\n      return 1\n    fi\n  fi\n\n  _err \"Add TXT record error.\"\n  return 1\n}\n\n#Usage: fulldomain txtvalue\n#Used to remove the txt record after validation\ndns_virakcloud_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  VIRAKCLOUD_API_TOKEN=\"${VIRAKCLOUD_API_TOKEN:-$(_readaccountconf_mutable VIRAKCLOUD_API_TOKEN)}\"\n\n  if [ -z \"$VIRAKCLOUD_API_TOKEN\" ]; then\n    _err \"You haven't configured your VirakCloud API token yet.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    http_code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n    if [ \"$http_code\" = \"401\" ]; then\n      return 1\n    fi\n    _err \"Invalid domain\"\n    return 1\n  fi\n\n  _debug _domain \"$_domain\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  _info \"Removing TXT record\"\n\n  _debug \"Getting list of records to find content ID\"\n  if ! _virakcloud_rest GET \"domains/${_domain}/records\" \"\"; then\n    return 1\n  fi\n\n  _debug2 \"Records response\" \"$response\"\n\n  contentid=\"\"\n  # Extract innermost objects (content objects) which look like {\"id\":\"...\",\"content_raw\":\"...\"}\n  # We filter for the one containing txtvalue\n\n  target_obj=$(echo \"$response\" | grep -o '{[^}]*}' | grep \"$txtvalue\" | _head_n 1)\n\n  if [ -n \"$target_obj\" ]; then\n    contentid=$(echo \"$target_obj\" | _egrep_o '\"id\":\"[^\"]*\"' | cut -d '\"' -f 4)\n  fi\n\n  if [ -z \"$contentid\" ]; then\n    _debug \"Could not find matching record ID in response\"\n    _info \"Record not found, may have been already removed\"\n    return 0\n  fi\n\n  _debug contentid \"$contentid\"\n\n  if _virakcloud_rest DELETE \"domains/${_domain}/records/${fulldomain}/TXT/${contentid}\" \"\"; then\n    if echo \"$response\" | grep -q \"success\" || [ -z \"$response\" ]; then\n      _info \"Removed, OK\"\n      return 0\n    elif echo \"$response\" | grep -q \"not found\" || echo \"$response\" | grep -q \"404\"; then\n      _info \"Record not found, OK\"\n      return 0\n    else\n      _err \"Remove TXT record error.\"\n      _err \"Response: $response\"\n      return 1\n    fi\n  fi\n\n  _err \"Remove TXT record error.\"\n  return 1\n}\n\n####################  Private functions below ##################################\n\n#_acme-challenge.www.domain.com\n#returns\n# _domain=domain.com\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  # Optimization: skip _acme-challenge subdomain to avoid 422 errors\n  if echo \"$domain\" | grep -q \"^_acme-challenge.\"; then\n    i=2\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n\n    if ! _virakcloud_rest GET \"domains/$h\" \"\"; then\n      http_code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n      if [ \"$http_code\" = \"401\" ]; then\n        return 1\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n      continue\n    fi\n\n    if echo \"$response\" | grep -q \"\\\"name\\\"\"; then\n      _domain=\"$h\"\n      return 0\n    fi\n\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_virakcloud_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug \"$ep\"\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $VIRAKCLOUD_API_TOKEN\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$VIRAKCLOUD_API_URL/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$VIRAKCLOUD_API_URL/$ep\")\"\n  fi\n\n  _ret=\"$?\"\n\n  if [ \"$_ret\" != \"0\" ]; then\n    _err \"error on $m $ep\"\n    return 1\n  fi\n\n  http_code=\"$(grep \"^HTTP\" \"$HTTP_HEADER\" | _tail_n 1 | cut -d \" \" -f 2 | tr -d \"\\r\\n\")\"\n  _debug \"http response code\" \"$http_code\"\n\n  if [ \"$http_code\" = \"401\" ]; then\n    _err \"VirakCloud API returned 401 Unauthorized.\"\n    _err \"Your VIRAKCLOUD_API_TOKEN is invalid or expired.\"\n    _err \"Please check your API token and try again.\"\n    return 1\n  fi\n\n  if [ \"$http_code\" = \"403\" ]; then\n    _err \"VirakCloud API returned 403 Forbidden.\"\n    _err \"Your API token does not have permission to access this resource.\"\n    return 1\n  fi\n\n  if [ -n \"$http_code\" ] && [ \"$http_code\" -ge 400 ]; then\n    _err \"VirakCloud API error. HTTP code: $http_code\"\n    _err \"Response: $response\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_vscale.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_vscale_info='vscale.io\nSite: vscale.io\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale\nOptions:\n VSCALE_API_KEY API Key\nAuthor: Alex Loban <@LAV45>\n'\n\nVSCALE_API_URL=\"https://api.vscale.io/v1\"\n\n########  Public functions #####################\n\n#Usage: dns_myapi_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_vscale_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  if [ -z \"$VSCALE_API_KEY\" ]; then\n    VSCALE_API_KEY=\"\"\n    _err \"You didn't specify the VSCALE api key yet.\"\n    _err \"Please create you key and try again.\"\n    return 1\n  fi\n\n  _saveaccountconf VSCALE_API_KEY \"$VSCALE_API_KEY\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _vscale_tmpl_json=\"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain.$_domain\\\",\\\"content\\\":\\\"$txtvalue\\\"}\"\n\n  if _vscale_rest POST \"domains/$_domain_id/records/\" \"$_vscale_tmpl_json\"; then\n    response=$(printf \"%s\\n\" \"$response\" | _egrep_o \"{\\\"error\\\": \\\".+\\\"\" | cut -d : -f 2)\n    if [ -z \"$response\" ]; then\n      _info \"txt record updated success.\"\n      return 0\n    fi\n  fi\n\n  return 1\n}\n\n#fulldomain txtvalue\ndns_vscale_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _vscale_rest GET \"domains/$_domain_id/records/\"\n\n  if [ -n \"$response\" ]; then\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"TXT\\\", \\\"id\\\": [0-9]+, \\\"name\\\": \\\"$_sub_domain.$_domain\\\"\" | cut -d : -f 2 | tr -d \", \\\"name\\\"\")\n    _debug record_id \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if _vscale_rest DELETE \"domains/$_domain_id/records/$record_id\" && [ -z \"$response\" ]; then\n      _info \"txt record deleted success.\"\n      return 0\n    fi\n    _debug response \"$response\"\n    return 1\n  fi\n\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=12345\n_get_root() {\n  domain=$1\n  i=2\n  p=1\n\n  if _vscale_rest GET \"domains/\"; then\n    response=\"$(echo \"$response\" | tr -d \"\\n\" | sed 's/{/\\n&/g')\"\n    while true; do\n      h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n      _debug h \"$h\"\n      if [ -z \"$h\" ]; then\n        #not valid\n        return 1\n      fi\n\n      hostedzone=\"$(echo \"$response\" | tr \"{\" \"\\n\" | _egrep_o \"\\\"name\\\":\\s*\\\"$h\\\".*}\")\"\n      if [ \"$hostedzone\" ]; then\n        _domain_id=$(printf \"%s\\n\" \"$hostedzone\" | _egrep_o \"\\\"id\\\":\\s*[0-9]+\" | _head_n 1 | cut -d : -f 2 | tr -d \\ )\n        if [ \"$_domain_id\" ]; then\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n          _domain=$h\n          return 0\n        fi\n        return 1\n      fi\n      p=$i\n      i=$(_math \"$i\" + 1)\n    done\n  fi\n  return 1\n}\n\n#method uri qstr data\n_vscale_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"X-Token: ${VSCALE_API_KEY}\"\n\n  if [ \"$mtd\" != \"GET\" ]; then\n    # both POST and DELETE.\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$VSCALE_API_URL/$ep\" \"\" \"$mtd\")\"\n  else\n    response=\"$(_get \"$VSCALE_API_URL/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_vultr.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_vultr_info='vultr.com\nSite: vultr.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vultr\nOptions:\n VULTR_API_KEY API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2374\n'\n\nVULTR_Api=\"https://api.vultr.com/v2\"\n\n########  Public functions #####################\n#\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_vultr_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  VULTR_API_KEY=\"${VULTR_API_KEY:-$(_readaccountconf_mutable VULTR_API_KEY)}\"\n  if test -z \"$VULTR_API_KEY\"; then\n    VULTR_API_KEY=''\n    _err 'VULTR_API_KEY was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable VULTR_API_KEY \"$VULTR_API_KEY\"\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug 'Getting txt records'\n  _vultr_rest GET \"domains/$_domain/records\"\n\n  if printf \"%s\\n\" \"$response\" | grep -- \"\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\"\" >/dev/null; then\n    _err 'Error'\n    return 1\n  fi\n\n  if ! _vultr_rest POST \"domains/$_domain/records\" \"{\\\"name\\\":\\\"$_sub_domain\\\",\\\"data\\\":\\\"$txtvalue\\\",\\\"type\\\":\\\"TXT\\\"}\"; then\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 _response \"$response\"\n  return 0\n}\n\n#fulldomain txtvalue\ndns_vultr_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  VULTR_API_KEY=\"${VULTR_API_KEY:-$(_readaccountconf_mutable VULTR_API_KEY)}\"\n  if test -z \"$VULTR_API_KEY\"; then\n    VULTR_API_KEY=\"\"\n    _err 'VULTR_API_KEY was not exported'\n    return 1\n  fi\n\n  _saveaccountconf_mutable VULTR_API_KEY \"$VULTR_API_KEY\"\n\n  _debug 'First detect the root zone'\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug 'Getting txt records'\n  _vultr_rest GET \"domains/$_domain/records\"\n\n  if printf \"%s\\n\" \"$response\" | grep -- \"\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$fulldomain\\\"\" >/dev/null; then\n    _err 'Error'\n    return 1\n  fi\n\n  _record_id=\"$(echo \"$response\" | tr '{}' '\\n' | grep '\"TXT\"' | grep -- \"$txtvalue\" | tr ',' '\\n' | grep -i 'id' | cut -d : -f 2 | tr -d '\"')\"\n  _debug _record_id \"$_record_id\"\n  if [ \"$_record_id\" ]; then\n    _info \"Successfully retrieved the record id for ACME challenge.\"\n  else\n    _info \"Empty record id, it seems no such record.\"\n    return 0\n  fi\n\n  if ! _vultr_rest DELETE \"domains/$_domain/records/$_record_id\"; then\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 _response \"$response\"\n  return 0\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  while true; do\n    _domain=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$_domain\"\n    if [ -z \"$_domain\" ]; then\n      return 1\n    fi\n\n    if ! _vultr_rest GET \"domains\"; then\n      return 1\n    fi\n\n    if printf \"%s\\n\" \"$response\" | grep -E '^\\{.*\\}' >/dev/null; then\n      if _contains \"$response\" \"\\\"domain\\\":\\\"$_domain\\\"\"; then\n        _sub_domain=\"$(echo \"$fulldomain\" | sed \"s/\\\\.$_domain\\$//\")\"\n        return 0\n      else\n        _debug \"Go to next level of $_domain\"\n      fi\n    else\n      _err \"$response\"\n      return 1\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n\n  return 1\n}\n\n_vultr_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  api_key_trimmed=$(echo \"$VULTR_API_KEY\" | tr -d '\"')\n\n  export _H1=\"Authorization: Bearer $api_key_trimmed\"\n  export _H2='Content-Type: application/json'\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$VULTR_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$VULTR_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error $ep\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_websupport.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_websupport_info='Websupport.sk\nSite: Websupport.sk\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_websupport\nOptions:\n WS_ApiKey API Key. Called \"Identifier\" in the WS Admin\n WS_ApiSecret API Secret. Called \"Secret key\" in the WS Admin\nIssues: github.com/acmesh-official/acme.sh/issues/3486\nAuthor: trgo.sk <@trgosk>, @akulumbeg\n'\n\n# Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey\n\nWS_Api=\"https://rest.websupport.sk\"\n\n########  Public functions #####################\n\ndns_websupport_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  WS_ApiKey=\"${WS_ApiKey:-$(_readaccountconf_mutable WS_ApiKey)}\"\n  WS_ApiSecret=\"${WS_ApiSecret:-$(_readaccountconf_mutable WS_ApiSecret)}\"\n\n  if [ \"$WS_ApiKey\" ] && [ \"$WS_ApiSecret\" ]; then\n    _saveaccountconf_mutable WS_ApiKey \"$WS_ApiKey\"\n    _saveaccountconf_mutable WS_ApiSecret \"$WS_ApiSecret\"\n  else\n    WS_ApiKey=\"\"\n    WS_ApiSecret=\"\"\n    _err \"You did not specify the API Key and/or API Secret\"\n    _err \"You can get the API login credentials from https://admin.websupport.sk/en/auth/apiKey\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so\n  # we can not use updating anymore.\n  #  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"count\\\":[^,]*\" | cut -d : -f 2)\n  #  _debug count \"$count\"\n  #  if [ \"$count\" = \"0\" ]; then\n  _info \"Adding record\"\n  if _ws_rest POST \"/v1/user/self/zone/$_domain/record\" \"{\\\"type\\\":\\\"TXT\\\",\\\"name\\\":\\\"$_sub_domain\\\",\\\"content\\\":\\\"$txtvalue\\\",\\\"ttl\\\":120}\"; then\n    if _contains \"$response\" \"$txtvalue\"; then\n      _info \"Added, OK\"\n      return 0\n    elif _contains \"$response\" \"The record already exists\"; then\n      _info \"Already exists, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\ndns_websupport_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _debug2 fulldomain \"$fulldomain\"\n  _debug2 txtvalue \"$txtvalue\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  _ws_rest GET \"/v1/user/self/zone/$_domain/record\"\n\n  if [ \"$(printf \"%s\" \"$response\" | tr -d \" \" | grep -c \\\"items\\\")\" -lt \"1\" ]; then\n    _err \"Error: $response\"\n    return 1\n  fi\n\n  record_line=\"$(_get_from_array \"$response\" \"$txtvalue\")\"\n  _debug record_line \"$record_line\"\n  if [ -z \"$record_line\" ]; then\n    _info \"Don't need to remove.\"\n  else\n    record_id=$(echo \"$record_line\" | _egrep_o \"\\\"id\\\": *[^,]*\" | _head_n 1 | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\n    _debug \"record_id\" \"$record_id\"\n    if [ -z \"$record_id\" ]; then\n      _err \"Can not get record id to remove.\"\n      return 1\n    fi\n    if ! _ws_rest DELETE \"/v1/user/self/zone/$_domain/record/$record_id\"; then\n      _err \"Delete record error.\"\n      return 1\n    fi\n    if [ \"$(printf \"%s\" \"$response\" | tr -d \" \" | grep -c \\\"success\\\")\" -lt \"1\" ]; then\n      return 1\n    else\n      return 0\n    fi\n  fi\n\n}\n\n####################  Private Functions ##################################\n\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _ws_rest GET \"/v1/user/self/zone\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"name\\\":\\\"$h\\\"\"; then\n      _domain_id=$(echo \"$response\" | _egrep_o \"\\[.\\\"id\\\": *[^,]*\" | _head_n 1 | cut -d : -f 2 | tr -d \\\" | tr -d \" \")\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_ws_rest() {\n  me=$1\n  pa=\"$2\"\n  da=\"$3\"\n\n  _debug2 api_key \"$WS_ApiKey\"\n  _debug2 api_secret \"$WS_ApiSecret\"\n\n  timestamp=$(_time)\n  datez=\"$(_utc_date | sed \"s/ /T/\" | sed \"s/$/+0000/\")\"\n  canonical_request=\"${me} ${pa} ${timestamp}\"\n  signature_hash=$(printf \"%s\" \"$canonical_request\" | _hmac sha1 \"$(printf \"%s\" \"$WS_ApiSecret\" | _hex_dump | tr -d \" \")\" hex)\n  basicauth=\"$(printf \"%s:%s\" \"$WS_ApiKey\" \"$signature_hash\" | _base64)\"\n\n  _debug2 method \"$me\"\n  _debug2 path \"$pa\"\n  _debug2 data \"$da\"\n  _debug2 timestamp \"$timestamp\"\n  _debug2 datez \"$datez\"\n  _debug2 canonical_request \"$canonical_request\"\n  _debug2 signature_hash \"$signature_hash\"\n  _debug2 basicauth \"$basicauth\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"Authorization: Basic ${basicauth}\"\n  export _H4=\"Date: ${datez}\"\n\n  _debug2 H1 \"$_H1\"\n  _debug2 H2 \"$_H2\"\n  _debug2 H3 \"$_H3\"\n  _debug2 H4 \"$_H4\"\n\n  if [ \"$me\" != \"GET\" ]; then\n    _debug2 \"${me} $WS_Api${pa}\"\n    _debug data \"$da\"\n    response=\"$(_post \"$da\" \"${WS_Api}${pa}\" \"\" \"$me\")\"\n  else\n    _debug2 \"GET $WS_Api${pa}\"\n    response=\"$(_get \"$WS_Api${pa}\")\"\n  fi\n\n  _debug2 response \"$response\"\n  return \"$?\"\n}\n\n_get_from_array() {\n  va=\"$1\"\n  fi=\"$2\"\n  for i in $(echo \"$va\" | sed \"s/{/ /g\"); do\n    if _contains \"$i\" \"$fi\"; then\n      echo \"$i\"\n      break\n    fi\n  done\n}\n"
  },
  {
    "path": "dnsapi/dns_west_cn.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_west_cn_info='West.cn\nSite: West.cn\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_west_cn\nOptions:\n WEST_Username API username\n WEST_Key API Key. Set at https://www.west.cn/manager/API/APIconfig.asp\nIssues: github.com/acmesh-official/acme.sh/issues/4894\n'\n\nREST_API=\"https://api.west.cn/API/v2\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_west_cn_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  WEST_Username=\"${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}\"\n  WEST_Key=\"${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}\"\n  if [ -z \"$WEST_Username\" ] || [ -z \"$WEST_Key\" ]; then\n    WEST_Username=\"\"\n    WEST_Key=\"\"\n    _err \"You don't specify west api key and username yet.\"\n    _err \"Please set you key and try again.\"\n    return 1\n  fi\n\n  #save the api key and email to the account conf file.\n  _saveaccountconf_mutable WEST_Username \"$WEST_Username\"\n  _saveaccountconf_mutable WEST_Key \"$WEST_Key\"\n\n  add_record \"$fulldomain\" \"$txtvalue\"\n}\n\n#Usage: rm _acme-challenge.www.domain.com\ndns_west_cn_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  WEST_Username=\"${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}\"\n  WEST_Key=\"${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}\"\n\n  if ! _rest POST \"domain/dns/\" \"act=dnsrec.list&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT\"; then\n    _err \"dnsrec.list error.\"\n    return 1\n  fi\n\n  if _contains \"$response\" 'no records'; then\n    _info \"Don't need to remove.\"\n    return 0\n  fi\n\n  record_id=$(echo \"$response\" | tr \"{\" \"\\n\" | grep -- \"$txtvalue\" | grep '^\"record_id\"' | cut -d : -f 2 | cut -d ',' -f 1)\n  _debug record_id \"$record_id\"\n  if [ -z \"$record_id\" ]; then\n    _err \"Can not get record id.\"\n    return 1\n  fi\n\n  if ! _rest POST \"domain/dns/\" \"act=dnsrec.remove&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_id=$record_id\"; then\n    _err \"dnsrec.remove error.\"\n    return 1\n  fi\n\n  _contains \"$response\" \"success\"\n}\n\n#add the txt record.\n#usage: add fulldomain txtvalue\nadd_record() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Adding record\"\n\n  if ! _rest POST \"domain/dns/\" \"act=dnsrec.add&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT&record_value=$txtvalue\"; then\n    return 1\n  fi\n\n  _contains \"$response\" \"success\"\n}\n\n#Usage: method  URI  data\n_rest() {\n  m=\"$1\"\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n  url=\"$REST_API/$ep\"\n\n  _debug url \"$url\"\n\n  if [ \"$m\" = \"GET\" ]; then\n    response=\"$(_get \"$url\" | tr -d '\\r')\"\n  else\n    _debug2 data \"$data\"\n    response=\"$(_post \"$data\" \"$url\" | tr -d '\\r')\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_world4you.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_world4you_info='World4You.com\nSite: World4You.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_world4you\nOptions:\n WORLD4YOU_USERNAME Username\n WORLD4YOU_PASSWORD Password\nIssues: github.com/acmesh-official/acme.sh/issues/3269\nAuthor: Lorenz Stechauner <@NerLOR>\n'\n\nWORLD4YOU_API=\"https://my.world4you.com/en\"\nPAKETNR=''\nTLD=''\nRECORD=''\n\n################ Public functions ################\n\n# Usage: dns_world4you_add <fqdn> <value>\ndns_world4you_add() {\n  fqdn=$(echo \"$1\" | _lower_case)\n  value=\"$2\"\n  _info \"Using world4you to add record\"\n  _debug fulldomain \"$fqdn\"\n  _debug txtvalue \"$value\"\n\n  _login\n  if [ \"$?\" != 0 ]; then\n    return 1\n  fi\n\n  export _H1=\"Cookie: W4YSESSID=$sessid\"\n  form=$(_get \"$WORLD4YOU_API/\")\n  _get_paketnr \"$fqdn\" \"$form\"\n  paketnr=\"$PAKETNR\"\n  if [ -z \"$paketnr\" ]; then\n    _err \"Unable to parse paketnr\"\n    return 3\n  fi\n  _debug paketnr \"$paketnr\"\n\n  export _H1=\"Cookie: W4YSESSID=$sessid\"\n  form=$(_get \"$WORLD4YOU_API/$paketnr/dns\")\n  formiddp=$(echo \"$form\" | grep 'AddDnsRecordForm\\[uniqueFormIdDP\\]' | sed 's/^.*name=\"AddDnsRecordForm\\[uniqueFormIdDP\\]\" value=\"\\([^\"]*\\)\".*$/\\1/')\n  form_token=$(echo \"$form\" | grep 'AddDnsRecordForm\\[_token\\]' | sed 's/^.*name=\"AddDnsRecordForm\\[_token\\]\" value=\"\\([^\"]*\\)\".*$/\\1/')\n  if [ -z \"$formiddp\" ]; then\n    _err \"Unable to parse form\"\n    return 3\n  fi\n\n  _resethttp\n  export ACME_HTTP_NO_REDIRECTS=1\n  body=\"AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&AddDnsRecordForm[value]=$value&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&AddDnsRecordForm[_token]=$form_token\"\n  _info \"Adding record...\"\n  ret=$(_post \"$body\" \"$WORLD4YOU_API/$paketnr/dns\" '' POST 'application/x-www-form-urlencoded')\n  _resethttp\n\n  if _contains \"$(_head_n 1 <\"$HTTP_HEADER\")\" '302'; then\n    res=$(_get \"$WORLD4YOU_API/$paketnr/dns\")\n    if _contains \"$res\" \"successfully\"; then\n      return 0\n    else\n      msg=$(echo \"$res\" | grep -A 15 'data-type=\"danger\"' | grep \"<h3[^>]*>[^<]\" | sed 's/<[^>]*>//g' | sed 's/^\\s*//g')\n      if [ \"$msg\" = '' ]; then\n        _err \"Unable to add record: Unknown error\"\n        echo \"$ret\" >'error-01.html'\n        echo \"$res\" >'error-02.html'\n        _err \"View error-01.html and error-02.html for debugging\"\n      else\n        _err \"Unable to add record: my.world4you.com: $msg\"\n      fi\n      return 1\n    fi\n  else\n    msg=$(echo \"$ret\" | grep '\"form-error-message\"' | sed 's/^.*<div class=\"form-error-message\">\\([^<]*\\)<\\/div>.*$/\\1/')\n    _err \"Unable to add record: my.world4you.com: $msg\"\n    return 1\n  fi\n}\n\n# Usage: dns_world4you_rm <fqdn> <value>\ndns_world4you_rm() {\n  fqdn=$(echo \"$1\" | _lower_case)\n  value=\"$2\"\n  _info \"Using world4you to remove record\"\n  _debug fulldomain \"$fqdn\"\n  _debug txtvalue \"$value\"\n\n  _login\n  if [ \"$?\" != 0 ]; then\n    return 1\n  fi\n\n  export _H1=\"Cookie: W4YSESSID=$sessid\"\n  form=$(_get \"$WORLD4YOU_API/\")\n  _get_paketnr \"$fqdn\" \"$form\"\n  paketnr=\"$PAKETNR\"\n  if [ -z \"$paketnr\" ]; then\n    _err \"Unable to parse paketnr\"\n    return 3\n  fi\n  _debug paketnr \"$paketnr\"\n\n  form=$(_get \"$WORLD4YOU_API/$paketnr/dns\")\n  formiddp=$(echo \"$form\" | grep 'DeleteDnsRecordForm\\[uniqueFormIdDP\\]' | sed 's/^.*name=\"DeleteDnsRecordForm\\[uniqueFormIdDP\\]\" value=\"\\([^\"]*\\)\".*$/\\1/')\n  form_token=$(echo \"$form\" | grep 'DeleteDnsRecordForm\\[_token\\]' | sed 's/^.*name=\"DeleteDnsRecordForm\\[_token\\]\" value=\"\\([^\"]*\\)\".*$/\\1/')\n  if [ -z \"$formiddp\" ]; then\n    _err \"Unable to parse form\"\n    return 3\n  fi\n\n  recordid=$(printf \"TXT:%s.:\\\"%s\\\"\" \"$fqdn\" \"$value\" | _base64)\n  _debug recordid \"$recordid\"\n\n  _resethttp\n  export ACME_HTTP_NO_REDIRECTS=1\n  body=\"DeleteDnsRecordForm[id]=$recordid&DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token\"\n  _info \"Removing record...\"\n  ret=$(_post \"$body\" \"$WORLD4YOU_API/$paketnr/dns/record/delete\" '' POST 'application/x-www-form-urlencoded')\n  _resethttp\n\n  if _contains \"$(_head_n 1 <\"$HTTP_HEADER\")\" '302'; then\n    res=$(_get \"$WORLD4YOU_API/$paketnr/dns\")\n    if _contains \"$res\" \"successfully\"; then\n      return 0\n    else\n      msg=$(echo \"$res\" | grep -A 15 'data-type=\"danger\"' | grep \"<h3[^>]*>[^<]\" | sed 's/<[^>]*>//g' | sed 's/^\\s*//g')\n      if [ \"$msg\" = '' ]; then\n        _err \"Unable to remove record: Unknown error\"\n        echo \"$ret\" >'error-01.html'\n        echo \"$res\" >'error-02.html'\n        _err \"View error-01.html and error-02.html for debugging\"\n      else\n        _err \"Unable to remove record: my.world4you.com: $msg\"\n      fi\n      return 1\n    fi\n  else\n    msg=$(echo \"$ret\" | grep \"form-error-message\" | sed 's/^.*<div class=\"form-error-message\">\\([^<]*\\)<\\/div>.*$/\\1/')\n    _err \"Unable to remove record: my.world4you.com: $msg\"\n    return 1\n  fi\n}\n\n################ Private functions ################\n\n# Usage: _login\n_login() {\n  WORLD4YOU_USERNAME=\"${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}\"\n  WORLD4YOU_PASSWORD=\"${WORLD4YOU_PASSWORD:-$(_readaccountconf_mutable WORLD4YOU_PASSWORD)}\"\n\n  if [ -z \"$WORLD4YOU_USERNAME\" ] || [ -z \"$WORLD4YOU_PASSWORD\" ]; then\n    WORLD4YOU_USERNAME=\"\"\n    WORLD4YOU_PASSWORD=\"\"\n    _err \"You didn't specify world4you username and password yet.\"\n    _err \"Usage: export WORLD4YOU_USERNAME=<name>\"\n    _err \"Usage: export WORLD4YOU_PASSWORD=<password>\"\n    return 1\n  fi\n\n  _saveaccountconf_mutable WORLD4YOU_USERNAME \"$WORLD4YOU_USERNAME\"\n  _saveaccountconf_mutable WORLD4YOU_PASSWORD \"$WORLD4YOU_PASSWORD\"\n\n  _resethttp\n  export ACME_HTTP_NO_REDIRECTS=1\n  page=$(_get \"$WORLD4YOU_API/login\")\n  _resethttp\n\n  if _contains \"$(_head_n 1 <\"$HTTP_HEADER\")\" '302'; then\n    _info \"Already logged in\"\n    _parse_sessid\n    return 0\n  fi\n\n  _info \"Logging in...\"\n\n  username=\"$WORLD4YOU_USERNAME\"\n  password=\"$WORLD4YOU_PASSWORD\"\n  csrf_token=$(echo \"$page\" | grep '_csrf_token' | sed 's/^.*<input[^>]*value=\\\"\\([^\"]*\\)\\\".*$/\\1/')\n  _parse_sessid\n\n  export _H1=\"Cookie: W4YSESSID=$sessid\"\n  export _H2=\"X-Requested-With: XMLHttpRequest\"\n  body=\"_username=$username&_password=$password&_csrf_token=$csrf_token\"\n  ret=$(_post \"$body\" \"$WORLD4YOU_API/login\" '' POST 'application/x-www-form-urlencoded')\n  unset _H2\n\n  _debug ret \"$ret\"\n  if _contains \"$ret\" \"\\\"success\\\":true\"; then\n    _info \"Successfully logged in\"\n    _parse_sessid\n  else\n    msg=$(echo \"$ret\" | sed 's/^.*\"message\":\"\\([^\\\"]*\\)\".*$/\\1/')\n    _err \"Unable to log in: my.world4you.com: $msg\"\n    return 1\n  fi\n}\n\n# Usage: _get_paketnr <fqdn> <form>\n_get_paketnr() {\n  fqdn=\"$1\"\n  form=\"$2\"\n\n  domains=$(echo \"$form\" | grep 'paketListData' | grep -o '\"fqdn\":\"[^\"]*\"' | sed 's/.*:\"\\(.*\\)\"/\\1/')\n  _debug domains \"$domains\"\n  domain=''\n  for domain in $domains; do\n    if _contains \"$fqdn\" \"$domain\\$\"; then\n      break\n    fi\n    domain=''\n  done\n  if [ -z \"$domain\" ]; then\n    return 1\n  fi\n\n  TLD=\"$domain\"\n  _debug domain \"$domain\"\n  RECORD=$(echo \"$fqdn\" | cut -c\"1-$((${#fqdn} - ${#TLD} - 1))\")\n  PAKETNR=$(echo \"$form\" | grep -o \"\\\"id\\\":[^{}]*\\\"fqdn\\\":\\\"$domain\\\"\" | sed 's/\"id\":\\([0-9]*\\).*$/\\1/')\n  return 0\n}\n\n# Usage: _parse_sessid\n_parse_sessid() {\n  sessid=$(grep 'W4YSESSID' <\"$HTTP_HEADER\" | _tail_n 1 | sed 's/^.*W4YSESSID=\\([^;]*\\);.*$/\\1/')\n}\n"
  },
  {
    "path": "dnsapi/dns_yandex360.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_yandex360_info='Yandex 360 for Business DNS API.\n Yandex 360 for Business is a digital environment for effective collaboration.\nSite: https://360.yandex.com/\nDocs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360\nOptions:\n YANDEX360_CLIENT_ID OAuth 2.0 ClientID\n YANDEX360_CLIENT_SECRET OAuth 2.0 Client secret\nOptionsAlt:\n YANDEX360_ORG_ID Organization ID. Optional.\n YANDEX360_ACCESS_TOKEN OAuth 2.0 Access token. Optional.\nIssues: https://github.com/acmesh-official/acme.sh/issues/5213\nAuthor: <Als@admin.ru.net>\n'\n\nYANDEX360_API_BASE='https://api360.yandex.net/directory/v1'\nYANDEX360_OAUTH_BASE='https://oauth.yandex.ru'\n\n########  Public functions #####################\n\ndns_yandex360_add() {\n  fulldomain=\"$(_idn \"$1\")\"\n  txtvalue=$2\n  _info 'Using Yandex 360 DNS API'\n\n  if ! _check_variables; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  sub_domain=$(echo \"$fulldomain\" | sed \"s/\\.$root_domain$//\")\n\n  _debug 'Adding Yandex 360 DNS record for subdomain' \"$sub_domain\"\n  dns_api_url=\"${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns\"\n  data='{\"name\":\"'\"$sub_domain\"'\",\"type\":\"TXT\",\"ttl\":60,\"text\":\"'\"$txtvalue\"'\"}'\n\n  response=\"$(_post \"$data\" \"$dns_api_url\" '' 'POST' 'application/json')\"\n\n  if _contains \"$response\" 'recordId'; then\n    return 0\n  else\n    _debug 'Response' \"$response\"\n    return 1\n  fi\n}\n\ndns_yandex360_rm() {\n  fulldomain=\"$(_idn \"$1\")\"\n  txtvalue=$2\n  _info 'Using Yandex 360 DNS API'\n\n  if ! _check_variables; then\n    return 1\n  fi\n\n  if ! _get_root \"$fulldomain\"; then\n    return 1\n  fi\n\n  _debug 'Retrieving 100 records from Yandex 360 DNS'\n  dns_api_url=\"${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns?perPage=100\"\n  response=\"$(_get \"$dns_api_url\" '' '')\"\n\n  if ! _contains \"$response\" \"$txtvalue\"; then\n    _info 'DNS record not found. Nothing to remove.'\n    _debug 'Response' \"$response\"\n    return 1\n  fi\n\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n\n  record_id=$(\n    echo \"$response\" |\n      _egrep_o '\\{[^}]*'\"${txtvalue}\"'[^}]*\\}' |\n      _egrep_o '\"recordId\":[0-9]*' |\n      cut -d':' -f2\n  )\n\n  if [ -z \"$record_id\" ]; then\n    _err 'Unable to get record ID to remove'\n    return 1\n  fi\n\n  _debug 'Removing DNS record' \"$record_id\"\n  delete_url=\"${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns/${record_id}\"\n\n  response=\"$(_post '' \"$delete_url\" '' 'DELETE')\"\n\n  if _contains \"$response\" '{}'; then\n    return 0\n  else\n    _debug 'Response' \"$response\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n_check_variables() {\n  YANDEX360_CLIENT_ID=\"${YANDEX360_CLIENT_ID:-$(_readaccountconf_mutable YANDEX360_CLIENT_ID)}\"\n  YANDEX360_CLIENT_SECRET=\"${YANDEX360_CLIENT_SECRET:-$(_readaccountconf_mutable YANDEX360_CLIENT_SECRET)}\"\n  YANDEX360_ORG_ID=\"${YANDEX360_ORG_ID:-$(_readaccountconf_mutable YANDEX360_ORG_ID)}\"\n  YANDEX360_ACCESS_TOKEN=\"${YANDEX360_ACCESS_TOKEN:-$(_readaccountconf_mutable YANDEX360_ACCESS_TOKEN)}\"\n  YANDEX360_REFRESH_TOKEN=\"${YANDEX360_REFRESH_TOKEN:-$(_readaccountconf_mutable YANDEX360_REFRESH_TOKEN)}\"\n\n  if [ -n \"$YANDEX360_ACCESS_TOKEN\" ]; then\n    _info '========================================='\n    _info '              ATTENTION'\n    _info '========================================='\n    _info 'A manually provided Yandex 360 access token has been detected, which is not recommended.'\n    _info 'Please note that this token is valid for a limited time after issuance.'\n    _info 'It is recommended to obtain the token interactively using acme.sh for one-time setup.'\n    _info 'Subsequent token renewals will be handled automatically.'\n    _info 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'\n    _info '========================================='\n\n    _saveaccountconf_mutable YANDEX360_ACCESS_TOKEN \"$YANDEX360_ACCESS_TOKEN\"\n    export _H1=\"Authorization: OAuth $YANDEX360_ACCESS_TOKEN\"\n\n  elif [ -z \"$YANDEX360_CLIENT_ID\" ] || [ -z \"$YANDEX360_CLIENT_SECRET\" ]; then\n    _err '========================================='\n    _err '                 ERROR'\n    _err '========================================='\n    _err 'The required environment variables YANDEX360_CLIENT_ID and YANDEX360_CLIENT_SECRET are not set.'\n    _err 'Alternatively, you can set YANDEX360_ACCESS_TOKEN environment variable.'\n    _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'\n    _err '========================================='\n    return 1\n\n  else\n    _saveaccountconf_mutable YANDEX360_CLIENT_ID \"$YANDEX360_CLIENT_ID\"\n    _saveaccountconf_mutable YANDEX360_CLIENT_SECRET \"$YANDEX360_CLIENT_SECRET\"\n\n    if [ -n \"$YANDEX360_REFRESH_TOKEN\" ]; then\n      _debug 'Refresh token found. Attempting to refresh access token.'\n    fi\n\n    _refresh_token || _get_token || return 1\n  fi\n\n  if [ -z \"$YANDEX360_ORG_ID\" ]; then\n    org_response=\"$(_get \"${YANDEX360_API_BASE}/org\" '' '')\"\n\n    if _contains \"$org_response\" '\"organizations\"'; then\n      org_response=\"$(echo \"$org_response\" | _normalizeJson)\"\n      YANDEX360_ORG_ID=$(\n        echo \"$org_response\" |\n          _egrep_o '\"id\":[[:space:]]*[0-9]+' |\n          cut -d':' -f2\n      )\n      _debug 'Automatically retrieved YANDEX360_ORG_ID' \"$YANDEX360_ORG_ID\"\n    else\n      _err '========================================='\n      _err '                 ERROR'\n      _err '========================================='\n      _err \"Failed to retrieve YANDEX360_ORG_ID automatically.\"\n      _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'\n      _err '========================================='\n      _debug 'Response' \"$org_response\"\n      return 1\n    fi\n  fi\n\n  return 0\n}\n\n_get_token() {\n  _info \"$(__red '=========================================')\"\n  _info \"$(__red '                 NOTICE')\"\n  _info \"$(__red '=========================================')\"\n  _info \"$(__red 'Before using the Yandex 360 API, you need to complete an authorization procedure.')\"\n  _info \"$(__red 'The initial access token is obtained interactively and is a one-time operation.')\"\n  _info \"$(__red 'Subsequent API requests will be handled automatically.')\"\n  _info \"$(__red '=========================================')\"\n\n  _info 'Initiating device authorization flow'\n  device_code_url=\"${YANDEX360_OAUTH_BASE}/device/code\"\n\n  hostname=$(uname -n)\n  data=\"client_id=$YANDEX360_CLIENT_ID&device_id=acme.sh ${hostname}&device_name=acme.sh ${hostname}\"\n\n  response=\"$(_post \"$data\" \"$device_code_url\" '' 'POST')\"\n\n  if ! _contains \"$response\" 'device_code'; then\n    _err 'Failed to get device code'\n    _debug 'Response' \"$response\"\n    return 1\n  fi\n\n  response=\"$(echo \"$response\" | _normalizeJson)\"\n\n  device_code=$(\n    echo \"$response\" |\n      _egrep_o '\"device_code\":\"[^\"]*\"' |\n      cut -d'\"' -f4\n  )\n  _debug 'Device code' \"$device_code\"\n\n  user_code=$(\n    echo \"$response\" |\n      _egrep_o '\"user_code\":\"[^\"]*\"' |\n      cut -d'\"' -f4\n  )\n  _debug 'User code' \"$user_code\"\n\n  verification_url=$(\n    echo \"$response\" |\n      _egrep_o '\"verification_url\":\"[^\"]*\"' |\n      cut -d'\"' -f4\n  )\n  _debug 'Verification URL' \"$verification_url\"\n\n  interval=$(\n    echo \"$response\" |\n      _egrep_o '\"interval\":[[:space:]]*[0-9]+' |\n      cut -d':' -f2\n  )\n  _debug 'Polling interval' \"$interval\"\n\n  _info \"$(__red 'Please visit '\"$verification_url\"' and log in as an organization administrator')\"\n  _info \"$(__red 'Once logged in, enter the code: '\"$user_code\"' on the page from the previous step')\"\n  _info \"$(__red 'Waiting for authorization...')\"\n\n  _debug 'Polling for token'\n  token_url=\"${YANDEX360_OAUTH_BASE}/token\"\n\n  while true; do\n    data=\"grant_type=device_code&code=$device_code&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET\"\n\n    response=\"$(_post \"$data\" \"$token_url\" '' 'POST')\"\n\n    if _contains \"$response\" 'access_token'; then\n      response=\"$(echo \"$response\" | _normalizeJson)\"\n      YANDEX360_ACCESS_TOKEN=$(\n        echo \"$response\" |\n          _egrep_o '\"access_token\":\"[^\"]*\"' |\n          cut -d'\"' -f4\n      )\n      YANDEX360_REFRESH_TOKEN=$(\n        echo \"$response\" |\n          _egrep_o '\"refresh_token\":\"[^\"]*\"' |\n          cut -d'\"' -f4\n      )\n\n      _secure_debug 'Obtained access token' \"$YANDEX360_ACCESS_TOKEN\"\n      _secure_debug 'Obtained refresh token' \"$YANDEX360_REFRESH_TOKEN\"\n\n      _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN \"$YANDEX360_REFRESH_TOKEN\"\n\n      export _H1=\"Authorization: OAuth $YANDEX360_ACCESS_TOKEN\"\n\n      _info 'Access token obtained successfully'\n      return 0\n    elif _contains \"$response\" 'authorization_pending'; then\n      _debug 'Response' \"$response\"\n      _debug \"Authorization pending. Waiting $interval seconds before next attempt.\"\n      _sleep \"$interval\"\n    else\n      _debug 'Response' \"$response\"\n      _err 'Failed to get access token'\n      return 1\n    fi\n  done\n}\n\n_refresh_token() {\n  token_url=\"${YANDEX360_OAUTH_BASE}/token\"\n\n  data=\"grant_type=refresh_token&refresh_token=$YANDEX360_REFRESH_TOKEN&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET\"\n\n  response=\"$(_post \"$data\" \"$token_url\" '' 'POST')\"\n\n  if _contains \"$response\" 'access_token'; then\n    response=\"$(echo \"$response\" | _normalizeJson)\"\n    YANDEX360_ACCESS_TOKEN=$(\n      echo \"$response\" |\n        _egrep_o '\"access_token\":\"[^\"]*\"' |\n        cut -d'\"' -f4\n    )\n    YANDEX360_REFRESH_TOKEN=$(\n      echo \"$response\" |\n        _egrep_o '\"refresh_token\":\"[^\"]*\"' |\n        cut -d'\"' -f4\n    )\n\n    _secure_debug 'Received access token' \"$YANDEX360_ACCESS_TOKEN\"\n    _secure_debug 'Received refresh token' \"$YANDEX360_REFRESH_TOKEN\"\n\n    _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN \"$YANDEX360_REFRESH_TOKEN\"\n\n    export _H1=\"Authorization: OAuth $YANDEX360_ACCESS_TOKEN\"\n\n    _info 'Access token refreshed successfully'\n    return 0\n  else\n    _info 'Failed to refresh token. Will attempt to obtain a new one.'\n    _debug 'Response' \"$response\"\n    return 1\n  fi\n}\n\n_get_root() {\n  domain=\"$1\"\n\n  for org_id in $YANDEX360_ORG_ID; do\n    _debug 'Checking organization ID' \"$org_id\"\n    domains_api_url=\"${YANDEX360_API_BASE}/org/${org_id}/domains\"\n\n    domains_response=\"$(_get \"$domains_api_url\" '' '')\"\n\n    if ! _contains \"$domains_response\" '\"domains\"'; then\n      _debug 'No domains found for organization' \"$org_id\"\n      _debug 'Response' \"$domains_response\"\n      continue\n    fi\n\n    domains_response=\"$(echo \"$domains_response\" | _normalizeJson)\"\n    domain_names=$(\n      echo \"$domains_response\" |\n        _egrep_o '\"name\":\"[^\"]*\"' |\n        cut -d'\"' -f4\n    )\n\n    for d in $domain_names; do\n      d=\"$(_idn \"$d\")\"\n      _debug 'Checking domain' \"$d\"\n\n      if _endswith \"$domain\" \"$d\"; then\n        root_domain=\"$d\"\n        break\n      fi\n    done\n\n    if [ -n \"$root_domain\" ]; then\n      _debug \"Root domain found: $root_domain in organization $org_id\"\n\n      YANDEX360_ORG_ID=\"$org_id\"\n      _saveaccountconf_mutable YANDEX360_ORG_ID \"$YANDEX360_ORG_ID\"\n\n      return 0\n    fi\n  done\n\n  if [ -z \"$root_domain\" ]; then\n    _err \"Could not find a matching root domain for $domain in any organization\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "dnsapi/dns_yc.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_yc_info='Yandex Cloud DNS\nSite: Cloud.Yandex.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yc\nOptions:\n YC_Zone_ID DNS Zone ID\n YC_Folder_ID YC Folder ID\n YC_SA_ID Service Account ID\n YC_SA_Key_ID Service Account IAM Key ID\n YC_SA_Key_File_Path Private key file path. Optional.\n YC_SA_Key_File_PEM_b64 Base64 content of private key file. Use instead of Path to private key file. Optional.\nIssues: github.com/acmesh-official/acme.sh/issues/4210\n'\n\nYC_Api=\"https://dns.api.cloud.yandex.net/dns/v1\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_yc_add() {\n  fulldomain=\"$(echo \"$1\". | _lower_case)\" # Add dot at end of domain name\n  txtvalue=$2\n\n  YC_SA_Key_File_PEM_b64=\"${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}\"\n  YC_SA_Key_File_Path=\"${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}\"\n\n  if [ \"$YC_SA_Key_File_PEM_b64\" ]; then\n    echo \"$YC_SA_Key_File_PEM_b64\" | _dbase64 >private.key\n    YC_SA_Key_File=\"private.key\"\n    _savedomainconf YC_SA_Key_File_PEM_b64 \"$YC_SA_Key_File_PEM_b64\"\n  else\n    YC_SA_Key_File=\"$YC_SA_Key_File_Path\"\n    _savedomainconf YC_SA_Key_File_Path \"$YC_SA_Key_File_Path\"\n  fi\n\n  YC_Zone_ID=\"${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}\"\n  YC_Folder_ID=\"${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}\"\n  YC_SA_ID=\"${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}\"\n  YC_SA_Key_ID=\"${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}\"\n\n  if [ \"$YC_SA_ID\" ] && [ \"$YC_SA_Key_ID\" ] && [ \"$YC_SA_Key_File\" ]; then\n    if [ -f \"$YC_SA_Key_File\" ]; then\n      if _isRSA \"$YC_SA_Key_File\" >/dev/null 2>&1; then\n        if [ \"$YC_Zone_ID\" ]; then\n          _savedomainconf YC_Zone_ID \"$YC_Zone_ID\"\n          _savedomainconf YC_SA_ID \"$YC_SA_ID\"\n          _savedomainconf YC_SA_Key_ID \"$YC_SA_Key_ID\"\n        elif [ \"$YC_Folder_ID\" ]; then\n          _savedomainconf YC_Folder_ID \"$YC_Folder_ID\"\n          _saveaccountconf_mutable YC_SA_ID \"$YC_SA_ID\"\n          _saveaccountconf_mutable YC_SA_Key_ID \"$YC_SA_Key_ID\"\n          _clearaccountconf_mutable YC_Zone_ID\n          _clearaccountconf YC_Zone_ID\n        else\n          _err \"You didn't specify a Yandex Cloud Zone ID or Folder ID yet.\"\n          return 1\n        fi\n      else\n        _err \"YC_SA_Key_File not a RSA file(_isRSA function return false).\"\n        return 1\n      fi\n    else\n      _err \"YC_SA_Key_File not found in path $YC_SA_Key_File.\"\n      return 1\n    fi\n  else\n    _clearaccountconf YC_Zone_ID\n    _clearaccountconf YC_Folder_ID\n    _clearaccountconf YC_SA_ID\n    _clearaccountconf YC_SA_Key_ID\n    _clearaccountconf YC_SA_Key_File_PEM_b64\n    _clearaccountconf YC_SA_Key_File_Path\n    _err \"You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File.\"\n    return 1\n  fi\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  if ! _yc_rest GET \"zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain\"; then\n    _err \"Error: $response\"\n    return 1\n  fi\n\n  _info \"Adding record\"\n  if _yc_rest POST \"zones/$_domain_id:upsertRecordSets\" \"{\\\"merges\\\": [ { \\\"name\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":\\\"120\\\",\\\"data\\\":[\\\"$txtvalue\\\"]}]}\"; then\n    if _contains \"$response\" \"\\\"done\\\": true\"; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Add txt record error.\"\n      return 1\n    fi\n  fi\n  _err \"Add txt record error.\"\n  return 1\n\n}\n\n#fulldomain txtvalue\ndns_yc_rm() {\n  fulldomain=\"$(echo \"$1\". | _lower_case)\" # Add dot at end of domain name\n  txtvalue=$2\n\n  YC_Zone_ID=\"${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}\"\n  YC_Folder_ID=\"${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}\"\n  YC_SA_ID=\"${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}\"\n  YC_SA_Key_ID=\"${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}\"\n\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n  _debug _domain_id \"$_domain_id\"\n  _debug _sub_domain \"$_sub_domain\"\n  _debug _domain \"$_domain\"\n\n  _debug \"Getting txt records\"\n  if _yc_rest GET \"zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain\"; then\n    exists_txtvalue=$(echo \"$response\" | _normalizeJson | _egrep_o \"\\\"data\\\".*\\][^,]*\" | _egrep_o \"[^:]*$\")\n    _debug exists_txtvalue \"$exists_txtvalue\"\n  else\n    _err \"Error: $response\"\n    return 1\n  fi\n\n  if _yc_rest POST \"zones/$_domain_id:updateRecordSets\" \"{\\\"deletions\\\": [ { \\\"name\\\":\\\"$_sub_domain\\\",\\\"type\\\":\\\"TXT\\\",\\\"ttl\\\":\\\"120\\\",\\\"data\\\":$exists_txtvalue}]}\"; then\n    if _contains \"$response\" \"\\\"done\\\": true\"; then\n      _info \"Delete, OK\"\n      return 0\n    else\n      _err \"Delete record error.\"\n      return 1\n    fi\n  fi\n  _err \"Delete record error.\"\n  return 1\n}\n\n####################  Private functions below ##################################\n#_acme-challenge.www.domain.com\n#returns\n# _sub_domain=_acme-challenge.www\n# _domain=domain.com\n# _domain_id=sdjkglgdfewsdfg\n_get_root() {\n  domain=$1\n  i=1\n  p=1\n\n  # Use Zone ID directly if provided\n  if [ \"$YC_Zone_ID\" ]; then\n    if ! _yc_rest GET \"zones/$YC_Zone_ID\"; then\n      return 1\n    else\n      if echo \"$response\" | tr -d \" \" | _egrep_o \"\\\"id\\\":\\\"$YC_Zone_ID\\\"\" >/dev/null; then\n        _domain=$(echo \"$response\" | _egrep_o \"\\\"zone\\\": *\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | _head_n 1 | tr -d \" \")\n        if [ \"$_domain\" ]; then\n          _cutlength=$((${#domain} - ${#_domain}))\n          _sub_domain=$(printf \"%s\" \"$domain\" | cut -c \"1-$_cutlength\")\n          _domain_id=$YC_Zone_ID\n          return 0\n        else\n          return 1\n        fi\n      else\n        return 1\n      fi\n    fi\n  fi\n\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n    if [ \"$YC_Folder_ID\" ]; then\n      if ! _yc_rest GET \"zones?folderId=$YC_Folder_ID\"; then\n        return 1\n      fi\n    else\n      echo \"You didn't specify a Yandex Cloud Folder ID.\"\n      return 1\n    fi\n    if _contains \"$response\" \"\\\"zone\\\": \\\"$h\\\"\"; then\n      _domain_id=$(echo \"$response\" | _normalizeJson | _egrep_o \"[^{]*\\\"zone\\\":\\\"$h\\\"[^}]*\" | _egrep_o \"\\\"id\\\"[^,]*\" | _egrep_o \"[^:]*$\" | tr -d '\"')\n      _debug _domain_id \"$_domain_id\"\n      if [ \"$_domain_id\" ]; then\n        _sub_domain=$(printf \"%s\" \"$domain\" | cut -d . -f 1-\"$p\")\n        _domain=$h\n        return 0\n      fi\n      return 1\n    fi\n    p=$i\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_yc_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  if [ ! \"$YC_Token\" ]; then\n    _debug \"Login\"\n    _yc_login\n  else\n    _debug \"Token already exists. Skip Login.\"\n  fi\n\n  token_trimmed=$(echo \"$YC_Token\" | tr -d '\"')\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Authorization: Bearer $token_trimmed\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$YC_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$YC_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_yc_login() {\n  header=$(echo \"{\\\"typ\\\":\\\"JWT\\\",\\\"alg\\\":\\\"PS256\\\",\\\"kid\\\":\\\"$YC_SA_Key_ID\\\"}\" | _normalizeJson | _base64 | _url_replace)\n  _debug header \"$header\"\n\n  _current_timestamp=$(_time)\n  _expire_timestamp=$(_math \"$_current_timestamp\" + 1200) # 20 minutes\n  payload=$(echo \"{\\\"iss\\\":\\\"$YC_SA_ID\\\",\\\"aud\\\":\\\"https://iam.api.cloud.yandex.net/iam/v1/tokens\\\",\\\"iat\\\":$_current_timestamp,\\\"exp\\\":$_expire_timestamp}\" | _normalizeJson | _base64 | _url_replace)\n  _debug payload \"$payload\"\n\n  #signature=$(printf \"%s.%s\" \"$header\" \"$payload\" | ${ACME_OPENSSL_BIN:-openssl} dgst -sign \"$YC_SA_Key_File -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1\" | _base64 | _url_replace )\n  _signature=$(printf \"%s.%s\" \"$header\" \"$payload\" | _sign \"$YC_SA_Key_File\" \"sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1\" | _url_replace)\n  _debug2 _signature \"$_signature\"\n\n  rm -rf \"$YC_SA_Key_File\"\n\n  _jwt=$(printf \"{\\\"jwt\\\": \\\"%s.%s.%s\\\"}\" \"$header\" \"$payload\" \"$_signature\")\n  _debug2 _jwt \"$_jwt\"\n\n  export _H1=\"Content-Type: application/json\"\n  _iam_response=\"$(_post \"$_jwt\" \"https://iam.api.cloud.yandex.net/iam/v1/tokens\" \"\" \"POST\")\"\n  _debug3 _iam_response \"$(echo \"$_iam_response\" | _normalizeJson)\"\n\n  YC_Token=\"$(echo \"$_iam_response\" | _normalizeJson | _egrep_o \"\\\"iamToken\\\"[^,]*\" | _egrep_o \"[^:]*$\" | tr -d '\"')\"\n  _debug3 YC_Token\n\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_zilore.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_zilore_info='Zilore.com\nSite: Zilore.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_zilore\nOptions:\n Zilore_Key API Key\n'\n\nZilore_API=\"https://api.zilore.com/dns/v1\"\n\n########  Public functions #####################\n\ndns_zilore_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using Zilore\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  Zilore_Key=\"${Zilore_Key:-$(_readaccountconf_mutable Zilore_Key)}\"\n  if [ -z \"$Zilore_Key\" ]; then\n    Zilore_Key=\"\"\n    _err \"Please define Zilore API key\"\n    return 1\n  fi\n  _saveaccountconf_mutable Zilore_Key \"$Zilore_Key\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Unable to determine root domain\"\n    return 1\n  else\n    _debug _domain \"$_domain\"\n  fi\n\n  if _zilore_rest POST \"domains/$_domain/records?record_type=TXT&record_ttl=600&record_name=$fulldomain&record_value=\\\"$txtvalue\\\"\"; then\n    if _contains \"$response\" '\"added\"' >/dev/null; then\n      _info \"Added TXT record, waiting for validation\"\n      return 0\n    else\n      _debug response \"$response\"\n      _err \"Error while adding DNS records\"\n      return 1\n    fi\n  fi\n\n  return 1\n}\n\ndns_zilore_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  _info \"Using Zilore\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  Zilore_Key=\"${Zilore_Key:-$(_readaccountconf_mutable Zilore_Key)}\"\n  if [ -z \"$Zilore_Key\" ]; then\n    Zilore_Key=\"\"\n    _err \"Please define Zilore API key\"\n    return 1\n  fi\n  _saveaccountconf_mutable Zilore_Key \"$Zilore_Key\"\n\n  if ! _get_root \"$fulldomain\"; then\n    _err \"Unable to determine root domain\"\n    return 1\n  else\n    _debug _domain \"$_domain\"\n  fi\n\n  _debug \"Getting TXT records\"\n  _zilore_rest GET \"domains/${_domain}/records?search_text=$txtvalue&search_record_type=TXT\"\n  _debug response \"$response\"\n\n  if ! _contains \"$response\" '\"ok\"' >/dev/null; then\n    _err \"Error while getting records list\"\n    return 1\n  else\n    _record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"record_id\\\":\\\"[^\\\"]+\\\"\" | cut -d : -f 2 | tr -d \\\" | _head_n 1)\n    if [ -z \"$_record_id\" ]; then\n      _err \"Cannot determine _record_id\"\n      return 1\n    else\n      _debug _record_id \"$_record_id\"\n    fi\n    if ! _zilore_rest DELETE \"domains/${_domain}/records?record_id=$_record_id\"; then\n      _err \"Error while deleting chosen record\"\n      return 1\n    fi\n    _contains \"$response\" '\"ok\"'\n  fi\n}\n\n####################  Private functions below ##################################\n\n_get_root() {\n  domain=$1\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      #not valid\n      return 1\n    fi\n\n    if ! _zilore_rest GET \"domains?search_text=$h\"; then\n      return 1\n    fi\n\n    if _contains \"$response\" \"\\\"$h\\\"\" >/dev/null; then\n      _domain=$h\n      return 0\n    else\n      _debug \"$h not found\"\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n  return 1\n}\n\n_zilore_rest() {\n  method=$1\n  param=$2\n  data=$3\n\n  export _H1=\"X-Auth-Key: $Zilore_Key\"\n\n  if [ \"$method\" != \"GET\" ]; then\n    response=\"$(_post \"$data\" \"$Zilore_API/$param\" \"\" \"$method\")\"\n  else\n    response=\"$(_get \"$Zilore_API/$param\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $param\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_zone.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_zone_info='Zone.eu\nSite: Zone.eu\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_zone\nOptions:\n ZONE_Username Username\n ZONE_Key API Key\nIssues: github.com/acmesh-official/acme.sh/issues/2146\n'\n\n# Zone.ee dns API\n# https://help.zone.eu/kb/zoneid-api-v2/\n\nZONE_Api=\"https://api.zone.eu/v2\"\n########  Public functions #####################\n\n#Usage: dns_zone_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_zone_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using zone.ee dns api\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  ZONE_Username=\"${ZONE_Username:-$(_readaccountconf_mutable ZONE_Username)}\"\n  ZONE_Key=\"${ZONE_Key:-$(_readaccountconf_mutable ZONE_Key)}\"\n  if [ -z \"$ZONE_Username\" ] || [ -z \"$ZONE_Key\" ]; then\n    ZONE_Username=\"\"\n    ZONE_Key=\"\"\n    _err \"Zone api key and username must be present.\"\n    return 1\n  fi\n  _saveaccountconf_mutable ZONE_Username \"$ZONE_Username\"\n  _saveaccountconf_mutable ZONE_Key \"$ZONE_Key\"\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug \"Adding txt record\"\n\n  if _zone_rest POST \"dns/${_domain}/txt\" \"{\\\"name\\\": \\\"$fulldomain\\\", \\\"destination\\\": \\\"$txtvalue\\\"}\"; then\n    if printf -- \"%s\" \"$response\" | grep \"$fulldomain\" >/dev/null; then\n      _info \"Added, OK\"\n      return 0\n    else\n      _err \"Adding txt record error.\"\n      return 1\n    fi\n  else\n    _err \"Adding txt record error.\"\n  fi\n}\n\n#Usage: fulldomain txtvalue\n#Remove the txt record after validation.\ndns_zone_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using zone.ee dns api\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n  ZONE_Username=\"${ZONE_Username:-$(_readaccountconf_mutable ZONE_Username)}\"\n  ZONE_Key=\"${ZONE_Key:-$(_readaccountconf_mutable ZONE_Key)}\"\n  if [ -z \"$ZONE_Username\" ] || [ -z \"$ZONE_Key\" ]; then\n    ZONE_Username=\"\"\n    ZONE_Key=\"\"\n    _err \"Zone api key and username must be present.\"\n    return 1\n  fi\n  _saveaccountconf_mutable ZONE_Username \"$ZONE_Username\"\n  _saveaccountconf_mutable ZONE_Key \"$ZONE_Key\"\n  _debug \"First detect the root zone\"\n  if ! _get_root \"$fulldomain\"; then\n    _err \"invalid domain\"\n    return 1\n  fi\n\n  _debug \"Getting txt records\"\n  _debug _domain \"$_domain\"\n\n  _zone_rest GET \"dns/${_domain}/txt\"\n\n  if printf \"%s\" \"$response\" | grep \\\"error\\\" >/dev/null; then\n    _err \"Error\"\n    return 1\n  fi\n\n  count=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"name\\\":\\\"$fulldomain\\\"\" | wc -l)\n  _debug count \"$count\"\n  if [ \"$count\" = \"0\" ]; then\n    _info \"Nothing to remove.\"\n  else\n    record_id=$(printf \"%s\\n\" \"$response\" | _egrep_o \"\\\"id\\\":\\\"[^\\\"]*\\\",\\\"resource_url\\\":\\\"[^\\\"]*\\\",\\\"name\\\":\\\"$fulldomain\\\",\" | cut -d : -f2 | cut -d , -f1 | tr -d \\\" | _head_n 1)\n    if [ -z \"$record_id\" ]; then\n      _err \"No id found to remove.\"\n      return 1\n    fi\n    if ! _zone_rest DELETE \"dns/${_domain}/txt/$record_id\"; then\n      _err \"Record deleting error.\"\n      return 1\n    fi\n    _info \"Record deleted\"\n    return 0\n  fi\n\n}\n\n####################  Private functions below ##################################\n\n_zone_rest() {\n  m=$1\n  ep=\"$2\"\n  data=\"$3\"\n  _debug \"$ep\"\n\n  realm=\"$(printf \"%s\" \"$ZONE_Username:$ZONE_Key\" | _base64)\"\n\n  export _H1=\"Authorization: Basic $realm\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$m\" != \"GET\" ]; then\n    _debug data \"$data\"\n    response=\"$(_post \"$data\" \"$ZONE_Api/$ep\" \"\" \"$m\")\"\n  else\n    response=\"$(_get \"$ZONE_Api/$ep\")\"\n  fi\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"error $ep\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n}\n\n_get_root() {\n  domain=$1\n  i=1\n  while true; do\n    h=$(printf \"%s\" \"$domain\" | cut -d . -f \"$i\"-100)\n    _debug h \"$h\"\n    if [ -z \"$h\" ]; then\n      return 1\n    fi\n    if ! _zone_rest GET \"dns/$h\"; then\n      return 1\n    fi\n    if _contains \"$response\" \"\\\"identificator\\\":\\\"$h\\\"\" >/dev/null; then\n      _domain=$h\n      return 0\n    fi\n    i=$(_math \"$i\" + 1)\n  done\n  return 0\n}\n"
  },
  {
    "path": "dnsapi/dns_zoneedit.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_zoneedit_info='ZoneEdit.com\nSite: ZoneEdit.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_zoneedit\nOptions:\n ZONEEDIT_ID ID\n ZONEEDIT_Token API Token\nIssues: github.com/acmesh-official/acme.sh/issues/6135\n'\n\n# https://github.com/blueslow/sslcertzoneedit\n\n########  Public functions #####################\n\n# Usage: dns_zoneedit_add   _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_zoneedit_add() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using ZoneEdit\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  # Load the credentials from the account conf file\n  ZONEEDIT_ID=\"${ZONEEDIT_ID:-$(_readaccountconf_mutable ZONEEDIT_ID)}\"\n  ZONEEDIT_Token=\"${ZONEEDIT_Token:-$(_readaccountconf_mutable ZONEEDIT_Token)}\"\n  if [ -z \"$ZONEEDIT_ID\" ] || [ -z \"$ZONEEDIT_Token\" ]; then\n    ZONEEDIT_ID=\"\"\n    ZONEEDIT_Token=\"\"\n    _err \"Please specify ZONEEDIT_ID and _Token.\"\n    _err \"Please export as ZONEEDIT_ID and ZONEEDIT_Token then try again.\"\n    return 1\n  fi\n\n  # Save the credentials to the account conf file\n  _saveaccountconf_mutable ZONEEDIT_ID \"$ZONEEDIT_ID\"\n  _saveaccountconf_mutable ZONEEDIT_Token \"$ZONEEDIT_Token\"\n\n  if _zoneedit_api \"CREATE\" \"$fulldomain\" \"$txtvalue\"; then\n    _info \"Added, OK\"\n    return 0\n  else\n    _err \"Add txt record error.\"\n    return 1\n  fi\n}\n\n# Usage: dns_zoneedit_rm   fulldomain   txtvalue\ndns_zoneedit_rm() {\n  fulldomain=$1\n  txtvalue=$2\n  _info \"Using ZoneEdit\"\n  _debug fulldomain \"$fulldomain\"\n  _debug txtvalue \"$txtvalue\"\n\n  # Load the credentials from the account conf file\n  ZONEEDIT_ID=\"${ZONEEDIT_ID:-$(_readaccountconf_mutable ZONEEDIT_ID)}\"\n  ZONEEDIT_Token=\"${ZONEEDIT_Token:-$(_readaccountconf_mutable ZONEEDIT_Token)}\"\n  if [ -z \"$ZONEEDIT_ID\" ] || [ -z \"$ZONEEDIT_Token\" ]; then\n    ZONEEDIT_ID=\"\"\n    ZONEEDIT_Token=\"\"\n    _err \"Please specify ZONEEDIT_ID and _Token.\"\n    _err \"Please export as ZONEEDIT_ID and ZONEEDIT_Token then try again.\"\n    return 1\n  fi\n\n  if _zoneedit_api \"DELETE\" \"$fulldomain\" \"$txtvalue\"; then\n    _info \"Deleted, OK\"\n    return 0\n  else\n    _err \"Delete txt record error.\"\n    return 1\n  fi\n}\n\n####################  Private functions below ##################################\n\n#Usage: _zoneedit_api   <CREATE|DELETE>   fulldomain   txtvalue\n_zoneedit_api() {\n  cmd=$1\n  fulldomain=$2\n  txtvalue=$3\n\n  # Construct basic authorization header\n  credentials=$(printf \"%s:%s\" \"$ZONEEDIT_ID\" \"$ZONEEDIT_Token\" | _base64)\n  export _H1=\"Authorization: Basic ${credentials}\"\n\n  # Generate request URL\n  case \"$cmd\" in\n  \"CREATE\")\n    # https://dynamic.zoneedit.com/txt-create.php?host=_acme-challenge.example.com&rdata=depE1VF_xshMm1IVY1Y56Kk9Zb_7jA2VFkP65WuNgu8W\n    geturl=\"https://dynamic.zoneedit.com/txt-create.php?host=${fulldomain}&rdata=${txtvalue}\"\n    ;;\n  \"DELETE\")\n    # https://dynamic.zoneedit.com/txt-delete.php?host=_acme-challenge.example.com&rdata=depE1VF_xshMm1IVY1Y56Kk9Zb_7jA2VFkP65WuNgu8W\n    geturl=\"https://dynamic.zoneedit.com/txt-delete.php?host=${fulldomain}&rdata=${txtvalue}\"\n    ze_sleep=2\n    ;;\n  *)\n    _err \"Unknown parameter : $cmd\"\n    return 1\n    ;;\n  esac\n\n  # Execute request\n  i=3 # Tries\n  while [ \"$i\" -gt 0 ]; do\n    i=$(_math \"$i\" - 1)\n\n    if ! response=$(_get \"$geturl\"); then\n      _err \"_get() failed ($response)\"\n      return 1\n    fi\n    _debug2 response \"$response\"\n    if _contains \"$response\" \"SUCCESS.*200\"; then\n      # Sleep (when needed) to work around a Zonedit API bug\n      # https://forum.zoneedit.com/threads/automating-changes-of-txt-records-in-dns.7394/page-2#post-23855\n      if [ \"$ze_sleep\" ]; then _sleep \"$ze_sleep\"; fi\n      return 0\n    elif _contains \"$response\" \"ERROR.*Minimum.*seconds\"; then\n      _info \"ZoneEdit responded with a rate limit of...\"\n      ze_ratelimit=$(echo \"$response\" | sed -n 's/.*Minimum \\([0-9]\\+\\) seconds.*/\\1/p')\n      if [ \"$ze_ratelimit\" ] && [ ! \"$(echo \"$ze_ratelimit\" | tr -d '0-9')\" ]; then\n        _info \"$ze_ratelimit seconds.\"\n      else\n        _err \"$response\"\n        _err \"not a number, or blank ($ze_ratelimit), API change?\"\n        unset ze_ratelimit\n      fi\n    else\n      _err \"$response\"\n      _err \"Unknown response, API change?\"\n    fi\n\n    # Retry\n    if [ \"$i\" -lt 1 ]; then\n      _err \"Tries exceeded, giving up.\"\n      return 1\n    fi\n    if [ \"$ze_ratelimit\" ]; then\n      _info \"Waiting $ze_ratelimit seconds...\"\n      _sleep \"$ze_ratelimit\"\n    else\n      _err \"Going to retry after 10 seconds...\"\n      _sleep 10\n    fi\n  done\n  return 1\n}\n"
  },
  {
    "path": "dnsapi/dns_zonomi.sh",
    "content": "#!/usr/bin/env sh\n# shellcheck disable=SC2034\ndns_zonomi_info='zonomi.com\nSite: zonomi.com\nDocs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_zonomi\nOptions:\n ZM_Key API Key\n'\n\nZM_Api=\"https://zonomi.com/app/dns/dyndns.jsp\"\n\n########  Public functions #####################\n\n#Usage: add  _acme-challenge.www.domain.com   \"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs\"\ndns_zonomi_add() {\n  fulldomain=$1\n  txtvalue=$2\n\n  ZM_Key=\"${ZM_Key:-$(_readaccountconf_mutable ZM_Key)}\"\n\n  if [ -z \"$ZM_Key\" ]; then\n    ZM_Key=\"\"\n    _err \"You don't specify zonomi api key yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  #save the api key to the account conf file.\n  _saveaccountconf_mutable ZM_Key \"$ZM_Key\"\n\n  _info \"Get existing txt records for $fulldomain\"\n  if ! _zm_request \"action=QUERY&name=$fulldomain\"; then\n    _err \"error\"\n    return 1\n  fi\n\n  if _contains \"$response\" \"<record\"; then\n    _debug \"get and update records\"\n    _qstr=\"action[1]=SET&type[1]=TXT&name[1]=$fulldomain&value[1]=$txtvalue\"\n    _qindex=2\n    for t in $(echo \"$response\" | tr -d \"\\r\\n\" | _egrep_o '<action.*</action>' | tr \"<\" \"\\n\" | grep record | grep 'type=\"TXT\"' | cut -d '\"' -f 6); do\n      _debug2 t \"$t\"\n      _qstr=\"$_qstr&action[$_qindex]=SET&type[$_qindex]=TXT&name[$_qindex]=$fulldomain&value[$_qindex]=$t\"\n      _qindex=\"$(_math \"$_qindex\" + 1)\"\n    done\n    _zm_request \"$_qstr\"\n  else\n    _debug \"Just add record\"\n    _zm_request \"action=SET&type=TXT&name=$fulldomain&value=$txtvalue\"\n  fi\n\n}\n\n#fulldomain txtvalue\ndns_zonomi_rm() {\n  fulldomain=$1\n  txtvalue=$2\n\n  ZM_Key=\"${ZM_Key:-$(_readaccountconf_mutable ZM_Key)}\"\n  if [ -z \"$ZM_Key\" ]; then\n    ZM_Key=\"\"\n    _err \"You don't specify zonomi api key yet.\"\n    _err \"Please create your key and try again.\"\n    return 1\n  fi\n\n  _zm_request \"action=DELETE&type=TXT&name=$fulldomain\"\n\n}\n\n####################  Private functions below ##################################\n#qstr\n_zm_request() {\n  qstr=\"$1\"\n\n  _debug2 \"qstr\" \"$qstr\"\n\n  _zm_url=\"$ZM_Api?api_key=$ZM_Key&$qstr\"\n  _debug2 \"_zm_url\" \"$_zm_url\"\n  response=\"$(_get \"$_zm_url\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    return 1\n  fi\n  _debug2 response \"$response\"\n  _contains \"$response\" \"<is_ok>OK:\"\n}\n"
  },
  {
    "path": "notify/aws_ses.sh",
    "content": "#!/usr/bin/env sh\n\n#\n#AWS_ACCESS_KEY_ID=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"\n#\n#AWS_SECRET_ACCESS_KEY=\"xxxxxxx\"\n#\n#AWS_SES_REGION=\"us-east-1\"\n#\n#AWS_SES_TO=\"xxxx@xxx.com\"\n#\n#AWS_SES_FROM=\"xxxx@cccc.com\"\n#\n#AWS_SES_FROM_NAME=\"Something something\"\n#This is the Amazon SES api wrapper for acme.sh\nAWS_WIKI=\"https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html\"\n\naws_ses_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  AWS_ACCESS_KEY_ID=\"${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}\"\n  AWS_SECRET_ACCESS_KEY=\"${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}\"\n  AWS_SES_REGION=\"${AWS_SES_REGION:-$(_readaccountconf_mutable AWS_SES_REGION)}\"\n\n  if [ -z \"$AWS_ACCESS_KEY_ID\" ] || [ -z \"$AWS_SECRET_ACCESS_KEY\" ]; then\n    _use_container_role || _use_instance_role\n  fi\n\n  if [ -z \"$AWS_ACCESS_KEY_ID\" ] || [ -z \"$AWS_SECRET_ACCESS_KEY\" ]; then\n    AWS_ACCESS_KEY_ID=\"\"\n    AWS_SECRET_ACCESS_KEY=\"\"\n    _err \"You haven't specified the aws SES api key id and and api key secret yet.\"\n    _err \"Please create your key and try again. see $(__green $AWS_WIKI)\"\n    return 1\n  fi\n\n  if [ -z \"$AWS_SES_REGION\" ]; then\n    AWS_SES_REGION=\"\"\n    _err \"You haven't specified the aws SES api region yet.\"\n    _err \"Please specify your region and try again. see https://docs.aws.amazon.com/general/latest/gr/ses.html\"\n    return 1\n  fi\n  _saveaccountconf_mutable AWS_SES_REGION \"$AWS_SES_REGION\"\n\n  #save for future use, unless using a role which will be fetched as needed\n  if [ -z \"$_using_role\" ]; then\n    _saveaccountconf_mutable AWS_ACCESS_KEY_ID \"$AWS_ACCESS_KEY_ID\"\n    _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY \"$AWS_SECRET_ACCESS_KEY\"\n  fi\n\n  AWS_SES_TO=\"${AWS_SES_TO:-$(_readaccountconf_mutable AWS_SES_TO)}\"\n  if [ -z \"$AWS_SES_TO\" ]; then\n    AWS_SES_TO=\"\"\n    _err \"You didn't specify an email to AWS_SES_TO receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable AWS_SES_TO \"$AWS_SES_TO\"\n\n  AWS_SES_FROM=\"${AWS_SES_FROM:-$(_readaccountconf_mutable AWS_SES_FROM)}\"\n  if [ -z \"$AWS_SES_FROM\" ]; then\n    AWS_SES_FROM=\"\"\n    _err \"You didn't specify an email to AWS_SES_FROM receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable AWS_SES_FROM \"$AWS_SES_FROM\"\n\n  AWS_SES_FROM_NAME=\"${AWS_SES_FROM_NAME:-$(_readaccountconf_mutable AWS_SES_FROM_NAME)}\"\n  _saveaccountconf_mutable AWS_SES_FROM_NAME \"$AWS_SES_FROM_NAME\"\n\n  AWS_SES_SENDFROM=\"$AWS_SES_FROM_NAME <$AWS_SES_FROM>\"\n\n  AWS_SES_ACTION=\"Action=SendEmail\"\n  AWS_SES_SOURCE=\"Source=$AWS_SES_SENDFROM\"\n  AWS_SES_TO=\"Destination.ToAddresses.member.1=$AWS_SES_TO\"\n  AWS_SES_SUBJECT=\"Message.Subject.Data=$_subject\"\n  AWS_SES_MESSAGE=\"Message.Body.Text.Data=$_content\"\n\n  _data=\"${AWS_SES_ACTION}&${AWS_SES_SOURCE}&${AWS_SES_TO}&${AWS_SES_SUBJECT}&${AWS_SES_MESSAGE}\"\n\n  response=\"$(aws_rest POST \"\" \"\" \"$_data\")\"\n}\n\n_use_metadata() {\n  _aws_creds=\"$(\n    _get \"$1\" \"\" 1 |\n      _normalizeJson |\n      tr '{,}' '\\n' |\n      while read -r _line; do\n        _key=\"$(echo \"${_line%%:*}\" | tr -d \\\")\"\n        _value=\"${_line#*:}\"\n        _debug3 \"_key\" \"$_key\"\n        _secure_debug3 \"_value\" \"$_value\"\n        case \"$_key\" in\n        AccessKeyId) echo \"AWS_ACCESS_KEY_ID=$_value\" ;;\n        SecretAccessKey) echo \"AWS_SECRET_ACCESS_KEY=$_value\" ;;\n        Token) echo \"AWS_SESSION_TOKEN=$_value\" ;;\n        esac\n      done |\n      paste -sd' ' -\n  )\"\n  _secure_debug \"_aws_creds\" \"$_aws_creds\"\n\n  if [ -z \"$_aws_creds\" ]; then\n    return 1\n  fi\n\n  eval \"$_aws_creds\"\n  _using_role=true\n}\n\n#method uri qstr data\naws_rest() {\n  mtd=\"$1\"\n  ep=\"$2\"\n  qsr=\"$3\"\n  data=\"$4\"\n\n  _debug mtd \"$mtd\"\n  _debug ep \"$ep\"\n  _debug qsr \"$qsr\"\n  _debug data \"$data\"\n\n  CanonicalURI=\"/$ep\"\n  _debug2 CanonicalURI \"$CanonicalURI\"\n\n  CanonicalQueryString=\"$qsr\"\n  _debug2 CanonicalQueryString \"$CanonicalQueryString\"\n\n  RequestDate=\"$(date -u +\"%Y%m%dT%H%M%SZ\")\"\n  _debug2 RequestDate \"$RequestDate\"\n\n  #RequestDate=\"20161120T141056Z\" ##############\n\n  export _H1=\"x-amz-date: $RequestDate\"\n\n  aws_host=\"email.$AWS_SES_REGION.amazonaws.com\"\n  CanonicalHeaders=\"host:$aws_host\\nx-amz-date:$RequestDate\\n\"\n  SignedHeaders=\"host;x-amz-date\"\n  if [ -n \"$AWS_SESSION_TOKEN\" ]; then\n    export _H3=\"x-amz-security-token: $AWS_SESSION_TOKEN\"\n    CanonicalHeaders=\"${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\\n\"\n    SignedHeaders=\"${SignedHeaders};x-amz-security-token\"\n  fi\n  _debug2 CanonicalHeaders \"$CanonicalHeaders\"\n  _debug2 SignedHeaders \"$SignedHeaders\"\n\n  RequestPayload=\"$data\"\n  _debug2 RequestPayload \"$RequestPayload\"\n\n  Hash=\"sha256\"\n\n  CanonicalRequest=\"$mtd\\n$CanonicalURI\\n$CanonicalQueryString\\n$CanonicalHeaders\\n$SignedHeaders\\n$(printf \"%s\" \"$RequestPayload\" | _digest \"$Hash\" hex)\"\n  _debug2 CanonicalRequest \"$CanonicalRequest\"\n\n  HashedCanonicalRequest=\"$(printf \"$CanonicalRequest%s\" | _digest \"$Hash\" hex)\"\n  _debug2 HashedCanonicalRequest \"$HashedCanonicalRequest\"\n\n  Algorithm=\"AWS4-HMAC-SHA256\"\n  _debug2 Algorithm \"$Algorithm\"\n\n  RequestDateOnly=\"$(echo \"$RequestDate\" | cut -c 1-8)\"\n  _debug2 RequestDateOnly \"$RequestDateOnly\"\n\n  Region=\"$AWS_SES_REGION\"\n  Service=\"ses\"\n\n  CredentialScope=\"$RequestDateOnly/$Region/$Service/aws4_request\"\n  _debug2 CredentialScope \"$CredentialScope\"\n\n  StringToSign=\"$Algorithm\\n$RequestDate\\n$CredentialScope\\n$HashedCanonicalRequest\"\n\n  _debug2 StringToSign \"$StringToSign\"\n\n  kSecret=\"AWS4$AWS_SECRET_ACCESS_KEY\"\n\n  #kSecret=\"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY\" ############################\n\n  _secure_debug2 kSecret \"$kSecret\"\n\n  kSecretH=\"$(printf \"%s\" \"$kSecret\" | _hex_dump | tr -d \" \")\"\n  _secure_debug2 kSecretH \"$kSecretH\"\n\n  kDateH=\"$(printf \"$RequestDateOnly%s\" | _hmac \"$Hash\" \"$kSecretH\" hex)\"\n  _debug2 kDateH \"$kDateH\"\n\n  kRegionH=\"$(printf \"$Region%s\" | _hmac \"$Hash\" \"$kDateH\" hex)\"\n  _debug2 kRegionH \"$kRegionH\"\n\n  kServiceH=\"$(printf \"$Service%s\" | _hmac \"$Hash\" \"$kRegionH\" hex)\"\n  _debug2 kServiceH \"$kServiceH\"\n\n  kSigningH=\"$(printf \"%s\" \"aws4_request\" | _hmac \"$Hash\" \"$kServiceH\" hex)\"\n  _debug2 kSigningH \"$kSigningH\"\n\n  signature=\"$(printf \"$StringToSign%s\" | _hmac \"$Hash\" \"$kSigningH\" hex)\"\n  _debug2 signature \"$signature\"\n\n  Authorization=\"$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature\"\n  _debug2 Authorization \"$Authorization\"\n\n  _H2=\"Authorization: $Authorization\"\n  _debug _H2 \"$_H2\"\n\n  url=\"https://$aws_host/$ep\"\n  if [ \"$qsr\" ]; then\n    url=\"https://$aws_host/$ep?$qsr\"\n  fi\n\n  if [ \"$mtd\" = \"GET\" ]; then\n    response=\"$(_get \"$url\")\"\n  else\n    response=\"$(_post \"$data\" \"$url\")\"\n  fi\n\n  _ret=\"$?\"\n  _debug2 response \"$response\"\n  if [ \"$_ret\" = \"0\" ]; then\n    if _contains \"$response\" \"<ErrorResponse\"; then\n      _err \"Response error:$response\"\n      return 1\n    fi\n  fi\n}\n"
  },
  {
    "path": "notify/bark.sh",
    "content": "#!/usr/bin/env sh\n\n# Support iOS Bark Notification\n\n# Every parameter explained: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#push\n\n# BARK_API_URL=\"https://api.day.app/xxxx\" (required)\n# BARK_GROUP=\"ACME\" (optional)\n# BARK_SOUND=\"alarm\" (optional)\n# BARK_LEVEL=\"active\" (optional)\n# BARK_BADGE=0 (optional)\n# BARK_AUTOMATICALLYCOPY=\"1\" (optional)\n# BARK_COPY=\"My clipboard Content\" (optional)\n# BARK_ICON=\"https://example.com/icon.png\" (optional)\n# BARK_ISARCHIVE=\"1\" (optional)\n# BARK_URL=\"https://example.com\" (optional)\n\n# subject content statusCode\nbark_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" # 0: success, 1: error, 2: skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  _content=$(echo \"$_content\" | _url_encode)\n  _subject=$(echo \"$_subject\" | _url_encode)\n\n  BARK_API_URL=\"${BARK_API_URL:-$(_readaccountconf_mutable BARK_API_URL)}\"\n  if [ -z \"$BARK_API_URL\" ]; then\n    _err \"You didn't specify a Bark API URL BARK_API_URL yet.\"\n    _err \"You can download Bark from App Store and get yours.\"\n    return 1\n  fi\n  _saveaccountconf_mutable BARK_API_URL \"$BARK_API_URL\"\n\n  BARK_GROUP=\"${BARK_GROUP:-$(_readaccountconf_mutable BARK_GROUP)}\"\n  if [ -z \"$BARK_GROUP\" ]; then\n    BARK_GROUP=\"ACME\"\n    _info \"The BARK_GROUP is not set, so use the default ACME as group name.\"\n  else\n    _saveaccountconf_mutable BARK_GROUP \"$BARK_GROUP\"\n  fi\n\n  BARK_SOUND=\"${BARK_SOUND:-$(_readaccountconf_mutable BARK_SOUND)}\"\n  if [ -n \"$BARK_SOUND\" ]; then\n    _saveaccountconf_mutable BARK_SOUND \"$BARK_SOUND\"\n  fi\n\n  BARK_LEVEL=\"${BARK_LEVEL:-$(_readaccountconf_mutable BARK_LEVEL)}\"\n  if [ -n \"$BARK_LEVEL\" ]; then\n    _saveaccountconf_mutable BARK_LEVEL \"$BARK_LEVEL\"\n  fi\n\n  BARK_BADGE=\"${BARK_BADGE:-$(_readaccountconf_mutable BARK_BADGE)}\"\n  if [ -n \"$BARK_BADGE\" ]; then\n    _saveaccountconf_mutable BARK_BADGE \"$BARK_BADGE\"\n  fi\n\n  BARK_AUTOMATICALLYCOPY=\"${BARK_AUTOMATICALLYCOPY:-$(_readaccountconf_mutable BARK_AUTOMATICALLYCOPY)}\"\n  if [ -n \"$BARK_AUTOMATICALLYCOPY\" ]; then\n    _saveaccountconf_mutable BARK_AUTOMATICALLYCOPY \"$BARK_AUTOMATICALLYCOPY\"\n  fi\n\n  BARK_COPY=\"${BARK_COPY:-$(_readaccountconf_mutable BARK_COPY)}\"\n  if [ -n \"$BARK_COPY\" ]; then\n    _saveaccountconf_mutable BARK_COPY \"$BARK_COPY\"\n  fi\n\n  BARK_ICON=\"${BARK_ICON:-$(_readaccountconf_mutable BARK_ICON)}\"\n  if [ -n \"$BARK_ICON\" ]; then\n    _saveaccountconf_mutable BARK_ICON \"$BARK_ICON\"\n  fi\n\n  BARK_ISARCHIVE=\"${BARK_ISARCHIVE:-$(_readaccountconf_mutable BARK_ISARCHIVE)}\"\n  if [ -n \"$BARK_ISARCHIVE\" ]; then\n    _saveaccountconf_mutable BARK_ISARCHIVE \"$BARK_ISARCHIVE\"\n  fi\n\n  BARK_URL=\"${BARK_URL:-$(_readaccountconf_mutable BARK_URL)}\"\n  if [ -n \"$BARK_URL\" ]; then\n    _saveaccountconf_mutable BARK_URL \"$BARK_URL\"\n  fi\n\n  _params=\"\"\n\n  if [ -n \"$BARK_SOUND\" ]; then\n    _params=\"$_params&sound=$BARK_SOUND\"\n  fi\n  if [ -n \"$BARK_GROUP\" ]; then\n    _params=\"$_params&group=$BARK_GROUP\"\n  fi\n  if [ -n \"$BARK_LEVEL\" ]; then\n    _params=\"$_params&level=$BARK_LEVEL\"\n  fi\n  if [ -n \"$BARK_BADGE\" ]; then\n    _params=\"$_params&badge=$BARK_BADGE\"\n  fi\n  if [ -n \"$BARK_AUTOMATICALLYCOPY\" ]; then\n    _params=\"$_params&automaticallyCopy=$BARK_AUTOMATICALLYCOPY\"\n  fi\n  if [ -n \"$BARK_COPY\" ]; then\n    _params=\"$_params&copy=$BARK_COPY\"\n  fi\n  if [ -n \"$BARK_ICON\" ]; then\n    _params=\"$_params&icon=$BARK_ICON\"\n  fi\n  if [ -n \"$BARK_ISARCHIVE\" ]; then\n    _params=\"$_params&isArchive=$BARK_ISARCHIVE\"\n  fi\n  if [ -n \"$BARK_URL\" ]; then\n    _params=\"$_params&url=$BARK_URL\"\n  fi\n\n  _params=$(echo \"$_params\" | sed 's/^&//') # remove leading '&' if exists\n\n  response=\"$(_get \"$BARK_API_URL/$_subject/$_content?$_params\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"success\"; then\n    _info \"Bark API fired success.\"\n    return 0\n  fi\n\n  _err \"Bark API fired error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/callmebotWhatsApp.sh",
    "content": "#!/usr/bin/env sh\n\n#Support CallMeBot Whatsapp webhooks\n\n#CALLMEBOT_YOUR_PHONE_NO=\"\"\n#CALLMEBOT_API_KEY=\"\"\n\ncallmebotWhatsApp_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  CALLMEBOT_YOUR_PHONE_NO=\"${CALLMEBOT_YOUR_PHONE_NO:-$(_readaccountconf_mutable CALLMEBOT_YOUR_PHONE_NO)}\"\n  if [ -z \"$CALLMEBOT_YOUR_PHONE_NO\" ]; then\n    CALLMEBOT_YOUR_PHONE_NO=\"\"\n    _err \"You didn't specify a Slack webhook url CALLMEBOT_YOUR_PHONE_NO yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable CALLMEBOT_YOUR_PHONE_NO \"$CALLMEBOT_YOUR_PHONE_NO\"\n\n  CALLMEBOT_API_KEY=\"${CALLMEBOT_API_KEY:-$(_readaccountconf_mutable CALLMEBOT_API_KEY)}\"\n  if [ \"$CALLMEBOT_API_KEY\" ]; then\n    _saveaccountconf_mutable CALLMEBOT_API_KEY \"$CALLMEBOT_API_KEY\"\n  fi\n\n  _waUrl=\"https://api.callmebot.com/whatsapp.php\"\n\n  _Phone_No=\"$(printf \"%s\" \"$CALLMEBOT_YOUR_PHONE_NO\" | _url_encode)\"\n  _apikey=\"$(printf \"%s\" \"$CALLMEBOT_API_KEY\" | _url_encode)\"\n  _message=\"$(printf \"*%s*\\\\n%s\" \"$_subject\" \"$_content\" | _url_encode)\"\n\n  _finalUrl=\"$_waUrl?phone=$_Phone_No&apikey=$_apikey&text=$_message\"\n  response=\"$(_get \"$_finalUrl\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \".<p><b>Message queued.</b> You will receive it in a few seconds.\"; then\n    _info \"wa send success.\"\n    return 0\n  fi\n  _err \"wa send error.\"\n  _debug \"URL\" \"$_finalUrl\"\n  _debug \"Response\" \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/cqhttp.sh",
    "content": "#!/usr/bin/env sh\n\n#Support for CQHTTP api. Push notification on CoolQ\n#CQHTTP_TOKEN=\"\" Recommended to be not empty, QQ application token\n#CQHTTP_USER=\"\" Required, QQ receiver ID\n#CQHTTP_APIROOT=\"\" Required, CQHTTP Server URL (without slash suffix)\n#CQHTTP_CUSTOM_MSGHEAD=\"\" Optional, custom message header\n\nCQHTTP_APIPATH=\"/send_private_msg\"\n\ncqhttp_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  CQHTTP_TOKEN=\"${CQHTTP_TOKEN:-$(_readaccountconf_mutable CQHTTP_TOKEN)}\"\n  if [ -z \"$CQHTTP_TOKEN\" ]; then\n    CQHTTP_TOKEN=\"\"\n    _info \"You didn't specify a CQHTTP application token yet, which is unsafe. Assuming it to be empty.\"\n  else\n    _saveaccountconf_mutable CQHTTP_TOKEN \"$CQHTTP_TOKEN\"\n  fi\n\n  CQHTTP_USER=\"${CQHTTP_USER:-$(_readaccountconf_mutable CQHTTP_USER)}\"\n  if [ -z \"$CQHTTP_USER\" ]; then\n    CQHTTP_USER=\"\"\n    _err \"You didn't specify a QQ user yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable CQHTTP_USER \"$CQHTTP_USER\"\n\n  CQHTTP_APIROOT=\"${CQHTTP_APIROOT:-$(_readaccountconf_mutable CQHTTP_APIROOT)}\"\n  if [ -z \"$CQHTTP_APIROOT\" ]; then\n    CQHTTP_APIROOT=\"\"\n    _err \"You didn't specify the API root yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable CQHTTP_APIROOT \"$CQHTTP_APIROOT\"\n\n  CQHTTP_CUSTOM_MSGHEAD=\"${CQHTTP_CUSTOM_MSGHEAD:-$(_readaccountconf_mutable CQHTTP_CUSTOM_MSGHEAD)}\"\n  if [ -z \"$CQHTTP_CUSTOM_MSGHEAD\" ]; then\n    CQHTTP_CUSTOM_MSGHEAD=\"A message from acme.sh:\"\n  else\n    _saveaccountconf_mutable CQHTTP_CUSTOM_MSGHEAD \"$CQHTTP_CUSTOM_MSGHEAD\"\n  fi\n\n  _access_token=\"$(printf \"%s\" \"$CQHTTP_TOKEN\" | _url_encode)\"\n  _user_id=\"$(printf \"%s\" \"$CQHTTP_USER\" | _url_encode)\"\n  _message=\"$(printf \"$CQHTTP_CUSTOM_MSGHEAD %s\\\\n%s\" \"$_subject\" \"$_content\" | _url_encode)\"\n\n  _finalUrl=\"$CQHTTP_APIROOT$CQHTTP_APIPATH?access_token=$_access_token&user_id=$_user_id&message=$_message\"\n  response=\"$(_get \"$_finalUrl\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"\\\"retcode\\\":0\" && _contains \"$response\" \"\\\"status\\\":\\\"ok\\\"\"; then\n    _info \"QQ send success.\"\n    return 0\n  fi\n\n  _err \"QQ send error.\"\n  _debug \"URL\" \"$_finalUrl\"\n  _debug \"Response\" \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/dingtalk.sh",
    "content": "#!/usr/bin/env sh\n\n#Support dingtalk webhooks api\n\n#DINGTALK_WEBHOOK=\"xxxx\"\n\n#optional\n#DINGTALK_KEYWORD=\"yyyy\"\n\n#DINGTALK_SIGNING_KEY=\"SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx\"\n\n# subject  content statusCode\ndingtalk_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  DINGTALK_WEBHOOK=\"${DINGTALK_WEBHOOK:-$(_readaccountconf_mutable DINGTALK_WEBHOOK)}\"\n  if [ -z \"$DINGTALK_WEBHOOK\" ]; then\n    DINGTALK_WEBHOOK=\"\"\n    _err \"You didn't specify a dingtalk webhooks DINGTALK_WEBHOOK yet.\"\n    _err \"You can get yours from https://dingtalk.com\"\n    return 1\n  fi\n  _saveaccountconf_mutable DINGTALK_WEBHOOK \"$DINGTALK_WEBHOOK\"\n\n  DINGTALK_KEYWORD=\"${DINGTALK_KEYWORD:-$(_readaccountconf_mutable DINGTALK_KEYWORD)}\"\n  if [ \"$DINGTALK_KEYWORD\" ]; then\n    _saveaccountconf_mutable DINGTALK_KEYWORD \"$DINGTALK_KEYWORD\"\n  fi\n\n  #  DINGTALK_SIGNING_KEY=\"${DINGTALK_SIGNING_KEY:-$(_readaccountconf_mutable DINGTALK_SIGNING_KEY)}\"\n  #  if [ -z \"$DINGTALK_SIGNING_KEY\" ]; then\n  #    DINGTALK_SIGNING_KEY=\"value1\"\n  #    _info \"The DINGTALK_SIGNING_KEY is not set, so use the default value1 as key.\"\n  #  elif ! _hasfield \"$_IFTTT_AVAIL_MSG_KEYS\" \"$DINGTALK_SIGNING_KEY\"; then\n  #    _err \"The DINGTALK_SIGNING_KEY \\\"$DINGTALK_SIGNING_KEY\\\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS\"\n  #    DINGTALK_SIGNING_KEY=\"\"\n  #    return 1\n  #  else\n  #    _saveaccountconf_mutable DINGTALK_SIGNING_KEY \"$DINGTALK_SIGNING_KEY\"\n  #  fi\n\n  #  if [ \"$DINGTALK_SIGNING_KEY\" = \"$IFTTT_CONTENT_KEY\" ]; then\n  #    DINGTALK_SIGNING_KEY=\"\"\n  #    IFTTT_CONTENT_KEY=\"\"\n  #    _err \"The DINGTALK_SIGNING_KEY must not be same as IFTTT_CONTENT_KEY.\"\n  #    return 1\n  #  fi\n\n  _content=$(echo \"$_content\" | _json_encode)\n  _subject=$(echo \"$_subject\" | _json_encode)\n  _data=\"{\\\"msgtype\\\": \\\"text\\\", \\\"text\\\": {\\\"content\\\": \\\"[$DINGTALK_KEYWORD]\\n$_subject\\n$_content\\\"}}\"\n\n  response=\"$(_post \"$_data\" \"$DINGTALK_WEBHOOK\" \"\" \"POST\" \"application/json\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"errmsg\\\":\\\"ok\"; then\n    _info \"dingtalk webhooks event fired success.\"\n    return 0\n  fi\n\n  _err \"dingtalk webhooks event fired error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/discord.sh",
    "content": "#!/usr/bin/env sh\n\n#Support Discord webhooks\n\n# Required:\n#DISCORD_WEBHOOK_URL=\"\"\n# Optional:\n#DISCORD_USERNAME=\"\"\n#DISCORD_AVATAR_URL=\"\"\n\ndiscord_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  DISCORD_WEBHOOK_URL=\"${DISCORD_WEBHOOK_URL:-$(_readaccountconf_mutable DISCORD_WEBHOOK_URL)}\"\n  if [ -z \"$DISCORD_WEBHOOK_URL\" ]; then\n    DISCORD_WEBHOOK_URL=\"\"\n    _err \"You didn't specify a Discord webhook url DISCORD_WEBHOOK_URL yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable DISCORD_WEBHOOK_URL \"$DISCORD_WEBHOOK_URL\"\n\n  DISCORD_USERNAME=\"${DISCORD_USERNAME:-$(_readaccountconf_mutable DISCORD_USERNAME)}\"\n  if [ \"$DISCORD_USERNAME\" ]; then\n    _saveaccountconf_mutable DISCORD_USERNAME \"$DISCORD_USERNAME\"\n  fi\n\n  DISCORD_AVATAR_URL=\"${DISCORD_AVATAR_URL:-$(_readaccountconf_mutable DISCORD_AVATAR_URL)}\"\n  if [ \"$DISCORD_AVATAR_URL\" ]; then\n    _saveaccountconf_mutable DISCORD_AVATAR_URL \"$DISCORD_AVATAR_URL\"\n  fi\n\n  export _H1=\"Content-Type: application/json\"\n\n  _content=\"$(printf \"**%s**\\n%s\" \"$_subject\" \"$_content\" | _json_encode)\"\n  _data=\"{\\\"content\\\": \\\"$_content\\\" \"\n  if [ \"$DISCORD_USERNAME\" ]; then\n    _data=\"$_data, \\\"username\\\": \\\"$DISCORD_USERNAME\\\" \"\n  fi\n  if [ \"$DISCORD_AVATAR_URL\" ]; then\n    _data=\"$_data, \\\"avatar_url\\\": \\\"$DISCORD_AVATAR_URL\\\" \"\n  fi\n  _data=\"$_data}\"\n\n  if _post \"$_data\" \"$DISCORD_WEBHOOK_URL?wait=true\"; then\n    # shellcheck disable=SC2154\n    if [ \"$response\" ]; then\n      _info \"discord send success.\"\n      return 0\n    fi\n  fi\n  _err \"discord send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/feishu.sh",
    "content": "#!/usr/bin/env sh\n\n#Support feishu webhooks api\n\n#required\n#FEISHU_WEBHOOK=\"xxxx\"\n\n#optional\n#FEISHU_KEYWORD=\"yyyy\"\n\n# subject content statusCode\nfeishu_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  FEISHU_WEBHOOK=\"${FEISHU_WEBHOOK:-$(_readaccountconf_mutable FEISHU_WEBHOOK)}\"\n  if [ -z \"$FEISHU_WEBHOOK\" ]; then\n    FEISHU_WEBHOOK=\"\"\n    _err \"You didn't specify a feishu webhooks FEISHU_WEBHOOK yet.\"\n    _err \"You can get yours from https://www.feishu.cn\"\n    return 1\n  fi\n  _saveaccountconf_mutable FEISHU_WEBHOOK \"$FEISHU_WEBHOOK\"\n\n  FEISHU_KEYWORD=\"${FEISHU_KEYWORD:-$(_readaccountconf_mutable FEISHU_KEYWORD)}\"\n  if [ \"$FEISHU_KEYWORD\" ]; then\n    _saveaccountconf_mutable FEISHU_KEYWORD \"$FEISHU_KEYWORD\"\n  fi\n\n  _content=$(echo \"$_content\" | _json_encode)\n  _subject=$(echo \"$_subject\" | _json_encode)\n  _data=\"{\\\"msg_type\\\": \\\"text\\\", \\\"content\\\": {\\\"text\\\": \\\"[$FEISHU_KEYWORD]\\n$_subject\\n$_content\\\"}}\"\n\n  response=\"$(_post \"$_data\" \"$FEISHU_WEBHOOK\" \"\" \"POST\" \"application/json\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"StatusCode\\\":0\"; then\n    _info \"feishu webhooks event fired success.\"\n    return 0\n  fi\n\n  _err \"feishu webhooks event fired error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/gotify.sh",
    "content": "#!/usr/bin/env sh\n\n#Support Gotify\n\n#GOTIFY_URL=\"https://gotify.example.com\"\n#GOTIFY_TOKEN=\"123456789ABCDEF\"\n\n#optional\n#GOTIFY_PRIORITY=0\n\n# subject  content statusCode\ngotify_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  GOTIFY_URL=\"${GOTIFY_URL:-$(_readaccountconf_mutable GOTIFY_URL)}\"\n  if [ -z \"$GOTIFY_URL\" ]; then\n    GOTIFY_URL=\"\"\n    _err \"You didn't specify the gotify server url GOTIFY_URL.\"\n    return 1\n  fi\n  _saveaccountconf_mutable GOTIFY_URL \"$GOTIFY_URL\"\n\n  GOTIFY_TOKEN=\"${GOTIFY_TOKEN:-$(_readaccountconf_mutable GOTIFY_TOKEN)}\"\n  if [ -z \"$GOTIFY_TOKEN\" ]; then\n    GOTIFY_TOKEN=\"\"\n    _err \"You didn't specify the gotify token GOTIFY_TOKEN.\"\n    return 1\n  fi\n  _saveaccountconf_mutable GOTIFY_TOKEN \"$GOTIFY_TOKEN\"\n\n  GOTIFY_PRIORITY=\"${GOTIFY_PRIORITY:-$(_readaccountconf_mutable GOTIFY_PRIORITY)}\"\n  if [ -z \"$GOTIFY_PRIORITY\" ]; then\n    GOTIFY_PRIORITY=0\n  else\n    _saveaccountconf_mutable GOTIFY_PRIORITY \"$GOTIFY_PRIORITY\"\n  fi\n\n  export _H1=\"X-Gotify-Key: ${GOTIFY_TOKEN}\"\n  export _H2=\"Content-Type: application/json\"\n\n  _content=$(echo \"$_content\" | _json_encode)\n  _subject=$(echo \"$_subject\" | _json_encode)\n\n  _data=\"{\\\"title\\\": \\\"${_subject}\\\", \\\"message\\\": \\\"${_content}\\\", \\\"priority\\\": ${GOTIFY_PRIORITY}}\"\n\n  response=\"$(_post \"${_data}\" \"${GOTIFY_URL}/message\" \"\" \"POST\" \"application/json\")\"\n\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Failed to send message\"\n    _err \"$response\"\n    return 1\n  fi\n\n  _debug2 response \"$response\"\n\n  return 0\n}\n"
  },
  {
    "path": "notify/ifttt.sh",
    "content": "#!/usr/bin/env sh\n\n#Support ifttt.com webhooks api\n\n#IFTTT_API_KEY=\"xxxx\"\n#IFTTT_EVENT_NAME=\"yyyy\"\n\n#IFTTT_SUBJECT_KEY=\"value1|value2|value3\"      #optional, use \"value1\" as default\n#IFTTT_CONTENT_KEY=\"value1|value2|value3\"      #optional, use \"value2\" as default\n\n_IFTTT_AVAIL_MSG_KEYS=\"value1,value2,value3\"\n\n# subject  content statusCode\nifttt_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  IFTTT_API_KEY=\"${IFTTT_API_KEY:-$(_readaccountconf_mutable IFTTT_API_KEY)}\"\n  if [ -z \"$IFTTT_API_KEY\" ]; then\n    IFTTT_API_KEY=\"\"\n    _err \"You didn't specify a ifttt webhooks api key IFTTT_API_KEY yet.\"\n    _err \"You can get yours from https://ifttt.com\"\n    return 1\n  fi\n  _saveaccountconf_mutable IFTTT_API_KEY \"$IFTTT_API_KEY\"\n\n  IFTTT_EVENT_NAME=\"${IFTTT_EVENT_NAME:-$(_readaccountconf_mutable IFTTT_EVENT_NAME)}\"\n  if [ -z \"$IFTTT_EVENT_NAME\" ]; then\n    IFTTT_EVENT_NAME=\"\"\n    _err \"You didn't specify a ifttt webhooks event name IFTTT_EVENT_NAME yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable IFTTT_EVENT_NAME \"$IFTTT_EVENT_NAME\"\n\n  IFTTT_SUBJECT_KEY=\"${IFTTT_SUBJECT_KEY:-$(_readaccountconf_mutable IFTTT_SUBJECT_KEY)}\"\n  if [ -z \"$IFTTT_SUBJECT_KEY\" ]; then\n    IFTTT_SUBJECT_KEY=\"value1\"\n    _info \"The IFTTT_SUBJECT_KEY is not set, so use the default value1 as key.\"\n  elif ! _hasfield \"$_IFTTT_AVAIL_MSG_KEYS\" \"$IFTTT_SUBJECT_KEY\"; then\n    _err \"The IFTTT_SUBJECT_KEY \\\"$IFTTT_SUBJECT_KEY\\\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS\"\n    IFTTT_SUBJECT_KEY=\"\"\n    return 1\n  else\n    _saveaccountconf_mutable IFTTT_SUBJECT_KEY \"$IFTTT_SUBJECT_KEY\"\n  fi\n\n  IFTTT_CONTENT_KEY=\"${IFTTT_CONTENT_KEY:-$(_readaccountconf_mutable IFTTT_CONTENT_KEY)}\"\n  if [ -z \"$IFTTT_CONTENT_KEY\" ]; then\n    IFTTT_CONTENT_KEY=\"value2\"\n    _info \"The IFTTT_CONTENT_KEY is not set, so use the default value2 as key.\"\n  elif ! _hasfield \"$_IFTTT_AVAIL_MSG_KEYS\" \"$IFTTT_CONTENT_KEY\"; then\n    _err \"The IFTTT_CONTENT_KEY \\\"$IFTTT_CONTENT_KEY\\\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS\"\n    IFTTT_CONTENT_KEY=\"\"\n    return 1\n  else\n    _saveaccountconf_mutable IFTTT_CONTENT_KEY \"$IFTTT_CONTENT_KEY\"\n  fi\n\n  if [ \"$IFTTT_SUBJECT_KEY\" = \"$IFTTT_CONTENT_KEY\" ]; then\n    IFTTT_SUBJECT_KEY=\"\"\n    IFTTT_CONTENT_KEY=\"\"\n    _err \"The IFTTT_SUBJECT_KEY must not be same as IFTTT_CONTENT_KEY.\"\n    return 1\n  fi\n\n  IFTTT_API_URL=\"https://maker.ifttt.com/trigger/$IFTTT_EVENT_NAME/with/key/$IFTTT_API_KEY\"\n\n  _content=$(echo \"$_content\" | _json_encode)\n  _subject=$(echo \"$_subject\" | _json_encode)\n  _data=\"{\\\"$IFTTT_SUBJECT_KEY\\\": \\\"$_subject\\\", \\\"$IFTTT_CONTENT_KEY\\\": \\\"$_content\\\"}\"\n\n  response=\"$(_post \"$_data\" \"$IFTTT_API_URL\" \"\" \"POST\" \"application/json\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"Congratulations\"; then\n    _info \"IFTTT webhooks event fired success.\"\n    return 0\n  fi\n\n  _err \"IFTTT webhooks event fired error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/mail.sh",
    "content": "#!/usr/bin/env sh\n\n#Support local mail app\n\n#MAIL_BIN=\"sendmail\"\n#MAIL_FROM=\"yyyy@gmail.com\"\n#MAIL_TO=\"yyyy@gmail.com\"\n#MAIL_NOVALIDATE=\"\"\n#MAIL_MSMTP_ACCOUNT=\"\"\n\nmail_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  MAIL_NOVALIDATE=\"${MAIL_NOVALIDATE:-$(_readaccountconf_mutable MAIL_NOVALIDATE)}\"\n  if [ -n \"$MAIL_NOVALIDATE\" ]; then\n    _saveaccountconf_mutable MAIL_NOVALIDATE 1\n  else\n    _clearaccountconf \"MAIL_NOVALIDATE\"\n  fi\n\n  MAIL_BIN=\"${MAIL_BIN:-$(_readaccountconf_mutable MAIL_BIN)}\"\n  if [ -n \"$MAIL_BIN\" ] && ! _exists \"$MAIL_BIN\"; then\n    _err \"It seems that the command $MAIL_BIN is not in path.\"\n    return 1\n  fi\n  _MAIL_BIN=$(_mail_bin)\n  if [ -n \"$MAIL_BIN\" ]; then\n    _saveaccountconf_mutable MAIL_BIN \"$MAIL_BIN\"\n  else\n    _clearaccountconf \"MAIL_BIN\"\n  fi\n\n  MAIL_FROM=\"${MAIL_FROM:-$(_readaccountconf_mutable MAIL_FROM)}\"\n  if [ -n \"$MAIL_FROM\" ]; then\n    if ! _mail_valid \"$MAIL_FROM\"; then\n      _err \"It seems that the MAIL_FROM=$MAIL_FROM is not a valid email address.\"\n      return 1\n    fi\n\n    _saveaccountconf_mutable MAIL_FROM \"$MAIL_FROM\"\n  fi\n\n  MAIL_TO=\"${MAIL_TO:-$(_readaccountconf_mutable MAIL_TO)}\"\n  if [ -n \"$MAIL_TO\" ]; then\n    if ! _mail_valid \"$MAIL_TO\"; then\n      _err \"It seems that the MAIL_TO=$MAIL_TO is not a valid email address.\"\n      return 1\n    fi\n\n    _saveaccountconf_mutable MAIL_TO \"$MAIL_TO\"\n  else\n    MAIL_TO=\"$(_readaccountconf ACCOUNT_EMAIL)\"\n    if [ -z \"$MAIL_TO\" ]; then\n      _err \"It seems that account email is empty.\"\n      return 1\n    fi\n  fi\n\n  contenttype=\"text/plain; charset=utf-8\"\n  subject=\"=?UTF-8?B?$(printf -- \"%b\" \"$_subject\" | _base64)?=\"\n  result=$({ _mail_body | eval \"$(_mail_cmnd)\"; } 2>&1)\n\n  # shellcheck disable=SC2181\n  if [ $? -ne 0 ]; then\n    _debug \"mail send error.\"\n    _err \"$result\"\n    return 1\n  fi\n\n  _debug \"mail send success.\"\n  return 0\n}\n\n_mail_bin() {\n  _MAIL_BIN=\"\"\n\n  for b in $MAIL_BIN sendmail ssmtp mutt mail msmtp; do\n    if _exists \"$b\"; then\n      _MAIL_BIN=\"$b\"\n      break\n    fi\n  done\n\n  if [ -z \"$_MAIL_BIN\" ]; then\n    _err \"Please install sendmail, ssmtp, mutt, mail or msmtp first.\"\n    return 1\n  fi\n\n  echo \"$_MAIL_BIN\"\n}\n\n_mail_cmnd() {\n  _MAIL_ARGS=\"\"\n\n  case $(basename \"$_MAIL_BIN\") in\n  sendmail)\n    if [ -n \"$MAIL_FROM\" ]; then\n      _MAIL_ARGS=\"-f '$MAIL_FROM'\"\n    fi\n    ;;\n  mutt | mail)\n    _MAIL_ARGS=\"-s '$_subject'\"\n    ;;\n  msmtp)\n    if [ -n \"$MAIL_FROM\" ]; then\n      _MAIL_ARGS=\"-f '$MAIL_FROM'\"\n    fi\n\n    if [ -n \"$MAIL_MSMTP_ACCOUNT\" ]; then\n      _MAIL_ARGS=\"$_MAIL_ARGS -a '$MAIL_MSMTP_ACCOUNT'\"\n    fi\n    ;;\n  *) ;;\n  esac\n\n  echo \"'$_MAIL_BIN' $_MAIL_ARGS '$MAIL_TO'\"\n}\n\n_mail_body() {\n  case $(basename \"$_MAIL_BIN\") in\n  sendmail | ssmtp | msmtp)\n    if [ -n \"$MAIL_FROM\" ]; then\n      echo \"From: $MAIL_FROM\"\n    fi\n\n    echo \"To: $MAIL_TO\"\n    echo \"Subject: $subject\"\n    echo \"Content-Type: $contenttype\"\n    echo \"MIME-Version: 1.0\"\n    echo\n    ;;\n  esac\n\n  echo \"$_content\"\n}\n\n_mail_valid() {\n  [ -n \"$MAIL_NOVALIDATE\" ] || _contains \"$1\" \"@\"\n}\n"
  },
  {
    "path": "notify/mailgun.sh",
    "content": "#!/usr/bin/env sh\n\n#Support mailgun.com api\n\n#MAILGUN_API_KEY=\"xxxx\"\n#MAILGUN_TO=\"yyyy@gmail.com\"\n\n#MAILGUN_REGION=\"us|eu\"          #optional, use \"us\" as default\n#MAILGUN_API_DOMAIN=\"xxxxxx.com\"  #optional, use the default sandbox domain\n#MAILGUN_FROM=\"xxx@xxxxx.com\"    #optional, use the default sandbox account\n\n_MAILGUN_BASE_US=\"https://api.mailgun.net/v3\"\n_MAILGUN_BASE_EU=\"https://api.eu.mailgun.net/v3\"\n\n_MAILGUN_BASE=\"$_MAILGUN_BASE_US\"\n\n# subject  content statusCode\nmailgun_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  MAILGUN_API_KEY=\"${MAILGUN_API_KEY:-$(_readaccountconf_mutable MAILGUN_API_KEY)}\"\n  if [ -z \"$MAILGUN_API_KEY\" ]; then\n    MAILGUN_API_KEY=\"\"\n    _err \"You didn't specify a mailgun api key MAILGUN_API_KEY yet .\"\n    _err \"You can get yours from here https://mailgun.com\"\n    return 1\n  fi\n  _saveaccountconf_mutable MAILGUN_API_KEY \"$MAILGUN_API_KEY\"\n\n  MAILGUN_REGION=\"${MAILGUN_REGION:-$(_readaccountconf_mutable MAILGUN_REGION)}\"\n  if [ -z \"$MAILGUN_REGION\" ]; then\n    MAILGUN_REGION=\"\"\n    _debug \"The MAILGUN_REGION is not set, so use the default us region.\"\n    _MAILGUN_BASE=\"$_MAILGUN_BASE_US\"\n  else\n    MAILGUN_REGION=\"$(echo \"$MAILGUN_REGION\" | _lower_case)\"\n    _saveaccountconf_mutable MAILGUN_REGION \"$MAILGUN_REGION\"\n    if [ \"$MAILGUN_REGION\" = \"us\" ]; then\n      _MAILGUN_BASE=\"$_MAILGUN_BASE_US\"\n    else\n      _MAILGUN_BASE=\"$_MAILGUN_BASE_EU\"\n    fi\n  fi\n  _debug _MAILGUN_BASE \"$_MAILGUN_BASE\"\n  MAILGUN_TO=\"${MAILGUN_TO:-$(_readaccountconf_mutable MAILGUN_TO)}\"\n  if [ -z \"$MAILGUN_TO\" ]; then\n    MAILGUN_TO=\"\"\n    _err \"You didn't specify an email to MAILGUN_TO receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable MAILGUN_TO \"$MAILGUN_TO\"\n\n  MAILGUN_API_DOMAIN=\"${MAILGUN_API_DOMAIN:-$(_readaccountconf_mutable MAILGUN_API_DOMAIN)}\"\n  if [ -z \"$MAILGUN_API_DOMAIN\" ]; then\n    _info \"The MAILGUN_API_DOMAIN is not set, try to get the default sending sandbox domain for you.\"\n    if ! _mailgun_rest GET \"/domains\"; then\n      _err \"Can not get sandbox domain.\"\n      return 1\n    fi\n    _sendboxDomain=\"$(echo \"$response\" | _egrep_o '\"name\": *\"sandbox.*.mailgun.org\"' | cut -d : -f 2 | tr -d '\" ')\"\n    _debug _sendboxDomain \"$_sendboxDomain\"\n    MAILGUN_API_DOMAIN=\"$_sendboxDomain\"\n    if [ -z \"$MAILGUN_API_DOMAIN\" ]; then\n      _err \"Can not get sandbox domain for MAILGUN_API_DOMAIN\"\n      return 1\n    fi\n\n    _info \"$(__green \"When using sandbox domain, you must verify your email first.\")\"\n    #todo: add recepient\n  fi\n  if [ -z \"$MAILGUN_API_DOMAIN\" ]; then\n    _err \"Can not get MAILGUN_API_DOMAIN\"\n    return 1\n  fi\n  _saveaccountconf_mutable MAILGUN_API_DOMAIN \"$MAILGUN_API_DOMAIN\"\n\n  MAILGUN_FROM=\"${MAILGUN_FROM:-$(_readaccountconf_mutable MAILGUN_FROM)}\"\n  if [ -z \"$MAILGUN_FROM\" ]; then\n    MAILGUN_FROM=\"$PROJECT_NAME@$MAILGUN_API_DOMAIN\"\n    _info \"The MAILGUN_FROM is not set, so use the default value: $MAILGUN_FROM\"\n  else\n    _debug MAILGUN_FROM \"$MAILGUN_FROM\"\n    _saveaccountconf_mutable MAILGUN_FROM \"$MAILGUN_FROM\"\n  fi\n\n  #send from url\n  _msg=\"/$MAILGUN_API_DOMAIN/messages?from=$(printf \"%s\" \"$MAILGUN_FROM\" | _url_encode)&to=$(printf \"%s\" \"$MAILGUN_TO\" | _url_encode)&subject=$(printf \"%s\" \"$_subject\" | _url_encode)&text=$(printf \"%s\" \"$_content\" | _url_encode)\"\n  _debug \"_msg\" \"$_msg\"\n  _mailgun_rest POST \"$_msg\"\n  if _contains \"$response\" \"Queued. Thank you.\"; then\n    _debug \"mailgun send success.\"\n    return 0\n  else\n    _err \"mailgun send error\"\n    _err \"$response\"\n    return 1\n  fi\n\n}\n\n# method uri  data\n_mailgun_rest() {\n  _method=\"$1\"\n  _mguri=\"$2\"\n  _mgdata=\"$3\"\n  _debug _mguri \"$_mguri\"\n  _mgurl=\"$_MAILGUN_BASE$_mguri\"\n  _debug _mgurl \"$_mgurl\"\n\n  _auth=\"$(printf \"%s\" \"api:$MAILGUN_API_KEY\" | _base64)\"\n  export _H1=\"Authorization: Basic $_auth\"\n  export _H2=\"Content-Type: application/json\"\n\n  if [ \"$_method\" = \"GET\" ]; then\n    response=\"$(_get \"$_mgurl\")\"\n  else\n    _debug _mgdata \"$_mgdata\"\n    response=\"$(_post \"$_mgdata\" \"$_mgurl\" \"\" \"$_method\")\"\n  fi\n  if [ \"$?\" != \"0\" ]; then\n    _err \"Error: $_mguri\"\n    _err \"$response\"\n    return 1\n  fi\n  _debug2 response \"$response\"\n  return 0\n\n}\n"
  },
  {
    "path": "notify/mattermost.sh",
    "content": "#!/usr/bin/env sh\n\n# Support mattermost bots\n\n#MATTERMOST_API_URL=\"\"\n#MATTERMOST_CHANNEL_ID=\"\"\n#MATTERMOST_BOT_TOKEN=\"\"\n\nmattermost_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  MATTERMOST_API_URL=\"${MATTERMOST_API_URL:-$(_readaccountconf_mutable MATTERMOST_API_URL)}\"\n  if [ -z \"$MATTERMOST_API_URL\" ]; then\n    _err \"You didn't specify a Mattermost API URL MATTERMOST_API_URL yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable MATTERMOST_API_URL \"$MATTERMOST_API_URL\"\n\n  MATTERMOST_CHANNEL_ID=\"${MATTERMOST_CHANNEL_ID:-$(_readaccountconf_mutable MATTERMOST_CHANNEL_ID)}\"\n  if [ -z \"$MATTERMOST_CHANNEL_ID\" ]; then\n    _err \"You didn't specify a Mattermost channel id MATTERMOST_CHANNEL_ID yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable MATTERMOST_CHANNEL_ID \"$MATTERMOST_CHANNEL_ID\"\n\n  MATTERMOST_BOT_TOKEN=\"${MATTERMOST_BOT_TOKEN:-$(_readaccountconf_mutable MATTERMOST_BOT_TOKEN)}\"\n  if [ -z \"$MATTERMOST_BOT_TOKEN\" ]; then\n    _err \"You didn't specify a Mattermost bot API token MATTERMOST_BOT_TOKEN yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable MATTERMOST_BOT_TOKEN \"$MATTERMOST_BOT_TOKEN\"\n\n  _content=\"$(printf \"*%s*\\n%s\" \"$_subject\" \"$_content\" | _json_encode)\"\n  _data=\"{\\\"channel_id\\\": \\\"$MATTERMOST_CHANNEL_ID\\\", \"\n  _data=\"$_data\\\"message\\\": \\\"$_content\\\"}\"\n\n  export _H1=\"Authorization: Bearer $MATTERMOST_BOT_TOKEN\"\n  response=\"\"\n  if _post \"$_data\" \"$MATTERMOST_API_URL\" \"\" \"POST\" \"application/json; charset=utf-8\"; then\n    MATTERMOST_RESULT_OK=$(echo \"$response\" | _egrep_o 'create_at')\n    if [ \"$?\" = \"0\" ] && [ \"$MATTERMOST_RESULT_OK\" ]; then\n      _info \"mattermost send success.\"\n      return 0\n    fi\n  fi\n  _err \"mattermost send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/ntfy.sh",
    "content": "#!/usr/bin/env sh\n\n# support ntfy\n\n#NTFY_URL=\"https://ntfy.sh\"\n#NTFY_TOPIC=\"xxxxxxxxxxxxx\"\n#NTFY_TOKEN=\"xxxxxxxxxxxxx\"\n\nntfy_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  _priority_default=\"default\"\n  _priority_error=\"high\"\n\n  _tag_success=\"white_check_mark\"\n  _tag_error=\"warning\"\n  _tag_info=\"information_source\"\n\n  NTFY_URL=\"${NTFY_URL:-$(_readaccountconf_mutable NTFY_URL)}\"\n  if [ \"$NTFY_URL\" ]; then\n    _saveaccountconf_mutable NTFY_URL \"$NTFY_URL\"\n  fi\n\n  NTFY_TOPIC=\"${NTFY_TOPIC:-$(_readaccountconf_mutable NTFY_TOPIC)}\"\n  if [ \"$NTFY_TOPIC\" ]; then\n    _saveaccountconf_mutable NTFY_TOPIC \"$NTFY_TOPIC\"\n  fi\n\n  NTFY_TOKEN=\"${NTFY_TOKEN:-$(_readaccountconf_mutable NTFY_TOKEN)}\"\n  if [ \"$NTFY_TOKEN\" ]; then\n    _saveaccountconf_mutable NTFY_TOKEN \"$NTFY_TOKEN\"\n    export _H1=\"Authorization: Bearer $NTFY_TOKEN\"\n  fi\n\n  case \"$_statusCode\" in\n  0)\n    _priority=\"$_priority_default\"\n    _tag=\"$_tag_success\"\n    ;;\n  1)\n    _priority=\"$_priority_error\"\n    _tag=\"$_tag_error\"\n    ;;\n  2)\n    _priority=\"$_priority_default\"\n    _tag=\"$_tag_info\"\n    ;;\n  esac\n\n  export _H2=\"Priority: $_priority\"\n  export _H3=\"Tags: $_tag\"\n  export _H4=\"Title: $PROJECT_NAME: $_subject\"\n\n  _data=\"$_content\"\n  response=\"$(_post \"$_data\" \"$NTFY_URL/$NTFY_TOPIC\" \"\" \"POST\" \"\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"expires\"; then\n    _info \"ntfy event fired success.\"\n    return 0\n  fi\n\n  _err \"ntfy event fired error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/opsgenie.sh",
    "content": "#!/usr/bin/env sh\n\n#Support OpsGenie API integration\n\n#OPSGENIE_API_KEY=\"\" Required, opsgenie api key\n#OPSGENIE_REGION=\"\" Optional, opsgenie region, can be EU or US (default: US)\n#OPSGENIE_PRIORITY_SUCCESS=\"\" Optional, opsgenie priority for success (default: P5)\n#OPSGENIE_PRIORITY_ERROR=\"\" Optional, opsgenie priority for error (default: P2)\n#OPSGENIE_PRIORITY_SKIP=\"\" Optional, opsgenie priority for renew skipped (default: P5)\n\n_OPSGENIE_AVAIL_REGION=\"US,EU\"\n_OPSGENIE_AVAIL_PRIORITIES=\"P1,P2,P3,P4,P5\"\n\nopsgenie_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _status_code=\"$3\" #0: success, 1: error, 2($RENEW_SKIP): skipped\n\n  OPSGENIE_API_KEY=\"${OPSGENIE_API_KEY:-$(_readaccountconf_mutable OPSGENIE_API_KEY)}\"\n  if [ -z \"$OPSGENIE_API_KEY\" ]; then\n    OPSGENIE_API_KEY=\"\"\n    _err \"You didn't specify an OpsGenie API key OPSGENIE_API_KEY yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable OPSGENIE_API_KEY \"$OPSGENIE_API_KEY\"\n  export _H1=\"Authorization: GenieKey $OPSGENIE_API_KEY\"\n\n  OPSGENIE_REGION=\"${OPSGENIE_REGION:-$(_readaccountconf_mutable OPSGENIE_REGION)}\"\n  if [ -z \"$OPSGENIE_REGION\" ]; then\n    OPSGENIE_REGION=\"US\"\n    _info \"The OPSGENIE_REGION is not set, so use the default US as regeion.\"\n  elif ! _hasfield \"$_OPSGENIE_AVAIL_REGION\" \"$OPSGENIE_REGION\"; then\n    _err \"The OPSGENIE_REGION \\\"$OPSGENIE_REGION\\\" is not available, should be one of $_OPSGENIE_AVAIL_REGION\"\n    OPSGENIE_REGION=\"\"\n    return 1\n  else\n    _saveaccountconf_mutable OPSGENIE_REGION \"$OPSGENIE_REGION\"\n  fi\n\n  OPSGENIE_PRIORITY_SUCCESS=\"${OPSGENIE_PRIORITY_SUCCESS:-$(_readaccountconf_mutable OPSGENIE_PRIORITY_SUCCESS)}\"\n  if [ -z \"$OPSGENIE_PRIORITY_SUCCESS\" ]; then\n    OPSGENIE_PRIORITY_SUCCESS=\"P5\"\n    _info \"The OPSGENIE_PRIORITY_SUCCESS is not set, so use the default P5 as priority.\"\n  elif ! _hasfield \"$_OPSGENIE_AVAIL_PRIORITIES\" \"$OPSGENIE_PRIORITY_SUCCESS\"; then\n    _err \"The OPSGENIE_PRIORITY_SUCCESS \\\"$OPSGENIE_PRIORITY_SUCCESS\\\" is not available, should be one of $_OPSGENIE_AVAIL_PRIORITIES\"\n    OPSGENIE_PRIORITY_SUCCESS=\"\"\n    return 1\n  else\n    _saveaccountconf_mutable OPSGENIE_PRIORITY_SUCCESS \"$OPSGENIE_PRIORITY_SUCCESS\"\n  fi\n\n  OPSGENIE_PRIORITY_ERROR=\"${OPSGENIE_PRIORITY_ERROR:-$(_readaccountconf_mutable OPSGENIE_PRIORITY_ERROR)}\"\n  if [ -z \"$OPSGENIE_PRIORITY_ERROR\" ]; then\n    OPSGENIE_PRIORITY_ERROR=\"P2\"\n    _info \"The OPSGENIE_PRIORITY_ERROR is not set, so use the default P2 as priority.\"\n  elif ! _hasfield \"$_OPSGENIE_AVAIL_PRIORITIES\" \"$OPSGENIE_PRIORITY_ERROR\"; then\n    _err \"The OPSGENIE_PRIORITY_ERROR \\\"$OPSGENIE_PRIORITY_ERROR\\\" is not available, should be one of $_OPSGENIE_AVAIL_PRIORITIES\"\n    OPSGENIE_PRIORITY_ERROR=\"\"\n    return 1\n  else\n    _saveaccountconf_mutable OPSGENIE_PRIORITY_ERROR \"$OPSGENIE_PRIORITY_ERROR\"\n  fi\n\n  OPSGENIE_PRIORITY_SKIP=\"${OPSGENIE_PRIORITY_SKIP:-$(_readaccountconf_mutable OPSGENIE_PRIORITY_SKIP)}\"\n  if [ -z \"$OPSGENIE_PRIORITY_SKIP\" ]; then\n    OPSGENIE_PRIORITY_SKIP=\"P5\"\n    _info \"The OPSGENIE_PRIORITY_SKIP is not set, so use the default P5 as priority.\"\n  elif ! _hasfield \"$_OPSGENIE_AVAIL_PRIORITIES\" \"$OPSGENIE_PRIORITY_SKIP\"; then\n    _err \"The OPSGENIE_PRIORITY_SKIP \\\"$OPSGENIE_PRIORITY_SKIP\\\" is not available, should be one of $_OPSGENIE_AVAIL_PRIORITIES\"\n    OPSGENIE_PRIORITY_SKIP=\"\"\n    return 1\n  else\n    _saveaccountconf_mutable OPSGENIE_PRIORITY_SKIP \"$OPSGENIE_PRIORITY_SKIP\"\n  fi\n\n  case \"$OPSGENIE_REGION\" in\n  \"US\")\n    _opsgenie_url=\"https://api.opsgenie.com/v2/alerts\"\n    ;;\n  \"EU\")\n    _opsgenie_url=\"https://api.eu.opsgenie.com/v2/alerts\"\n    ;;\n  *)\n    _err \"opsgenie region error.\"\n    return 1\n    ;;\n  esac\n\n  case $_status_code in\n  0)\n    _priority=$OPSGENIE_PRIORITY_SUCCESS\n    ;;\n  1)\n    _priority=$OPSGENIE_PRIORITY_ERROR\n    ;;\n  2)\n    _priority=$OPSGENIE_PRIORITY_SKIP\n    ;;\n  *)\n    _priority=$OPSGENIE_PRIORITY_ERROR\n    ;;\n  esac\n\n  _subject_json=$(echo \"$_subject\" | _json_encode)\n  _content_json=$(echo \"$_content\" | _json_encode)\n  _subject_underscore=$(echo \"$_subject\" | sed 's/ /_/g')\n  _alias_json=$(echo \"acme.sh-$(hostname)-$_subject_underscore-$(date +%Y%m%d)\" | base64 --wrap=0 | _json_encode)\n\n  _data=\"{\n    \\\"message\\\": \\\"$_subject_json\\\",\n    \\\"alias\\\": \\\"$_alias_json\\\",\n    \\\"description\\\": \\\"$_content_json\\\",\n    \\\"tags\\\": [\n        \\\"acme.sh\\\",\n        \\\"host:$(hostname)\\\"\n    ],\n    \\\"entity\\\": \\\"$(hostname -f)\\\",\n    \\\"priority\\\": \\\"$_priority\\\"\n}\"\n\n  if response=$(_post \"$_data\" \"$_opsgenie_url\" \"\" \"\" \"application/json\"); then\n    if ! _contains \"$response\" error; then\n      _info \"opsgenie send success.\"\n      return 0\n    fi\n  fi\n  _err \"opsgenie send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/pop.sh",
    "content": "#!/usr/bin/env sh\n\n# support pop\n\npop_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  _err \"Not implemented yet.\"\n  return 1\n}\n"
  },
  {
    "path": "notify/postmark.sh",
    "content": "#!/usr/bin/env sh\n\n#Support postmarkapp.com API (https://postmarkapp.com/developer/user-guide/sending-email/sending-with-api)\n\n#POSTMARK_TOKEN=\"\"\n#POSTMARK_TO=\"xxxx@xxx.com\"\n#POSTMARK_FROM=\"xxxx@cccc.com\"\n\npostmark_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  POSTMARK_TOKEN=\"${POSTMARK_TOKEN:-$(_readaccountconf_mutable POSTMARK_TOKEN)}\"\n  if [ -z \"$POSTMARK_TOKEN\" ]; then\n    POSTMARK_TOKEN=\"\"\n    _err \"You didn't specify a POSTMARK api token POSTMARK_TOKEN yet .\"\n    _err \"You can get yours from here https://account.postmarkapp.com\"\n    return 1\n  fi\n  _saveaccountconf_mutable POSTMARK_TOKEN \"$POSTMARK_TOKEN\"\n\n  POSTMARK_TO=\"${POSTMARK_TO:-$(_readaccountconf_mutable POSTMARK_TO)}\"\n  if [ -z \"$POSTMARK_TO\" ]; then\n    POSTMARK_TO=\"\"\n    _err \"You didn't specify an email to POSTMARK_TO receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable POSTMARK_TO \"$POSTMARK_TO\"\n\n  POSTMARK_FROM=\"${POSTMARK_FROM:-$(_readaccountconf_mutable POSTMARK_FROM)}\"\n  if [ -z \"$POSTMARK_FROM\" ]; then\n    POSTMARK_FROM=\"\"\n    _err \"You didn't specify an email from POSTMARK_FROM receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable POSTMARK_FROM \"$POSTMARK_FROM\"\n\n  export _H1=\"Accept: application/json\"\n  export _H2=\"Content-Type: application/json\"\n  export _H3=\"X-Postmark-Server-Token: $POSTMARK_TOKEN\"\n\n  _content=\"$(echo \"$_content\" | _json_encode)\"\n  _data=\"{\\\"To\\\": \\\"$POSTMARK_TO\\\", \\\"From\\\": \\\"$POSTMARK_FROM\\\", \\\"Subject\\\": \\\"$_subject\\\", \\\"TextBody\\\": \\\"$_content\\\"}\"\n  if _post \"$_data\" \"https://api.postmarkapp.com/email\"; then\n    # shellcheck disable=SC2154\n    _message=$(printf \"%s\\n\" \"$response\" | _lower_case | _egrep_o \"\\\"message\\\":\\\"[^\\\"]*\\\"\" | cut -d : -f 2 | tr -d \\\" | head -n 1)\n    if [ \"$_message\" = \"ok\" ]; then\n      _info \"postmark send success.\"\n      return 0\n    fi\n  fi\n  _err \"postmark send error.\"\n  _err \"$response\"\n  return 1\n\n}\n"
  },
  {
    "path": "notify/pushbullet.sh",
    "content": "#!/usr/bin/env sh\n\n#Support for pushbullet.com's api. Push notification, notification sync and message platform for multiple platforms\n#PUSHBULLET_TOKEN=\"\" Required, pushbullet application token\n#PUSHBULLET_DEVICE=\"\" Optional, Specific device, ignore to send to all devices\n\nPUSHBULLET_URI=\"https://api.pushbullet.com/v2/pushes\"\npushbullet_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  PUSHBULLET_TOKEN=\"${PUSHBULLET_TOKEN:-$(_readaccountconf_mutable PUSHBULLET_TOKEN)}\"\n  if [ -z \"$PUSHBULLET_TOKEN\" ]; then\n    PUSHBULLET_TOKEN=\"\"\n    _err \"You didn't specify a Pushbullet application token yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable PUSHBULLET_TOKEN \"$PUSHBULLET_TOKEN\"\n\n  PUSHBULLET_DEVICE=\"${PUSHBULLET_DEVICE:-$(_readaccountconf_mutable PUSHBULLET_DEVICE)}\"\n  if [ -z \"$PUSHBULLET_DEVICE\" ]; then\n    _clearaccountconf_mutable PUSHBULLET_DEVICE\n  else\n    _saveaccountconf_mutable PUSHBULLET_DEVICE \"$PUSHBULLET_DEVICE\"\n  fi\n\n  export _H1=\"Content-Type: application/json\"\n  export _H2=\"Access-Token: ${PUSHBULLET_TOKEN}\"\n  _content=\"$(printf \"*%s*\\n\" \"$_content\" | _json_encode)\"\n  _subject=\"$(printf \"*%s*\\n\" \"$_subject\" | _json_encode)\"\n  _data=\"{\\\"type\\\": \\\"note\\\",\\\"title\\\": \\\"${_subject}\\\",\\\"body\\\": \\\"${_content}\\\",\\\"device_iden\\\": \\\"${PUSHBULLET_DEVICE}\\\"}\"\n  response=\"$(_post \"$_data\" \"$PUSHBULLET_URI\")\"\n\n  if [ \"$?\" != \"0\" ] || _contains \"$response\" \"\\\"error_code\\\"\"; then\n    _err \"PUSHBULLET send error.\"\n    _err \"$response\"\n    return 1\n  fi\n\n  _info \"PUSHBULLET send success.\"\n  return 0\n}\n"
  },
  {
    "path": "notify/pushover.sh",
    "content": "#!/usr/bin/env sh\n\n#Support for pushover.net's api. Push notification platform for multiple platforms\n#PUSHOVER_TOKEN=\"\" Required, pushover application token\n#PUSHOVER_USER=\"\" Required, pushover userkey\n#PUSHOVER_DEVICE=\"\" Optional, Specific device or devices by hostnames, joining multiples with a comma (such as device=iphone,nexus5)\n#PUSHOVER_PRIORITY=\"\" Optional, Lowest Priority (-2), Low Priority (-1), Normal Priority (0), High Priority (1)\n\nPUSHOVER_URI=\"https://api.pushover.net/1/messages.json\"\n\npushover_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  PUSHOVER_TOKEN=\"${PUSHOVER_TOKEN:-$(_readaccountconf_mutable PUSHOVER_TOKEN)}\"\n  if [ -z \"$PUSHOVER_TOKEN\" ]; then\n    PUSHOVER_TOKEN=\"\"\n    _err \"You didn't specify a PushOver application token yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable PUSHOVER_TOKEN \"$PUSHOVER_TOKEN\"\n\n  PUSHOVER_USER=\"${PUSHOVER_USER:-$(_readaccountconf_mutable PUSHOVER_USER)}\"\n  if [ -z \"$PUSHOVER_USER\" ]; then\n    PUSHOVER_USER=\"\"\n    _err \"You didn't specify a PushOver UserKey yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable PUSHOVER_USER \"$PUSHOVER_USER\"\n\n  PUSHOVER_DEVICE=\"${PUSHOVER_DEVICE:-$(_readaccountconf_mutable PUSHOVER_DEVICE)}\"\n  if [ \"$PUSHOVER_DEVICE\" ]; then\n    _saveaccountconf_mutable PUSHOVER_DEVICE \"$PUSHOVER_DEVICE\"\n  fi\n\n  PUSHOVER_PRIORITY=\"${PUSHOVER_PRIORITY:-$(_readaccountconf_mutable PUSHOVER_PRIORITY)}\"\n  if [ \"$PUSHOVER_PRIORITY\" ]; then\n    _saveaccountconf_mutable PUSHOVER_PRIORITY \"$PUSHOVER_PRIORITY\"\n  fi\n\n  PUSHOVER_SOUND=\"${PUSHOVER_SOUND:-$(_readaccountconf_mutable PUSHOVER_SOUND)}\"\n  if [ \"$PUSHOVER_SOUND\" ]; then\n    _saveaccountconf_mutable PUSHOVER_SOUND \"$PUSHOVER_SOUND\"\n  fi\n\n  export _H1=\"Content-Type: application/json\"\n  _content=\"$(printf \"%s\" \"$_content\" | _json_encode)\"\n  _subject=\"$(printf \"%s\" \"$_subject\" | _json_encode)\"\n  _data=\"{\\\"token\\\": \\\"$PUSHOVER_TOKEN\\\",\\\"user\\\": \\\"$PUSHOVER_USER\\\",\\\"title\\\": \\\"$_subject\\\",\\\"message\\\": \\\"$_content\\\",\\\"sound\\\": \\\"$PUSHOVER_SOUND\\\", \\\"device\\\": \\\"$PUSHOVER_DEVICE\\\", \\\"priority\\\": \\\"$PUSHOVER_PRIORITY\\\"}\"\n\n  response=\"$(_post \"$_data\" \"$PUSHOVER_URI\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"{\\\"status\\\":1\"; then\n    _info \"PUSHOVER send success.\"\n    return 0\n  fi\n\n  _err \"PUSHOVER send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/sendgrid.sh",
    "content": "#!/usr/bin/env sh\n\n#Support SENDGRID.com api\n\n#SENDGRID_API_KEY=\"\"\n#SENDGRID_TO=\"xxxx@xxx.com\"\n#SENDGRID_FROM=\"xxxx@cccc.com\"\n\nsendgrid_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  SENDGRID_API_KEY=\"${SENDGRID_API_KEY:-$(_readaccountconf_mutable SENDGRID_API_KEY)}\"\n  if [ -z \"$SENDGRID_API_KEY\" ]; then\n    SENDGRID_API_KEY=\"\"\n    _err \"You didn't specify a sendgrid api key SENDGRID_API_KEY yet .\"\n    _err \"You can get yours from here https://sendgrid.com\"\n    return 1\n  fi\n  _saveaccountconf_mutable SENDGRID_API_KEY \"$SENDGRID_API_KEY\"\n\n  SENDGRID_TO=\"${SENDGRID_TO:-$(_readaccountconf_mutable SENDGRID_TO)}\"\n  if [ -z \"$SENDGRID_TO\" ]; then\n    SENDGRID_TO=\"\"\n    _err \"You didn't specify an email to SENDGRID_TO receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable SENDGRID_TO \"$SENDGRID_TO\"\n\n  SENDGRID_FROM=\"${SENDGRID_FROM:-$(_readaccountconf_mutable SENDGRID_FROM)}\"\n  if [ -z \"$SENDGRID_FROM\" ]; then\n    SENDGRID_FROM=\"\"\n    _err \"You didn't specify an email to SENDGRID_FROM receive messages.\"\n    return 1\n  fi\n  _saveaccountconf_mutable SENDGRID_FROM \"$SENDGRID_FROM\"\n\n  SENDGRID_FROM_NAME=\"${SENDGRID_FROM_NAME:-$(_readaccountconf_mutable SENDGRID_FROM_NAME)}\"\n  _saveaccountconf_mutable SENDGRID_FROM_NAME \"$SENDGRID_FROM_NAME\"\n\n  export _H1=\"Authorization: Bearer $SENDGRID_API_KEY\"\n  export _H2=\"Content-Type: application/json\"\n\n  _content=\"$(echo \"$_content\" | _json_encode)\"\n\n  if [ -z \"$SENDGRID_FROM_NAME\" ]; then\n    _data=\"{\\\"personalizations\\\": [{\\\"to\\\": [{\\\"email\\\": \\\"$SENDGRID_TO\\\"}]}],\\\"from\\\": {\\\"email\\\": \\\"$SENDGRID_FROM\\\"},\\\"subject\\\": \\\"$_subject\\\",\\\"content\\\": [{\\\"type\\\": \\\"text/plain\\\", \\\"value\\\": \\\"$_content\\\"}]}\"\n  else\n    _data=\"{\\\"personalizations\\\": [{\\\"to\\\": [{\\\"email\\\": \\\"$SENDGRID_TO\\\"}]}],\\\"from\\\": {\\\"email\\\": \\\"$SENDGRID_FROM\\\", \\\"name\\\": \\\"$SENDGRID_FROM_NAME\\\"},\\\"subject\\\": \\\"$_subject\\\",\\\"content\\\": [{\\\"type\\\": \\\"text/plain\\\", \\\"value\\\": \\\"$_content\\\"}]}\"\n  fi\n  response=\"$(_post \"$_data\" \"https://api.sendgrid.com/v3/mail/send\")\"\n\n  if [ \"$?\" = \"0\" ] && [ -z \"$response\" ]; then\n    _info \"sendgrid send sccess.\"\n    return 0\n  fi\n\n  _err \"sendgrid send error.\"\n  _err \"$response\"\n  return 1\n\n}\n"
  },
  {
    "path": "notify/slack.sh",
    "content": "#!/usr/bin/env sh\n\n#Support Slack webhooks\n\n#SLACK_WEBHOOK_URL=\"\"\n#SLACK_CHANNEL=\"\"\n#SLACK_USERNAME=\"\"\n\nslack_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  SLACK_WEBHOOK_URL=\"${SLACK_WEBHOOK_URL:-$(_readaccountconf_mutable SLACK_WEBHOOK_URL)}\"\n  if [ -z \"$SLACK_WEBHOOK_URL\" ]; then\n    SLACK_WEBHOOK_URL=\"\"\n    _err \"You didn't specify a Slack webhook url SLACK_WEBHOOK_URL yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable SLACK_WEBHOOK_URL \"$SLACK_WEBHOOK_URL\"\n\n  SLACK_CHANNEL=\"${SLACK_CHANNEL:-$(_readaccountconf_mutable SLACK_CHANNEL)}\"\n  if [ -n \"$SLACK_CHANNEL\" ]; then\n    _saveaccountconf_mutable SLACK_CHANNEL \"$SLACK_CHANNEL\"\n  fi\n\n  SLACK_USERNAME=\"${SLACK_USERNAME:-$(_readaccountconf_mutable SLACK_USERNAME)}\"\n  if [ -n \"$SLACK_USERNAME\" ]; then\n    _saveaccountconf_mutable SLACK_USERNAME \"$SLACK_USERNAME\"\n  fi\n\n  export _H1=\"Content-Type: application/json\"\n\n  _content=\"$(printf \"*%s*\\n%s\" \"$_subject\" \"$_content\" | _json_encode)\"\n  _data=\"{\\\"text\\\": \\\"$_content\\\", \"\n  if [ -n \"$SLACK_CHANNEL\" ]; then\n    _data=\"$_data\\\"channel\\\": \\\"$SLACK_CHANNEL\\\", \"\n  fi\n  if [ -n \"$SLACK_USERNAME\" ]; then\n    _data=\"$_data\\\"username\\\": \\\"$SLACK_USERNAME\\\", \"\n  fi\n  _data=\"$_data\\\"mrkdwn\\\": \\\"true\\\"}\"\n\n  if _post \"$_data\" \"$SLACK_WEBHOOK_URL\"; then\n    # shellcheck disable=SC2154\n    if [ \"$response\" = \"ok\" ]; then\n      _info \"slack send success.\"\n      return 0\n    fi\n  fi\n  _err \"slack send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/slack_app.sh",
    "content": "#!/usr/bin/env sh\n\n#Support Slack APP notifications\n\n#SLACK_APP_CHANNEL=\"\"\n#SLACK_APP_TOKEN=\"\"\n\nslack_app_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  SLACK_APP_CHANNEL=\"${SLACK_APP_CHANNEL:-$(_readaccountconf_mutable SLACK_APP_CHANNEL)}\"\n  if [ -n \"$SLACK_APP_CHANNEL\" ]; then\n    _saveaccountconf_mutable SLACK_APP_CHANNEL \"$SLACK_APP_CHANNEL\"\n  fi\n\n  SLACK_APP_TOKEN=\"${SLACK_APP_TOKEN:-$(_readaccountconf_mutable SLACK_APP_TOKEN)}\"\n  if [ -n \"$SLACK_APP_TOKEN\" ]; then\n    _saveaccountconf_mutable SLACK_APP_TOKEN \"$SLACK_APP_TOKEN\"\n  fi\n\n  _content=\"$(printf \"*%s*\\n%s\" \"$_subject\" \"$_content\" | _json_encode)\"\n  _data=\"{\\\"text\\\": \\\"$_content\\\", \"\n  if [ -n \"$SLACK_APP_CHANNEL\" ]; then\n    _data=\"$_data\\\"channel\\\": \\\"$SLACK_APP_CHANNEL\\\", \"\n  fi\n  _data=\"$_data\\\"mrkdwn\\\": \\\"true\\\"}\"\n\n  export _H1=\"Authorization: Bearer $SLACK_APP_TOKEN\"\n\n  SLACK_APP_API_URL=\"https://slack.com/api/chat.postMessage\"\n  if _post \"$_data\" \"$SLACK_APP_API_URL\" \"\" \"POST\" \"application/json; charset=utf-8\"; then\n    # shellcheck disable=SC2154\n    SLACK_APP_RESULT_OK=$(echo \"$response\" | _egrep_o 'ok\" *: *true')\n    if [ \"$?\" = \"0\" ] && [ \"$SLACK_APP_RESULT_OK\" ]; then\n      _info \"slack send success.\"\n      return 0\n    fi\n  fi\n  _err \"slack send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/smtp.sh",
    "content": "#!/usr/bin/env sh\n\n# support smtp\n\n# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3358\n\n# This implementation uses either curl or Python (3 or 2.7).\n# (See also the \"mail\" notify hook, which supports other ways to send mail.)\n\n# SMTP_FROM=\"from@example.com\"  # required\n# SMTP_TO=\"to@example.com\"  # required\n# SMTP_HOST=\"smtp.example.com\"  # required\n# SMTP_PORT=\"25\"  # defaults to 25, 465 or 587 depending on SMTP_SECURE\n# SMTP_SECURE=\"tls\"  # one of \"none\", \"ssl\" (implicit TLS, TLS Wrapper), \"tls\" (explicit TLS, STARTTLS)\n# SMTP_USERNAME=\"\"  # set if SMTP server requires login\n# SMTP_PASSWORD=\"\"  # set if SMTP server requires login\n# SMTP_TIMEOUT=\"30\"  # seconds for SMTP operations to timeout\n# SMTP_BIN=\"/path/to/python_or_curl\"  # default finds first of python3, python2.7, python, pypy3, pypy, curl on PATH\n\nSMTP_SECURE_DEFAULT=\"tls\"\nSMTP_TIMEOUT_DEFAULT=\"30\"\n\n# subject content statuscode\nsmtp_send() {\n  SMTP_SUBJECT=\"$1\"\n  SMTP_CONTENT=\"$2\"\n  # UNUSED: _statusCode=\"$3\" # 0: success, 1: error 2($RENEW_SKIP): skipped\n\n  # Load and validate config:\n  SMTP_BIN=\"$(_readaccountconf_mutable_default SMTP_BIN)\"\n  if [ -n \"$SMTP_BIN\" ] && ! _exists \"$SMTP_BIN\"; then\n    _err \"SMTP_BIN '$SMTP_BIN' does not exist.\"\n    return 1\n  fi\n  if [ -z \"$SMTP_BIN\" ]; then\n    # Look for a command that can communicate with an SMTP server.\n    # (Please don't add sendmail, ssmtp, mutt, mail, or msmtp here.\n    # Those are already handled by the \"mail\" notify hook.)\n    for cmd in python3 python2.7 python pypy3 pypy curl; do\n      if _exists \"$cmd\"; then\n        SMTP_BIN=\"$cmd\"\n        break\n      fi\n    done\n    if [ -z \"$SMTP_BIN\" ]; then\n      _err \"The smtp notify-hook requires curl or Python, but can't find any.\"\n      _err 'If you have one of them, define SMTP_BIN=\"/path/to/curl_or_python\".'\n      _err 'Otherwise, see if you can use the \"mail\" notify-hook instead.'\n      return 1\n    fi\n  fi\n  _debug SMTP_BIN \"$SMTP_BIN\"\n  _saveaccountconf_mutable_default SMTP_BIN \"$SMTP_BIN\"\n\n  SMTP_FROM=\"$(_readaccountconf_mutable_default SMTP_FROM)\"\n  SMTP_FROM=\"$(_clean_email_header \"$SMTP_FROM\")\"\n  if [ -z \"$SMTP_FROM\" ]; then\n    _err \"You must define SMTP_FROM as the sender email address.\"\n    return 1\n  fi\n  if _email_has_display_name \"$SMTP_FROM\"; then\n    _err \"SMTP_FROM must be only a simple email address (sender@example.com).\"\n    _err \"Change your SMTP_FROM='$SMTP_FROM' to remove the display name.\"\n    return 1\n  fi\n  _debug SMTP_FROM \"$SMTP_FROM\"\n  _saveaccountconf_mutable_default SMTP_FROM \"$SMTP_FROM\"\n\n  SMTP_TO=\"$(_readaccountconf_mutable_default SMTP_TO)\"\n  SMTP_TO=\"$(_clean_email_header \"$SMTP_TO\")\"\n  if [ -z \"$SMTP_TO\" ]; then\n    _err \"You must define SMTP_TO as the recipient email address(es).\"\n    return 1\n  fi\n  if _email_has_display_name \"$SMTP_TO\"; then\n    _err \"SMTP_TO must be only simple email addresses (to@example.com,to2@example.com).\"\n    _err \"Change your SMTP_TO='$SMTP_TO' to remove the display name(s).\"\n    return 1\n  fi\n  _debug SMTP_TO \"$SMTP_TO\"\n  _saveaccountconf_mutable_default SMTP_TO \"$SMTP_TO\"\n\n  SMTP_HOST=\"$(_readaccountconf_mutable_default SMTP_HOST)\"\n  if [ -z \"$SMTP_HOST\" ]; then\n    _err \"You must define SMTP_HOST as the SMTP server hostname.\"\n    return 1\n  fi\n  _debug SMTP_HOST \"$SMTP_HOST\"\n  _saveaccountconf_mutable_default SMTP_HOST \"$SMTP_HOST\"\n\n  SMTP_SECURE=\"$(_readaccountconf_mutable_default SMTP_SECURE \"$SMTP_SECURE_DEFAULT\")\"\n  case \"$SMTP_SECURE\" in\n  \"none\") smtp_port_default=\"25\" ;;\n  \"ssl\") smtp_port_default=\"465\" ;;\n  \"tls\") smtp_port_default=\"587\" ;;\n  *)\n    _err \"Invalid SMTP_SECURE='$SMTP_SECURE'. It must be 'ssl', 'tls' or 'none'.\"\n    return 1\n    ;;\n  esac\n  _debug SMTP_SECURE \"$SMTP_SECURE\"\n  _saveaccountconf_mutable_default SMTP_SECURE \"$SMTP_SECURE\" \"$SMTP_SECURE_DEFAULT\"\n\n  SMTP_PORT=\"$(_readaccountconf_mutable_default SMTP_PORT \"$smtp_port_default\")\"\n  case \"$SMTP_PORT\" in\n  *[!0-9]*)\n    _err \"Invalid SMTP_PORT='$SMTP_PORT'. It must be a port number.\"\n    return 1\n    ;;\n  esac\n  _debug SMTP_PORT \"$SMTP_PORT\"\n  _saveaccountconf_mutable_default SMTP_PORT \"$SMTP_PORT\" \"$smtp_port_default\"\n\n  SMTP_USERNAME=\"$(_readaccountconf_mutable_default SMTP_USERNAME)\"\n  _debug SMTP_USERNAME \"$SMTP_USERNAME\"\n  _saveaccountconf_mutable_default SMTP_USERNAME \"$SMTP_USERNAME\"\n\n  SMTP_PASSWORD=\"$(_readaccountconf_mutable_default SMTP_PASSWORD)\"\n  _secure_debug SMTP_PASSWORD \"$SMTP_PASSWORD\"\n  _saveaccountconf_mutable_default SMTP_PASSWORD \"$SMTP_PASSWORD\"\n\n  SMTP_TIMEOUT=\"$(_readaccountconf_mutable_default SMTP_TIMEOUT \"$SMTP_TIMEOUT_DEFAULT\")\"\n  _debug SMTP_TIMEOUT \"$SMTP_TIMEOUT\"\n  _saveaccountconf_mutable_default SMTP_TIMEOUT \"$SMTP_TIMEOUT\" \"$SMTP_TIMEOUT_DEFAULT\"\n\n  SMTP_X_MAILER=\"$(_clean_email_header \"$PROJECT_NAME $VER --notify-hook smtp\")\"\n\n  # Run with --debug 2 (or above) to echo the transcript of the SMTP session.\n  # Careful: this may include SMTP_PASSWORD in plaintext!\n  if [ \"${DEBUG:-$DEBUG_LEVEL_NONE}\" -ge \"$DEBUG_LEVEL_2\" ]; then\n    SMTP_SHOW_TRANSCRIPT=\"True\"\n  else\n    SMTP_SHOW_TRANSCRIPT=\"\"\n  fi\n\n  SMTP_SUBJECT=$(_clean_email_header \"$SMTP_SUBJECT\")\n  _debug SMTP_SUBJECT \"$SMTP_SUBJECT\"\n  _debug SMTP_CONTENT \"$SMTP_CONTENT\"\n\n  # Send the message:\n  case \"$(basename \"$SMTP_BIN\")\" in\n  curl) _smtp_send=_smtp_send_curl ;;\n  py*) _smtp_send=_smtp_send_python ;;\n  *)\n    _err \"Can't figure out how to invoke '$SMTP_BIN'.\"\n    _err \"Check your SMTP_BIN setting.\"\n    return 1\n    ;;\n  esac\n\n  if ! smtp_output=\"$($_smtp_send)\"; then\n    _err \"Error sending message with $SMTP_BIN.\"\n    if [ -n \"$smtp_output\" ]; then\n      _err \"$smtp_output\"\n    fi\n    return 1\n  fi\n\n  return 0\n}\n\n# Strip CR and NL from text to prevent MIME header injection\n# text\n_clean_email_header() {\n  printf \"%s\" \"$(echo \"$1\" | tr -d \"\\r\\n\")\"\n}\n\n# Simple check for display name in an email address (< > or \")\n# email\n_email_has_display_name() {\n  _email=\"$1\"\n  echo \"$_email\" | grep -q -E '^.*[<>\"]'\n}\n\n##\n## curl smtp sending\n##\n\n# Send the message via curl using SMTP_* variables\n_smtp_send_curl() {\n  # Build curl args in $@\n  case \"$SMTP_SECURE\" in\n  none)\n    set -- --url \"smtp://${SMTP_HOST}:${SMTP_PORT}\"\n    ;;\n  ssl)\n    set -- --url \"smtps://${SMTP_HOST}:${SMTP_PORT}\"\n    ;;\n  tls)\n    set -- --url \"smtp://${SMTP_HOST}:${SMTP_PORT}\" --ssl-reqd\n    ;;\n  *)\n    # This will only occur if someone adds a new SMTP_SECURE option above\n    # without updating this code for it.\n    _err \"Unhandled SMTP_SECURE='$SMTP_SECURE' in _smtp_send_curl\"\n    _err \"Please re-run with --debug and report a bug.\"\n    return 1\n    ;;\n  esac\n\n  set -- \"$@\" \\\n    --upload-file - \\\n    --mail-from \"$SMTP_FROM\" \\\n    --max-time \"$SMTP_TIMEOUT\"\n\n  # Burst comma-separated $SMTP_TO into individual --mail-rcpt args.\n  _to=\"${SMTP_TO},\"\n  while [ -n \"$_to\" ]; do\n    _rcpt=\"${_to%%,*}\"\n    _to=\"${_to#*,}\"\n    set -- \"$@\" --mail-rcpt \"$_rcpt\"\n  done\n\n  _smtp_login=\"${SMTP_USERNAME}:${SMTP_PASSWORD}\"\n  if [ \"$_smtp_login\" != \":\" ]; then\n    set -- \"$@\" --user \"$_smtp_login\"\n  fi\n\n  if [ \"$SMTP_SHOW_TRANSCRIPT\" = \"True\" ]; then\n    set -- \"$@\" --verbose\n  else\n    set -- \"$@\" --silent --show-error\n  fi\n\n  raw_message=\"$(_smtp_raw_message)\"\n\n  _debug2 \"curl command:\" \"$SMTP_BIN\" \"$*\"\n  _debug2 \"raw_message:\\n$raw_message\"\n\n  echo \"$raw_message\" | \"$SMTP_BIN\" \"$@\"\n}\n\n# Output an RFC-822 / RFC-5322 email message using SMTP_* variables.\n# (This assumes variables have already been cleaned for use in email headers.)\n_smtp_raw_message() {\n  echo \"From: $SMTP_FROM\"\n  echo \"To: $SMTP_TO\"\n  echo \"Subject: $(_mime_encoded_word \"$SMTP_SUBJECT\")\"\n  echo \"Date: $(_rfc2822_date)\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"X-Mailer: $SMTP_X_MAILER\"\n  echo\n  echo \"$SMTP_CONTENT\"\n}\n\n# Convert text to RFC-2047 MIME \"encoded word\" format if it contains non-ASCII chars\n# text\n_mime_encoded_word() {\n  _text=\"$1\"\n  # (regex character ranges like [a-z] can be locale-dependent; enumerate ASCII chars to avoid that)\n  _ascii='] $`\"'\"[!#%&'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ~^_abcdefghijklmnopqrstuvwxyz{|}~-\"\n  if echo \"$_text\" | grep -q -E \"^.*[^$_ascii]\"; then\n    # At least one non-ASCII char; convert entire thing to encoded word\n    printf \"%s\" \"=?UTF-8?B?$(printf \"%s\" \"$_text\" | _base64)?=\"\n  else\n    # Just printable ASCII, no conversion needed\n    printf \"%s\" \"$_text\"\n  fi\n}\n\n# Output current date in RFC-2822 Section 3.3 format as required in email headers\n# (e.g., \"Mon, 15 Feb 2021 14:22:01 -0800\")\n_rfc2822_date() {\n  # Notes:\n  #   - this is deliberately not UTC, because it \"SHOULD express local time\" per spec\n  #   - the spec requires weekday and month in the C locale (English), not localized\n  #   - this date format specifier has been tested on Linux, Mac, Solaris and FreeBSD\n  _old_lc_time=\"$LC_TIME\"\n  LC_TIME=C\n  date +'%a, %-d %b %Y %H:%M:%S %z'\n  LC_TIME=\"$_old_lc_time\"\n}\n\n##\n## Python smtp sending\n##\n\n# Send the message via Python using SMTP_* variables\n_smtp_send_python() {\n  _debug \"Python version\" \"$(\"$SMTP_BIN\" --version 2>&1)\"\n\n  # language=Python\n  \"$SMTP_BIN\" <<PYTHON\n# This code is meant to work with either Python 2.7.x or Python 3.4+.\ntry:\n    try:\n        from email.message import EmailMessage\n        from email.policy import default as email_policy_default\n    except ImportError:\n        # Python 2 (or < 3.3)\n        from email.mime.text import MIMEText as EmailMessage\n        email_policy_default = None\n    from email.utils import formatdate as rfc2822_date\n    from smtplib import SMTP, SMTP_SSL, SMTPException\n    from socket import error as SocketError\nexcept ImportError as err:\n    print(\"A required Python standard package is missing. This system may have\"\n          \" a reduced version of Python unsuitable for sending mail: %s\" % err)\n    exit(1)\n\nshow_transcript = \"\"\"$SMTP_SHOW_TRANSCRIPT\"\"\" == \"True\"\n\nsmtp_host = \"\"\"$SMTP_HOST\"\"\"\nsmtp_port = int(\"\"\"$SMTP_PORT\"\"\")\nsmtp_secure = \"\"\"$SMTP_SECURE\"\"\"\nusername = \"\"\"$SMTP_USERNAME\"\"\"\npassword = \"\"\"$SMTP_PASSWORD\"\"\"\ntimeout=int(\"\"\"$SMTP_TIMEOUT\"\"\")  # seconds\nx_mailer=\"\"\"$SMTP_X_MAILER\"\"\"\n\nfrom_email=\"\"\"$SMTP_FROM\"\"\"\nto_emails=\"\"\"$SMTP_TO\"\"\"  # can be comma-separated\nsubject=\"\"\"$SMTP_SUBJECT\"\"\"\ncontent=\"\"\"$SMTP_CONTENT\"\"\"\n\ntry:\n    msg = EmailMessage(policy=email_policy_default)\n    msg.set_content(content)\nexcept (AttributeError, TypeError):\n    # Python 2 MIMEText\n    msg = EmailMessage(content)\nmsg[\"Subject\"] = subject\nmsg[\"From\"] = from_email\nmsg[\"To\"] = to_emails\nmsg[\"Date\"] = rfc2822_date(localtime=True)\nmsg[\"X-Mailer\"] = x_mailer\n\nsmtp = None\ntry:\n    if smtp_secure == \"ssl\":\n        smtp = SMTP_SSL(smtp_host, smtp_port, timeout=timeout)\n    else:\n        smtp = SMTP(smtp_host, smtp_port, timeout=timeout)\n    smtp.set_debuglevel(show_transcript)\n    if smtp_secure == \"tls\":\n        smtp.starttls()\n    if username or password:\n        smtp.login(username, password)\n    smtp.sendmail(msg[\"From\"], msg[\"To\"].split(\",\"), msg.as_string())\n\nexcept SMTPException as err:\n    # Output just the error (skip the Python stack trace) for SMTP errors\n    print(\"Error sending: %r\" % err)\n    exit(1)\n\nexcept SocketError as err:\n    print(\"Error connecting to %s:%d: %r\" % (smtp_host, smtp_port, err))\n    exit(1)\n\nfinally:\n    if smtp is not None:\n        smtp.quit()\nPYTHON\n}\n\n##\n## Conf helpers\n##\n\n#_readaccountconf_mutable_default name default_value\n# Given a name like MY_CONF:\n#   - if MY_CONF is set and non-empty, output $MY_CONF\n#   - if MY_CONF is set _empty_, output $default_value\n#     (lets user `export MY_CONF=` to clear previous saved value\n#     and return to default, without user having to know default)\n#   - otherwise if _readaccountconf_mutable MY_CONF is non-empty, return that\n#     (value of SAVED_MY_CONF from account.conf)\n#   - otherwise output $default_value\n_readaccountconf_mutable_default() {\n  _name=\"$1\"\n  _default_value=\"$2\"\n\n  eval \"_value=\\\"\\$$_name\\\"\"\n  eval \"_name_is_set=\\\"\\${${_name}+true}\\\"\"\n  # ($_name_is_set is \"true\" if $$_name is set to anything, including empty)\n  if [ -z \"${_value}\" ] && [ \"${_name_is_set:-}\" != \"true\" ]; then\n    _value=\"$(_readaccountconf_mutable \"$_name\")\"\n  fi\n  if [ -z \"${_value}\" ]; then\n    _value=\"$_default_value\"\n  fi\n  printf \"%s\" \"$_value\"\n}\n\n#_saveaccountconf_mutable_default name value default_value base64encode\n# Like _saveaccountconf_mutable, but if value is default_value\n# then _clearaccountconf_mutable instead\n_saveaccountconf_mutable_default() {\n  _name=\"$1\"\n  _value=\"$2\"\n  _default_value=\"$3\"\n  _base64encode=\"$4\"\n\n  if [ \"$_value\" != \"$_default_value\" ]; then\n    _saveaccountconf_mutable \"$_name\" \"$_value\" \"$_base64encode\"\n  else\n    _clearaccountconf_mutable \"$_name\"\n  fi\n}\n"
  },
  {
    "path": "notify/teams.sh",
    "content": "#!/usr/bin/env sh\n\n#Support Microsoft Teams webhooks\n\n#TEAMS_WEBHOOK_URL=\"\"\n\nteams_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  _color_success=\"Good\"\n  _color_danger=\"Attention\"\n  _color_muted=\"Accent\"\n\n  TEAMS_WEBHOOK_URL=\"${TEAMS_WEBHOOK_URL:-$(_readaccountconf_mutable TEAMS_WEBHOOK_URL)}\"\n  if [ -z \"$TEAMS_WEBHOOK_URL\" ]; then\n    TEAMS_WEBHOOK_URL=\"\"\n    _err \"You didn't specify a Microsoft Teams webhook url TEAMS_WEBHOOK_URL yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable TEAMS_WEBHOOK_URL \"$TEAMS_WEBHOOK_URL\"\n\n  export _H1=\"Content-Type: application/json\"\n\n  _subject=$(echo \"$_subject\" | _json_encode)\n  _content=$(echo \"$_content\" | _json_encode)\n\n  case \"$_statusCode\" in\n  0)\n    _color=\"${TEAMS_SUCCESS_COLOR:-$_color_success}\"\n    ;;\n  1)\n    _color=\"${TEAMS_ERROR_COLOR:-$_color_danger}\"\n    ;;\n  2)\n    _color=\"${TEAMS_SKIP_COLOR:-$_color_muted}\"\n    ;;\n  esac\n\n  _data=\"{\n    \\\"type\\\": \\\"message\\\",\n    \\\"attachments\\\": [\n        {\n            \\\"contentType\\\": \\\"application/vnd.microsoft.card.adaptive\\\",\n            \\\"contentUrl\\\": null,\n            \\\"content\\\": {\n                \\\"schema\\\": \\\"http://adaptivecards.io/schemas/adaptive-card.json\\\",\n                \\\"type\\\": \\\"AdaptiveCard\\\",\n                \\\"version\\\": \\\"1.2\\\",\n                \\\"body\\\": [\n                    {\n                        \\\"type\\\": \\\"TextBlock\\\",\n                        \\\"size\\\": \\\"large\\\",\n                        \\\"weight\\\": \\\"bolder\\\",\n                        \\\"wrap\\\": true,\n                        \\\"color\\\": \\\"$_color\\\",\n                        \\\"text\\\": \\\"$_subject\\\"\n                    },\n                    {\n                        \\\"type\\\": \\\"TextBlock\\\",\n                        \\\"text\\\": \\\"$_content\\\",\n                        \\\"wrap\\\": true\n                    }\n                ]\n            }\n        }\n    ]\n}\"\n\n  if response=$(_post \"$_data\" \"$TEAMS_WEBHOOK_URL\"); then\n    if ! _contains \"$response\" error; then\n      _info \"teams send success.\"\n      return 0\n    fi\n  fi\n  _err \"teams send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/telegram.sh",
    "content": "#!/usr/bin/env sh\n\n#Support Telegram Bots\n\n#TELEGRAM_BOT_APITOKEN=\"\"\n#TELEGRAM_BOT_CHATID=\"\"\n#TELEGRAM_BOT_URLBASE=\"\"\n#TELEGRAM_BOT_THREADID=\"\"\n\n# To get TELEGRAM_BOT_THREADID, just copy the link of the message from the thread.\n# https://t.me/c/123456789/XXX/1520 - XXX is the TELEGRAM_BOT_THREADID\n\ntelegram_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  TELEGRAM_BOT_APITOKEN=\"${TELEGRAM_BOT_APITOKEN:-$(_readaccountconf_mutable TELEGRAM_BOT_APITOKEN)}\"\n  if [ -z \"$TELEGRAM_BOT_APITOKEN\" ]; then\n    TELEGRAM_BOT_APITOKEN=\"\"\n    _err \"You didn't specify a Telegram BOT API Token TELEGRAM_BOT_APITOKEN yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable TELEGRAM_BOT_APITOKEN \"$TELEGRAM_BOT_APITOKEN\"\n\n  TELEGRAM_BOT_CHATID=\"${TELEGRAM_BOT_CHATID:-$(_readaccountconf_mutable TELEGRAM_BOT_CHATID)}\"\n  if [ -z \"$TELEGRAM_BOT_CHATID\" ]; then\n    TELEGRAM_BOT_CHATID=\"\"\n    _err \"You didn't specify a Telegram Chat id TELEGRAM_BOT_CHATID yet.\"\n    return 1\n  fi\n  _saveaccountconf_mutable TELEGRAM_BOT_CHATID \"$TELEGRAM_BOT_CHATID\"\n\n  TELEGRAM_BOT_THREADID=\"${TELEGRAM_BOT_THREADID:-$(_readaccountconf_mutable TELEGRAM_BOT_THREADID)}\"\n  if [ -z \"$TELEGRAM_BOT_THREADID\" ]; then\n    TELEGRAM_BOT_THREADID=\"\"\n  fi\n  _saveaccountconf_mutable TELEGRAM_BOT_THREADID \"$TELEGRAM_BOT_THREADID\"\n\n  TELEGRAM_BOT_URLBASE=\"${TELEGRAM_BOT_URLBASE:-$(_readaccountconf_mutable TELEGRAM_BOT_URLBASE)}\"\n  if [ -z \"$TELEGRAM_BOT_URLBASE\" ]; then\n    TELEGRAM_BOT_URLBASE=\"https://api.telegram.org\"\n  fi\n  _saveaccountconf_mutable TELEGRAM_BOT_URLBASE \"$TELEGRAM_BOT_URLBASE\"\n\n  _subject=\"$(printf \"%s\" \"$_subject\" | sed -E 's/([][()~`>#+=|{}.!*_\\\\-])/\\\\\\\\\\1/g')\"\n  _content=\"$(printf \"%s\" \"$_content\" | sed -E 's/([][()~`>#+=|{}.!*_\\\\-])/\\\\\\\\\\1/g')\"\n  _content=\"$(printf \"*%s*\\n%s\" \"$_subject\" \"$_content\" | _json_encode)\"\n  _data=\"{\\\"text\\\": \\\"$_content\\\", \"\n  _data=\"$_data\\\"chat_id\\\": \\\"$TELEGRAM_BOT_CHATID\\\", \"\n  if [ -n \"$TELEGRAM_BOT_THREADID\" ]; then\n    _data=\"$_data\\\"message_thread_id\\\": \\\"$TELEGRAM_BOT_THREADID\\\", \"\n  fi\n  _data=\"$_data\\\"parse_mode\\\": \\\"MarkdownV2\\\", \"\n  _data=\"$_data\\\"disable_web_page_preview\\\": \\\"1\\\"}\"\n\n  _debug \"$_data\"\n\n  export _H1=\"Content-Type: application/json\"\n  _telegram_bot_url=\"${TELEGRAM_BOT_URLBASE}/bot${TELEGRAM_BOT_APITOKEN}/sendMessage\"\n  if _post \"$_data\" \"$_telegram_bot_url\" >/dev/null; then\n    # shellcheck disable=SC2154\n    _message=$(printf \"%s\\n\" \"$response\" | sed -n 's/.*\"ok\":\\([^,]*\\).*/\\1/p')\n    if [ \"$_message\" = \"true\" ]; then\n      _info \"telegram send success.\"\n      return 0\n    fi\n  fi\n  _err \"telegram send error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/weixin_work.sh",
    "content": "#!/usr/bin/env sh\n\n#Support weixin work webhooks api\n\n#WEIXIN_WORK_WEBHOOK=\"xxxx\"\n\n#optional\n#WEIXIN_WORK_KEYWORD=\"yyyy\"\n\n#`WEIXIN_WORK_SIGNING_KEY`=\"SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx\"\n\n# subject  content statusCode\nweixin_work_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  WEIXIN_WORK_WEBHOOK=\"${WEIXIN_WORK_WEBHOOK:-$(_readaccountconf_mutable WEIXIN_WORK_WEBHOOK)}\"\n  if [ -z \"$WEIXIN_WORK_WEBHOOK\" ]; then\n    WEIXIN_WORK_WEBHOOK=\"\"\n    _err \"You didn't specify a weixin_work webhooks WEIXIN_WORK_WEBHOOK yet.\"\n    _err \"You can get yours from https://work.weixin.qq.com/api/doc/90000/90136/91770\"\n    return 1\n  fi\n  _saveaccountconf_mutable WEIXIN_WORK_WEBHOOK \"$WEIXIN_WORK_WEBHOOK\"\n\n  WEIXIN_WORK_KEYWORD=\"${WEIXIN_WORK_KEYWORD:-$(_readaccountconf_mutable WEIXIN_WORK_KEYWORD)}\"\n  if [ \"$WEIXIN_WORK_KEYWORD\" ]; then\n    _saveaccountconf_mutable WEIXIN_WORK_KEYWORD \"$WEIXIN_WORK_KEYWORD\"\n  fi\n\n  _content=$(echo \"$_content\" | _json_encode)\n  _subject=$(echo \"$_subject\" | _json_encode)\n  _data=\"{\\\"msgtype\\\": \\\"text\\\", \\\"text\\\": {\\\"content\\\": \\\"[$WEIXIN_WORK_KEYWORD]\\n$_subject\\n$_content\\\"}}\"\n\n  response=\"$(_post \"$_data\" \"$WEIXIN_WORK_WEBHOOK\" \"\" \"POST\" \"application/json\")\"\n\n  if [ \"$?\" = \"0\" ] && _contains \"$response\" \"errmsg\\\":\\\"ok\"; then\n    _info \"weixin_work webhooks event fired success.\"\n    return 0\n  fi\n\n  _err \"weixin_work webhooks event fired error.\"\n  _err \"$response\"\n  return 1\n}\n"
  },
  {
    "path": "notify/xmpp.sh",
    "content": "#!/usr/bin/env sh\n\n#Support xmpp via sendxmpp\n\n#XMPP_BIN=\"/usr/bin/sendxmpp\"\n#XMPP_BIN_ARGS=\"-n -t --tls-ca-path=/etc/ssl/certs\"\n#XMPP_TO=\"zzzz@example.com\"\n\nxmpp_send() {\n  _subject=\"$1\"\n  _content=\"$2\"\n  _statusCode=\"$3\" #0: success, 1: error 2($RENEW_SKIP): skipped\n  _debug \"_subject\" \"$_subject\"\n  _debug \"_content\" \"$_content\"\n  _debug \"_statusCode\" \"$_statusCode\"\n\n  XMPP_BIN=\"${XMPP_BIN:-$(_readaccountconf_mutable XMPP_BIN)}\"\n  if [ -n \"$XMPP_BIN\" ] && ! _exists \"$XMPP_BIN\"; then\n    _err \"It seems that the command $XMPP_BIN is not in path.\"\n    return 1\n  fi\n  _XMPP_BIN=$(_xmpp_bin)\n  if [ -n \"$XMPP_BIN\" ]; then\n    _saveaccountconf_mutable XMPP_BIN \"$XMPP_BIN\"\n  else\n    _clearaccountconf \"XMPP_BIN\"\n  fi\n\n  XMPP_BIN_ARGS=\"${XMPP_BIN_ARGS:-$(_readaccountconf_mutable XMPP_BIN_ARGS)}\"\n  if [ -n \"$XMPP_BIN_ARGS\" ]; then\n    _saveaccountconf_mutable XMPP_BIN_ARGS \"$XMPP_BIN_ARGS\"\n  else\n    _clearaccountconf \"XMPP_BIN_ARGS\"\n  fi\n\n  XMPP_TO=\"${XMPP_TO:-$(_readaccountconf_mutable XMPP_TO)}\"\n  if [ -n \"$XMPP_TO\" ]; then\n    if ! _xmpp_valid \"$XMPP_TO\"; then\n      _err \"It seems that the XMPP_TO=$XMPP_TO is not a valid xmpp address.\"\n      return 1\n    fi\n\n    _saveaccountconf_mutable XMPP_TO \"$XMPP_TO\"\n  fi\n\n  result=$({ _xmpp_message | eval \"$(_xmpp_cmnd)\"; } 2>&1)\n\n  # shellcheck disable=SC2181\n  if [ $? -ne 0 ]; then\n    _debug \"xmpp send error.\"\n    _err \"$result\"\n    return 1\n  fi\n\n  _debug \"xmpp send success.\"\n  return 0\n}\n\n_xmpp_bin() {\n  if [ -n \"$XMPP_BIN\" ]; then\n    _XMPP_BIN=\"$XMPP_BIN\"\n  elif _exists \"sendxmpp\"; then\n    _XMPP_BIN=\"sendxmpp\"\n  else\n    _err \"Please install sendxmpp first.\"\n    return 1\n  fi\n\n  echo \"$_XMPP_BIN\"\n}\n\n_xmpp_cmnd() {\n  case $(basename \"$_XMPP_BIN\") in\n  sendxmpp)\n    echo \"'$_XMPP_BIN' '$XMPP_TO' $XMPP_BIN_ARGS\"\n    ;;\n  *)\n    _err \"Command $XMPP_BIN is not supported, use sendxmpp.\"\n    return 1\n    ;;\n  esac\n}\n\n_xmpp_message() {\n  echo \"$_subject\"\n}\n\n_xmpp_valid() {\n  _contains \"$1\" \"@\"\n}\n"
  }
]