Browse Source

Merge branch 'acmesh-official:master' into master

pull/3959/head
Martin Arndt 2 years ago
committed by GitHub
parent
commit
8cc7c5349a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 719
      .github/workflows/DNS.yml
  2. 71
      .github/workflows/DragonFlyBSD.yml
  3. 27
      .github/workflows/FreeBSD.yml
  4. 10
      .github/workflows/Linux.yml
  5. 9
      .github/workflows/MacOS.yml
  6. 71
      .github/workflows/NetBSD.yml
  7. 76
      .github/workflows/OpenBSD.yml
  8. 24
      .github/workflows/PebbleStrict.yml
  9. 27
      .github/workflows/Solaris.yml
  10. 27
      .github/workflows/Ubuntu.yml
  11. 11
      .github/workflows/Windows.yml
  12. 16
      .github/workflows/dockerhub.yml
  13. 19
      .github/workflows/issue.yml
  14. 30
      .github/workflows/pr_dns.yml
  15. 30
      .github/workflows/pr_notify.yml
  16. 11
      .github/workflows/shellcheck.yml
  17. 13
      Dockerfile
  18. 52
      README.md
  19. 354
      acme.sh
  20. 185
      deploy/cpanel_uapi.sh
  21. 15
      deploy/gcore_cdn.sh
  22. 2
      deploy/gitlab.sh
  23. 16
      deploy/mailcow.sh
  24. 132
      deploy/proxmoxve.sh
  25. 4
      deploy/qiniu.sh
  26. 409
      deploy/ssh.sh
  27. 2
      deploy/synology_dsm.sh
  28. 21
      deploy/truenas.sh
  29. 79
      deploy/vault.sh
  30. 47
      deploy/vault_cli.sh
  31. 2
      dnsapi/dns_acmeproxy.sh
  32. 13
      dnsapi/dns_arvan.sh
  33. 50
      dnsapi/dns_aws.sh
  34. 248
      dnsapi/dns_bunny.sh
  35. 11
      dnsapi/dns_cf.sh
  36. 2
      dnsapi/dns_cloudns.sh
  37. 9
      dnsapi/dns_cpanel.sh
  38. 2
      dnsapi/dns_cyon.sh
  39. 5
      dnsapi/dns_dgon.sh
  40. 248
      dnsapi/dns_dnsservices.sh
  41. 8
      dnsapi/dns_dynv6.sh
  42. 2
      dnsapi/dns_edgedns.sh
  43. 2
      dnsapi/dns_gandi_livedns.sh
  44. 6
      dnsapi/dns_gcloud.sh
  45. 187
      dnsapi/dns_gcore.sh
  46. 62
      dnsapi/dns_gd.sh
  47. 173
      dnsapi/dns_googledomains.sh
  48. 98
      dnsapi/dns_huaweicloud.sh
  49. 4
      dnsapi/dns_infomaniak.sh
  50. 157
      dnsapi/dns_ipv64.sh
  51. 12
      dnsapi/dns_ispconfig.sh
  52. 303
      dnsapi/dns_kas.sh
  53. 2
      dnsapi/dns_kinghost.sh
  54. 147
      dnsapi/dns_la.sh
  55. 4
      dnsapi/dns_leaseweb.sh
  56. 1
      dnsapi/dns_miab.sh
  57. 16
      dnsapi/dns_mydnsjp.sh
  58. 6
      dnsapi/dns_namecheap.sh
  59. 2
      dnsapi/dns_namesilo.sh
  60. 59
      dnsapi/dns_nanelo.sh
  61. 10
      dnsapi/dns_netlify.sh
  62. 1
      dnsapi/dns_oci.sh
  63. 12
      dnsapi/dns_openstack.sh
  64. 4
      dnsapi/dns_opnsense.sh
  65. 8
      dnsapi/dns_ovh.sh
  66. 115
      dnsapi/dns_rage4.sh
  67. 4
      dnsapi/dns_regru.sh
  68. 94
      dnsapi/dns_selfhost.sh
  69. 2
      dnsapi/dns_servercow.sh
  70. 25
      dnsapi/dns_transip.sh
  71. 21
      dnsapi/dns_ultra.sh
  72. 24
      dnsapi/dns_vultr.sh
  73. 52
      dnsapi/dns_world4you.sh
  74. 264
      dnsapi/dns_yc.sh
  75. 45
      notify/slack_app.sh
  76. 4
      notify/smtp.sh

719
.github/workflows/DNS.yml

@ -1,254 +1,465 @@
name: DNS
on:
push:
paths:
- 'dnsapi/*.sh'
- '.github/workflows/DNS.yml'
pull_request:
branches:
- 'dev'
paths:
- 'dnsapi/*.sh'
- '.github/workflows/DNS.yml'
jobs:
CheckToken:
runs-on: ubuntu-latest
outputs:
hasToken: ${{ steps.step_one.outputs.hasToken }}
steps:
- name: Set the value
id: step_one
run: |
if [ "${{secrets.TokenName1}}" ] ; then
echo "::set-output name=hasToken::true"
else
echo "::set-output name=hasToken::false"
fi
- name: Check the value
run: echo ${{ steps.step_one.outputs.hasToken }}
Fail:
runs-on: ubuntu-latest
needs: CheckToken
if: "contains(needs.CheckToken.outputs.hasToken, 'false')"
steps:
- name: "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
run: |
echo "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
if [ "${{github.repository_owner}}" != "acmesh-official" ]; then
false
fi
Docker:
runs-on: ubuntu-latest
needs: CheckToken
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: 1
steps:
- uses: actions/checkout@v2
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Set env file
run: |
cd ../acmetest
if [ "${{ secrets.TokenName1}}" ] ; then
echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env
fi
if [ "${{ secrets.TokenName2}}" ] ; then
echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env
fi
if [ "${{ secrets.TokenName3}}" ] ; then
echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env
fi
if [ "${{ secrets.TokenName4}}" ] ; then
echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env
fi
if [ "${{ secrets.TokenName5}}" ] ; then
echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env
fi
echo "TEST_DNS_NO_WILDCARD" >> docker.env
echo "TEST_DNS_SLEEP" >> docker.env
- name: Run acmetest
run: cd ../acmetest && ./rundocker.sh testall
MacOS:
runs-on: macos-latest
needs: Docker
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: 1
steps:
- uses: actions/checkout@v2
- name: Install tools
run: brew install socat
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
fi
cd ../acmetest
./letest.sh
Windows:
runs-on: windows-latest
needs: MacOS
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: 1
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
- uses: actions/checkout@v2
- name: Install cygwin base packages with chocolatey
run: |
choco config get cacheLocation
choco install --no-progress cygwin
shell: cmd
- name: Install cygwin additional packages
run: |
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
shell: cmd
- name: Set ENV
shell: cmd
run: |
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
shell: bash
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
fi
cd ../acmetest
./letest.sh
FreeBSD:
runs-on: macos-10.15
needs: Windows
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: 1
steps:
- uses: actions/checkout@v2
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v0.1.4
with:
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: pkg install -y socat curl
usesh: true
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
fi
cd ../acmetest
./letest.sh
Solaris:
runs-on: macos-10.15
needs: FreeBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: 1
steps:
- uses: actions/checkout@v2
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v0.0.5
with:
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: pkgutil -y -i socat
run: |
pkg set-mediator -v -I default@1.1 openssl
export PATH=/usr/gnu/bin:$PATH
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
fi
cd ../acmetest
./letest.sh
name: DNS
on:
push:
paths:
- 'dnsapi/*.sh'
- '.github/workflows/DNS.yml'
pull_request:
branches:
- 'dev'
paths:
- 'dnsapi/*.sh'
- '.github/workflows/DNS.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
CheckToken:
runs-on: ubuntu-latest
outputs:
hasToken: ${{ steps.step_one.outputs.hasToken }}
steps:
- name: Set the value
id: step_one
run: |
if [ "${{secrets.TokenName1}}" ] ; then
echo "::set-output name=hasToken::true"
else
echo "::set-output name=hasToken::false"
fi
- name: Check the value
run: echo ${{ steps.step_one.outputs.hasToken }}
Fail:
runs-on: ubuntu-latest
needs: CheckToken
if: "contains(needs.CheckToken.outputs.hasToken, 'false')"
steps:
- name: "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
run: |
echo "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
if [ "${{github.repository_owner}}" != "acmesh-official" ]; then
false
fi
Docker:
runs-on: ubuntu-latest
needs: CheckToken
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Set env file
run: |
cd ../acmetest
if [ "${{ secrets.TokenName1}}" ] ; then
echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env
fi
if [ "${{ secrets.TokenName2}}" ] ; then
echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env
fi
if [ "${{ secrets.TokenName3}}" ] ; then
echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env
fi
if [ "${{ secrets.TokenName4}}" ] ; then
echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env
fi
if [ "${{ secrets.TokenName5}}" ] ; then
echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env
fi
- name: Run acmetest
run: cd ../acmetest && ./rundocker.sh testall
MacOS:
runs-on: macos-latest
needs: Docker
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Install tools
run: brew install socat
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
Windows:
runs-on: windows-latest
needs: MacOS
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
- uses: actions/checkout@v3
- name: Install cygwin base packages with chocolatey
run: |
choco config get cacheLocation
choco install --no-progress cygwin
shell: cmd
- name: Install cygwin additional packages
run: |
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
shell: cmd
- name: Set ENV
shell: cmd
run: |
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
shell: bash
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
FreeBSD:
runs-on: macos-12
needs: Windows
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v0
with:
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}}'
prepare: pkg install -y socat curl
usesh: true
copyback: false
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
OpenBSD:
runs-on: macos-12
needs: FreeBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v0
with:
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}}'
prepare: pkg_add socat curl
usesh: true
copyback: false
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
NetBSD:
runs-on: macos-12
needs: OpenBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v0
with:
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}}'
prepare: |
pkg_add curl socat
usesh: true
copyback: false
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
DragonFlyBSD:
runs-on: macos-12
needs: NetBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v0
with:
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}}'
prepare: |
pkg install -y curl socat
usesh: true
copyback: false
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh
Solaris:
runs-on: macos-12
needs: DragonFlyBSD
env:
TEST_DNS : ${{ secrets.TEST_DNS }}
TestingDomain: ${{ secrets.TestingDomain }}
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
CASE: le_test_dnsapi
TEST_LOCAL: 1
DEBUG: ${{ secrets.DEBUG }}
http_proxy: ${{ secrets.http_proxy }}
https_proxy: ${{ secrets.https_proxy }}
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Solaris doesn't accept the expired ISRG X1 root
TokenName1: ${{ secrets.TokenName1}}
TokenName2: ${{ secrets.TokenName2}}
TokenName3: ${{ secrets.TokenName3}}
TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}}
steps:
- uses: actions/checkout@v3
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v0
with:
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}}'
copyback: false
prepare: pkgutil -y -i socat
run: |
pkg set-mediator -v -I default@1.1 openssl
export PATH=/usr/gnu/bin:$PATH
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi
if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi
if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi
if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi
if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi
cd ../acmetest
./letest.sh

71
.github/workflows/DragonFlyBSD.yml

@ -0,0 +1,71 @@
name: DragonFlyBSD
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/DragonFlyBSD.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/DragonFlyBSD.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
DragonFlyBSD:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
steps:
- uses: actions/checkout@v3
- uses: vmactions/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v0
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
copyback: "false"
nat: |
"8080": "80"
prepare: |
pkg install -y curl socat
usesh: true
run: |
cd ../acmetest \
&& ./letest.sh

27
.github/workflows/FreeBSD.yml

@ -14,6 +14,11 @@ on:
- '*.sh'
- '.github/workflows/FreeBSD.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
FreeBSD:
@ -25,12 +30,18 @@ jobs:
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: macos-10.15
runs-on: macos-12
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -38,9 +49,10 @@ jobs:
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v3
- uses: vmactions/cf-tunnel@v0
id: tunnel
with:
protocol: http
@ -48,14 +60,15 @@ jobs:
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v0.1.5
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v0
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg install -y socat curl
prepare: pkg install -y socat curl wget
usesh: true
copyback: false
run: |
cd ../acmetest \
&& ./letest.sh

10
.github/workflows/Linux.yml

@ -15,6 +15,12 @@ on:
- '.github/workflows/Linux.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Linux:
@ -27,11 +33,11 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_ACME_Server: "LetsEncrypt.org_test"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Clone acmetest
run: |
cd .. \
&& git clone https://github.com/acmesh-official/acmetest.git \
&& git clone --depth=1 https://github.com/acmesh-official/acmetest.git \
&& cp -r acme.sh acmetest/
- name: Run acmetest
run: |

9
.github/workflows/MacOS.yml

@ -14,6 +14,11 @@ on:
- '*.sh'
- '.github/workflows/MacOS.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
MacOS:
@ -39,13 +44,13 @@ jobs:
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install tools
run: brew install socat
- name: Clone acmetest
run: |
cd .. \
&& git clone https://github.com/acmesh-official/acmetest.git \
&& git clone --depth=1 https://github.com/acmesh-official/acmetest.git \
&& cp -r acme.sh acmetest/
- name: Run acmetest
run: |

71
.github/workflows/NetBSD.yml

@ -0,0 +1,71 @@
name: NetBSD
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/NetBSD.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/NetBSD.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
NetBSD:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
steps:
- uses: actions/checkout@v3
- uses: vmactions/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v0
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
nat: |
"8080": "80"
prepare: |
pkg_add curl socat
usesh: true
copyback: false
run: |
cd ../acmetest \
&& ./letest.sh

76
.github/workflows/OpenBSD.yml

@ -0,0 +1,76 @@
name: OpenBSD
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/OpenBSD.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/OpenBSD.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
OpenBSD:
strategy:
matrix:
include:
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
CA_ECDSA: ${{ matrix.CA_ECDSA }}
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v3
- uses: vmactions/cf-tunnel@v0
id: tunnel
with:
protocol: http
port: 8080
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v0
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg_add socat curl wget
usesh: true
copyback: false
run: |
cd ../acmetest \
&& ./letest.sh

24
.github/workflows/PebbleStrict.yml

@ -13,6 +13,13 @@ on:
- '*.sh'
- '.github/workflows/PebbleStrict.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
PebbleStrict:
runs-on: ubuntu-latest
@ -26,7 +33,7 @@ jobs:
TEST_CA: "Pebble Intermediate CA"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install tools
run: sudo apt-get install -y socat
- name: Run Pebble
@ -34,15 +41,15 @@ jobs:
- name: Set up Pebble
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
run: cd ../acmetest && ./letest.sh
PebbleStrict_IPCert:
runs-on: ubuntu-latest
env:
TestingDomain: 10.30.50.1
ACME_DIRECTORY: https://localhost:14000/dir
TestingDomain: 1.23.45.67
TEST_ACME_Server: https://localhost:14000/dir
HTTPS_INSECURE: 1
Le_HTTPPort: 5002
Le_TLSPort: 5001
@ -51,12 +58,15 @@ jobs:
TEST_IPCERT: 1
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install tools
run: sudo apt-get install -y socat
- name: Run Pebble
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d
run: |
docker run --rm -itd --name=pebble \
-e PEBBLE_VA_ALWAYS_VALID=1 \
-p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
run: cd ../acmetest && ./letest.sh

27
.github/workflows/Solaris.yml

@ -15,6 +15,11 @@ on:
- '.github/workflows/Solaris.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Solaris:
strategy:
@ -25,12 +30,18 @@ jobs:
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: ""
runs-on: macos-10.15
runs-on: macos-12
env:
TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -38,9 +49,10 @@ jobs:
CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v3
- uses: vmactions/cf-tunnel@v0
id: tunnel
with:
protocol: http
@ -48,13 +60,14 @@ jobs:
- name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v0.0.5
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v0
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
copyback: "false"
nat: |
"8080": "80"
prepare: pkgutil -y -i socat curl
prepare: pkgutil -y -i socat curl wget
run: |
cd ../acmetest \
&& ./letest.sh

27
.github/workflows/Ubuntu.yml

@ -14,6 +14,11 @@ on:
- '*.sh'
- '.github/workflows/Ubuntu.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Ubuntu:
@ -25,6 +30,12 @@ jobs:
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
ACME_USE_WGET: 1
- TEST_ACME_Server: "ZeroSSL.com"
CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
CA: "ZeroSSL RSA Domain Secure Site CA"
@ -57,10 +68,11 @@ jobs:
NO_REVOKE: ${{ matrix.NO_REVOKE }}
TEST_IPCERT: ${{ matrix.TEST_IPCERT }}
TestingDomain: ${{ matrix.TestingDomain }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install tools
run: sudo apt-get install -y socat
run: sudo apt-get install -y socat wget
- name: Start StepCA
if: ${{ matrix.TEST_ACME_Server=='https://localhost:9000/acme/acme/directory' }}
run: |
@ -68,15 +80,20 @@ jobs:
-p 9000:9000 \
-e "DOCKER_STEPCA_INIT_NAME=Smallstep" \
-e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \
-e "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true" \
-e "DOCKER_STEPCA_INIT_PASSWORD=test" \
--name stepca \
smallstep/step-ca \
&& sleep 5 && docker exec stepca step ca provisioner add acme --type ACME \
smallstep/step-ca:0.23.1
sleep 5
docker exec stepca bash -c "echo test >test" \
&& docker exec stepca step ca provisioner add acme --type ACME --admin-subject step --admin-password-file=/home/step/test \
&& docker exec stepca kill -1 1 \
&& docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt"
- name: Clone acmetest
run: |
cd .. \
&& git clone https://github.com/acmesh-official/acmetest.git \
&& git clone --depth=1 https://github.com/acmesh-official/acmetest.git \
&& cp -r acme.sh acmetest/
- name: Run acmetest
run: |

11
.github/workflows/Windows.yml

@ -15,6 +15,11 @@ on:
- '.github/workflows/Windows.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Windows:
strategy:
@ -44,7 +49,7 @@ jobs:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install cygwin base packages with chocolatey
run: |
choco config get cacheLocation
@ -52,7 +57,7 @@ jobs:
shell: cmd
- name: Install cygwin additional packages
run: |
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,xxd
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,xxd
shell: cmd
- name: Set ENV
shell: cmd
@ -64,7 +69,7 @@ jobs:
echo "PATH=%PATH%"
- name: Clone acmetest
shell: cmd
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- name: Run acmetest
shell: cmd
run: cd ../acmetest && bash.exe -c ./letest.sh

16
.github/workflows/dockerhub.yml

@ -11,7 +11,11 @@ on:
- "Dockerfile"
- '.github/workflows/dockerhub.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
CheckToken:
runs-on: ubuntu-latest
@ -24,9 +28,9 @@ jobs:
id: step_one
run: |
if [ "$DOCKER_PASSWORD" ] ; then
echo "::set-output name=hasToken::true"
echo "hasToken=true" >>$GITHUB_OUTPUT
else
echo "::set-output name=hasToken::false"
echo "hasToken=false" >>$GITHUB_OUTPUT
fi
- name: Check the value
run: echo ${{ steps.step_one.outputs.hasToken }}
@ -37,11 +41,11 @@ jobs:
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
steps:
- name: checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: login to docker hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

19
.github/workflows/issue.yml

@ -0,0 +1,19 @@
name: "Update issues"
on:
issues:
types: [opened]
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
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."
})

30
.github/workflows/pr_dns.yml

@ -0,0 +1,30 @@
name: Check dns api
on:
pull_request_target:
types:
- opened
branches:
- 'dev'
paths:
- 'dnsapi/*.sh'
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
script: |
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `**Welcome**
Please make sure you're read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test).
Then reply on this message, otherwise, your code will not be reviewed or merged.
We look forward to reviewing your Pull request shortly ✨
`
})

30
.github/workflows/pr_notify.yml

@ -0,0 +1,30 @@
name: Check dns api
on:
pull_request_target:
types:
- opened
branches:
- 'dev'
paths:
- 'notify/*.sh'
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
script: |
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `**Welcome**
Please make sure you're read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify).
Then reply on this message, otherwise, your code will not be reviewed or merged.
We look forward to reviewing your Pull request shortly ✨
`
})

11
.github/workflows/shellcheck.yml

@ -13,20 +13,25 @@ on:
- '**.sh'
- '.github/workflows/shellcheck.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ShellCheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install Shellcheck
run: sudo apt-get install -y shellcheck
- name: DoShellcheck
run: shellcheck -V && shellcheck -e SC2181 **/*.sh && echo "shellcheck OK"
run: shellcheck -V && shellcheck -e SC2181 -e SC2089 **/*.sh && echo "shellcheck OK"
shfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install shfmt
run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt
- name: shfmt

13
Dockerfile

@ -1,4 +1,4 @@
FROM alpine:3.15
FROM alpine:3.17
RUN apk --no-cache add -f \
openssl \
@ -12,7 +12,8 @@ RUN apk --no-cache add -f \
oath-toolkit-oathtool \
tar \
libidn \
jq
jq \
cronie
ENV LE_CONFIG_HOME /acme.sh
@ -25,7 +26,7 @@ COPY ./ /install_acme.sh/
RUN 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/
RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null##' | crontab -
RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null#> /proc/1/fd/1 2>/proc/1/fd/2#' | crontab -
RUN for verb in help \
version \
@ -64,12 +65,10 @@ RUN for verb in help \
RUN printf "%b" '#!'"/usr/bin/env sh\n \
if [ \"\$1\" = \"daemon\" ]; then \n \
trap \"echo stop && killall crond && exit 0\" SIGTERM SIGINT \n \
crond && sleep infinity &\n \
wait \n \
exec crond -n -s -m off \n \
else \n \
exec -- \"\$@\"\n \
fi" >/entry.sh && chmod +x /entry.sh
fi\n" >/entry.sh && chmod +x /entry.sh
VOLUME /acme.sh

52
README.md

@ -1,10 +1,14 @@
# An ACME Shell script: acme.sh
[![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)
[![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)
[![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)
[![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)
[![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)
[![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)
[![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)
[![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)
![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg)
![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg)
@ -68,21 +72,23 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
|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
|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
|6|NA|pfsense
|7|NA|OpenBSD
|8|[![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
|9|[![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)|CentOS
|10|[![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
|11|[![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)
|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)|Archlinux
|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)|fedora
|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)|Kali Linux
|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)|Oracle Linux
|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)|Mageia
|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)|Gentoo Linux
|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)|ClearLinux
|19|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|20|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|21|[![](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)
|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
|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
|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
|10|[![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
|11|[![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)|CentOS
|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)|openSUSE
|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)|Alpine Linux (with curl)
|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)|Archlinux
|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)|fedora
|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)|Kali Linux
|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)|Oracle Linux
|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)|Mageia
|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)|Gentoo Linux
|10|[![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)|ClearLinux
|11|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|22|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|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)
Check our [testing project](https://github.com/acmesh-official/acmetest):
@ -355,10 +361,6 @@ Ok, it's done.
# 10. Issue ECC certificates
`Let's Encrypt` can now issue **ECDSA** certificates.
And we support them too!
Just set the `keylength` parameter with a prefix `ec-`.
For example:
@ -379,10 +381,12 @@ Please look at the `keylength` parameter above.
Valid values are:
1. **ec-256 (prime256v1, "ECDSA P-256")**
1. **ec-256 (prime256v1, "ECDSA P-256", which is the default key type)**
2. **ec-384 (secp384r1, "ECDSA P-384")**
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
4. **2048 (RSA2048)**
5. **3072 (RSA3072)**
6. **4096 (RSA4096)**
# 11. Issue Wildcard certificates
@ -503,6 +507,12 @@ Support this project with your organization. Your logo will show up here with a
<a href="https://opencollective.com/acmesh/organization/8/website"><img src="https://opencollective.com/acmesh/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/avatar.svg"></a>
#### Sponsors
[![quantumca-acmesh-logo](https://user-images.githubusercontent.com/8305679/183255712-634ee1db-bb61-4c03-bca0-bacce99e078c.svg)](https://www.quantumca.com.cn/?__utm_source=acmesh-donation)
# 19. License & Others
License is GPLv3

354
acme.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh
VER=3.0.5
VER=3.0.6
PROJECT_NAME="acme.sh"
@ -53,8 +53,8 @@ CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$
DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
DEFAULT_ACCOUNT_KEY_LENGTH=2048
DEFAULT_DOMAIN_KEY_LENGTH=2048
DEFAULT_ACCOUNT_KEY_LENGTH=ec-256
DEFAULT_DOMAIN_KEY_LENGTH=ec-256
DEFAULT_OPENSSL_BIN="openssl"
@ -91,6 +91,7 @@ END_CERT="-----END CERTIFICATE-----"
CONTENT_TYPE_JSON="application/jose+json"
RENEW_SKIP=2
CODE_DNS_MANUAL=3
B64CONF_START="__ACME_BASE64__START_"
B64CONF_END="__ACME_BASE64__END_"
@ -436,21 +437,13 @@ _secure_debug3() {
}
_upper_case() {
if _is_solaris; then
tr '[:lower:]' '[:upper:]'
else
# shellcheck disable=SC2018,SC2019
tr 'a-z' 'A-Z'
fi
# shellcheck disable=SC2018,SC2019
tr '[a-z]' '[A-Z]'
}
_lower_case() {
if _is_solaris; then
tr '[:upper:]' '[:lower:]'
else
# shellcheck disable=SC2018,SC2019
tr 'A-Z' 'a-z'
fi
# shellcheck disable=SC2018,SC2019
tr '[A-Z]' '[a-z]'
}
_startswith() {
@ -1193,7 +1186,7 @@ _createkey() {
_is_idn() {
_is_idn_d="$1"
_debug2 _is_idn_d "$_is_idn_d"
_idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-_')
_idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_')
_debug2 _idn_temp "$_idn_temp"
[ "$_idn_temp" ]
}
@ -1242,7 +1235,7 @@ _createcsr() {
_debug2 csr "$csr"
_debug2 csrconf "$csrconf"
printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\n" >"$csrconf"
printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\nextendedKeyUsage=serverAuth,clientAuth\n" >"$csrconf"
if [ "$acmeValidationv1" ]; then
domainlist="$(_idn "$domainlist")"
@ -1644,7 +1637,7 @@ _stat() {
#keyfile
_isRSA() {
keyfile=$1
if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" >/dev/null 2>&1; then
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
return 0
fi
return 1
@ -1653,7 +1646,7 @@ _isRSA() {
#keyfile
_isEcc() {
keyfile=$1
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:" >/dev/null 2>&1; then
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
return 0
fi
return 1
@ -1751,7 +1744,7 @@ _calcjwk() {
_debug3 x64 "$x64"
xend=$(_math "$xend" + 1)
y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-10000)"
y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-2048)"
_debug3 y "$y"
y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)"
@ -1859,9 +1852,15 @@ _inithttp() {
_ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
fi
if _contains "$(curl --help 2>&1)" "--globoff"; then
if _contains "$(curl --help 2>&1)" "--globoff" || _contains "$(curl --help curl 2>&1)" "--globoff"; then
_ACME_CURL="$_ACME_CURL -g "
fi
#don't use --fail-with-body
##from curl 7.76: return fail on HTTP errors but keep the body
#if _contains "$(curl --help http 2>&1)" "--fail-with-body"; then
# _ACME_CURL="$_ACME_CURL --fail-with-body "
#fi
fi
if [ -z "$_ACME_WGET" ] && _exists "wget"; then
@ -1879,11 +1878,11 @@ _inithttp() {
elif [ "$CA_BUNDLE" ]; then
_ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
fi
fi
#from wget 1.14: do not skip body on 404 error
if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--content-on-error"; then
_ACME_WGET="$_ACME_WGET --content-on-error "
#from wget 1.14: do not skip body on 404 error
if _contains "$(wget --help 2>&1)" "--content-on-error"; then
_ACME_WGET="$_ACME_WGET --content-on-error "
fi
fi
__HTTP_INITIALIZED=1
@ -2006,7 +2005,13 @@ _post() {
if [ "$_ret" != "0" ]; then
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
fi
_sed_i "s/^ *//g" "$HTTP_HEADER"
if _contains "$_WGET" " -d "; then
# Demultiplex wget debug output
cat "$HTTP_HEADER" >&2
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
fi
# remove leading whitespaces from header to match curl format
_sed_i 's/^ //g' "$HTTP_HEADER"
else
_ret="$?"
_err "Neither curl nor wget is found, can not do $httpmethod."
@ -2059,9 +2064,21 @@ _get() {
fi
_debug "_WGET" "$_WGET"
if [ "$onlyheader" ]; then
$_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1 | sed 's/^[ ]*//g'
_wget_out="$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)"
if _contains "$_WGET" " -d "; then
# Demultiplex wget debug output
echo "$_wget_out" >&2
echo "$_wget_out" | sed '/^[^ ][^ ]/d; /^ *$/d; s/^ //g' -
fi
else
$_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -O - "$url"
$_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O - "$url" 2>"$HTTP_HEADER"
if _contains "$_WGET" " -d "; then
# Demultiplex wget debug output
cat "$HTTP_HEADER" >&2
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
fi
# remove leading whitespaces from header to match curl format
_sed_i 's/^ //g' "$HTTP_HEADER"
fi
ret=$?
if [ "$ret" = "8" ]; then
@ -2212,6 +2229,20 @@ _send_signed_request() {
_debug3 _body "$_body"
fi
_retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
if [ "$code" = '503' ]; then
_sleep_overload_retry_sec=$_retryafter
if [ -z "$_sleep_overload_retry_sec" ]; then
_sleep_overload_retry_sec=5
fi
if [ $_sleep_overload_retry_sec -le 600 ]; then
_info "It seems the CA server is currently overloaded, let's wait and retry. Sleeping $_sleep_overload_retry_sec seconds."
_sleep $_sleep_overload_retry_sec
continue
else
_info "The retryafter=$_retryafter is too large > 600, not retry anymore."
fi
fi
if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then
_info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds."
_CACHED_NONCE=""
@ -2246,12 +2277,18 @@ _setopt() {
if [ ! -f "$__conf" ]; then
touch "$__conf"
fi
if [ -n "$(tail -c 1 <"$__conf")" ]; then
echo >>"$__conf"
fi
if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
_debug3 OK
if _contains "$__val" "&"; then
__val="$(echo "$__val" | sed 's/&/\\&/g')"
fi
if _contains "$__val" "|"; then
__val="$(echo "$__val" | sed 's/|/\\|/g')"
fi
text="$(cat "$__conf")"
printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
@ -2259,6 +2296,9 @@ _setopt() {
if _contains "$__val" "&"; then
__val="$(echo "$__val" | sed 's/&/\\&/g')"
fi
if _contains "$__val" "|"; then
__val="$(echo "$__val" | sed 's/|/\\|/g')"
fi
text="$(cat "$__conf")"
printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
@ -2332,6 +2372,26 @@ _readdomainconf() {
_read_conf "$DOMAIN_CONF" "$1"
}
#_migratedomainconf oldkey newkey base64encode
_migratedomainconf() {
_old_key="$1"
_new_key="$2"
_b64encode="$3"
_value=$(_readdomainconf "$_old_key")
if [ -z "$_value" ]; then
return 1 # oldkey is not found
fi
_savedomainconf "$_new_key" "$_value" "$_b64encode"
_cleardomainconf "$_old_key"
_debug "Domain config $_old_key has been migrated to $_new_key"
}
#_migratedeployconf oldkey newkey base64encode
_migratedeployconf() {
_migratedomainconf "$1" "SAVED_$2" "$3" ||
_migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found
}
#key value base64encode
_savedeployconf() {
_savedomainconf "SAVED_$1" "$2" "$3"
@ -2346,12 +2406,14 @@ _getdeployconf() {
if [ "$_rac_value" ]; then
if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
_debug2 "trim quotation marks"
eval "export $_rac_key=$_rac_value"
eval $_rac_key=$_rac_value
export $_rac_key
fi
return 0 # do nothing
fi
_saved=$(_readdomainconf "SAVED_$_rac_key")
eval "export $_rac_key=\"\$_saved\""
_saved="$(_readdomainconf "SAVED_$_rac_key")"
eval $_rac_key=\$_saved
export $_rac_key
}
#_saveaccountconf key value base64encode
@ -2568,7 +2630,7 @@ __initHome() {
_script_home="$(dirname "$_script")"
_debug "_script_home" "$_script_home"
if [ -d "$_script_home" ]; then
_SCRIPT_HOME="$_script_home"
export _SCRIPT_HOME="$_script_home"
else
_err "It seems the script home is not correct:$_script_home"
fi
@ -2815,9 +2877,10 @@ _initpath() {
if _isEccKey "$_ilength"; then
DOMAIN_PATH="$domainhomeecc"
else
elif [ -z "$__SELECTED_RSA_KEY" ]; then
if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then
_info "The domain '$domain' seems to have a ECC cert already, please add '$(__red "--ecc")' parameter if you want to use that cert."
_info "The domain '$domain' seems to have a ECC cert already, lets use ecc cert."
DOMAIN_PATH="$domainhomeecc"
fi
fi
_debug DOMAIN_PATH "$DOMAIN_PATH"
@ -3975,7 +4038,7 @@ _ns_purge_cf() {
#checks if cf server is available
_ns_is_available_cf() {
if _get "https://cloudflare-dns.com" "" 1 >/dev/null 2>&1; then
if _get "https://cloudflare-dns.com" "" 10 >/dev/null; then
return 0
else
return 1
@ -3983,7 +4046,7 @@ _ns_is_available_cf() {
}
_ns_is_available_google() {
if _get "https://dns.google" "" 1 >/dev/null 2>&1; then
if _get "https://dns.google" "" 10 >/dev/null; then
return 0
else
return 1
@ -3999,7 +4062,7 @@ _ns_lookup_google() {
}
_ns_is_available_ali() {
if _get "https://dns.alidns.com" "" 1 >/dev/null 2>&1; then
if _get "https://dns.alidns.com" "" 10 >/dev/null; then
return 0
else
return 1
@ -4015,7 +4078,7 @@ _ns_lookup_ali() {
}
_ns_is_available_dp() {
if _get "https://doh.pub" "" 1 >/dev/null 2>&1; then
if _get "https://doh.pub" "" 10 >/dev/null; then
return 0
else
return 1
@ -4030,8 +4093,7 @@ _ns_lookup_dp() {
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
}
#domain, type
_ns_lookup() {
_ns_select_doh() {
if [ -z "$DOH_USE" ]; then
_debug "Detect dns server first."
if _ns_is_available_cf; then
@ -4050,7 +4112,11 @@ _ns_lookup() {
_err "No doh"
fi
fi
}
#domain, type
_ns_lookup() {
_ns_select_doh
if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
_ns_lookup_cf "$@"
elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then
@ -4073,6 +4139,7 @@ __check_txt() {
_debug "_c_txtdomain" "$_c_txtdomain"
_debug "_c_aliasdomain" "$_c_aliasdomain"
_debug "_c_txt" "$_c_txt"
_ns_select_doh
_answers="$(_ns_lookup "$_c_aliasdomain" TXT)"
_contains "$_answers" "$_c_txt"
@ -4202,7 +4269,7 @@ _match_issuer() {
_isIPv4() {
for seg in $(echo "$1" | tr '.' ' '); do
_debug2 seg "$seg"
if [ "$(echo "$seg" | tr -d [0-9])" ]; then
if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then
#not all number
return 1
fi
@ -4403,6 +4470,7 @@ issue() {
_debug "_saved_account_key_hash is not changed, skip register account."
fi
export Le_Next_Domain_Key="$CERT_KEY_PATH.next"
if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then
_info "Signing from existing CSR."
else
@ -4415,14 +4483,30 @@ issue() {
fi
_debug "Read key length:$_key"
if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then
if ! createDomainKey "$_main_domain" "$_key_length"; then
_err "Create domain key error."
_clearup
_on_issue_err "$_post_hook"
if [ "$Le_ForceNewDomainKey" = "1" ] && [ -f "$Le_Next_Domain_Key" ]; then
_info "Using pre generated key: $Le_Next_Domain_Key"
cat "$Le_Next_Domain_Key" >"$CERT_KEY_PATH"
echo "" >"$Le_Next_Domain_Key"
else
if ! createDomainKey "$_main_domain" "$_key_length"; then
_err "Create domain key error."
_clearup
_on_issue_err "$_post_hook"
return 1
fi
fi
fi
if [ "$Le_ForceNewDomainKey" ]; then
_info "Generate next pre-generate key."
if [ ! -e "$Le_Next_Domain_Key" ]; then
touch "$Le_Next_Domain_Key"
chmod 600 "$Le_Next_Domain_Key"
fi
if ! _createkey "$_key_length" "$Le_Next_Domain_Key"; then
_err "Can not pre generate domain key"
return 1
fi
fi
if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then
_err "Create CSR error."
_clearup
@ -4762,7 +4846,9 @@ $_authorizations_map"
_err "Please add the TXT records to the domains, and re-run with --renew."
_on_issue_err "$_post_hook"
_clearup
return 1
# If asked to be in manual DNS mode, flag this exit with a separate
# error so it can be distinguished from other failures.
return $CODE_DNS_MANUAL
fi
fi
@ -4871,7 +4957,9 @@ $_authorizations_map"
_on_issue_err "$_post_hook" "$vlist"
return 1
fi
if ! chmod a+r "$wellknown_path/$token"; then
_debug "chmod failed, but we just continue."
fi
if [ ! "$usingApache" ]; then
if webroot_owner=$(_stat "$_currentRoot"); then
_debug "Changing owner/group of .well-known to $webroot_owner"
@ -5154,6 +5242,9 @@ $_authorizations_map"
[ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in: $(__green "$CA_CERT_PATH")"
[ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green "$CERT_FULLCHAIN_PATH")"
if [ "$Le_ForceNewDomainKey" ] && [ -e "$Le_Next_Domain_Key" ]; then
_info "Your pre-generated next key for future cert key change is in: $(__green "$Le_Next_Domain_Key")"
fi
Le_CertCreateTime=$(_time)
_savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime"
@ -5205,11 +5296,25 @@ $_authorizations_map"
_info "The domain is set to be valid to: $_valid_to"
_info "It can not be renewed automatically"
_info "See: $_VALIDITY_WIKI"
else
_now=$(_time)
_debug2 "_now" "$_now"
_lifetime=$(_math $Le_NextRenewTime - $_now)
_debug2 "_lifetime" "$_lifetime"
if [ $_lifetime -gt 86400 ]; then
#if lifetime is logner than one day, it will renew one day before
Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
else
#if lifetime is less than 24 hours, it will renew one hour before
Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
fi
fi
else
Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
fi
_savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
_savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
@ -5681,6 +5786,7 @@ deploy() {
return 1
fi
_debug2 DOMAIN_CONF "$DOMAIN_CONF"
. "$DOMAIN_CONF"
_savedomainconf Le_DeployHook "$_hooks"
@ -5714,7 +5820,8 @@ installcert() {
_savedomainconf "Le_RealKeyPath" "$_real_key"
_savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
_savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
export Le_ForceNewDomainKey="$(_readdomainconf Le_ForceNewDomainKey)"
export Le_Next_Domain_Key
_installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"
}
@ -5752,7 +5859,9 @@ _installcert() {
if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then
cp "$_real_cert" "$_backup_path/cert.bak"
fi
cat "$CERT_PATH" >"$_real_cert" || return 1
if [ "$CERT_PATH" != "$_real_cert" ]; then
cat "$CERT_PATH" >"$_real_cert" || return 1
fi
fi
if [ "$_real_ca" ]; then
@ -5764,7 +5873,9 @@ _installcert() {
if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then
cp "$_real_ca" "$_backup_path/ca.bak"
fi
cat "$CA_CERT_PATH" >"$_real_ca" || return 1
if [ "$CA_CERT_PATH" != "$_real_ca" ]; then
cat "$CA_CERT_PATH" >"$_real_ca" || return 1
fi
fi
fi
@ -5773,12 +5884,14 @@ _installcert() {
if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then
cp "$_real_key" "$_backup_path/key.bak"
fi
if [ -f "$_real_key" ]; then
cat "$CERT_KEY_PATH" >"$_real_key" || return 1
else
touch "$_real_key" || return 1
chmod 600 "$_real_key"
cat "$CERT_KEY_PATH" >"$_real_key" || return 1
if [ "$CERT_KEY_PATH" != "$_real_key" ]; then
if [ -f "$_real_key" ]; then
cat "$CERT_KEY_PATH" >"$_real_key" || return 1
else
touch "$_real_key" || return 1
chmod 600 "$_real_key"
cat "$CERT_KEY_PATH" >"$_real_key" || return 1
fi
fi
fi
@ -5787,7 +5900,9 @@ _installcert() {
if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then
cp "$_real_fullchain" "$_backup_path/fullchain.bak"
fi
cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
if [ "$_real_fullchain" != "$CERT_FULLCHAIN_PATH" ]; then
cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
fi
fi
if [ "$_reload_cmd" ]; then
@ -5798,6 +5913,8 @@ _installcert() {
export CA_CERT_PATH
export CERT_FULLCHAIN_PATH
export Le_Domain="$_main_domain"
export Le_ForceNewDomainKey
export Le_Next_Domain_Key
cd "$DOMAIN_PATH" && eval "$_reload_cmd"
); then
_info "$(__green "Reload success")"
@ -6029,12 +6146,28 @@ revoke() {
uri="${ACME_REVOKE_CERT}"
_info "Try account key first."
if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then
if [ -z "$response" ]; then
_info "Revoke success."
rm -f "$CERT_PATH"
cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked"
cat "$CSR_PATH" >"$CSR_PATH.revoked"
return 0
else
_err "Revoke error."
_debug "$response"
fi
fi
if [ -f "$CERT_KEY_PATH" ]; then
_info "Try domain key first."
_info "Try domain key."
if _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then
if [ -z "$response" ]; then
_info "Revoke success."
rm -f "$CERT_PATH"
cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked"
cat "$CSR_PATH" >"$CSR_PATH.revoked"
return 0
else
_err "Revoke error by domain key."
@ -6044,19 +6177,6 @@ revoke() {
else
_info "Domain key file doesn't exist."
fi
_info "Try account key."
if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then
if [ -z "$response" ]; then
_info "Revoke success."
rm -f "$CERT_PATH"
return 0
else
_err "Revoke error."
_debug "$response"
fi
fi
return 1
}
@ -6536,7 +6656,7 @@ install() {
if [ "$_accountemail" ]; then
_saveaccountconf "ACCOUNT_EMAIL" "$_accountemail"
fi
_saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
_info OK
}
@ -6630,6 +6750,13 @@ _send_notify() {
return 0
fi
_nsource="$NOTIFY_SOURCE"
if [ -z "$_nsource" ]; then
_nsource="$(hostname)"
fi
_nsubject="$_nsubject by $_nsource"
_send_err=0
for _n_hook in $(echo "$_nhooks" | tr ',' " "); do
_n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")"
@ -6684,11 +6811,12 @@ setnotify() {
_nhook="$1"
_nlevel="$2"
_nmode="$3"
_nsource="$4"
_initpath
if [ -z "$_nhook$_nlevel$_nmode" ]; then
_usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook <hookname>] [--notify-level <0|1|2|3>] [--notify-mode <0|1>]"
if [ -z "$_nhook$_nlevel$_nmode$_nsource" ]; then
_usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook <hookname>] [--notify-level <0|1|2|3>] [--notify-mode <0|1>] [--notify-source <hostname>]"
_usage "$_NOTIFY_WIKI"
return 1
fi
@ -6705,6 +6833,12 @@ setnotify() {
_saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE"
fi
if [ "$_nsource" ]; then
_info "Set notify source to: $_nsource"
export "NOTIFY_SOURCE=$_nsource"
_saveaccountconf "NOTIFY_SOURCE" "$NOTIFY_SOURCE"
fi
if [ "$_nhook" ]; then
_info "Set notify hook to: $_nhook"
if [ "$_nhook" = "$NO_VALUE" ]; then
@ -6767,37 +6901,37 @@ Commands:
Parameters:
-d, --domain <domain.tld> Specifies a domain, used to issue, renew or revoke etc.
--challenge-alias <domain.tld> The challenge domain alias for DNS alias mode.
See: $_DNS_ALIAS_WIKI
See: $_DNS_ALIAS_WIKI
--domain-alias <domain.tld> The domain alias for DNS alias mode.
See: $_DNS_ALIAS_WIKI
See: $_DNS_ALIAS_WIKI
--preferred-chain <chain> If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
If no match, the default offered chain will be used. (default: empty)
See: $_PREFERRED_CHAIN_WIKI
If no match, the default offered chain will be used. (default: empty)
See: $_PREFERRED_CHAIN_WIKI
--valid-to <date-time> Request the NotAfter field of the cert.
See: $_VALIDITY_WIKI
See: $_VALIDITY_WIKI
--valid-from <date-time> Request the NotBefore field of the cert.
See: $_VALIDITY_WIKI
See: $_VALIDITY_WIKI
-f, --force Force install, force cert renewal or override sudo restrictions.
--staging, --test Use staging server, for testing.
--debug [0|1|2|3] Output debug info. Defaults to 1 if argument is omitted.
--output-insecure Output all the sensitive messages.
By default all the credentials/sensitive messages are hidden from the output/debug/log for security.
By default all the credentials/sensitive messages are hidden from the output/debug/log for security.
-w, --webroot <directory> Specifies the web root folder for web root mode.
--standalone Use standalone mode.
--alpn Use standalone alpn mode.
--stateless Use stateless mode.
See: $_STATELESS_WIKI
See: $_STATELESS_WIKI
--apache Use apache mode.
--dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted.
See: $_DNS_API_WIKI
See: $_DNS_API_WIKI
--dnssleep <seconds> The time in seconds to wait for all the txt records to propagate in dns api mode.
It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.
It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.
-k, --keylength <bits> Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.
-ak, --accountkeylength <bits> Specifies the account key length: 2048, 3072, 4096
--log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted.
@ -6816,7 +6950,7 @@ Parameters:
--reloadcmd <command> Command to execute after issue/renew to reload the server.
--server <server_uri> ACME Directory Resource URI. (default: $DEFAULT_CA)
See: $_SERVER_WIKI
See: $_SERVER_WIKI
--accountconf <file> Specifies a customized account config file.
--home <directory> Specifies the home dir for $PROJECT_NAME.
@ -6835,7 +6969,7 @@ Parameters:
--ca-bundle <file> Specifies the path to the CA certificate bundle to verify api server's certificate.
--ca-path <directory> Specifies directory containing CA certificates in PEM format, used by wget or curl.
--no-cron Only valid for '--install' command, which means: do not install the default cron job.
In this case, the certs will not be renewed automatically.
In this case, the certs will not be renewed automatically.
--no-profile Only valid for '--install' command, which means: do not install aliases to user profile.
--no-color Do not output color text.
--force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.
@ -6853,20 +6987,21 @@ Parameters:
--openssl-bin <file> Specifies a custom openssl bin location.
--use-wget Force to use wget, if you have both curl and wget installed.
--yes-I-know-dns-manual-mode-enough-go-ahead-please Force use of dns manual mode.
See: $_DNS_MANUAL_WIKI
See: $_DNS_MANUAL_WIKI
-b, --branch <branch> Only valid for '--upgrade' command, specifies the branch name to upgrade to.
--notify-level <0|1|2|3> Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT.
0: disabled, no notification will be sent.
1: send notifications only when there is an error.
2: send notifications when a cert is successfully renewed, or there is an error.
3: send notifications when a cert is skipped, renewed, or error.
0: disabled, no notification will be sent.
1: send notifications only when there is an error.
2: send notifications when a cert is successfully renewed, or there is an error.
3: send notifications when a cert is skipped, renewed, or error.
--notify-mode <0|1> Set notification mode. Default value is $NOTIFY_MODE_DEFAULT.
0: Bulk mode. Send all the domain's notifications in one message(mail).
1: Cert mode. Send a message for every single cert.
0: Bulk mode. Send all the domain's notifications in one message(mail).
1: Cert mode. Send a message for every single cert.
--notify-hook <hookname> Set the notify hook
--notify-source <server name> Set the server name in the notification message
--revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command.
See: $_REVOKE_WIKI
See: $_REVOKE_WIKI
--password <password> Add a password to exported pfx file. Use with --to-pkcs12.
@ -6900,8 +7035,6 @@ installOnline() {
chmod +x $PROJECT_ENTRY
if ./$PROJECT_ENTRY --install "$@"; then
_info "Install success!"
_initpath
_saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
fi
cd ..
@ -7024,7 +7157,9 @@ _selectServer() {
_getCAShortName() {
caurl="$1"
if [ -z "$caurl" ]; then
caurl="$DEFAULT_CA"
#use letsencrypt as default value if the Le_API is empty
#this case can only come from the old upgrading.
caurl="$CA_LETSENCRYPT_V2"
fi
if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then
caurl="$CA_SSLCOM_RSA" #just hack to get the short name
@ -7141,6 +7276,7 @@ _process() {
_notify_hook=""
_notify_level=""
_notify_mode=""
_notify_source=""
_revoke_reason=""
_eab_kid=""
_eab_hmac_key=""
@ -7386,6 +7522,9 @@ _process() {
--keylength | -k)
_keylength="$2"
shift
if [ "$_keylength" ] && ! _isEccKey "$_keylength"; then
export __SELECTED_RSA_KEY=1
fi
;;
-ak | --accountkeylength)
_accountkeylength="$2"
@ -7421,17 +7560,17 @@ _process() {
shift
;;
--home)
LE_WORKING_DIR="$2"
export LE_WORKING_DIR="$(echo "$2" | sed 's|/$||')"
shift
;;
--cert-home | --certhome)
_certhome="$2"
CERT_HOME="$_certhome"
export CERT_HOME="$_certhome"
shift
;;
--config-home)
_confighome="$2"
LE_CONFIG_HOME="$_confighome"
export LE_CONFIG_HOME="$_confighome"
shift
;;
--useragent)
@ -7633,6 +7772,15 @@ _process() {
_notify_mode="$_nmode"
shift
;;
--notify-source)
_nsource="$2"
if _startswith "$_nsource" "-"; then
_err "'$_nsource' is not valid host name for '$1'"
return 1
fi
_notify_source="$_nsource"
shift
;;
--revoke-reason)
_revoke_reason="$2"
if _startswith "$_revoke_reason" "-"; then
@ -7787,7 +7935,7 @@ _process() {
createCSR "$_domain" "$_altdomains" "$_ecc"
;;
setnotify)
setnotify "$_notify_hook" "$_notify_level" "$_notify_mode"
setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source"
;;
setdefaultca)
setdefaultca

185
deploy/cpanel_uapi.sh

@ -3,18 +3,29 @@
# Uses command line uapi. --user option is needed only if run as root.
# Returns 0 when success.
#
# Configure DEPLOY_CPANEL_AUTO_<...> options to enable or restrict automatic
# detection of deployment targets through UAPI (if not set, defaults below are used.)
# - ENABLED : 'true' for multi-site / wildcard capability; otherwise single-site mode.
# - NOMATCH : 'true' to allow deployment to sites that do not match the certificate.
# - INCLUDE : Comma-separated list - sites must match this field.
# - EXCLUDE : Comma-separated list - sites must NOT match this field.
# INCLUDE/EXCLUDE both support non-lexical, glob-style matches using '*'
#
# Please note that I am no longer using Github. If you want to report an issue
# or contact me, visit https://forum.webseodesigners.com/web-design-seo-and-hosting-f16/
#
# Written by Santeri Kannisto <santeri.kannisto@webseodesigners.com>
# Public domain, 2017-2018
#export DEPLOY_CPANEL_USER=myusername
#
# export DEPLOY_CPANEL_USER=myusername
# export DEPLOY_CPANEL_AUTO_ENABLED='true'
# export DEPLOY_CPANEL_AUTO_NOMATCH='false'
# export DEPLOY_CPANEL_AUTO_INCLUDE='*'
# export DEPLOY_CPANEL_AUTO_EXCLUDE=''
######## Public functions #####################
#domain keyfile certfile cafile fullchain
cpanel_uapi_deploy() {
_cdomain="$1"
_ckey="$2"
@ -22,6 +33,9 @@ cpanel_uapi_deploy() {
_cca="$4"
_cfullchain="$5"
# re-declare vars inherited from acme.sh but not passed to make ShellCheck happy
: "${Le_Alt:=""}"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
@ -32,31 +46,166 @@ cpanel_uapi_deploy() {
_err "The command uapi is not found."
return 1
fi
# declare useful constants
uapi_error_response='status: 0'
# read cert and key files and urlencode both
_cert=$(_url_encode <"$_ccert")
_key=$(_url_encode <"$_ckey")
_debug _cert "$_cert"
_debug _key "$_key"
_debug2 _cert "$_cert"
_debug2 _key "$_key"
if [ "$(id -u)" = 0 ]; then
if [ -z "$DEPLOY_CPANEL_USER" ]; then
_getdeployconf DEPLOY_CPANEL_USER
# fallback to _readdomainconf for old installs
if [ -z "${DEPLOY_CPANEL_USER:=$(_readdomainconf DEPLOY_CPANEL_USER)}" ]; then
_err "It seems that you are root, please define the target user name: export DEPLOY_CPANEL_USER=username"
return 1
fi
_savedomainconf DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER"
_response=$(uapi --user="$DEPLOY_CPANEL_USER" SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
else
_response=$(uapi SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
_debug DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER"
_savedeployconf DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER"
_uapi_user="$DEPLOY_CPANEL_USER"
fi
error_response="status: 0"
if test "${_response#*$error_response}" != "$_response"; then
_err "Error in deploying certificate:"
_err "$_response"
return 1
# Load all AUTO envars and set defaults - see above for usage
__cpanel_initautoparam ENABLED 'true'
__cpanel_initautoparam NOMATCH 'false'
__cpanel_initautoparam INCLUDE '*'
__cpanel_initautoparam EXCLUDE ''
# Auto mode
if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then
# call API for site config
_response=$(uapi DomainInfo list_domains)
# exit if error in response
if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
_err "Error in deploying certificate - cannot retrieve sitelist:"
_err "\n$_response"
return 1
fi
# parse response to create site list
sitelist=$(__cpanel_parse_response "$_response")
_debug "UAPI sites found: $sitelist"
# filter sitelist using configured domains
# skip if NOMATCH is "true"
if [ "$DEPLOY_CPANEL_AUTO_NOMATCH" = "true" ]; then
_debug "DEPLOY_CPANEL_AUTO_NOMATCH is true"
_info "UAPI nomatch mode is enabled - Will not validate sites are valid for the certificate"
else
_debug "DEPLOY_CPANEL_AUTO_NOMATCH is false"
d="$(echo "${Le_Alt}," | sed -e "s/^$_cdomain,//" -e "s/,$_cdomain,/,/")"
d="$(echo "$_cdomain,$d" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\[\^\.\]\*/g')"
sitelist="$(echo "$sitelist" | grep -ix "$d")"
_debug2 "Matched UAPI sites: $sitelist"
fi
# filter sites that do not match $DEPLOY_CPANEL_AUTO_INCLUDE
_info "Applying sitelist filter DEPLOY_CPANEL_AUTO_INCLUDE: $DEPLOY_CPANEL_AUTO_INCLUDE"
sitelist="$(echo "$sitelist" | grep -ix "$(echo "$DEPLOY_CPANEL_AUTO_INCLUDE" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\.\*/g')")"
_debug2 "Remaining sites: $sitelist"
# filter sites that match $DEPLOY_CPANEL_AUTO_EXCLUDE
_info "Applying sitelist filter DEPLOY_CPANEL_AUTO_EXCLUDE: $DEPLOY_CPANEL_AUTO_EXCLUDE"
sitelist="$(echo "$sitelist" | grep -vix "$(echo "$DEPLOY_CPANEL_AUTO_EXCLUDE" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\.\*/g')")"
_debug2 "Remaining sites: $sitelist"
# counter for success / failure check
successes=0
if [ -n "$sitelist" ]; then
sitetotal="$(echo "$sitelist" | wc -l)"
_debug "$sitetotal sites to deploy"
else
sitetotal=0
_debug "No sites to deploy"
fi
# for each site: call uapi to publish cert and log result. Only return failure if all fail
for site in $sitelist; do
# call uapi to publish cert, check response for errors and log them.
if [ -n "$_uapi_user" ]; then
_response=$(uapi --user="$_uapi_user" SSL install_ssl domain="$site" cert="$_cert" key="$_key")
else
_response=$(uapi SSL install_ssl domain="$site" cert="$_cert" key="$_key")
fi
if [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
_err "Error in deploying certificate to $site:"
_err "$_response"
else
successes=$((successes + 1))
_debug "$_response"
_info "Succcessfully deployed to $site"
fi
done
# Raise error if all updates fail
if [ "$sitetotal" -gt 0 ] && [ "$successes" -eq 0 ]; then
_err "Could not deploy to any of $sitetotal sites via UAPI"
_debug "successes: $successes, sitetotal: $sitetotal"
return 1
fi
_info "Successfully deployed certificate to $successes of $sitetotal sites via UAPI"
return 0
else
# "classic" mode - will only try to deploy to the primary domain; will not check UAPI first
if [ -n "$_uapi_user" ]; then
_response=$(uapi --user="$_uapi_user" SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
else
_response=$(uapi SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
fi
if [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
_err "Error in deploying certificate:"
_err "$_response"
return 1
fi
_debug response "$_response"
_info "Certificate successfully deployed"
return 0
fi
}
######## Private functions #####################
# Internal utility to process YML from UAPI - looks at main_domain, sub_domains, addon domains and parked domains
#[response]
__cpanel_parse_response() {
if [ $# -gt 0 ]; then resp="$*"; else resp="$(cat)"; fi
echo "$resp" |
sed -En \
-e 's/\r$//' \
-e 's/^( *)([_.[:alnum:]]+) *: *(.*)/\1,\2,\3/p' \
-e 's/^( *)- (.*)/\1,-,\2/p' |
awk -F, '{
level = length($1)/2;
section[level] = $2;
for (i in section) {if (i > level) {delete section[i]}}
if (length($3) > 0) {
prefix="";
for (i=0; i < level; i++)
{ prefix = (prefix)(section[i])("/") }
printf("%s%s=%s\n", prefix, $2, $3);
}
}' |
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p'
}
# Load parameter by prefix+name - fallback to default if not set, and save to config
#pname pdefault
__cpanel_initautoparam() {
pname="$1"
pdefault="$2"
pkey="DEPLOY_CPANEL_AUTO_$pname"
_debug response "$_response"
_info "Certificate successfully deployed"
return 0
_getdeployconf "$pkey"
[ -n "$(eval echo "\"\$$pkey\"")" ] || eval "$pkey=\"$pdefault\""
_debug2 "$pkey" "$(eval echo "\"\$$pkey\"")"
_savedeployconf "$pkey" "$(eval echo "\"\$$pkey\"")"
}

15
deploy/gcore_cdn.sh

@ -1,10 +1,11 @@
#!/usr/bin/env sh
# Here is the script to deploy the cert to G-Core CDN service (https://gcorelabs.com/ru/) using the G-Core Labs API (https://docs.gcorelabs.com/cdn/).
# 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).
# Returns 0 when success.
#
# Written by temoffey <temofffey@gmail.com>
# Public domain, 2019
# Update by DreamOfIce <admin@dreamofice.cn> in 2023
#export DEPLOY_GCORE_CDN_USERNAME=myusername
#export DEPLOY_GCORE_CDN_PASSWORD=mypassword
@ -56,7 +57,7 @@ gcore_cdn_deploy() {
_request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
_debug _request "$_request"
export _H1="Content-Type:application/json"
_response=$(_post "$_request" "https://api.gcdn.co/auth/jwt/login")
_response=$(_post "$_request" "https://api.gcore.com/auth/jwt/login")
_debug _response "$_response"
_regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
_debug _regex "$_regex"
@ -69,8 +70,8 @@ gcore_cdn_deploy() {
fi
_info "Find CDN resource with cname $_cdomain"
export _H2="Authorization:Token $_token"
_response=$(_get "https://api.gcdn.co/resources")
export _H2="Authorization:Bearer $_token"
_response=$(_get "https://api.gcore.com/cdn/resources")
_debug _response "$_response"
_regex="\"primary_resource\":null},"
_debug _regex "$_regex"
@ -102,7 +103,7 @@ gcore_cdn_deploy() {
_date=$(date "+%d.%m.%Y %H:%M:%S")
_request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}"
_debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/sslData")
_response=$(_post "$_request" "https://api.gcore.com/cdn/sslData")
_debug _response "$_response"
_regex=".*\"id\":\([0-9]*\).*$"
_debug _regex "$_regex"
@ -117,7 +118,7 @@ gcore_cdn_deploy() {
_info "Update CDN resource"
_request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}"
_debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/resources/$_resourceId" '' "PUT")
_response=$(_post "$_request" "https://api.gcore.com/cdn/resources/$_resourceId" '' "PUT")
_debug _response "$_response"
_regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex"
@ -133,7 +134,7 @@ gcore_cdn_deploy() {
_info "Not found old SSL certificate"
else
_info "Delete old SSL certificate"
_response=$(_post '' "https://api.gcdn.co/sslData/$_sslDataOld" '' "DELETE")
_response=$(_post '' "https://api.gcore.com/cdn/sslData/$_sslDataOld" '' "DELETE")
_debug _response "$_response"
fi

2
deploy/gitlab.sh

@ -67,7 +67,7 @@ gitlab_deploy() {
error_response="error"
if test "${_response#*$error_response}" != "$_response"; then
if test "${_response#*"$error_response"}" != "$_response"; then
_err "Error in deploying certificate:"
_err "$_response"
return 1

16
deploy/mailcow.sh

@ -44,30 +44,20 @@ mailcow_deploy() {
return 1
fi
# ECC or RSA
length=$(_readdomainconf Le_Keylength)
if _isEccKey "$length"; then
_info "ECC key type detected"
_cert_name_prefix="ecdsa-"
else
_info "RSA key type detected"
_cert_name_prefix=""
fi
_info "Copying key and cert"
_real_key="$_ssl_path/${_cert_name_prefix}key.pem"
_real_key="$_ssl_path/key.pem"
if ! cat "$_ckey" >"$_real_key"; then
_err "Error: write key file to: $_real_key"
return 1
fi
_real_fullchain="$_ssl_path/${_cert_name_prefix}cert.pem"
_real_fullchain="$_ssl_path/cert.pem"
if ! cat "$_cfullchain" >"$_real_fullchain"; then
_err "Error: write cert file to: $_real_fullchain"
return 1
fi
DEFAULT_MAILCOW_RELOAD="docker restart \$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow)"
DEFAULT_MAILCOW_RELOAD="docker restart \$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow --filter name=postfix-mailcow)"
_reload="${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}"
_info "Run reload: $_reload"

132
deploy/proxmoxve.sh

@ -0,0 +1,132 @@
#!/usr/bin/env sh
# Deploy certificates to a proxmox virtual environment node using the API.
#
# Environment variables that can be set are:
# `DEPLOY_PROXMOXVE_SERVER`: The hostname of the proxmox ve node. Defaults to
# _cdomain.
# `DEPLOY_PROXMOXVE_SERVER_PORT`: The port number the management interface is on.
# Defaults to 8006.
# `DEPLOY_PROXMOXVE_NODE_NAME`: The name of the node we'll be connecting to.
# Defaults to the host portion of the server
# domain name.
# `DEPLOY_PROXMOXVE_USER`: The user we'll connect as. Defaults to root.
# `DEPLOY_PROXMOXVE_USER_REALM`: The authentication realm the user authenticates
# with. Defaults to pam.
# `DEPLOY_PROXMOXVE_API_TOKEN_NAME`: The name of the API token created for the
# user account. Defaults to acme.
# `DEPLOY_PROXMOXVE_API_TOKEN_KEY`: The API token. Required.
proxmoxve_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug2 _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
# "Sane" defaults.
_getdeployconf DEPLOY_PROXMOXVE_SERVER
if [ -z "$DEPLOY_PROXMOXVE_SERVER" ]; then
_target_hostname="$_cdomain"
else
_target_hostname="$DEPLOY_PROXMOXVE_SERVER"
_savedeployconf DEPLOY_PROXMOXVE_SERVER "$DEPLOY_PROXMOXVE_SERVER"
fi
_debug2 DEPLOY_PROXMOXVE_SERVER "$_target_hostname"
_getdeployconf DEPLOY_PROXMOXVE_SERVER_PORT
if [ -z "$DEPLOY_PROXMOXVE_SERVER_PORT" ]; then
_target_port="8006"
else
_target_port="$DEPLOY_PROXMOXVE_SERVER_PORT"
_savedeployconf DEPLOY_PROXMOXVE_SERVER_PORT "$DEPLOY_PROXMOXVE_SERVER_PORT"
fi
_debug2 DEPLOY_PROXMOXVE_SERVER_PORT "$_target_port"
_getdeployconf DEPLOY_PROXMOXVE_NODE_NAME
if [ -z "$DEPLOY_PROXMOXVE_NODE_NAME" ]; then
_node_name=$(echo "$_target_hostname" | cut -d. -f1)
else
_node_name="$DEPLOY_PROXMOXVE_NODE_NAME"
_savedeployconf DEPLOY_PROXMOXVE_NODE_NAME "$DEPLOY_PROXMOXVE_NODE_NAME"
fi
_debug2 DEPLOY_PROXMOXVE_NODE_NAME "$_node_name"
# Complete URL.
_target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/${_node_name}/certificates/custom"
_debug TARGET_URL "$_target_url"
# More "sane" defaults.
_getdeployconf DEPLOY_PROXMOXVE_USER
if [ -z "$DEPLOY_PROXMOXVE_USER" ]; then
_proxmoxve_user="root"
else
_proxmoxve_user="$DEPLOY_PROXMOXVE_USER"
_savedeployconf DEPLOY_PROXMOXVE_USER "$DEPLOY_PROXMOXVE_USER"
fi
_debug2 DEPLOY_PROXMOXVE_USER "$_proxmoxve_user"
_getdeployconf DEPLOY_PROXMOXVE_USER_REALM
if [ -z "$DEPLOY_PROXMOXVE_USER_REALM" ]; then
_proxmoxve_user_realm="pam"
else
_proxmoxve_user_realm="$DEPLOY_PROXMOXVE_USER_REALM"
_savedeployconf DEPLOY_PROXMOXVE_USER_REALM "$DEPLOY_PROXMOXVE_USER_REALM"
fi
_debug2 DEPLOY_PROXMOXVE_USER_REALM "$_proxmoxve_user_realm"
_getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME
if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_NAME" ]; then
_proxmoxve_api_token_name="acme"
else
_proxmoxve_api_token_name="$DEPLOY_PROXMOXVE_API_TOKEN_NAME"
_savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME "$DEPLOY_PROXMOXVE_API_TOKEN_NAME"
fi
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_NAME "$_proxmoxve_api_token_name"
# This is required.
_getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY
if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_KEY" ]; then
_err "API key not provided."
return 1
else
_proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
_savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
fi
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY _proxmoxve_api_token_key
# PVE API Token header value. Used in "Authorization: PVEAPIToken".
_proxmoxve_header_api_token="${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}"
_debug2 "Auth Header" _proxmoxve_header_api_token
# Ugly. I hate putting heredocs inside functions because heredocs don't
# account for whitespace correctly but it _does_ work and is several times
# cleaner than anything else I had here.
#
# This dumps the json payload to a variable that should be passable to the
# _psot function.
_json_payload=$(
cat <<HEREDOC
{
"certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')",
"key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')",
"node":"$_node_name",
"restart":"1",
"force":"1"
}
HEREDOC
)
_debug2 Payload "$_json_payload"
# Push certificates to server.
export _HTTPS_INSECURE=1
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
_post "$_json_payload" "$_target_url" "" POST "application/json"
}

4
deploy/qiniu.sh

@ -53,7 +53,7 @@ qiniu_deploy() {
sslcert_access_token="$(_make_access_token "$sslcert_path")"
_debug sslcert_access_token "$sslcert_access_token"
export _H1="Authorization: QBox $sslcert_access_token"
sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64 "multiline")
sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64)
if ! _contains "$sslcert_response" "certID"; then
_err "Error in creating certificate:"
@ -75,7 +75,7 @@ qiniu_deploy() {
update_access_token="$(_make_access_token "$update_path")"
_debug update_access_token "$update_access_token"
export _H1="Authorization: QBox $update_access_token"
update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64 "multiline")
update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64)
if _contains "$update_response" "error"; then
_err "Error in updating domain $domain httpsconf:"

409
deploy/ssh.sh

@ -14,7 +14,7 @@
# The following examples are for QNAP NAS running QTS 4.2
# export DEPLOY_SSH_CMD="" # defaults to "ssh -T"
# export DEPLOY_SSH_USER="admin" # required
# export DEPLOY_SSH_SERVER="qnap" # defaults to domain name
# export DEPLOY_SSH_SERVER="host1 host2:8022 192.168.0.1:9022" # defaults to domain name, support multiple servers with optional port
# export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem"
# export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem"
# export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem"
@ -23,6 +23,8 @@
# export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value
# export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy
# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
# export DEPLOY_SSH_USE_SCP="" yes or no, default to no
# export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q"
#
######## Public functions #####################
@ -42,72 +44,134 @@ ssh_deploy() {
_debug _cfullchain "$_cfullchain"
# USER is required to login by SSH to remote host.
_migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER
_getdeployconf DEPLOY_SSH_USER
_debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
if [ -z "$DEPLOY_SSH_USER" ]; then
if [ -z "$Le_Deploy_ssh_user" ]; then
_err "DEPLOY_SSH_USER not defined."
return 1
fi
else
Le_Deploy_ssh_user="$DEPLOY_SSH_USER"
_savedomainconf Le_Deploy_ssh_user "$Le_Deploy_ssh_user"
_err "DEPLOY_SSH_USER not defined."
return 1
fi
_savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
# SERVER is optional. If not provided then use _cdomain
_migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER
_getdeployconf DEPLOY_SSH_SERVER
_debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
if [ -n "$DEPLOY_SSH_SERVER" ]; then
Le_Deploy_ssh_server="$DEPLOY_SSH_SERVER"
_savedomainconf Le_Deploy_ssh_server "$Le_Deploy_ssh_server"
elif [ -z "$Le_Deploy_ssh_server" ]; then
Le_Deploy_ssh_server="$_cdomain"
if [ -z "$DEPLOY_SSH_SERVER" ]; then
DEPLOY_SSH_SERVER="$_cdomain"
fi
_savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
# CMD is optional. If not provided then use ssh
_migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD
_getdeployconf DEPLOY_SSH_CMD
_debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
if [ -n "$DEPLOY_SSH_CMD" ]; then
Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD"
_savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd"
elif [ -z "$Le_Deploy_ssh_cmd" ]; then
Le_Deploy_ssh_cmd="ssh -T"
if [ -z "$DEPLOY_SSH_CMD" ]; then
DEPLOY_SSH_CMD="ssh -T"
fi
_savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
# BACKUP is optional. If not provided then default to previously saved value or yes.
_migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP
_getdeployconf DEPLOY_SSH_BACKUP
_debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then
Le_Deploy_ssh_backup="no"
elif [ -z "$Le_Deploy_ssh_backup" ] || [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
Le_Deploy_ssh_backup="yes"
if [ -z "$DEPLOY_SSH_BACKUP" ]; then
DEPLOY_SSH_BACKUP="yes"
fi
_savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup"
_savedeployconf DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
# BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
_migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH
_getdeployconf DEPLOY_SSH_BACKUP_PATH
_debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
if [ -n "$DEPLOY_SSH_BACKUP_PATH" ]; then
Le_Deploy_ssh_backup_path="$DEPLOY_SSH_BACKUP_PATH"
elif [ -z "$Le_Deploy_ssh_backup_path" ]; then
Le_Deploy_ssh_backup_path=".acme_ssh_deploy"
if [ -z "$DEPLOY_SSH_BACKUP_PATH" ]; then
DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy"
fi
_savedomainconf Le_Deploy_ssh_backup_path "$Le_Deploy_ssh_backup_path"
_savedeployconf DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
# MULTI_CALL is optional. If not provided then default to previously saved
# value (which may be undefined... equivalent to "no").
_migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL
_getdeployconf DEPLOY_SSH_MULTI_CALL
_debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
Le_Deploy_ssh_multi_call="yes"
_savedomainconf Le_Deploy_ssh_multi_call "$Le_Deploy_ssh_multi_call"
elif [ "$DEPLOY_SSH_MULTI_CALL" = "no" ]; then
Le_Deploy_ssh_multi_call=""
_cleardomainconf Le_Deploy_ssh_multi_call
if [ -z "$DEPLOY_SSH_MULTI_CALL" ]; then
DEPLOY_SSH_MULTI_CALL="no"
fi
_savedeployconf DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
# KEYFILE is optional.
# If provided then private key will be copied to provided filename.
_migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE
_getdeployconf DEPLOY_SSH_KEYFILE
_debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
_savedeployconf DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
fi
# CERTFILE is optional.
# If provided then certificate will be copied or appended to provided filename.
_migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE
_getdeployconf DEPLOY_SSH_CERTFILE
_debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
_savedeployconf DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
fi
# CAFILE is optional.
# If provided then CA intermediate certificate will be copied or appended to provided filename.
_migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE
_getdeployconf DEPLOY_SSH_CAFILE
_debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
if [ -n "$DEPLOY_SSH_CAFILE" ]; then
_savedeployconf DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
fi
# FULLCHAIN is optional.
# If provided then fullchain certificate will be copied or appended to provided filename.
_migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN
_getdeployconf DEPLOY_SSH_FULLCHAIN
_debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
_savedeployconf DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
fi
# REMOTE_CMD is optional.
# If provided then this command will be executed on remote host.
_migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD
_getdeployconf DEPLOY_SSH_REMOTE_CMD
_debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
_savedeployconf DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
fi
# USE_SCP is optional. If not provided then default to previously saved
# value (which may be undefined... equivalent to "no").
_getdeployconf DEPLOY_SSH_USE_SCP
_debug2 DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
if [ -z "$DEPLOY_SSH_USE_SCP" ]; then
DEPLOY_SSH_USE_SCP="no"
fi
_savedeployconf DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
# SCP_CMD is optional. If not provided then use scp
_getdeployconf DEPLOY_SSH_SCP_CMD
_debug2 DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
if [ -z "$DEPLOY_SSH_SCP_CMD" ]; then
DEPLOY_SSH_SCP_CMD="scp -q"
fi
_savedeployconf DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
DEPLOY_SSH_MULTI_CALL="yes"
_info "Using scp as alternate method for copying files. Multicall Mode is implicit"
elif [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
_info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
else
_info "Required commands batched and sent in single call to remote host"
fi
_deploy_ssh_servers=$Le_Deploy_ssh_server
for Le_Deploy_ssh_server in $_deploy_ssh_servers; do
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
_ssh_deploy
done
}
@ -117,16 +181,25 @@ _ssh_deploy() {
_cmdstr=""
_backupprefix=""
_backupdir=""
_local_cert_file=""
_local_ca_file=""
_local_full_file=""
_info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
_info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
else
_info "Required commands batched and sent in single call to remote host"
fi
case $DEPLOY_SSH_SERVER in
*:*)
_host=${DEPLOY_SSH_SERVER%:*}
_port=${DEPLOY_SSH_SERVER##*:}
;;
*)
_host=$DEPLOY_SSH_SERVER
_port=
;;
esac
if [ "$Le_Deploy_ssh_backup" = "yes" ]; then
_backupprefix="$Le_Deploy_ssh_backup_path/$_cdomain-backup"
_info "Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port"
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
_backupprefix="$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup"
_backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
# run cleanup on the backup directory, erase all older
# than 180 days (15552000 seconds).
@ -138,7 +211,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
_cmdstr="mkdir -p $_backupdir; $_cmdstr"
_info "Backup of old certificate files will be placed in remote directory $_backupdir"
_info "Backup directories erased after 180 days."
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
@ -146,129 +219,184 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
fi
fi
# KEYFILE is optional.
# If provided then private key will be copied to provided filename.
_getdeployconf DEPLOY_SSH_KEYFILE
_debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
Le_Deploy_ssh_keyfile="$DEPLOY_SSH_KEYFILE"
_savedomainconf Le_Deploy_ssh_keyfile "$Le_Deploy_ssh_keyfile"
fi
if [ -n "$Le_Deploy_ssh_keyfile" ]; then
if [ "$Le_Deploy_ssh_backup" = "yes" ]; then
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_keyfile $_backupdir >/dev/null;"
_cmdstr="$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $Le_Deploy_ssh_keyfile;"
_info "will copy private key to remote file $Le_Deploy_ssh_keyfile"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
# copy new key into file.
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
# scp the file
if ! _scp_remote_cmd "$_ckey" "$DEPLOY_SSH_KEYFILE"; then
return $_err_code
fi
_cmdstr=""
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;"
_info "will copy private key to remote file $DEPLOY_SSH_KEYFILE"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
fi
# CERTFILE is optional.
# If provided then certificate will be copied or appended to provided filename.
_getdeployconf DEPLOY_SSH_CERTFILE
_debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
_savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
fi
if [ -n "$Le_Deploy_ssh_certfile" ]; then
_pipe=">"
if [ "$Le_Deploy_ssh_certfile" = "$Le_Deploy_ssh_keyfile" ]; then
if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
# if filename is same as previous file then append.
_pipe=">>"
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_certfile $_backupdir >/dev/null;"
_cmdstr="$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $Le_Deploy_ssh_certfile;"
_info "will copy certificate to remote file $Le_Deploy_ssh_certfile"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
# scp the file
_local_cert_file=$(_mktemp)
if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
cat "$_ckey" >>"$_local_cert_file"
fi
cat "$_ccert" >>"$_local_cert_file"
if ! _scp_remote_cmd "$_local_cert_file" "$DEPLOY_SSH_CERTFILE"; then
return $_err_code
fi
_cmdstr=""
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;"
_info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
fi
# CAFILE is optional.
# If provided then CA intermediate certificate will be copied or appended to provided filename.
_getdeployconf DEPLOY_SSH_CAFILE
_debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
if [ -n "$DEPLOY_SSH_CAFILE" ]; then
Le_Deploy_ssh_cafile="$DEPLOY_SSH_CAFILE"
_savedomainconf Le_Deploy_ssh_cafile "$Le_Deploy_ssh_cafile"
fi
if [ -n "$Le_Deploy_ssh_cafile" ]; then
_pipe=">"
if [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_keyfile" ] ||
[ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_certfile" ]; then
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ] ||
[ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
# if filename is same as previous file then append.
_pipe=">>"
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_cafile $_backupdir >/dev/null;"
_cmdstr="$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $Le_Deploy_ssh_cafile;"
_info "will copy CA file to remote file $Le_Deploy_ssh_cafile"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
# scp the file
_local_ca_file=$(_mktemp)
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
cat "$_ckey" >>"$_local_ca_file"
fi
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
cat "$_ccert" >>"$_local_ca_file"
fi
cat "$_cca" >>"$_local_ca_file"
if ! _scp_remote_cmd "$_local_ca_file" "$DEPLOY_SSH_CAFILE"; then
return $_err_code
fi
_cmdstr=""
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;"
_info "will copy CA file to remote file $DEPLOY_SSH_CAFILE"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
fi
# FULLCHAIN is optional.
# If provided then fullchain certificate will be copied or appended to provided filename.
_getdeployconf DEPLOY_SSH_FULLCHAIN
_debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
Le_Deploy_ssh_fullchain="$DEPLOY_SSH_FULLCHAIN"
_savedomainconf Le_Deploy_ssh_fullchain "$Le_Deploy_ssh_fullchain"
fi
if [ -n "$Le_Deploy_ssh_fullchain" ]; then
_pipe=">"
if [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_keyfile" ] ||
[ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_certfile" ] ||
[ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_cafile" ]; then
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ] ||
[ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ] ||
[ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
# if filename is same as previous file then append.
_pipe=">>"
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite.
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_fullchain $_backupdir >/dev/null;"
_cmdstr="$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;"
if [ "$DEPLOY_SSH_FULLCHAIN" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $Le_Deploy_ssh_fullchain;"
_info "will copy fullchain to remote file $Le_Deploy_ssh_fullchain"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
# scp the file
_local_full_file=$(_mktemp)
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ]; then
cat "$_ckey" >>"$_local_full_file"
fi
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ]; then
cat "$_ccert" >>"$_local_full_file"
fi
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
cat "$_cca" >>"$_local_full_file"
fi
cat "$_cfullchain" >>"$_local_full_file"
if ! _scp_remote_cmd "$_local_full_file" "$DEPLOY_SSH_FULLCHAIN"; then
return $_err_code
fi
_cmdstr=""
else
# ssh echo to the file
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;"
_info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
fi
# REMOTE_CMD is optional.
# If provided then this command will be executed on remote host.
_getdeployconf DEPLOY_SSH_REMOTE_CMD
_debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
Le_Deploy_ssh_remote_cmd="$DEPLOY_SSH_REMOTE_CMD"
_savedomainconf Le_Deploy_ssh_remote_cmd "$Le_Deploy_ssh_remote_cmd"
# cleanup local files if any
if [ -f "$_local_cert_file" ]; then
rm -f "$_local_cert_file"
fi
if [ -f "$_local_ca_file" ]; then
rm -f "$_local_ca_file"
fi
if [ -n "$Le_Deploy_ssh_remote_cmd" ]; then
_cmdstr="$_cmdstr $Le_Deploy_ssh_remote_cmd;"
_info "Will execute remote command $Le_Deploy_ssh_remote_cmd"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if [ -f "$_local_full_file" ]; then
rm -f "$_local_full_file"
fi
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
_cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;"
_info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD"
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
@ -282,17 +410,25 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
return $_err_code
fi
fi
# cleanup in case all is ok
return 0
}
#cmd
_ssh_remote_cmd() {
_cmd="$1"
_ssh_cmd="$DEPLOY_SSH_CMD"
if [ -n "$_port" ]; then
_ssh_cmd="$_ssh_cmd -p $_port"
fi
_secure_debug "Remote commands to execute: $_cmd"
_info "Submitting sequence of commands to remote server by ssh"
_info "Submitting sequence of commands to remote server by $_ssh_cmd"
# quotations in bash cmd below intended. Squash travis spellcheck error
# shellcheck disable=SC2029
$Le_Deploy_ssh_cmd "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmd'"
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'"
_err_code="$?"
if [ "$_err_code" != "0" ]; then
@ -301,3 +437,26 @@ _ssh_remote_cmd() {
return $_err_code
}
# cmd scp
_scp_remote_cmd() {
_src=$1
_dest=$2
_scp_cmd="$DEPLOY_SSH_SCP_CMD"
if [ -n "$_port" ]; then
_scp_cmd="$_scp_cmd -P $_port"
fi
_secure_debug "Remote copy source $_src to destination $_dest"
_info "Submitting secure copy by $_scp_cmd"
$_scp_cmd "$_src" "$DEPLOY_SSH_USER"@"$_host":"$_dest"
_err_code="$?"
if [ "$_err_code" != "0" ]; then
_err "Error code $_err_code returned from scp"
fi
return $_err_code
}

2
deploy/synology_dsm.sh

@ -108,7 +108,7 @@ synology_dsm_deploy() {
_debug3 H1 "${_H1}"
fi
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$otp_code" "$_base_url/webapi/auth.cgi?enable_syno_token=yes")
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes")
token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p')
_debug3 response "$response"
_debug token "$token"

21
deploy/truenas.sh

@ -184,6 +184,27 @@ truenas_deploy() {
_info "S3 certificate is not configured or is not the same as TrueNAS web UI"
fi
_info "Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
if _exists jq; then
_info "Query all chart release"
_release_list=$(_get "$_api_url/chart/release")
_related_name_list=$(printf "%s" "$_release_list" | jq -r "[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique")
_release_length=$(printf "%s" "$_related_name_list" | jq -r "length")
_info "Found $_release_length related chart release in list: $_related_name_list"
for i in $(seq 0 $((_release_length - 1))); do
_release_name=$(echo "$_related_name_list" | jq -r ".[$i]")
_info "Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name"
#Read the chart release configuration
_chart_config=$(printf "%s" "$_release_list" | jq -r ".[] | select(.name==\"$_release_name\")")
#Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress
_updated_chart_config=$(printf "%s" "$_chart_config" | jq "(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert ) |= $_cert_id | .config.ingress ")
_update_chart_result="$(_post "{\"values\" : { \"ingress\" : $_updated_chart_config } }" "$_api_url/chart/release/id/$_release_name" "" "PUT" "application/json")"
_debug3 _update_chart_result "$_update_chart_result"
done
else
_info "Tool 'jq' does not exists, skip chart release checking"
fi
_info "Deleting old certificate"
_delete_result="$(_post "" "$_api_url/certificate/id/$_active_cert_id" "" "DELETE" "application/json")"

79
deploy/vault.sh

@ -7,13 +7,16 @@
#
# VAULT_PREFIX - this contains the prefix path in vault
# VAULT_ADDR - vault requires this to find your vault server
# VAULT_SAVE_TOKEN - set to anything if you want to save the token
# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying
# VAULT_KV_V2 - set to anything if you are using v2 of the kv engine
#
# additionally, you need to ensure that VAULT_TOKEN is avialable
# to access the vault server
#returns 0 means success, otherwise error.
######## Public functions #####################
######## Public functions #####################
#domain keyfile certfile cafile fullchain
vault_deploy() {
@ -45,6 +48,26 @@ vault_deploy() {
fi
_savedeployconf VAULT_ADDR "$VAULT_ADDR"
_getdeployconf VAULT_SAVE_TOKEN
_savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN"
_getdeployconf VAULT_RENEW_TOKEN
_savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN"
_getdeployconf VAULT_KV_V2
_savedeployconf VAULT_KV_V2 "$VAULT_KV_V2"
_getdeployconf VAULT_TOKEN
if [ -z "$VAULT_TOKEN" ]; then
_err "VAULT_TOKEN needs to be defined"
return 1
fi
if [ -n "$VAULT_SAVE_TOKEN" ]; then
_savedeployconf VAULT_TOKEN "$VAULT_TOKEN"
fi
_migratedeployconf FABIO VAULT_FABIO_MODE
# JSON does not allow multiline strings.
# So replacing new-lines with "\n" here
_ckey=$(sed -z 's/\n/\\n/g' <"$2")
@ -52,26 +75,56 @@ vault_deploy() {
_cca=$(sed -z 's/\n/\\n/g' <"$4")
_cfullchain=$(sed -z 's/\n/\\n/g' <"$5")
URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain"
export _H1="X-Vault-Token: $VAULT_TOKEN"
if [ -n "$FABIO" ]; then
if [ -n "$VAULT_RENEW_TOKEN" ]; then
URL="$VAULT_ADDR/v1/auth/token/renew-self"
_info "Renew the Vault token to default TTL"
if ! _post "" "$URL" >/dev/null; then
_err "Failed to renew the Vault token"
return 1
fi
fi
URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain"
if [ -n "$VAULT_FABIO_MODE" ]; then
_info "Writing certificate and key to $URL in Fabio mode"
if [ -n "$VAULT_KV_V2" ]; then
_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL"
_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL" >/dev/null || return 1
else
_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL"
_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" >/dev/null || return 1
fi
else
if [ -n "$VAULT_KV_V2" ]; then
_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem"
_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key"
_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem"
_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem"
_info "Writing certificate to $URL/cert.pem"
_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem" >/dev/null || return 1
_info "Writing key to $URL/cert.key"
_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key" >/dev/null || return 1
_info "Writing CA certificate to $URL/ca.pem"
_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem" >/dev/null || return 1
_info "Writing full-chain certificate to $URL/fullchain.pem"
_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem" >/dev/null || return 1
else
_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem"
_post "{\"value\": \"$_ckey\"}" "$URL/cert.key"
_post "{\"value\": \"$_cca\"}" "$URL/chain.pem"
_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem"
_info "Writing certificate to $URL/cert.pem"
_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem" >/dev/null || return 1
_info "Writing key to $URL/cert.key"
_post "{\"value\": \"$_ckey\"}" "$URL/cert.key" >/dev/null || return 1
_info "Writing CA certificate to $URL/ca.pem"
_post "{\"value\": \"$_cca\"}" "$URL/ca.pem" >/dev/null || return 1
_info "Writing full-chain certificate to $URL/fullchain.pem"
_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem" >/dev/null || return 1
fi
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions
if _contains "$(_get "$URL/chain.pem")" "-----BEGIN CERTIFICATE-----"; then
_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"
_info "Updating CA certificate to $URL/chain.pem for backward compatibility"
if [ -n "$VAULT_KV_V2" ]; then
_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem" >/dev/null || return 1
else
_post "{\"value\": \"$_cca\"}" "$URL/chain.pem" >/dev/null || return 1
fi
fi
fi

47
deploy/vault_cli.sh

@ -8,6 +8,8 @@
#
# VAULT_PREFIX - this contains the prefix path in vault
# VAULT_ADDR - vault requires this to find your vault server
# VAULT_SAVE_TOKEN - set to anything if you want to save the token
# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying
#
# additionally, you need to ensure that VAULT_TOKEN is avialable or
# `vault auth` has applied the appropriate authorization for the vault binary
@ -33,15 +35,36 @@ vault_cli_deploy() {
_debug _cfullchain "$_cfullchain"
# validate required env vars
_getdeployconf VAULT_PREFIX
if [ -z "$VAULT_PREFIX" ]; then
_err "VAULT_PREFIX needs to be defined (contains prefix path in vault)"
return 1
fi
_savedeployconf VAULT_PREFIX "$VAULT_PREFIX"
_getdeployconf VAULT_ADDR
if [ -z "$VAULT_ADDR" ]; then
_err "VAULT_ADDR needs to be defined (contains vault connection address)"
return 1
fi
_savedeployconf VAULT_ADDR "$VAULT_ADDR"
_getdeployconf VAULT_SAVE_TOKEN
_savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN"
_getdeployconf VAULT_RENEW_TOKEN
_savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN"
_getdeployconf VAULT_TOKEN
if [ -z "$VAULT_TOKEN" ]; then
_err "VAULT_TOKEN needs to be defined"
return 1
fi
if [ -n "$VAULT_SAVE_TOKEN" ]; then
_savedeployconf VAULT_TOKEN "$VAULT_TOKEN"
fi
_migratedeployconf FABIO VAULT_FABIO_MODE
VAULT_CMD=$(command -v vault)
if [ ! $? ]; then
@ -49,13 +72,33 @@ vault_cli_deploy() {
return 1
fi
if [ -n "$FABIO" ]; then
if [ -n "$VAULT_RENEW_TOKEN" ]; then
_info "Renew the Vault token to default TTL"
if ! $VAULT_CMD token renew; then
_err "Failed to renew the Vault token"
return 1
fi
fi
if [ -n "$VAULT_FABIO_MODE" ]; then
_info "Writing certificate and key to ${VAULT_PREFIX}/${_cdomain} in Fabio mode"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
else
_info "Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
_info "Writing key to ${VAULT_PREFIX}/${_cdomain}/cert.key"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
_info "Writing CA certificate to ${VAULT_PREFIX}/${_cdomain}/ca.pem"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/ca.pem" value=@"$_cca" || return 1
_info "Writing full-chain certificate to ${VAULT_PREFIX}/${_cdomain}/fullchain.pem"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions
if $VAULT_CMD kv get "${VAULT_PREFIX}/${_cdomain}/chain.pem" >/dev/null; then
_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"
_info "Updating CA certificate to ${VAULT_PREFIX}/${_cdomain}/chain.pem for backward compatibility"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
fi
fi
}

2
dnsapi/dns_acmeproxy.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh
## Acmeproxy DNS provider to be used with acmeproxy (http://github.com/mdbraber/acmeproxy)
## Acmeproxy DNS provider to be used with acmeproxy (https://github.com/mdbraber/acmeproxy)
## API integration by Maarten den Braber
##
## Report any bugs via https://github.com/mdbraber/acme.sh

13
dnsapi/dns_arvan.sh

@ -1,10 +1,10 @@
#!/usr/bin/env sh
#Arvan_Token="Apikey xxxx"
# Arvan_Token="Apikey xxxx"
ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains"
#Author: Vahid Fardi
#Report Bugs here: https://github.com/Neilpang/acme.sh
ARVAN_API_URL="https://napi.arvancloud.ir/cdn/4.0/domains"
# Author: Vahid Fardi
# Report Bugs here: https://github.com/Neilpang/acme.sh
#
######## Public functions #####################
@ -18,7 +18,7 @@ dns_arvan_add() {
if [ -z "$Arvan_Token" ]; then
_err "You didn't specify \"Arvan_Token\" token yet."
_err "You can get yours from here https://npanel.arvancloud.com/profile/api-keys"
_err "You can get yours from here https://npanel.arvancloud.ir/profile/api-keys"
return 1
fi
#save the api token to the account conf file.
@ -40,7 +40,7 @@ dns_arvan_add() {
_info "response id is $response"
_info "Added, OK"
return 0
elif _contains "$response" "Record Data is Duplicated"; then
elif _contains "$response" "Record Data is duplicate"; then
_info "Already exists, OK"
return 0
else
@ -141,6 +141,7 @@ _arvan_rest() {
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
elif [ "$mtd" = "POST" ]; then
export _H2="Content-Type: application/json"
export _H3="Accept: application/json"
_debug data "$data"
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
else

50
dnsapi/dns_aws.sh

@ -155,31 +155,20 @@ _get_root() {
i=1
p=1
if aws_rest GET "2013-04-01/hostedzone"; then
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug2 "Checking domain: $h"
if [ -z "$h" ]; then
if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
_debug "IsTruncated"
_nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug "NextMarker" "$_nextMarker"
if aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"; then
_debug "Truncated request OK"
i=2
p=1
continue
else
_err "Truncated request error."
fi
fi
#not valid
_err "Invalid domain"
return 1
fi
# iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug "Checking domain: $h"
if [ -z "$h" ]; then
_error "invalid domain"
return 1
fi
# iterate over paginated result for list_hosted_zones
aws_rest GET "2013-04-01/hostedzone"
while true; do
if _contains "$response" "<Name>$h.</Name>"; then
hostedzone="$(echo "$response" | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")"
hostedzone="$(echo "$response" | tr -d '\n' | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")"
_debug hostedzone "$hostedzone"
if [ "$hostedzone" ]; then
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>")
@ -192,10 +181,19 @@ _get_root() {
return 1
fi
fi
p=$i
i=$(_math "$i" + 1)
if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
_debug "IsTruncated"
_nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug "NextMarker" "$_nextMarker"
else
break
fi
_debug "Checking domain: $h - Next Page "
aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"
done
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}

248
dnsapi/dns_bunny.sh

@ -0,0 +1,248 @@
#!/usr/bin/env sh
## Will be called by acme.sh to add the TXT record via the Bunny DNS API.
## returns 0 means success, otherwise error.
## Author: nosilver4u <nosilver4u at ewww.io>
## GitHub: https://github.com/nosilver4u/acme.sh
##
## Environment Variables Required:
##
## BUNNY_API_KEY="75310dc4-ca77-9ac3-9a19-f6355db573b49ce92ae1-2655-3ebd-61ac-3a3ae34834cc"
##
##################### Public functions #####################
## Create the text record for validation.
## Usage: fulldomain txtvalue
## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs"
dns_bunny_add() {
fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2
BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}"
# Check if API Key is set
if [ -z "$BUNNY_API_KEY" ]; then
BUNNY_API_KEY=""
_err "You did not specify Bunny.net API key."
_err "Please export BUNNY_API_KEY and try again."
return 1
fi
_info "Using Bunny.net dns validation - add record"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
## save the env vars (key and domain split location) for later automated use
_saveaccountconf_mutable BUNNY_API_KEY "$BUNNY_API_KEY"
## split the domain for Bunny API
if ! _get_base_domain "$fulldomain"; then
_err "domain not found in your account for addition"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug _domain_id "$_domain_id"
## Set the header with our post type and auth key
export _H1="Accept: application/json"
export _H2="AccessKey: $BUNNY_API_KEY"
export _H3="Content-Type: application/json"
PURL="https://api.bunny.net/dnszone/$_domain_id/records"
PBODY='{"Id":'$_domain_id',"Type":3,"Name":"'$_sub_domain'","Value":"'$txtvalue'","ttl":120}'
_debug PURL "$PURL"
_debug PBODY "$PBODY"
## the create request - POST
## args: BODY, URL, [need64, httpmethod]
response="$(_post "$PBODY" "$PURL" "" "PUT")"
## check response
if [ "$?" != "0" ]; then
_err "error in response: $response"
return 1
fi
_debug2 response "$response"
## finished correctly
return 0
}
## Remove the txt record after validation.
## Usage: fulldomain txtvalue
## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs"
dns_bunny_rm() {
fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2
BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}"
# Check if API Key Exists
if [ -z "$BUNNY_API_KEY" ]; then
BUNNY_API_KEY=""
_err "You did not specify Bunny.net API key."
_err "Please export BUNNY_API_KEY and try again."
return 1
fi
_info "Using Bunny.net dns validation - remove record"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
## split the domain for Bunny API
if ! _get_base_domain "$fulldomain"; then
_err "Domain not found in your account for TXT record removal"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug _domain_id "$_domain_id"
## Set the header with our post type and key auth key
export _H1="Accept: application/json"
export _H2="AccessKey: $BUNNY_API_KEY"
## get URL for the list of DNS records
GURL="https://api.bunny.net/dnszone/$_domain_id"
## 1) Get the domain/zone records
## the fetch request - GET
## args: URL, [onlyheader, timeout]
domain_list="$(_get "$GURL")"
## check response
if [ "$?" != "0" ]; then
_err "error in domain_list response: $domain_list"
return 1
fi
_debug2 domain_list "$domain_list"
## 2) search through records
## check for what we are looking for: "Type":3,"Value":"$txtvalue","Name":"$_sub_domain"
record="$(echo "$domain_list" | _egrep_o "\"Id\"\s*\:\s*\"*[0-9]+\"*,\s*\"Type\"[^}]*\"Value\"\s*\:\s*\"$txtvalue\"[^}]*\"Name\"\s*\:\s*\"$_sub_domain\"")"
if [ -n "$record" ]; then
## We found records
rec_ids="$(echo "$record" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
_debug rec_ids "$rec_ids"
if [ -n "$rec_ids" ]; then
echo "$rec_ids" | while IFS= read -r rec_id; do
## delete the record
## delete URL for removing the one we dont want
DURL="https://api.bunny.net/dnszone/$_domain_id/records/$rec_id"
## the removal request - DELETE
## args: BODY, URL, [need64, httpmethod]
response="$(_post "" "$DURL" "" "DELETE")"
## check response (sort of)
if [ "$?" != "0" ]; then
_err "error in remove response: $response"
return 1
fi
_debug2 response "$response"
done
fi
fi
## finished correctly
return 0
}
##################### Private functions below #####################
## Split the domain provided into the "base domain" and the "start prefix".
## This function searches for the longest subdomain in your account
## for the full domain given and splits it into the base domain (zone)
## and the prefix/record to be added/removed
## USAGE: fulldomain
## EG: "_acme-challenge.two.three.four.domain.com"
## returns
## _sub_domain="_acme-challenge.two"
## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists
## _domain_id=234
## if only "domain.com" exists it will return
## _sub_domain="_acme-challenge.two.three.four"
## _domain="domain.com"
## _domain_id=234
_get_base_domain() {
# args
fulldomain="$(echo "$1" | _lower_case)"
_debug fulldomain "$fulldomain"
# domain max legal length = 253
MAX_DOM=255
page=1
## get a list of domains for the account to check thru
## Set the headers
export _H1="Accept: application/json"
export _H2="AccessKey: $BUNNY_API_KEY"
_debug BUNNY_API_KEY "$BUNNY_API_KEY"
## get URL for the list of domains
## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
DOMURL="https://api.bunny.net/dnszone"
## while we dont have a matching domain we keep going
while [ -z "$found" ]; do
## get the domain list (current page)
domain_list="$(_get "$DOMURL")"
## check response
if [ "$?" != "0" ]; then
_err "error in domain_list response: $domain_list"
return 1
fi
_debug2 domain_list "$domain_list"
i=1
while [ $i -gt 0 ]; do
## get next longest domain
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
## check we got something back from our cut (or are we at the end)
if [ -z "$_domain" ]; then
break
fi
## we got part of a domain back - grep it out
found="$(echo "$domain_list" | _egrep_o "\"Id\"\s*:\s*\"*[0-9]+\"*,\s*\"Domain\"\s*\:\s*\"$_domain\"")"
## check if it exists
if [ -n "$found" ]; then
## exists - exit loop returning the parts
sub_point=$(_math $i - 1)
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
_domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
_debug _domain_id "$_domain_id"
_debug _domain "$_domain"
_debug _sub_domain "$_sub_domain"
found=""
return 0
fi
## increment cut point $i
i=$(_math $i + 1)
done
if [ -z "$found" ]; then
page=$(_math $page + 1)
nextpage="https://api.bunny.net/dnszone?page=$page"
## Find the next page if we don't have a match.
hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")"
if [ -z "$hasnextpage" ]; then
_err "No record and no nextpage in Bunny.net domain search."
found=""
return 1
fi
_debug2 nextpage "$nextpage"
DOMURL="$nextpage"
fi
done
## We went through the entire domain zone list and didn't find one that matched.
## If we ever get here, something is broken in the code...
_err "Domain not found in Bunny.net account, but we should never get here!"
found=""
return 1
}

11
dnsapi/dns_cf.sh

@ -32,7 +32,8 @@ dns_cf_add() {
else
_saveaccountconf_mutable CF_Token "$CF_Token"
_saveaccountconf_mutable CF_Account_ID "$CF_Account_ID"
_saveaccountconf_mutable CF_Zone_ID "$CF_Zone_ID"
_clearaccountconf_mutable CF_Zone_ID
_clearaccountconf CF_Zone_ID
fi
else
if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
@ -51,6 +52,14 @@ dns_cf_add() {
#save the api key and email to the account conf file.
_saveaccountconf_mutable CF_Key "$CF_Key"
_saveaccountconf_mutable CF_Email "$CF_Email"
_clearaccountconf_mutable CF_Token
_clearaccountconf_mutable CF_Account_ID
_clearaccountconf_mutable CF_Zone_ID
_clearaccountconf CF_Token
_clearaccountconf CF_Account_ID
_clearaccountconf CF_Zone_ID
fi
_debug "First detect the root zone"

2
dnsapi/dns_cloudns.sh

@ -78,7 +78,7 @@ dns_cloudns_rm() {
return 1
fi
for i in $(echo "$response" | tr '{' "\n" | grep "$record"); do
for i in $(echo "$response" | tr '{' "\n" | grep -- "$record"); do
record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g')
if [ -n "$record_id" ]; then

9
dnsapi/dns_cpanel.sh

@ -13,6 +13,7 @@
# cPanel_Hostname=hostname
#
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_cpanel_add() {
fulldomain=$1
@ -120,7 +121,7 @@ _myget() {
_get_root() {
_myget 'json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzones'
_domains=$(echo "$_result" | sed 's/.*\(zones.*\[\).*/\1/' | cut -d':' -f2 | sed 's/"//g' | sed 's/{//g')
_domains=$(echo "$_result" | _egrep_o '"[a-z0-9\.\-]*":\["; cPanel first' | cut -d':' -f1 | sed 's/"//g' | sed 's/{//g')
_debug "_result is: $_result"
_debug "_domains is: $_domains"
if [ -z "$_domains" ]; then
@ -138,15 +139,15 @@ _get_root() {
}
_successful_update() {
if (echo "$_result" | grep -q 'newserial'); then return 0; fi
return 1
if (echo "$_result" | _egrep_o 'data":\[[^]]*]' | grep -q '"newserial":null'); then return 1; fi
return 0
}
_findentry() {
_debug "In _findentry"
#returns id of dns entry, if it exists
_myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&domain=$_domain"
_id=$(echo "$_result" | sed "s/.*\(line.*$fulldomain.*$txtvalue\).*/\1/" | cut -d ':' -f 2 | cut -d ',' -f 1)
_id=$(echo "$_result" | sed -e "s/},{/},\n{/g" | grep "$fulldomain" | grep "$txtvalue" | _egrep_o 'line":[0-9]+' | cut -d ':' -f 2)
_debug "_result is: $_result"
_debug "fulldomain. is $fulldomain."
_debug "txtvalue is $txtvalue"

2
dnsapi/dns_cyon.sh

@ -44,7 +44,7 @@ dns_cyon_rm() {
_cyon_load_credentials() {
# Convert loaded password to/from base64 as needed.
if [ "${CY_Password_B64}" ]; then
CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64 "multiline")"
CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64)"
elif [ "${CY_Password}" ]; then
CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)"
fi

5
dnsapi/dns_dgon.sh

@ -192,6 +192,7 @@ _get_base_domain() {
## get URL for the list of domains
## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
DOMURL="https://api.digitalocean.com/v2/domains"
found=""
## while we dont have a matching domain we keep going
while [ -z "$found" ]; do
@ -205,9 +206,7 @@ _get_base_domain() {
fi
_debug2 domain_list "$domain_list"
## for each shortening of our $fulldomain, check if it exists in the $domain_list
## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge"
i=2
i=1
while [ $i -gt 0 ]; do
## get next longest domain
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")

248
dnsapi/dns_dnsservices.sh

@ -0,0 +1,248 @@
#!/usr/bin/env sh
#This file name is "dns_dnsservices.sh"
#Script for Danish DNS registra and DNS hosting provider https://dns.services
#Author: Bjarke Bruun <bbruun@gmail.com>
#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/4152
# Global variable to connect to the DNS.Services API
DNSServices_API=https://dns.services/api
######## Public functions #####################
#Usage: dns_dnsservices_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dnsservices_add() {
fulldomain="$1"
txtvalue="$2"
_info "Using dns.services to create ACME DNS challenge"
_debug2 add_fulldomain "$fulldomain"
_debug2 add_txtvalue "$txtvalue"
# Read username/password from environment or .acme.sh/accounts.conf
DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}"
DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}"
if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
DnsServices_Username=""
DnsServices_Password=""
_err "You didn't specify dns.services api username and password yet."
_err "Set environment variables DnsServices_Username and DnsServices_Password"
return 1
fi
# Setup GET/POST/DELETE headers
_setup_headers
#save the credentials to the account conf file.
_saveaccountconf_mutable DnsServices_Username "$DnsServices_Username"
_saveaccountconf_mutable DnsServices_Password "$DnsServices_Password"
if ! _contains "$DnsServices_Username" "@"; then
_err "It seems that the username variable DnsServices_Username has not been set/left blank"
_err "or is not a valid email. Please correct and try again."
return 1
fi
if ! _get_root "${fulldomain}"; then
_err "Invalid domain ${fulldomain}"
return 1
fi
if ! createRecord "$fulldomain" "${txtvalue}"; then
_err "Error creating TXT record in domain $fulldomain in $rootZoneName"
return 1
fi
_debug2 challenge-created "Created $fulldomain"
return 0
}
#Usage: fulldomain txtvalue
#Description: Remove the txt record after validation.
dns_dnsservices_rm() {
fulldomain="$1"
txtvalue="$2"
_info "Using dns.services to remove DNS record $fulldomain TXT $txtvalue"
_debug rm_fulldomain "$fulldomain"
_debug rm_txtvalue "$txtvalue"
# Read username/password from environment or .acme.sh/accounts.conf
DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}"
DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}"
if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
DnsServices_Username=""
DnsServices_Password=""
_err "You didn't specify dns.services api username and password yet."
_err "Set environment variables DnsServices_Username and DnsServices_Password"
return 1
fi
# Setup GET/POST/DELETE headers
_setup_headers
if ! _get_root "${fulldomain}"; then
_err "Invalid domain ${fulldomain}"
return 1
fi
_debug2 rm_rootDomainInfo "found root domain $rootZoneName for $fulldomain"
if ! deleteRecord "${fulldomain}" "${txtvalue}"; then
_err "Error removing record: $fulldomain TXT ${txtvalue}"
return 1
fi
return 0
}
#################### Private functions below ##################################
_setup_headers() {
# Set up API Headers for _get() and _post()
# The <function>_add or <function>_rm must have been called before to work
if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
_err "Could not setup BASIC authentication headers, they are missing"
return 1
fi
DnsServiceCredentials="$(printf "%s" "$DnsServices_Username:$DnsServices_Password" | _base64)"
export _H1="Authorization: Basic $DnsServiceCredentials"
export _H2="Content-Type: application/json"
# Just return if headers are set
return 0
}
_get_root() {
domain="$1"
_debug2 _get_root "Get the root domain of ${domain} for DNS API"
# Setup _get() and _post() headers
#_setup_headers
result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns")
result2="$(printf "%s\n" "$result" | tr '[' '\n' | grep '"name"')"
result3="$(printf "%s\n" "$result2" | tr '}' '\n' | grep '"name"' | sed "s,^\,,,g" | sed "s,$,},g")"
useResult=""
_debug2 _get_root "Got the following root domain(s) $result"
_debug2 _get_root "- JSON: $result"
if [ "$(printf "%s\n" "$result" | tr '}' '\n' | grep -c '"name"')" -gt "1" ]; then
checkMultiZones="true"
_debug2 _get_root "- multiple zones found"
else
checkMultiZones="false"
_debug2 _get_root "- single zone found"
fi
# Find/isolate the root zone to work with in createRecord() and deleteRecord()
rootZone=""
if [ "$checkMultiZones" = "true" ]; then
#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)
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)
if [ "$rootZone" != "" ]; then
_debug2 _rootZone "- root zone for $domain is $rootZone"
else
_err "Could not find root zone for $domain, is it correctly typed?"
return 1
fi
else
rootZone=$(echo "$result" | tr '}' '\n' | _egrep_o '"name":"[^"]*' | cut -d'"' -f4)
_debug2 _get_root "- only found 1 domain in API: $rootZone"
fi
if [ -z "$rootZone" ]; then
_err "Could not find root domain for $domain - is it correctly typed?"
return 1
fi
# Make sure we use the correct API zone data
useResult="$(printf "%s\n" "${result3}" tr ',' '\n' | grep "$rootZone")"
_debug2 _useResult "useResult=$useResult"
# Setup variables used by other functions to communicate with DNS.Services API
#zoneInfo=$(printf "%s\n" "$useResult" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g')
zoneInfo=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep '"name"' | cut -d'"' -f4)
rootZoneName="$rootZone"
subDomainName="$(printf "%s\n" "$domain" | sed "s,\.$rootZone,,g")"
subDomainNameClean="$(printf "%s\n" "$domain" | sed "s,_acme-challenge.,,g")"
rootZoneDomainID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep domain_id | cut -d'"' -f4)
rootZoneServiceID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep service_id | cut -d'"' -f4)
_debug2 _zoneInfo "Zone info from API : $zoneInfo"
_debug2 _get_root "Root zone name : $rootZoneName"
_debug2 _get_root "Root zone domain ID : $rootZoneDomainID"
_debug2 _get_root "Root zone service ID: $rootZoneServiceID"
_debug2 _get_root "Sub domain : $subDomainName"
_debug _get_root "Found valid root domain $rootZone for $subDomainNameClean"
return 0
}
createRecord() {
fulldomain="$1"
txtvalue="$2"
# Get root domain information - needed for DNS.Services API communication
if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
_get_root "$fulldomain"
fi
if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
_err "Something happend - could not get the API zone information"
return 1
fi
_debug2 createRecord "CNAME TXT value is: $txtvalue"
# Prepare data to send to API
data="{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\", \"ttl\":\"10\"}"
_debug2 createRecord "data to API: $data"
result=$(_post "$data" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records" "" "POST")
_debug2 createRecord "result from API: $result"
if [ "$(echo "$result" | _egrep_o "\"success\":true")" = "" ]; then
_err "Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName"
_err "$result"
return 1
fi
_info "Record \"$fulldomain TXT $txtvalue\" has been created"
return 0
}
deleteRecord() {
fulldomain="$1"
txtvalue="$2"
_log deleteRecord "Deleting $fulldomain TXT $txtvalue record"
if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
_get_root "$fulldomain"
fi
result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")"
#recordInfo="$(echo "$result" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}")"
#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')"
recordID="$(printf "%s\n" "$result" | tr '}' '\n' | grep -- "$txtvalue" | tr ',' '\n' | grep '"id"' | cut -d'"' -f4)"
_debug2 _recordID "recordID used for deletion of record: $recordID"
if [ -z "$recordID" ]; then
_info "Record $fulldomain TXT $txtvalue not found or already deleted"
return 0
else
_debug2 deleteRecord "Found recordID=$recordID"
fi
_debug2 deleteRecord "DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID"
_log "curl DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID"
result="$(_H1="$_H1" _H2="$_H2" _post "" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" "" "DELETE")"
_debug2 deleteRecord "API Delete result \"$result\""
_log "curl API Delete result \"$result\""
# Return OK regardless
return 0
}

8
dnsapi/dns_dynv6.sh

@ -94,8 +94,8 @@ _get_domain() {
_your_hosts="$(echo "$_your_hosts" | awk '/\./ {print $1}')"
for l in $_your_hosts; do
#echo "host: $l"
if test "${_full_domain#*$l}" != "$_full_domain"; then
_record="${_full_domain%.$l}"
if test "${_full_domain#*"$l"}" != "$_full_domain"; then
_record=${_full_domain%."$l"}
_host=$l
_debug "The host is $_host and the record $_record"
return 0
@ -143,7 +143,7 @@ _dns_dynv6_add_http() {
return 1
fi
_get_zone_name "$_zone_id"
record="${fulldomain%%.$_zone_name}"
record=${fulldomain%%."$_zone_name"}
_set_record TXT "$record" "$txtvalue"
if _contains "$response" "$txtvalue"; then
_info "Successfully added record"
@ -161,7 +161,7 @@ _dns_dynv6_rm_http() {
return 1
fi
_get_zone_name "$_zone_id"
record="${fulldomain%%.$_zone_name}"
record=${fulldomain%%."$_zone_name"}
_get_record_id "$_zone_id" "$record" "$txtvalue"
_del_record "$_zone_id" "$_record_id"
if [ -z "$response" ]; then

2
dnsapi/dns_edgedns.sh

@ -418,7 +418,7 @@ _edgedns_make_data_to_sign() {
_secure_debug2 "hdr" "$hdr"
_edgedns_make_content_hash
path="$(echo "$_request_url_path" | tr -d "\n\r" | sed 's/https\?:\/\///')"
path="${path#*$AKAMAI_HOST}"
path=${path#*"$AKAMAI_HOST"}
_debug "hier path" "$path"
# dont expose headers to sign so use MT string
_mdata="$(printf "%s\thttps\t%s\t%s\t%s\t%s\t%s" "$_request_method" "$AKAMAI_HOST" "$path" "" "$_hash" "$hdr")"

2
dnsapi/dns_gandi_livedns.sh

@ -1,7 +1,7 @@
#!/usr/bin/env sh
# Gandi LiveDNS v5 API
# http://doc.livedns.gandi.net/
# https://doc.livedns.gandi.net/
# currently under beta
#
# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable

6
dnsapi/dns_gcloud.sh

@ -39,7 +39,7 @@ dns_gcloud_rm() {
_dns_gcloud_start_tr || return $?
_dns_gcloud_get_rrdatas || return $?
echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
echo "$rrdatas" | grep -F -v -- "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
_dns_gcloud_execute_tr || return $?
_info "$fulldomain record added"
@ -98,7 +98,7 @@ _dns_gcloud_remove_rrs() {
--ttl="$ttl" \
--type=TXT \
--zone="$managedZone" \
--transaction-file="$tr"; then
--transaction-file="$tr" --; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_remove_rrs: failed to remove RRs"
@ -113,7 +113,7 @@ _dns_gcloud_add_rrs() {
--ttl="$ttl" \
--type=TXT \
--zone="$managedZone" \
--transaction-file="$tr"; then
--transaction-file="$tr" --; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_add_rrs: failed to add RRs"

187
dnsapi/dns_gcore.sh

@ -0,0 +1,187 @@
#!/usr/bin/env sh
#
#GCORE_Key='773$7b7adaf2a2b32bfb1b83787b4ff32a67eb178e3ada1af733e47b1411f2461f7f4fa7ed7138e2772a46124377bad7384b3bb8d87748f87b3f23db4b8bbe41b2bb'
#
GCORE_Api="https://api.gcorelabs.com/dns/v2"
GCORE_Doc="https://apidocs.gcore.com/dns"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_gcore_add() {
fulldomain=$1
txtvalue=$2
GCORE_Key="${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}"
if [ -z "$GCORE_Key" ]; then
GCORE_Key=""
_err "You didn't specify a Gcore api key yet."
_err "You can get yours from here $GCORE_Doc"
return 1
fi
#save the api key to the account conf file.
_saveaccountconf_mutable GCORE_Key "$GCORE_Key"
_debug "First detect the zone name"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _zone_name "$_zone_name"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
_gcore_rest GET "zones/$_zone_name/$fulldomain/TXT"
payload=""
if echo "$response" | grep "record is not found" >/dev/null; then
_info "Record doesn't exists"
payload="{\"resource_records\":[{\"content\":[\"$txtvalue\"],\"enabled\":true}],\"ttl\":120}"
elif echo "$response" | grep "$txtvalue" >/dev/null; then
_info "Already exists, OK"
return 0
elif echo "$response" | tr -d " " | grep \"name\":\""$fulldomain"\",\"type\":\"TXT\" >/dev/null; then
_info "Record with mismatch txtvalue, try update it"
payload=$(echo "$response" | tr -d " " | sed 's/"updated_at":[0-9]\+,//g' | sed 's/"meta":{}}]}/"meta":{}},{"content":['\""$txtvalue"\"'],"enabled":true}]}/')
fi
# For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
# we can not use updating anymore.
# count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
# _debug count "$count"
# if [ "$count" = "0" ]; then
_info "Adding record"
if _gcore_rest PUT "zones/$_zone_name/$fulldomain/TXT" "$payload"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
return 0
elif _contains "$response" "rrset is already exists"; then
_info "Already exists, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain txtvalue
dns_gcore_rm() {
fulldomain=$1
txtvalue=$2
GCORE_Key="${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _zone_name "$_zone_name"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
_gcore_rest GET "zones/$_zone_name/$fulldomain/TXT"
if echo "$response" | grep "record is not found" >/dev/null; then
_info "No such txt recrod"
return 0
fi
if ! echo "$response" | tr -d " " | grep \"name\":\""$fulldomain"\",\"type\":\"TXT\" >/dev/null; then
_err "Error: $response"
return 1
fi
if ! echo "$response" | tr -d " " | grep \""$txtvalue"\" >/dev/null; then
_info "No such txt recrod"
return 0
fi
count="$(echo "$response" | grep -o "content" | wc -l)"
if [ "$count" = "1" ]; then
if ! _gcore_rest DELETE "zones/$_zone_name/$fulldomain/TXT"; then
_err "Delete record error. $response"
return 1
fi
return 0
fi
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/,\]/\]/')"
if ! _gcore_rest PUT "zones/$_zone_name/$fulldomain/TXT" "$payload"; then
_err "Delete record error. $response"
fi
}
#################### Private functions below ##################################
#_acme-challenge.sub.domain.com
#returns
# _sub_domain=_acme-challenge.sub or _acme-challenge
# _domain=domain.com
# _zone_name=domain.com or sub.domain.com
_get_root() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if ! _gcore_rest GET "zones/$h"; then
return 1
fi
if _contains "$response" "\"name\":\"$h\""; then
_zone_name=$h
if [ "$_zone_name" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
return 0
fi
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_gcore_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
key_trimmed=$(echo "$GCORE_Key" | tr -d '"')
export _H1="Content-Type: application/json"
export _H2="Authorization: APIKey $key_trimmed"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$GCORE_Api/$ep" "" "$m")"
else
response="$(_get "$GCORE_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

62
dnsapi/dns_gd.sh

@ -1,10 +1,12 @@
#!/usr/bin/env sh
#Godaddy domain api
# Get API key and secret from https://developer.godaddy.com/
#
#GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
# GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
# GD_Secret="asdfsdfsfsdfsdfdfsdf"
#
#GD_Secret="asdfsdfsfsdfsdfdfsdf"
# Ex.: acme.sh --issue --staging --dns dns_gd -d "*.s.example.com" -d "s.example.com"
GD_Api="https://api.godaddy.com/v1"
@ -20,8 +22,8 @@ dns_gd_add() {
if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then
GD_Key=""
GD_Secret=""
_err "You don't specify godaddy api key and secret yet."
_err "Please create you key and try again."
_err "You didn't specify godaddy api key and secret yet."
_err "Please create your key and try again."
return 1
fi
@ -44,14 +46,15 @@ dns_gd_add() {
fi
if _contains "$response" "$txtvalue"; then
_info "The record is existing, skip"
_info "This record already exists, skipping"
return 0
fi
_add_data="{\"data\":\"$txtvalue\"}"
for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do
_debug2 t "$t"
if [ "$t" ]; then
# ignore empty (previously removed) records, to prevent useless _acme-challenge TXT entries
if [ "$t" ] && [ "$t" != '""' ]; then
_add_data="$_add_data,{\"data\":$t}"
fi
done
@ -59,13 +62,25 @@ dns_gd_add() {
_info "Adding record"
if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
_info "Added, sleeping 10 seconds"
_sleep 10
#todo: check if the record takes effect
return 0
_debug "Checking updated records of '${fulldomain}'"
if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then
_err "Validating TXT record for '${fulldomain}' with rest error [$?]." "$response"
return 1
fi
if ! _contains "$response" "$txtvalue"; then
_err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!"
return 1
fi
else
_err "Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set."
return 1
fi
_err "Add txt record error."
return 1
_sleep 10
_info "Added TXT record '${txtvalue}' for '${fulldomain}'."
return 0
}
#fulldomain
@ -107,11 +122,20 @@ dns_gd_rm() {
fi
done
if [ -z "$_add_data" ]; then
_add_data="{\"data\":\"\"}"
# delete empty record
_debug "Delete last record for '${fulldomain}'"
if ! _gd_rest DELETE "domains/$_domain/records/TXT/$_sub_domain"; then
_err "Cannot delete empty TXT record for '$fulldomain'"
return 1
fi
else
# remove specific TXT value, keeping other entries
_debug2 _add_data "$_add_data"
if ! _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
_err "Cannot update TXT record for '$fulldomain'"
return 1
fi
fi
_debug2 _add_data "$_add_data"
_gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"
}
#################### Private functions below ##################################
@ -156,15 +180,15 @@ _gd_rest() {
export _H1="Authorization: sso-key $GD_Key:$GD_Secret"
export _H2="Content-Type: application/json"
if [ "$data" ]; then
_debug data "$data"
if [ "$data" ] || [ "$m" = "DELETE" ]; then
_debug "data ($m): " "$data"
response="$(_post "$data" "$GD_Api/$ep" "" "$m")"
else
response="$(_get "$GD_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
_err "error on rest call ($m): $ep"
return 1
fi
_debug2 response "$response"

173
dnsapi/dns_googledomains.sh

@ -0,0 +1,173 @@
#!/usr/bin/env sh
# Author: Alex Leigh <leigh at alexleigh dot me>
# Created: 2023-03-02
#GOOGLEDOMAINS_ACCESS_TOKEN="xxxx"
#GOOGLEDOMAINS_ZONE="xxxx"
GOOGLEDOMAINS_API="https://acmedns.googleapis.com/v1/acmeChallengeSets"
######## Public functions ########
#Usage: dns_googledomains_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_googledomains_add() {
fulldomain=$1
txtvalue=$2
_info "Invoking Google Domains ACME DNS API."
if ! _dns_googledomains_setup; then
return 1
fi
zone="$(_dns_googledomains_get_zone "$fulldomain")"
if [ -z "$zone" ]; then
_err "Could not find a Google Domains-managed zone containing the requested domain."
return 1
fi
_debug zone "$zone"
_debug txtvalue "$txtvalue"
_info "Adding TXT record for $fulldomain."
if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToAdd\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then
if _contains "$response" "$txtvalue"; then
_info "TXT record added."
return 0
else
_err "Error adding TXT record."
return 1
fi
fi
_err "Error adding TXT record."
return 1
}
#Usage: dns_googledomains_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_googledomains_rm() {
fulldomain=$1
txtvalue=$2
_info "Invoking Google Domains ACME DNS API."
if ! _dns_googledomains_setup; then
return 1
fi
zone="$(_dns_googledomains_get_zone "$fulldomain")"
if [ -z "$zone" ]; then
_err "Could not find a Google Domains-managed domain based on request."
return 1
fi
_debug zone "$zone"
_debug txtvalue "$txtvalue"
_info "Removing TXT record for $fulldomain."
if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToRemove\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then
if _contains "$response" "$txtvalue"; then
_err "Error removing TXT record."
return 1
else
_info "TXT record removed."
return 0
fi
fi
_err "Error removing TXT record."
return 1
}
######## Private functions ########
_dns_googledomains_setup() {
if [ -n "$GOOGLEDOMAINS_SETUP_COMPLETED" ]; then
return 0
fi
GOOGLEDOMAINS_ACCESS_TOKEN="${GOOGLEDOMAINS_ACCESS_TOKEN:-$(_readaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN)}"
GOOGLEDOMAINS_ZONE="${GOOGLEDOMAINS_ZONE:-$(_readaccountconf_mutable GOOGLEDOMAINS_ZONE)}"
if [ -z "$GOOGLEDOMAINS_ACCESS_TOKEN" ]; then
GOOGLEDOMAINS_ACCESS_TOKEN=""
_err "Google Domains access token was not specified."
_err "Please visit Google Domains Security settings to provision an ACME DNS API access token."
return 1
fi
if [ "$GOOGLEDOMAINS_ZONE" ]; then
_savedomainconf GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN"
_savedomainconf GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE"
else
_saveaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN"
_clearaccountconf_mutable GOOGLEDOMAINS_ZONE
_clearaccountconf GOOGLEDOMAINS_ZONE
fi
_debug GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN"
_debug GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE"
GOOGLEDOMAINS_SETUP_COMPLETED=1
return 0
}
_dns_googledomains_get_zone() {
domain=$1
# Use zone directly if provided
if [ "$GOOGLEDOMAINS_ZONE" ]; then
if ! _dns_googledomains_api "$GOOGLEDOMAINS_ZONE"; then
return 1
fi
echo "$GOOGLEDOMAINS_ZONE"
return 0
fi
i=2
while true; do
curr=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug curr "$curr"
if [ -z "$curr" ]; then
return 1
fi
if _dns_googledomains_api "$curr"; then
echo "$curr"
return 0
fi
i=$(_math "$i" + 1)
done
return 1
}
_dns_googledomains_api() {
zone=$1
apimethod=$2
data="$3"
if [ -z "$data" ]; then
response="$(_get "$GOOGLEDOMAINS_API/$zone$apimethod")"
else
_debug data "$data"
export _H1="Content-Type: application/json"
response="$(_post "$data" "$GOOGLEDOMAINS_API/$zone$apimethod")"
fi
_debug response "$response"
if [ "$?" != "0" ]; then
_err "Error"
return 1
fi
if _contains "$response" "\"error\": {"; then
return 1
fi
return 0
}

98
dnsapi/dns_huaweicloud.sh

@ -2,7 +2,7 @@
# HUAWEICLOUD_Username
# HUAWEICLOUD_Password
# HUAWEICLOUD_ProjectID
# HUAWEICLOUD_DomainName
iam_api="https://iam.myhuaweicloud.com"
dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
@ -14,6 +14,8 @@ dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
#
# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html
#
# About "DomainName" parameters see: https://support.huaweicloud.com/api-iam/iam_01_0006.html
#
dns_huaweicloud_add() {
fulldomain=$1
@ -21,16 +23,16 @@ dns_huaweicloud_add() {
HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}"
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
# Check information
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
_err "Not enough information provided to dns_huaweicloud!"
return 1
fi
unset token # Clear token
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
if [ -z "${token}" ]; then # Check token
_err "dns_api(dns_huaweicloud): Error getting token."
return 1
@ -56,7 +58,7 @@ dns_huaweicloud_add() {
# Do saving work if all succeeded
_saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}"
_saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}"
_saveaccountconf_mutable HUAWEICLOUD_ProjectID "${HUAWEICLOUD_ProjectID}"
_saveaccountconf_mutable HUAWEICLOUD_DomainName "${HUAWEICLOUD_DomainName}"
return 0
}
@ -72,16 +74,16 @@ dns_huaweicloud_rm() {
HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}"
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
# Check information
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
_err "Not enough information provided to dns_huaweicloud!"
return 1
fi
unset token # Clear token
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
if [ -z "${token}" ]; then # Check token
_err "dns_api(dns_huaweicloud): Error getting token."
return 1
@ -96,19 +98,59 @@ dns_huaweicloud_rm() {
fi
_debug "Zone ID is:" "${zoneid}"
record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")"
_recursive_rm_record "${token}" "${fulldomain}" "${zoneid}" "${record_id}"
ret="$?"
if [ "${ret}" != "0" ]; then
_err "dns_api(dns_huaweicloud): Error removing record."
return 1
fi
return 0
}
################### Private functions below ##################################
# _recursive_rm_record
# remove all records from the record set
#
# _token=$1
# _domain=$2
# _zoneid=$3
# _record_id=$4
#
# Returns 0 on success
_recursive_rm_record() {
_token=$1
_domain=$2
_zoneid=$3
_record_id=$4
# Most likely to have problems will huaweicloud side if more than 50 attempts but still cannot fully remove the record set
# Maybe can be removed manually in the dashboard
_retry_cnt=50
# Remove all records
# Therotically HuaweiCloud does not allow more than one record set
# But remove them recurringly to increase robusty
while [ "${record_id}" != "0" ]; do
while [ "${_record_id}" != "0" ] && [ "${_retry_cnt}" != "0" ]; do
_debug "Removing Record"
_rm_record "${token}" "${zoneid}" "${record_id}"
record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")"
_retry_cnt=$((_retry_cnt - 1))
_rm_record "${_token}" "${_zoneid}" "${_record_id}"
_record_id="$(_get_recordset_id "${_token}" "${_domain}" "${_zoneid}")"
_debug2 "Checking record exists: record_id=${_record_id}"
done
# Check if retry count is reached
if [ "${_retry_cnt}" = "0" ]; then
_debug "Failed to remove record after 50 attempts, please try removing it manually in the dashboard"
return 1
fi
return 0
}
################### Private functions below ##################################
# _get_zoneid
#
# _token=$1
@ -122,7 +164,7 @@ _get_zoneid() {
i=1
while true; do
h=$(printf "%s" "${_domain_string}" | cut -d . -f $i-100)
h=$(printf "%s" "${_domain_string}" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
@ -133,11 +175,11 @@ _get_zoneid() {
if _contains "${response}" '"id"'; then
zoneidlist=$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")
zonenamelist=$(echo "${response}" | _egrep_o "\"name\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")
_debug2 "Return Zone ID(s):" "${zoneidlist}"
_debug2 "Return Zone Name(s):" "${zonenamelist}"
_debug2 "Returned Zone ID(s):" "${zoneidlist}"
_debug2 "Returned Zone Name(s):" "${zonenamelist}"
zoneidnum=0
zoneidcount=$(echo "${zoneidlist}" | grep -c '^')
_debug "Retund Zone ID(s) Count:" "${zoneidcount}"
_debug "Returned Zone ID(s) Count:" "${zoneidcount}"
while [ "${zoneidnum}" -lt "${zoneidcount}" ]; do
zoneidnum=$(_math "$zoneidnum" + 1)
_zoneid=$(echo "${zoneidlist}" | sed -n "${zoneidnum}p")
@ -204,8 +246,7 @@ _add_record() {
\"type\": \"TXT\",
\"ttl\": 1,
\"records\": [
${_exist_record},
\"\\\"${_txtvalue}\\\"\"
${_exist_record},\"\\\"${_txtvalue}\\\"\"
]
}"
fi
@ -213,19 +254,16 @@ _add_record() {
_record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")"
_debug "Record Set ID is:" "${_record_id}"
# Remove all records
while [ "${_record_id}" != "0" ]; do
_debug "Removing Record"
_rm_record "${_token}" "${zoneid}" "${_record_id}"
_record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")"
done
# Add brand new records with all old and new records
export _H2="Content-Type: application/json"
export _H1="X-Auth-Token: ${_token}"
_debug2 "${_post_body}"
_post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets" >/dev/null
if [ -z "${_exist_record}" ]; then
_post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets" >/dev/null
else
_post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets/${_record_id}" false "PUT" >/dev/null
fi
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
if [ "$_code" != "202" ]; then
_err "dns_huaweicloud: http code ${_code}"
@ -253,7 +291,7 @@ _rm_record() {
_get_token() {
_username=$1
_password=$2
_project=$3
_domain_name=$3
_debug "Getting Token"
body="{
@ -267,14 +305,14 @@ _get_token() {
\"name\": \"${_username}\",
\"password\": \"${_password}\",
\"domain\": {
\"name\": \"${_username}\"
\"name\": \"${_domain_name}\"
}
}
}
},
\"scope\": {
\"project\": {
\"id\": \"${_project}\"
\"name\": \"ap-southeast-1\"
}
}
}

4
dnsapi/dns_infomaniak.sh

@ -76,7 +76,7 @@ dns_infomaniak_add() {
domain_id=${zone_and_id#* }
# extract first part of domain
key=${fulldomain%.$zone}
key=${fulldomain%."$zone"}
_debug "zone:$zone id:$domain_id key:$key"
@ -149,7 +149,7 @@ dns_infomaniak_rm() {
domain_id=${zone_and_id#* }
# extract first part of domain
key=${fulldomain%.$zone}
key=${fulldomain%."$zone"}
_debug "zone:$zone id:$domain_id key:$key"

157
dnsapi/dns_ipv64.sh

@ -0,0 +1,157 @@
#!/usr/bin/env sh
#Created by Roman Lumetsberger, to use ipv64.net's API to add/remove text records
#2022/11/29
# Pass credentials before "acme.sh --issue --dns dns_ipv64 ..."
# --
# export IPv64_Token="aaaaaaaaaaaaaaaaaaaaaaaaaa"
# --
#
IPv64_API="https://ipv64.net/api"
######## Public functions ######################
#Usage: dns_ipv64_add _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ipv64_add() {
fulldomain=$1
txtvalue=$2
IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}"
if [ -z "$IPv64_Token" ]; then
_err "You must export variable: IPv64_Token"
_err "The API Key for your IPv64 account is necessary."
_err "You can look it up in your IPv64 account."
return 1
fi
# Now save the credentials.
_saveaccountconf_mutable IPv64_Token "$IPv64_Token"
if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
# convert to lower case
_domain="$(echo "$_domain" | _lower_case)"
_sub_domain="$(echo "$_sub_domain" | _lower_case)"
# Now add the TXT record
_info "Trying to add TXT record"
if _ipv64_rest "POST" "add_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then
_info "TXT record has been successfully added."
return 0
else
_err "Errors happened during adding the TXT record, response=$_response"
return 1
fi
}
#Usage: fulldomain txtvalue
#Usage: dns_ipv64_rm _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
#Remove the txt record after validation.
dns_ipv64_rm() {
fulldomain=$1
txtvalue=$2
IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}"
if [ -z "$IPv64_Token" ]; then
_err "You must export variable: IPv64_Token"
_err "The API Key for your IPv64 account is necessary."
_err "You can look it up in your IPv64 account."
return 1
fi
if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
# convert to lower case
_domain="$(echo "$_domain" | _lower_case)"
_sub_domain="$(echo "$_sub_domain" | _lower_case)"
# Now delete the TXT record
_info "Trying to delete TXT record"
if _ipv64_rest "DELETE" "del_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then
_info "TXT record has been successfully deleted."
return 0
else
_err "Errors happened during deleting the TXT record, response=$_response"
return 1
fi
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain="$1"
i=1
p=1
_ipv64_get "get_domains"
domain_data=$_response
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
fi
#if _contains "$domain_data" "\""$h"\"\:"; then
if _contains "$domain_data" "\"""$h""\"\:"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
#send get request to api
# $1 has to set the api-function
_ipv64_get() {
url="$IPv64_API?$1"
export _H1="Authorization: Bearer $IPv64_Token"
_response=$(_get "$url")
_response="$(echo "$_response" | _normalizeJson)"
if _contains "$_response" "429 Too Many Requests"; then
_info "API throttled, sleeping to reset the limit"
_sleep 10
_response=$(_get "$url")
_response="$(echo "$_response" | _normalizeJson)"
fi
}
_ipv64_rest() {
url="$IPv64_API"
export _H1="Authorization: Bearer $IPv64_Token"
export _H2="Content-Type: application/x-www-form-urlencoded"
_response=$(_post "$2" "$url" "" "$1")
if _contains "$_response" "429 Too Many Requests"; then
_info "API throttled, sleeping to reset the limit"
_sleep 10
_response=$(_post "$2" "$url" "" "$1")
fi
if ! _contains "$_response" "\"info\":\"success\""; then
return 1
fi
_debug2 response "$_response"
return 0
}

12
dnsapi/dns_ispconfig.sh

@ -32,6 +32,10 @@ dns_ispconfig_rm() {
#################### Private functions below ##################################
_ISPC_credentials() {
ISPC_User="${ISPC_User:-$(_readaccountconf_mutable ISPC_User)}"
ISPC_Password="${ISPC_Password:-$(_readaccountconf_mutable ISPC_Password)}"
ISPC_Api="${ISPC_Api:-$(_readaccountconf_mutable ISPC_Api)}"
ISPC_Api_Insecure="${ISPC_Api_Insecure:-$(_readaccountconf_mutable ISPC_Api_Insecure)}"
if [ -z "${ISPC_User}" ] || [ -z "${ISPC_Password}" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then
ISPC_User=""
ISPC_Password=""
@ -40,10 +44,10 @@ _ISPC_credentials() {
_err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again."
return 1
else
_saveaccountconf ISPC_User "${ISPC_User}"
_saveaccountconf ISPC_Password "${ISPC_Password}"
_saveaccountconf ISPC_Api "${ISPC_Api}"
_saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}"
_saveaccountconf_mutable ISPC_User "${ISPC_User}"
_saveaccountconf_mutable ISPC_Password "${ISPC_Password}"
_saveaccountconf_mutable ISPC_Api "${ISPC_Api}"
_saveaccountconf_mutable ISPC_Api_Insecure "${ISPC_Api_Insecure}"
# Set whether curl should use secure or insecure mode
export HTTPS_INSECURE="${ISPC_Api_Insecure}"
fi

303
dnsapi/dns_kas.sh

@ -5,51 +5,81 @@
# Environment variables:
#
# - $KAS_Login (Kasserver API login name)
# - $KAS_Authtype (Kasserver API auth type. Default: sha1)
# - $KAS_Authtype (Kasserver API auth type. Default: plain)
# - $KAS_Authdata (Kasserver API auth data.)
#
# Author: Martin Kammerlander, Phlegx Systems OG <martin.kammerlander@phlegx.com>
# Updated by: Marc-Oliver Lange <git@die-lang.es>
# Credits: Inspired by dns_he.sh. Thanks a lot man!
# Git repo: https://github.com/phlegx/acme.sh
# TODO: Better Error handling
# Last update: squared GmbH <github@squaredgmbh.de>
# Credits:
# - dns_he.sh. Thanks a lot man!
# - Martin Kammerlander, Phlegx Systems OG <martin.kammerlander@phlegx.com>
# - Marc-Oliver Lange <git@die-lang.es>
# - https://github.com/o1oo11oo/kasapi.sh
########################################################################
KAS_Api="https://kasapi.kasserver.com/dokumentation/formular.php"
KAS_Api_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl")"
KAS_Api="$(echo "$KAS_Api_GET" | tr -d ' ' | grep -i "<soap:addresslocation=" | sed "s/='/\n/g" | grep -i "http" | sed "s/'\/>//g")"
_info "[KAS] -> API URL $KAS_Api"
KAS_Auth_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasAuth.wsdl")"
KAS_Auth="$(echo "$KAS_Auth_GET" | tr -d ' ' | grep -i "<soap:addresslocation=" | sed "s/='/\n/g" | grep -i "http" | sed "s/'\/>//g")"
_info "[KAS] -> AUTH URL $KAS_Auth"
KAS_default_ratelimit=5 # TODO - Every response delivers a ratelimit (seconds) where KASAPI is blocking a request.
######## Public functions #####################
dns_kas_add() {
_fulldomain=$1
_txtvalue=$2
_info "Using DNS-01 All-inkl/Kasserver hook"
_info "Adding $_fulldomain DNS TXT entry on All-inkl/Kasserver"
_info "Check and Save Props"
_info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook"
_info "[KAS] -> Check and Save Props"
_check_and_save
_info "Checking Zone and Record_Name"
_info "[KAS] -> Adding $_fulldomain DNS TXT entry on all-inkl.com/Kasserver"
_info "[KAS] -> Retriving Credential Token"
_get_credential_token
_info "[KAS] -> Checking Zone and Record_Name"
_get_zone_and_record_name "$_fulldomain"
_info "Getting Record ID"
_info "[KAS] -> Checking for existing Record entries"
_get_record_id
_info "Creating TXT DNS record"
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&var1=record_name"
params="$params&wert1=$_record_name"
params="$params&var2=record_type"
params="$params&wert2=TXT"
params="$params&var3=record_data"
params="$params&wert3=$_txtvalue"
params="$params&var4=record_aux"
params="$params&wert4=0"
params="$params&kas_action=add_dns_settings"
params="$params&var5=zone_host"
params="$params&wert5=$_zone"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params")"
_debug2 "response" "$response"
if ! _contains "$response" "TRUE"; then
_err "An unkown error occurred, please check manually."
# If there is a record_id, delete the entry
if [ -n "$_record_id" ]; then
_info "[KAS] -> Existing records found. Now deleting old entries"
for i in $_record_id; do
_delete_RecordByID "$i"
done
else
_info "[KAS] -> No record found."
fi
_info "[KAS] -> Creating TXT DNS record"
action="add_dns_settings"
kasReqParam="\"record_name\":\"$_record_name\""
kasReqParam="$kasReqParam,\"record_type\":\"TXT\""
kasReqParam="$kasReqParam,\"record_data\":\"$_txtvalue\""
kasReqParam="$kasReqParam,\"record_aux\":\"0\""
kasReqParam="$kasReqParam,\"zone_host\":\"$_zone\""
response="$(_callAPI "$action" "$kasReqParam")"
_debug2 "[KAS] -> Response" "$response"
if [ -z "$response" ]; then
_info "[KAS] -> Response was empty, please check manually."
return 1
elif _contains "$response" "<SOAP-ENV:Fault>"; then
faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
case "${faultstring}" in
"record_already_exists")
_info "[KAS] -> The record already exists, which must not be a problem. Please check manually."
;;
*)
_err "[KAS] -> An error =>$faultstring<= occurred, please check manually."
return 1
;;
esac
elif ! _contains "$response" "<item><key xsi:type=\"xsd:string\">ReturnString</key><value xsi:type=\"xsd:string\">TRUE</value></item>"; then
_err "[KAS] -> An unknown error occurred, please check manually."
return 1
fi
return 0
@ -58,45 +88,62 @@ dns_kas_add() {
dns_kas_rm() {
_fulldomain=$1
_txtvalue=$2
_info "Using DNS-01 All-inkl/Kasserver hook"
_info "Cleaning up after All-inkl/Kasserver hook"
_info "Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
_info "Check and Save Props"
_info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook"
_info "[KAS] -> Check and Save Props"
_check_and_save
_info "Checking Zone and Record_Name"
_info "[KAS] -> Cleaning up after All-inkl/Kasserver hook"
_info "[KAS] -> Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
_info "[KAS] -> Retriving Credential Token"
_get_credential_token
_info "[KAS] -> Checking Zone and Record_Name"
_get_zone_and_record_name "$_fulldomain"
_info "Getting Record ID"
_info "[KAS] -> Getting Record ID"
_get_record_id
_info "[KAS] -> Removing entries with ID: $_record_id"
# If there is a record_id, delete the entry
if [ -n "$_record_id" ]; then
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&kas_action=delete_dns_settings"
for i in $_record_id; do
params2="$params&var1=record_id"
params2="$params2&wert1=$i"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params2")"
_debug2 "response" "$response"
if ! _contains "$response" "TRUE"; then
_err "Either the txt record is not found or another error occurred, please check manually."
return 1
fi
_delete_RecordByID "$i"
done
else # Cannot delete or unkown error
_err "No record_id found that can be deleted. Please check manually."
return 1
_info "[KAS] -> No record_id found that can be deleted. Please check manually."
fi
return 0
}
########################## PRIVATE FUNCTIONS ###########################
# Delete Record ID
_delete_RecordByID() {
recId=$1
action="delete_dns_settings"
kasReqParam="\"record_id\":\"$recId\""
response="$(_callAPI "$action" "$kasReqParam")"
_debug2 "[KAS] -> Response" "$response"
if [ -z "$response" ]; then
_info "[KAS] -> Response was empty, please check manually."
return 1
elif _contains "$response" "<SOAP-ENV:Fault>"; then
faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
case "${faultstring}" in
"record_id_not_found")
_info "[KAS] -> The record was not found, which perhaps is not a problem. Please check manually."
;;
*)
_err "[KAS] -> An error =>$faultstring<= occurred, please check manually."
return 1
;;
esac
elif ! _contains "$response" "<item><key xsi:type=\"xsd:string\">ReturnString</key><value xsi:type=\"xsd:string\">TRUE</value></item>"; then
_err "[KAS] -> An unknown error occurred, please check manually."
return 1
fi
}
# Checks for the ENV variables and saves them
_check_and_save() {
KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}"
@ -107,7 +154,7 @@ _check_and_save() {
KAS_Login=
KAS_Authtype=
KAS_Authdata=
_err "No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
_err "[KAS] -> No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
return 1
fi
_saveaccountconf_mutable KAS_Login "$KAS_Login"
@ -119,50 +166,116 @@ _check_and_save() {
# Gets back the base domain/zone and record name.
# See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
_get_zone_and_record_name() {
params="?kas_login=$KAS_Login"
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&kas_action=get_domains"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params")"
_debug2 "response" "$response"
_zonen="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "domain_name" | tr '<' '\n' | grep "domain_name" | sed "s/domain_name>=>//g")"
_domain="$1"
_temp_domain="$(echo "$1" | sed 's/\.$//')"
_rootzone="$_domain"
for i in $_zonen; do
l1=${#_rootzone}
action="get_domains"
response="$(_callAPI "$action")"
_debug2 "[KAS] -> Response" "$response"
if [ -z "$response" ]; then
_info "[KAS] -> Response was empty, please check manually."
return 1
elif _contains "$response" "<SOAP-ENV:Fault>"; then
faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
_err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually."
return 1
fi
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')"
domain="$1"
temp_domain="$(echo "$1" | sed 's/\.$//')"
rootzone="$domain"
for i in $zonen; do
l1=${#rootzone}
l2=${#i}
if _endswith "$_domain" "$i" && [ "$l1" -ge "$l2" ]; then
_rootzone="$i"
if _endswith "$domain" "$i" && [ "$l1" -ge "$l2" ]; then
rootzone="$i"
fi
done
_zone="${_rootzone}."
_temp_record_name="$(echo "$_temp_domain" | sed "s/$_rootzone//g")"
_record_name="$(echo "$_temp_record_name" | sed 's/\.$//')"
_debug2 "Zone:" "$_zone"
_debug2 "Domain:" "$_domain"
_debug2 "Record_Name:" "$_record_name"
_zone="${rootzone}."
temp_record_name="$(echo "$temp_domain" | sed "s/$rootzone//g")"
_record_name="$(echo "$temp_record_name" | sed 's/\.$//')"
_debug "[KAS] -> Zone:" "$_zone"
_debug "[KAS] -> Domain:" "$domain"
_debug "[KAS] -> Record_Name:" "$_record_name"
return 0
}
# Retrieve the DNS record ID
_get_record_id() {
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&kas_action=get_dns_settings"
params="$params&var1=zone_host"
params="$params&wert1=$_zone"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params")"
_debug2 "response" "$response"
_record_id="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "=>$_record_name<" | grep '>TXT<' | tr '<' '\n' | grep record_id | sed "s/record_id>=>//g")"
_debug2 _record_id "$_record_id"
action="get_dns_settings"
kasReqParam="\"zone_host\":\"$_zone\""
response="$(_callAPI "$action" "$kasReqParam")"
_debug2 "[KAS] -> Response" "$response"
if [ -z "$response" ]; then
_info "[KAS] -> Response was empty, please check manually."
return 1
elif _contains "$response" "<SOAP-ENV:Fault>"; then
faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
_err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually."
return 1
fi
_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")"
_debug "[KAS] -> Record Id: " "$_record_id"
return 0
}
# Retrieve credential token
_get_credential_token() {
baseParamAuth="\"kas_login\":\"$KAS_Login\""
baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"$KAS_Authtype\""
baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$KAS_Authdata\""
baseParamAuth="$baseParamAuth,\"session_lifetime\":600"
baseParamAuth="$baseParamAuth,\"session_update_lifetime\":\"Y\""
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">{'
data="$data$baseParamAuth}</Params></ns1:KasAuth></SOAP-ENV:Body></SOAP-ENV:Envelope>"
_debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API."
_sleep $KAS_default_ratelimit
contentType="text/xml"
export _H1="SOAPAction: urn:xmethodsKasApiAuthentication#KasAuth"
response="$(_post "$data" "$KAS_Auth" "" "POST" "$contentType")"
_debug2 "[KAS] -> Response" "$response"
if [ -z "$response" ]; then
_info "[KAS] -> Response was empty, please check manually."
return 1
elif _contains "$response" "<SOAP-ENV:Fault>"; then
faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
_err "[KAS] -> Could not retrieve login token or antoher error =>$faultstring<= occurred, please check manually."
return 1
fi
_credential_token="$(echo "$response" | tr '\n' ' ' | sed 's/.*return xsi:type="xsd:string">\(.*\)<\/return>/\1/' | sed 's/<\/ns1:KasAuthResponse\(.*\)Envelope>.*//')"
_debug "[KAS] -> Credential Token: " "$_credential_token"
return 0
}
_callAPI() {
kasaction=$1
kasReqParams=$2
baseParamAuth="\"kas_login\":\"$KAS_Login\""
baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"session\""
baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$_credential_token\""
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">{'
data="$data$baseParamAuth,\"kas_action\":\"$kasaction\""
if [ -n "$kasReqParams" ]; then
data="$data,\"KasRequestParams\":{$kasReqParams}"
fi
data="$data}</Params></ns1:KasApi></SOAP-ENV:Body></SOAP-ENV:Envelope>"
_debug2 "[KAS] -> Request" "$data"
_debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API."
_sleep $KAS_default_ratelimit
contentType="text/xml"
export _H1="SOAPAction: urn:xmethodsKasApi#KasApi"
response="$(_post "$data" "$KAS_Api" "" "POST" "$contentType")"
_debug2 "[KAS] -> Response" "$response"
echo "$response"
}

2
dnsapi/dns_kinghost.sh

@ -2,7 +2,7 @@
############################################################
# KingHost API support #
# http://api.kinghost.net/doc/ #
# https://api.kinghost.net/doc/ #
# #
# Author: Felipe Keller Braz <felipebraz@kinghost.com.br> #
# Report Bugs here: https://github.com/kinghost/acme.sh #

147
dnsapi/dns_la.sh

@ -0,0 +1,147 @@
#!/usr/bin/env sh
#LA_Id="test123"
#LA_Key="d1j2fdo4dee3948"
LA_Api="https://api.dns.la/api"
######## Public functions #####################
#Usage: dns_la_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_la_add() {
fulldomain=$1
txtvalue=$2
LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}"
LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}"
if [ -z "$LA_Id" ] || [ -z "$LA_Key" ]; then
LA_Id=""
LA_Key=""
_err "You didn't specify a dnsla api id and key yet."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable LA_Id "$LA_Id"
_saveaccountconf_mutable LA_Key "$LA_Key"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_info "Adding record"
if _la_rest "record.ashx?cmd=create&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue&recordline="; then
if _contains "$response" '"resultid":'; then
_info "Added, OK"
return 0
elif _contains "$response" '"code":532'; then
_info "Already exists, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain txtvalue
dns_la_rm() {
fulldomain=$1
txtvalue=$2
LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}"
LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
if ! _la_rest "record.ashx?cmd=listn&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue"; then
_err "Error"
return 1
fi
if ! _contains "$response" '"recordid":'; then
_info "Don't need to remove."
return 0
fi
record_id=$(printf "%s" "$response" | grep '"recordid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n')
_debug "record_id" "$record_id"
if [ -z "$record_id" ]; then
_err "Can not get record id to remove."
return 1
fi
if ! _la_rest "record.ashx?cmd=remove&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&recordid=$record_id"; then
_err "Delete record error."
return 1
fi
_contains "$response" '"code":300'
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then
#not valid
return 1
fi
if ! _la_rest "domain.ashx?cmd=get&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domain=$h"; then
return 1
fi
if _contains "$response" '"domainid":'; then
_domain_id=$(printf "%s" "$response" | grep '"domainid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n')
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
fi
return 1
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
#Usage: URI
_la_rest() {
url="$LA_Api/$1"
_debug "$url"
if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then
_err "Error: $url"
return 1
fi
_debug2 response "$response"
return 0
}

4
dnsapi/dns_leaseweb.sh

@ -3,10 +3,10 @@
#Author: Rolph Haspers <r.haspers@global.leaseweb.com>
#Utilize leaseweb.com API to finish dns-01 verifications.
#Requires a Leaseweb API Key (export LSW_Key="Your Key")
#See http://developer.leaseweb.com for more information.
#See https://developer.leaseweb.com for more information.
######## Public functions #####################
LSW_API="https://api.leaseweb.com/hosting/v2/domains/"
LSW_API="https://api.leaseweb.com/hosting/v2/domains"
#Usage: dns_leaseweb_add _acme-challenge.www.domain.com
dns_leaseweb_add() {

1
dnsapi/dns_miab.sh

@ -163,6 +163,7 @@ _retrieve_miab_env() {
_saveaccountconf_mutable MIAB_Username "$MIAB_Username"
_saveaccountconf_mutable MIAB_Password "$MIAB_Password"
_saveaccountconf_mutable MIAB_Server "$MIAB_Server"
return 0
}
#Useage: _miab_rest "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" "custom/_acme-challenge.www.domain.com/txt "POST"

16
dnsapi/dns_mydnsjp.sh

@ -150,7 +150,7 @@ _get_root() {
_mydnsjp_retrieve_domain() {
_debug "Login to MyDNS.JP"
response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")"
response="$(_post "MENU=100&masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/members/")"
cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
# If cookies is not empty then logon successful
@ -159,22 +159,8 @@ _mydnsjp_retrieve_domain() {
return 1
fi
_debug "Retrieve DOMAIN INFO page"
export _H1="Cookie:${cookie}"
response="$(_get "$MYDNSJP_API/?MENU=300")"
if [ "$?" != "0" ]; then
_err "Fail to retrieve DOMAIN INFO."
return 1
fi
_root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/')
# Logout
response="$(_get "$MYDNSJP_API/?MENU=090")"
_debug _root_domain "$_root_domain"
if [ -z "$_root_domain" ]; then

6
dnsapi/dns_namecheap.sh

@ -82,7 +82,7 @@ _get_root() {
_debug "Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api."
# The above "getList" api will only return hosts *owned* by the calling user. However, if the calling
# user is not the owner, but still has administrative rights, we must query the getHosts api directly.
# See this comment and the official namecheap response: http://disq.us/p/1q6v9x9
# See this comment and the official namecheap response: https://disq.us/p/1q6v9x9
if ! _get_root_by_getHosts "$fulldomain"; then
return 1
fi
@ -259,7 +259,7 @@ _set_namecheap_TXT() {
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
_error "Hosts not found"
_err "Hosts not found"
return 1
fi
@ -313,7 +313,7 @@ _del_namecheap_TXT() {
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
_error "Hosts not found"
_err "Hosts not found"
return 1
fi

2
dnsapi/dns_namesilo.sh

@ -110,7 +110,7 @@ _get_root() {
return 1
fi
if _contains "$response" "<domain>$host"; then
if _contains "$response" ">$host</domain>"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$host"
return 0

59
dnsapi/dns_nanelo.sh

@ -0,0 +1,59 @@
#!/usr/bin/env sh
# Official DNS API for Nanelo.com
# Provide the required API Key like this:
# NANELO_TOKEN="FmD408PdqT1E269gUK57"
NANELO_API="https://api.nanelo.com/v1/"
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_nanelo_add() {
fulldomain=$1
txtvalue=$2
NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}"
if [ -z "$NANELO_TOKEN" ]; then
NANELO_TOKEN=""
_err "You didn't configure a Nanelo API Key yet."
_err "Please set NANELO_TOKEN and try again."
_err "Login to Nanelo.com and go to Settings > API Keys to get a Key"
return 1
fi
_saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN"
_info "Adding TXT record to ${fulldomain}"
response="$(_get "$NANELO_API$NANELO_TOKEN/dns/addrecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")"
if _contains "${response}" 'success'; then
return 0
fi
_err "Could not create resource record, please check the logs"
_err "${response}"
return 1
}
dns_nanelo_rm() {
fulldomain=$1
txtvalue=$2
NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}"
if [ -z "$NANELO_TOKEN" ]; then
NANELO_TOKEN=""
_err "You didn't configure a Nanelo API Key yet."
_err "Please set NANELO_TOKEN and try again."
_err "Login to Nanelo.com and go to Settings > API Keys to get a Key"
return 1
fi
_saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN"
_info "Deleting resource record $fulldomain"
response="$(_get "$NANELO_API$NANELO_TOKEN/dns/deleterecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")"
if _contains "${response}" 'success'; then
return 0
fi
_err "Could not delete resource record, please check the logs"
_err "${response}"
return 1
}

10
dnsapi/dns_netlify.sh

@ -18,15 +18,15 @@ dns_netlify_add() {
NETLIFY_ACCESS_TOKEN=""
_err "Please specify your Netlify Access Token and try again."
return 1
else
_saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
fi
_info "Using Netlify"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
if ! _get_root "$fulldomain" "$accesstoken"; then
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
@ -62,9 +62,9 @@ dns_netlify_rm() {
_debug txtdomain "$txtdomain"
_debug txt "$txt"
_saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}"
if ! _get_root "$txtdomain" "$accesstoken"; then
if ! _get_root "$txtdomain"; then
_err "invalid domain"
return 1
fi

1
dnsapi/dns_oci.sh

@ -265,6 +265,7 @@ _signed_request() {
_response="$(_get "https://${_sig_host}${_sig_target}")"
elif [ "$_curl_method" = "PATCH" ]; then
export _H1="$_date_header"
# shellcheck disable=SC2090
export _H2="$_sig_body_sha256"
export _H3="$_sig_body_type"
export _H4="$_sig_body_length"

12
dnsapi/dns_openstack.sh

@ -57,16 +57,16 @@ _dns_openstack_create_recordset() {
if [ -z "$_recordset_id" ]; then
_info "Creating a new recordset"
if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record "$txtvalue" "$_zone_id" "$fulldomain."); then
if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record="$txtvalue" "$_zone_id" "$fulldomain."); then
_err "No recordset ID found after create"
return 1
fi
else
_info "Updating existing recordset"
# Build new list of --record <rec> args for update
_record_args="--record $txtvalue"
# Build new list of --record=<rec> args for update
_record_args="--record=$txtvalue"
for _rec in $_records; do
_record_args="$_record_args --record $_rec"
_record_args="$_record_args --record=$_rec"
done
# shellcheck disable=SC2086
if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then
@ -107,13 +107,13 @@ _dns_openstack_delete_recordset() {
fi
else
_info "Found existing records, updating recordset"
# Build new list of --record <rec> args for update
# Build new list of --record=<rec> args for update
_record_args=""
for _rec in $_records; do
if [ "$_rec" = "$txtvalue" ]; then
continue
fi
_record_args="$_record_args --record $_rec"
_record_args="$_record_args --record=$_rec"
done
# shellcheck disable=SC2086
if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then

4
dnsapi/dns_opnsense.sh

@ -137,7 +137,7 @@ _get_root() {
domain=$1
i=2
p=1
if _opns_rest "GET" "/domain/get"; then
if _opns_rest "GET" "/domain/searchMasterDomain"; then
_domain_response="$response"
else
return 1
@ -150,7 +150,7 @@ _get_root() {
return 1
fi
_debug h "$h"
id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":{\"[^\"]*\":{[^}]*}},\"transferkeyalgo\":{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^}]*}},\"transferkey\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"master\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
if [ -n "$id" ]; then
_debug id "$id"
_host=$(printf "%s" "$domain" | cut -d . -f 1-$p)

8
dnsapi/dns_ovh.sh

@ -92,7 +92,7 @@ _initAuth() {
if [ "$OVH_AK" != "$(_readaccountconf OVH_AK)" ]; then
_info "It seems that your ovh key is changed, let's clear consumer key first."
_clearaccountconf OVH_CK
_clearaccountconf_mutable OVH_CK
fi
_saveaccountconf_mutable OVH_AK "$OVH_AK"
_saveaccountconf_mutable OVH_AS "$OVH_AS"
@ -118,13 +118,14 @@ _initAuth() {
#return and wait for retry.
return 1
fi
_saveaccountconf_mutable OVH_CK "$OVH_CK"
_info "Checking authentication"
if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL" || _contains "$response" "NOT_CREDENTIAL"; then
_err "The consumer key is invalid: $OVH_CK"
_err "Please retry to create a new one."
_clearaccountconf OVH_CK
_clearaccountconf_mutable OVH_CK
return 1
fi
_info "Consumer key is ok."
@ -235,8 +236,7 @@ _ovh_authentication() {
_secure_debug consumerKey "$consumerKey"
OVH_CK="$consumerKey"
_saveaccountconf OVH_CK "$OVH_CK"
_saveaccountconf_mutable OVH_CK "$OVH_CK"
_info "Please open this link to do authentication: $(__green "$validationUrl")"
_info "Here is a guide for you: $(__green "$wiki")"

115
dnsapi/dns_rage4.sh

@ -0,0 +1,115 @@
#!/usr/bin/env sh
#
#RAGE4_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
#RAGE4_USERNAME="xxxx@sss.com"
RAGE4_Api="https://rage4.com/rapi/"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_rage4_add() {
fulldomain=$1
txtvalue=$2
unquotedtxtvalue=$(echo "$txtvalue" | tr -d \")
RAGE4_USERNAME="${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}"
RAGE4_TOKEN="${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}"
if [ -z "$RAGE4_USERNAME" ] || [ -z "$RAGE4_TOKEN" ]; then
RAGE4_USERNAME=""
RAGE4_TOKEN=""
_err "You didn't specify a Rage4 api token and username yet."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable RAGE4_USERNAME "$RAGE4_USERNAME"
_saveaccountconf_mutable RAGE4_TOKEN "$RAGE4_TOKEN"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1"
return 0
}
#fulldomain txtvalue
dns_rage4_rm() {
fulldomain=$1
txtvalue=$2
RAGE4_USERNAME="${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}"
RAGE4_TOKEN="${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug "Getting txt records"
_rage4_rest "getrecords/?id=${_domain_id}"
_record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p')
_rage4_rest "deleterecord/?id=${_record_id}"
return 0
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
if ! _rage4_rest "getdomains"; then
return 1
fi
_debug _get_root_domain "$domain"
for line in $(echo "$response" | tr '}' '\n'); do
__domain=$(echo "$line" | sed -rn 's/.*"name":"([^"]*)",.*/\1/p')
__domain_id=$(echo "$line" | sed -rn 's/.*"id":([^,]*),.*/\1/p')
if [ "$domain" != "${domain%"$__domain"*}" ]; then
_domain_id="$__domain_id"
break
fi
done
if [ -z "$_domain_id" ]; then
return 1
fi
return 0
}
_rage4_rest() {
ep="$1"
_debug "$ep"
username_trimmed=$(echo "$RAGE4_USERNAME" | tr -d '"')
token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"')
auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64)
export _H1="Content-Type: application/json"
export _H2="Authorization: Basic $auth"
response="$(_get "$RAGE4_Api$ep")"
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

4
dnsapi/dns_regru.sh

@ -92,10 +92,10 @@ _get_root() {
domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
for ITEM in ${domains_list}; do
IDN_ITEM="$(_idn "${ITEM}")"
IDN_ITEM=${ITEM}
case "${domain}" in
*${IDN_ITEM}*)
_domain=${IDN_ITEM}
_domain="$(_idn "${ITEM}")"
_debug _domain "${_domain}"
return 0
;;

94
dnsapi/dns_selfhost.sh

@ -0,0 +1,94 @@
#!/usr/bin/env sh
#
# Author: Marvin Edeler
# Report Bugs here: https://github.com/Marvo2011/acme.sh/issues/1
# Last Edit: 17.02.2022
dns_selfhost_add() {
fulldomain=$1
txt=$2
_info "Calling acme-dns on selfhost"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txt"
SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl"
# Get values, but don't save until we successfully validated
SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}"
SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}"
# These values are domain dependent, so read them from there
SELFHOSTDNS_MAP="${SELFHOSTDNS_MAP:-$(_readdomainconf SELFHOSTDNS_MAP)}"
# Selfhost api can't dynamically add TXT record,
# so we have to store the last used RID of the domain to support a second RID for wildcard domains
# (format: 'fulldomainA:lastRid fulldomainB:lastRid ...')
SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(_readdomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL)
if [ -z "${SELFHOSTDNS_USERNAME:-}" ] || [ -z "${SELFHOSTDNS_PASSWORD:-}" ]; then
_err "SELFHOSTDNS_USERNAME and SELFHOSTDNS_PASSWORD must be set"
return 1
fi
# get the domain entry from SELFHOSTDNS_MAP
# only match full domains (at the beginning of the string or with a leading whitespace),
# e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
# if the domain is defined multiple times only the last occurance will be matched
mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p")
_debug2 mapEntry "$mapEntry"
if test -z "$mapEntry"; then
_err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
return 1
fi
# get the RIDs from the map entry
rid1=$(echo "$mapEntry" | cut -d: -f2)
rid2=$(echo "$mapEntry" | cut -d: -f3)
# read last used rid domain
lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p")
_debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
rid="$rid1"
if [ "$lastUsedRidForDomain" = "$rid" ] && ! test -z "$rid2"; then
rid="$rid2"
fi
_info "Trying to add $txt on selfhost for rid: $rid"
data="?username=$SELFHOSTDNS_USERNAME&password=$SELFHOSTDNS_PASSWORD&rid=$rid&content=$txt"
response="$(_get "$SELFHOSTDNS_UPDATE_URL$data")"
if ! echo "$response" | grep "200 OK" >/dev/null; then
_err "Invalid response of acme-dns for selfhost"
return 1
fi
# write last used rid domain
newLastUsedRidForDomainEntry="$fulldomain:$rid"
if ! test -z "$lastUsedRidForDomainEntry"; then
# replace last used rid entry for domain
SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/$lastUsedRidForDomainEntry/$newLastUsedRidForDomainEntry/p")
else
# add last used rid entry for domain
if test -z "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"; then
SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$newLastUsedRidForDomainEntry"
else
SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$SELFHOSTDNS_MAP_LAST_USED_INTERNAL $newLastUsedRidForDomainEntry"
fi
fi
# Now that we know the values are good, save them
_saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME"
_saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD"
# These values are domain dependent, so store them there
_savedomainconf SELFHOSTDNS_MAP "$SELFHOSTDNS_MAP"
_savedomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"
}
dns_selfhost_rm() {
fulldomain=$1
txt=$2
_debug fulldomain "$fulldomain"
_debug txtvalue "$txt"
_info "Creating and removing of records is not supported by selfhost API, will not delete anything."
}

2
dnsapi/dns_servercow.sh

@ -53,7 +53,7 @@ dns_servercow_add() {
if printf -- "%s" "$response" | grep "{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\"" >/dev/null; then
_info "A txt record with the same name already exists."
# trim the string on the left
txtvalue_old=${response#*{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\",\"content\":\"}
txtvalue_old=${response#*{\"name\":\""$_sub_domain"\",\"ttl\":20,\"type\":\"TXT\",\"content\":\"}
# trim the string on the right
txtvalue_old=${txtvalue_old%%\"*}

25
dnsapi/dns_transip.sh

@ -1,7 +1,6 @@
#!/usr/bin/env sh
TRANSIP_Api_Url="https://api.transip.nl/v6"
TRANSIP_Token_Read_Only="false"
TRANSIP_Token_Global_Key="false"
TRANSIP_Token_Expiration="30 minutes"
# You can't reuse a label token, so we leave this empty normally
TRANSIP_Token_Label=""
@ -96,7 +95,11 @@ _transip_get_token() {
nonce=$(echo "TRANSIP$(_time)" | _digest sha1 hex | cut -c 1-32)
_debug nonce "$nonce"
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}\"}"
# make IP whitelisting configurable
TRANSIP_Token_Global_Key="${TRANSIP_Token_Global_Key:-$(_readaccountconf_mutable TRANSIP_Token_Global_Key)}"
_saveaccountconf_mutable TRANSIP_Token_Global_Key "$TRANSIP_Token_Global_Key"
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}\"}"
_debug data "$data"
#_signature=$(printf "%s" "$data" | openssl dgst -sha512 -sign "$TRANSIP_Key_File" | _base64)
@ -139,6 +142,18 @@ _transip_setup() {
_saveaccountconf_mutable TRANSIP_Username "$TRANSIP_Username"
_saveaccountconf_mutable TRANSIP_Key_File "$TRANSIP_Key_File"
# download key file if it's an URL
if _startswith "$TRANSIP_Key_File" "http"; then
_debug "download transip key file"
TRANSIP_Key_URL=$TRANSIP_Key_File
TRANSIP_Key_File="$(_mktemp)"
chmod 600 "$TRANSIP_Key_File"
if ! _get "$TRANSIP_Key_URL" >"$TRANSIP_Key_File"; then
_err "Error getting key file from : $TRANSIP_Key_URL"
return 1
fi
fi
if [ -f "$TRANSIP_Key_File" ]; then
if ! grep "BEGIN PRIVATE KEY" "$TRANSIP_Key_File" >/dev/null 2>&1; then
_err "Key file doesn't seem to be a valid key: ${TRANSIP_Key_File}"
@ -156,6 +171,12 @@ _transip_setup() {
fi
fi
if [ -n "${TRANSIP_Key_URL}" ]; then
_debug "delete transip key file"
rm "${TRANSIP_Key_File}"
TRANSIP_Key_File=$TRANSIP_Key_URL
fi
_get_root "$fulldomain" || return 1
return 0

21
dnsapi/dns_ultra.sh

@ -5,7 +5,8 @@
#
# ULTRA_PWD="some_password_goes_here"
ULTRA_API="https://restapi.ultradns.com/v2/"
ULTRA_API="https://api.ultradns.com/v3/"
ULTRA_AUTH_API="https://api.ultradns.com/v2/"
#Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt"
dns_ultra_add() {
@ -121,7 +122,7 @@ _get_root() {
return 1
fi
if _contains "${response}" "${h}." >/dev/null; then
_domain_id=$(echo "$response" | _egrep_o "${h}")
_domain_id=$(echo "$response" | _egrep_o "${h}" | head -1)
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="${h}"
@ -142,23 +143,25 @@ _ultra_rest() {
ep="$2"
data="$3"
_debug "$ep"
_debug TOKEN "${AUTH_TOKEN}"
if [ -z "$AUTH_TOKEN" ]; then
_ultra_login
fi
_debug TOKEN "$AUTH_TOKEN"
_ultra_login
export _H1="Content-Type: application/json"
export _H2="Authorization: Bearer ${AUTH_TOKEN}"
export _H2="Authorization: Bearer $AUTH_TOKEN"
if [ "$m" != "GET" ]; then
_debug data "${data}"
response="$(_post "${data}" "${ULTRA_API}"/"${ep}" "" "${m}")"
_debug data "$data"
response="$(_post "$data" "$ULTRA_API$ep" "" "$m")"
else
response="$(_get "$ULTRA_API/$ep")"
response="$(_get "$ULTRA_API$ep")"
fi
}
_ultra_login() {
export _H1=""
export _H2=""
AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_AUTH_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
export AUTH_TOKEN
}

24
dnsapi/dns_vultr.sh

@ -3,10 +3,10 @@
#
#VULTR_API_KEY=000011112222333344445555666677778888
VULTR_Api="https://api.vultr.com/v1"
VULTR_Api="https://api.vultr.com/v2"
######## Public functions #####################
#
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_vultr_add() {
fulldomain=$1
@ -31,14 +31,14 @@ dns_vultr_add() {
_debug _domain "$_domain"
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
_vultr_rest GET "domains/$_domain/records"
if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
if ! _vultr_rest POST 'dns/create_record' "domain=$_domain&name=$_sub_domain&data=\"$txtvalue\"&type=TXT"; then
if ! _vultr_rest POST "domains/$_domain/records" "{\"name\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"type\":\"TXT\"}"; then
_err "$response"
return 1
fi
@ -71,14 +71,14 @@ dns_vultr_rm() {
_debug _domain "$_domain"
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
_vultr_rest GET "domains/$_domain/records"
if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
_record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
_record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'id' | cut -d : -f 2 | tr -d '"')"
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
@ -87,7 +87,7 @@ dns_vultr_rm() {
return 0
fi
if ! _vultr_rest POST 'dns/delete_record' "domain=$_domain&RECORDID=$_record_id"; then
if ! _vultr_rest DELETE "domains/$_domain/records/$_record_id"; then
_err "$response"
return 1
fi
@ -112,11 +112,11 @@ _get_root() {
return 1
fi
if ! _vultr_rest GET "dns/list"; then
if ! _vultr_rest GET "domains"; then
return 1
fi
if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
if printf "%s\n" "$response" | grep -E '^\{.*\}' >/dev/null; then
if _contains "$response" "\"domain\":\"$_domain\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
return 0
@ -139,10 +139,10 @@ _vultr_rest() {
data="$3"
_debug "$ep"
api_key_trimmed=$(echo $VULTR_API_KEY | tr -d '"')
api_key_trimmed=$(echo "$VULTR_API_KEY" | tr -d '"')
export _H1="Api-Key: $api_key_trimmed"
export _H2='Content-Type: application/x-www-form-urlencoded'
export _H1="Authorization: Bearer $api_key_trimmed"
export _H2='Content-Type: application/json'
if [ "$m" != "GET" ]; then
_debug data "$data"

52
dnsapi/dns_world4you.sh

@ -12,7 +12,7 @@ RECORD=''
# Usage: dns_world4you_add <fqdn> <value>
dns_world4you_add() {
fqdn="$1"
fqdn=$(echo "$1" | _lower_case)
value="$2"
_info "Using world4you to add record"
_debug fulldomain "$fqdn"
@ -49,12 +49,12 @@ dns_world4you_add() {
ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded')
_resethttp
if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
res=$(_get "$WORLD4YOU_API/$paketnr/dns")
if _contains "$res" "successfully"; then
return 0
else
msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>\|^\s*//g')
msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g')
if [ "$msg" = '' ]; then
_err "Unable to add record: Unknown error"
echo "$ret" >'error-01.html'
@ -66,15 +66,15 @@ dns_world4you_add() {
return 1
fi
else
_err "$(_head_n 3 <"$HTTP_HEADER")"
_err "View $HTTP_HEADER for debugging"
msg=$(echo "$ret" | grep '"form-error-message"' | sed 's/^.*<div class="form-error-message">\([^<]*\)<\/div>.*$/\1/')
_err "Unable to add record: my.world4you.com: $msg"
return 1
fi
}
# Usage: dns_world4you_rm <fqdn> <value>
dns_world4you_rm() {
fqdn="$1"
fqdn=$(echo "$1" | _lower_case)
value="$2"
_info "Using world4you to remove record"
_debug fulldomain "$fqdn"
@ -113,12 +113,12 @@ dns_world4you_rm() {
ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded')
_resethttp
if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
res=$(_get "$WORLD4YOU_API/$paketnr/dns")
if _contains "$res" "successfully"; then
return 0
else
msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>\|^\s*//g')
msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g')
if [ "$msg" = '' ]; then
_err "Unable to remove record: Unknown error"
echo "$ret" >'error-01.html'
@ -130,8 +130,8 @@ dns_world4you_rm() {
return 1
fi
else
_err "$(_head_n 3 <"$HTTP_HEADER")"
_err "View $HTTP_HEADER for debugging"
msg=$(echo "$ret" | grep "form-error-message" | sed 's/^.*<div class="form-error-message">\([^<]*\)<\/div>.*$/\1/')
_err "Unable to remove record: my.world4you.com: $msg"
return 1
fi
}
@ -155,34 +155,47 @@ _login() {
_saveaccountconf_mutable WORLD4YOU_USERNAME "$WORLD4YOU_USERNAME"
_saveaccountconf_mutable WORLD4YOU_PASSWORD "$WORLD4YOU_PASSWORD"
_resethttp
export ACME_HTTP_NO_REDIRECTS=1
page=$(_get "$WORLD4YOU_API/login")
_resethttp
if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
_info "Already logged in"
_parse_sessid
return 0
fi
_info "Logging in..."
username="$WORLD4YOU_USERNAME"
password="$WORLD4YOU_PASSWORD"
csrf_token=$(_get "$WORLD4YOU_API/login" | grep '_csrf_token' | sed 's/^.*<input[^>]*value=\"\([^"]*\)\".*$/\1/')
sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
csrf_token=$(echo "$page" | grep '_csrf_token' | sed 's/^.*<input[^>]*value=\"\([^"]*\)\".*$/\1/')
_parse_sessid
export _H1="Cookie: W4YSESSID=$sessid"
export _H2="X-Requested-With: XMLHttpRequest"
body="_username=$username&_password=$password&_csrf_token=$csrf_token"
ret=$(_post "$body" "$WORLD4YOU_API/login" '' POST 'application/x-www-form-urlencoded')
unset _H2
_debug ret "$ret"
if _contains "$ret" "\"success\":true"; then
_info "Successfully logged in"
sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
_parse_sessid
else
_err "Unable to log in: $(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')"
msg=$(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')
_err "Unable to log in: my.world4you.com: $msg"
return 1
fi
}
# Usage _get_paketnr <fqdn> <form>
# Usage: _get_paketnr <fqdn> <form>
_get_paketnr() {
fqdn="$1"
form="$2"
domains=$(echo "$form" | grep 'header-paket-domain' | sed 's/<[^>]*>//g' | sed 's/^.*>\([^>]*\)$/\1/')
domains=$(echo "$form" | grep '<ul class="nav header-paket-list">' | sed 's/<li/\n<li/g' | sed 's/<[^>]*>/ /g' | sed 's/^.*>\([^>]*\)$/\1/')
domain=''
for domain in $domains; do
if _contains "$fqdn" "$domain\$"; then
@ -197,6 +210,11 @@ _get_paketnr() {
TLD="$domain"
_debug domain "$domain"
RECORD=$(echo "$fqdn" | cut -c"1-$((${#fqdn} - ${#TLD} - 1))")
PAKETNR=$(echo "$form" | grep "data-textfilter=\".* $domain " | _tail_n 1 | sed "s|.*$WORLD4YOU_API/\\([0-9]*\\)/.*|\\1|")
PAKETNR=$(echo "$domains" | grep "$domain" | sed 's/^[^,]*, *\([0-9]*\).*$/\1/')
return 0
}
# Usage: _parse_sessid
_parse_sessid() {
sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | _tail_n 1 | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
}

264
dnsapi/dns_yc.sh

@ -0,0 +1,264 @@
#!/usr/bin/env sh
#YC_Zone_ID="" # DNS Zone ID
#YC_Folder_ID="" # YC Folder ID
#YC_SA_ID="" # Service Account ID
#YC_SA_Key_ID="" # Service Account IAM Key ID
#YC_SA_Key_File_Path="/path/to/private.key" # Path to private.key use instead of YC_SA_Key_File_PEM_b64
#YC_SA_Key_File_PEM_b64="" # Base64 content of private.key use instead of YC_SA_Key_File_Path
YC_Api="https://dns.api.cloud.yandex.net/dns/v1"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_yc_add() {
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
txtvalue=$2
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
if [ "$YC_SA_Key_File_PEM_b64" ]; then
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
YC_SA_Key_File="private.key"
_savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64"
else
YC_SA_Key_File="$YC_SA_Key_File_Path"
_savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path"
fi
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
if [ -f "$YC_SA_Key_File" ]; then
if _isRSA "$YC_SA_Key_File" >/dev/null 2>&1; then
if [ "$YC_Zone_ID" ]; then
_savedomainconf YC_Zone_ID "$YC_Zone_ID"
_savedomainconf YC_SA_ID "$YC_SA_ID"
_savedomainconf YC_SA_Key_ID "$YC_SA_Key_ID"
elif [ "$YC_Folder_ID" ]; then
_savedomainconf YC_Folder_ID "$YC_Folder_ID"
_saveaccountconf_mutable YC_SA_ID "$YC_SA_ID"
_saveaccountconf_mutable YC_SA_Key_ID "$YC_SA_Key_ID"
_clearaccountconf_mutable YC_Zone_ID
_clearaccountconf YC_Zone_ID
else
_err "You didn't specify a Yandex Cloud Zone ID or Folder ID yet."
return 1
fi
else
_err "YC_SA_Key_File not a RSA file(_isRSA function return false)."
return 1
fi
else
_err "YC_SA_Key_File not found in path $YC_SA_Key_File."
return 1
fi
else
_clearaccountconf YC_Zone_ID
_clearaccountconf YC_Folder_ID
_clearaccountconf YC_SA_ID
_clearaccountconf YC_SA_Key_ID
_clearaccountconf YC_SA_Key_File_PEM_b64
_clearaccountconf YC_SA_Key_File_Path
_err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
_err "Error: $response"
return 1
fi
_info "Adding record"
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
if _contains "$response" "\"done\": true"; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain txtvalue
dns_yc_rm() {
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
txtvalue=$2
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$")
_debug exists_txtvalue "$exists_txtvalue"
else
_err "Error: $response"
return 1
fi
if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then
if _contains "$response" "\"done\": true"; then
_info "Delete, OK"
return 0
else
_err "Delete record error."
return 1
fi
fi
_err "Delete record error."
return 1
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
i=1
p=1
# Use Zone ID directly if provided
if [ "$YC_Zone_ID" ]; then
if ! _yc_rest GET "zones/$YC_Zone_ID"; then
return 1
else
if echo "$response" | tr -d " " | _egrep_o "\"id\":\"$YC_Zone_ID\"" >/dev/null; then
_domain=$(echo "$response" | _egrep_o "\"zone\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
if [ "$_domain" ]; then
_cutlength=$((${#domain} - ${#_domain}))
_sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength")
_domain_id=$YC_Zone_ID
return 0
else
return 1
fi
else
return 1
fi
fi
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if [ "$YC_Folder_ID" ]; then
if ! _yc_rest GET "zones?folderId=$YC_Folder_ID"; then
return 1
fi
else
echo "You didn't specify a Yandex Cloud Folder ID."
return 1
fi
if _contains "$response" "\"zone\": \"$h\""; then
_domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
return 0
fi
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_yc_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
if [ ! "$YC_Token" ]; then
_debug "Login"
_yc_login
else
_debug "Token already exists. Skip Login."
fi
token_trimmed=$(echo "$YC_Token" | tr -d '"')
export _H1="Content-Type: application/json"
export _H2="Authorization: Bearer $token_trimmed"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$YC_Api/$ep" "" "$m")"
else
response="$(_get "$YC_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}
_yc_login() {
header=$(echo "{\"typ\":\"JWT\",\"alg\":\"PS256\",\"kid\":\"$YC_SA_Key_ID\"}" | _normalizeJson | _base64 | _url_replace)
_debug header "$header"
_current_timestamp=$(_time)
_expire_timestamp=$(_math "$_current_timestamp" + 1200) # 20 minutes
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)
_debug payload "$payload"
#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 )
_signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
_debug2 _signature "$_signature"
rm -rf "$YC_SA_Key_File"
_jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
_debug2 _jwt "$_jwt"
export _H1="Content-Type: application/json"
_iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
_debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
_debug3 YC_Token
return 0
}

45
notify/slack_app.sh

@ -0,0 +1,45 @@
#!/usr/bin/env sh
#Support Slack APP notifications
#SLACK_APP_CHANNEL=""
#SLACK_APP_TOKEN=""
slack_app_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
SLACK_APP_CHANNEL="${SLACK_APP_CHANNEL:-$(_readaccountconf_mutable SLACK_APP_CHANNEL)}"
if [ -n "$SLACK_APP_CHANNEL" ]; then
_saveaccountconf_mutable SLACK_APP_CHANNEL "$SLACK_APP_CHANNEL"
fi
SLACK_APP_TOKEN="${SLACK_APP_TOKEN:-$(_readaccountconf_mutable SLACK_APP_TOKEN)}"
if [ -n "$SLACK_APP_TOKEN" ]; then
_saveaccountconf_mutable SLACK_APP_TOKEN "$SLACK_APP_TOKEN"
fi
_content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)"
_data="{\"text\": \"$_content\", "
if [ -n "$SLACK_APP_CHANNEL" ]; then
_data="$_data\"channel\": \"$SLACK_APP_CHANNEL\", "
fi
_data="$_data\"mrkdwn\": \"true\"}"
export _H1="Authorization: Bearer $SLACK_APP_TOKEN"
SLACK_APP_API_URL="https://slack.com/api/chat.postMessage"
if _post "$_data" "$SLACK_APP_API_URL" "" "POST" "application/json; charset=utf-8"; then
# shellcheck disable=SC2154
SLACK_APP_RESULT_OK=$(echo "$response" | _egrep_o 'ok" *: *true')
if [ "$?" = "0" ] && [ "$SLACK_APP_RESULT_OK" ]; then
_info "slack send success."
return 0
fi
fi
_err "slack send error."
_err "$response"
return 1
}

4
notify/smtp.sh

@ -169,7 +169,7 @@ _clean_email_header() {
# email
_email_has_display_name() {
_email="$1"
expr "$_email" : '^.*[<>"]' >/dev/null
echo "$_email" | grep -q -E '^.*[<>"]'
}
##
@ -249,7 +249,7 @@ _mime_encoded_word() {
_text="$1"
# (regex character ranges like [a-z] can be locale-dependent; enumerate ASCII chars to avoid that)
_ascii='] $`"'"[!#%&'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ~^_abcdefghijklmnopqrstuvwxyz{|}~-"
if expr "$_text" : "^.*[^$_ascii]" >/dev/null; then
if echo "$_text" | grep -q -E "^.*[^$_ascii]"; then
# At least one non-ASCII char; convert entire thing to encoded word
printf "%s" "=?UTF-8?B?$(printf "%s" "$_text" | _base64)?="
else

Loading…
Cancel
Save