Browse Source

Merge branch 'acmesh-official:master' into cloudhub-v2

pull/4360/head
Guilherme Pereira 11 months ago
committed by GitHub
parent
commit
476fb76fba
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 177
      .github/workflows/DNS.yml
  2. 18
      .github/workflows/DragonFlyBSD.yml
  3. 10
      .github/workflows/FreeBSD.yml
  4. 4
      .github/workflows/Linux.yml
  5. 4
      .github/workflows/MacOS.yml
  6. 17
      .github/workflows/NetBSD.yml
  7. 75
      .github/workflows/Omnios.yml
  8. 12
      .github/workflows/OpenBSD.yml
  9. 8
      .github/workflows/PebbleStrict.yml
  10. 17
      .github/workflows/Solaris.yml
  11. 13
      .github/workflows/Ubuntu.yml
  12. 6
      .github/workflows/Windows.yml
  13. 10
      .github/workflows/dockerhub.yml
  14. 6
      .github/workflows/pr_dns.yml
  15. 2
      .github/workflows/pr_notify.yml
  16. 6
      .github/workflows/shellcheck.yml
  17. 13
      Dockerfile
  18. 49
      README.md
  19. 429
      acme.sh
  20. 27
      deploy/docker.sh
  21. 15
      deploy/gcore_cdn.sh
  22. 2
      deploy/gitlab.sh
  23. 2
      deploy/haproxy.sh
  24. 158
      deploy/panos.sh
  25. 8
      deploy/proxmoxve.sh
  26. 6
      deploy/routeros.sh
  27. 409
      deploy/ssh.sh
  28. 216
      deploy/synology_dsm.sh
  29. 21
      deploy/truenas.sh
  30. 79
      deploy/vault.sh
  31. 47
      deploy/vault_cli.sh
  32. 160
      dnsapi/dns_1984hosting.sh
  33. 2
      dnsapi/dns_acmeproxy.sh
  34. 2
      dnsapi/dns_ali.sh
  35. 180
      dnsapi/dns_artfiles.sh
  36. 13
      dnsapi/dns_arvan.sh
  37. 2
      dnsapi/dns_aws.sh
  38. 89
      dnsapi/dns_bookmyname.sh
  39. 2
      dnsapi/dns_cloudns.sh
  40. 185
      dnsapi/dns_dnsexit.sh
  41. 148
      dnsapi/dns_do.sh
  42. 8
      dnsapi/dns_dynv6.sh
  43. 2
      dnsapi/dns_edgedns.sh
  44. 35
      dnsapi/dns_gandi_livedns.sh
  45. 2
      dnsapi/dns_gcloud.sh
  46. 187
      dnsapi/dns_gcore.sh
  47. 6
      dnsapi/dns_gd.sh
  48. 173
      dnsapi/dns_googledomains.sh
  49. 78
      dnsapi/dns_huaweicloud.sh
  50. 4
      dnsapi/dns_infomaniak.sh
  51. 2
      dnsapi/dns_inwx.sh
  52. 157
      dnsapi/dns_ipv64.sh
  53. 4
      dnsapi/dns_kappernet.sh
  54. 2
      dnsapi/dns_kas.sh
  55. 2
      dnsapi/dns_kinghost.sh
  56. 4
      dnsapi/dns_leaseweb.sh
  57. 2
      dnsapi/dns_loopia.sh
  58. 2
      dnsapi/dns_namecheap.sh
  59. 59
      dnsapi/dns_nanelo.sh
  60. 1
      dnsapi/dns_oci.sh
  61. 12
      dnsapi/dns_openstack.sh
  62. 4
      dnsapi/dns_opnsense.sh
  63. 17
      dnsapi/dns_ovh.sh
  64. 60
      dnsapi/dns_pleskxml.sh
  65. 2
      dnsapi/dns_servercow.sh
  66. 211
      dnsapi/dns_tencent.sh
  67. 21
      dnsapi/dns_variomedia.sh
  68. 6
      dnsapi/dns_vultr.sh
  69. 105
      dnsapi/dns_west_cn.sh
  70. 226
      notify/aws_ses.sh
  71. 52
      notify/mattermost.sh
  72. 4
      notify/smtp.sh

177
.github/workflows/DNS.yml

@ -65,9 +65,9 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - 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: Set env file - name: Set env file
run: | run: |
cd ../acmetest cd ../acmetest
@ -113,27 +113,27 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: brew install socat run: brew install socat
- name: Clone acmetest - 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 - name: Run acmetest
run: | run: |
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh
@ -164,7 +164,7 @@ jobs:
- name: Set git to use LF - name: Set git to use LF
run: | run: |
git config --global core.autocrlf false git config --global core.autocrlf false
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install cygwin base packages with chocolatey - name: Install cygwin base packages with chocolatey
run: | run: |
choco config get cacheLocation choco config get cacheLocation
@ -172,31 +172,31 @@ jobs:
shell: cmd shell: cmd
- name: Install cygwin additional packages - name: Install cygwin additional packages
run: | run: |
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
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 shell: cmd
- name: Set ENV - name: Set ENV
shell: cmd shell: cmd
run: | run: |
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV% echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
- name: Clone acmetest - 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 - name: Run acmetest
shell: bash shell: bash
run: | run: |
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh
@ -204,7 +204,7 @@ jobs:
FreeBSD: FreeBSD:
runs-on: macos-12
runs-on: ubuntu-latest
needs: Windows needs: Windows
env: env:
TEST_DNS : ${{ secrets.TEST_DNS }} TEST_DNS : ${{ secrets.TEST_DNS }}
@ -223,10 +223,10 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v1
with: 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}}' 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 prepare: pkg install -y socat curl
@ -234,19 +234,19 @@ jobs:
copyback: false copyback: false
run: | run: |
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh
@ -255,7 +255,7 @@ jobs:
OpenBSD: OpenBSD:
runs-on: macos-12
runs-on: ubuntu-latest
needs: FreeBSD needs: FreeBSD
env: env:
TEST_DNS : ${{ secrets.TEST_DNS }} TEST_DNS : ${{ secrets.TEST_DNS }}
@ -274,10 +274,10 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v1
with: 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}}' 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 prepare: pkg_add socat curl
@ -285,19 +285,19 @@ jobs:
copyback: false copyback: false
run: | run: |
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh
@ -306,7 +306,7 @@ jobs:
NetBSD: NetBSD:
runs-on: macos-12
runs-on: ubuntu-latest
needs: OpenBSD needs: OpenBSD
env: env:
TEST_DNS : ${{ secrets.TEST_DNS }} TEST_DNS : ${{ secrets.TEST_DNS }}
@ -325,31 +325,31 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v1
with: 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}}' 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: | prepare: |
pkg_add curl socat
/usr/sbin/pkg_add curl socat
usesh: true usesh: true
copyback: false copyback: false
run: | run: |
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh
@ -358,7 +358,7 @@ jobs:
DragonFlyBSD: DragonFlyBSD:
runs-on: macos-12
runs-on: ubuntu-latest
needs: NetBSD needs: NetBSD
env: env:
TEST_DNS : ${{ secrets.TEST_DNS }} TEST_DNS : ${{ secrets.TEST_DNS }}
@ -377,31 +377,31 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v1
with: 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}}' 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: | prepare: |
pkg install -y curl socat
pkg install -y curl socat libnghttp2
usesh: true usesh: true
copyback: false copyback: false
run: | run: |
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh
@ -413,7 +413,7 @@ jobs:
Solaris: Solaris:
runs-on: macos-12
runs-on: ubuntu-latest
needs: DragonFlyBSD needs: DragonFlyBSD
env: env:
TEST_DNS : ${{ secrets.TEST_DNS }} TEST_DNS : ${{ secrets.TEST_DNS }}
@ -433,10 +433,10 @@ jobs:
TokenName4: ${{ secrets.TokenName4}} TokenName4: ${{ secrets.TokenName4}}
TokenName5: ${{ secrets.TokenName5}} TokenName5: ${{ secrets.TokenName5}}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v1
with: 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}}' 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 copyback: false
@ -445,19 +445,68 @@ jobs:
pkg set-mediator -v -I default@1.1 openssl pkg set-mediator -v -I default@1.1 openssl
export PATH=/usr/gnu/bin:$PATH export PATH=/usr/gnu/bin:$PATH
if [ "${{ secrets.TokenName1}}" ] ; then if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
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
Omnios:
runs-on: ubuntu-latest
needs: Solaris
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 Omnios 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@v4
- name: Clone acmetest
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/omnios-vm@v1
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: pkg install socat
run: |
if [ "${{ secrets.TokenName1}}" ] ; then
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
fi fi
if [ "${{ secrets.TokenName2}}" ] ; then if [ "${{ secrets.TokenName2}}" ] ; then
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
fi fi
if [ "${{ secrets.TokenName3}}" ] ; then if [ "${{ secrets.TokenName3}}" ] ; then
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
fi fi
if [ "${{ secrets.TokenName4}}" ] ; then if [ "${{ secrets.TokenName4}}" ] ; then
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
fi fi
if [ "${{ secrets.TokenName5}}" ] ; then if [ "${{ secrets.TokenName5}}" ] ; then
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
fi fi
cd ../acmetest cd ../acmetest
./letest.sh ./letest.sh

18
.github/workflows/DragonFlyBSD.yml

@ -20,7 +20,6 @@ concurrency:
jobs: jobs:
DragonFlyBSD: DragonFlyBSD:
strategy: strategy:
@ -36,7 +35,7 @@ jobs:
# CA: "ZeroSSL RSA Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh" # CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: "" # TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
runs-on: ubuntu-latest
env: env:
TEST_LOCAL: 1 TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -44,9 +43,10 @@ jobs:
CA: ${{ matrix.CA }} CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }} CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps: steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
id: tunnel id: tunnel
with: with:
protocol: http protocol: http
@ -54,16 +54,16 @@ jobs:
- name: Set envs - name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/dragonflybsd-vm@v1
with: with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
copyback: "false"
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: | nat: |
"8080": "80" "8080": "80"
prepare: | prepare: |
pkg install -y curl socat
pkg install -y curl socat libnghttp2
usesh: true usesh: true
copyback: false
run: | run: |
cd ../acmetest \ cd ../acmetest \
&& ./letest.sh && ./letest.sh

10
.github/workflows/FreeBSD.yml

@ -41,7 +41,7 @@ jobs:
# CA: "ZeroSSL RSA Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh" # CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: "" # TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
runs-on: ubuntu-latest
env: env:
TEST_LOCAL: 1 TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps: steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
id: tunnel id: tunnel
with: with:
protocol: http protocol: http
@ -60,8 +60,8 @@ jobs:
- name: Set envs - name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/freebsd-vm@v1
with: with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: | nat: |

4
.github/workflows/Linux.yml

@ -33,11 +33,11 @@ jobs:
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_ACME_Server: "LetsEncrypt.org_test" TEST_ACME_Server: "LetsEncrypt.org_test"
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Clone acmetest - name: Clone acmetest
run: | run: |
cd .. \ 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/ && cp -r acme.sh acmetest/
- name: Run acmetest - name: Run acmetest
run: | run: |

4
.github/workflows/MacOS.yml

@ -44,13 +44,13 @@ jobs:
CA_EMAIL: ${{ matrix.CA_EMAIL }} CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: brew install socat run: brew install socat
- name: Clone acmetest - name: Clone acmetest
run: | run: |
cd .. \ 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/ && cp -r acme.sh acmetest/
- name: Run acmetest - name: Run acmetest
run: | run: |

17
.github/workflows/NetBSD.yml

@ -20,7 +20,6 @@ concurrency:
jobs: jobs:
NetBSD: NetBSD:
strategy: strategy:
@ -36,7 +35,7 @@ jobs:
# CA: "ZeroSSL RSA Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh" # CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: "" # TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
runs-on: ubuntu-latest
env: env:
TEST_LOCAL: 1 TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -44,9 +43,10 @@ jobs:
CA: ${{ matrix.CA }} CA: ${{ matrix.CA }}
CA_EMAIL: ${{ matrix.CA_EMAIL }} CA_EMAIL: ${{ matrix.CA_EMAIL }}
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps: steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
id: tunnel id: tunnel
with: with:
protocol: http protocol: http
@ -54,15 +54,14 @@ jobs:
- name: Set envs - name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/netbsd-vm@v1
with: 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: | nat: |
"8080": "80" "8080": "80"
prepare: | prepare: |
export PKG_PATH="http://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f '1 2' -d.)/All/"
pkg_add curl socat
/usr/sbin/pkg_add curl socat
usesh: true usesh: true
copyback: false copyback: false
run: | run: |

75
.github/workflows/Omnios.yml

@ -0,0 +1,75 @@
name: Omnios
on:
push:
branches:
- '*'
paths:
- '*.sh'
- '.github/workflows/Omnios.yml'
pull_request:
branches:
- dev
paths:
- '*.sh'
- '.github/workflows/Omnios.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Omnios:
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: ubuntu-latest
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@v4
- 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/omnios-vm@v1
with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: |
"8080": "80"
prepare: pkg install socat wget
copyback: false
run: |
cd ../acmetest \
&& ./letest.sh

12
.github/workflows/OpenBSD.yml

@ -41,7 +41,7 @@ jobs:
# CA: "ZeroSSL RSA Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh" # CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: "" # TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
runs-on: ubuntu-latest
env: env:
TEST_LOCAL: 1 TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps: steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
id: tunnel id: tunnel
with: with:
protocol: http protocol: http
@ -60,13 +60,13 @@ jobs:
- name: Set envs - name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/openbsd-vm@v1
with: with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
nat: | nat: |
"8080": "80" "8080": "80"
prepare: pkg_add socat curl wget
prepare: pkg_add socat curl wget libnghttp2
usesh: true usesh: true
copyback: false copyback: false
run: | run: |

8
.github/workflows/PebbleStrict.yml

@ -33,7 +33,7 @@ jobs:
TEST_CA: "Pebble Intermediate CA" TEST_CA: "Pebble Intermediate CA"
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: sudo apt-get install -y socat run: sudo apt-get install -y socat
- name: Run Pebble - name: Run Pebble
@ -41,7 +41,7 @@ jobs:
- name: Set up Pebble - name: Set up Pebble
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
- name: Clone acmetest - 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 - name: Run acmetest
run: cd ../acmetest && ./letest.sh run: cd ../acmetest && ./letest.sh
@ -58,7 +58,7 @@ jobs:
TEST_IPCERT: 1 TEST_IPCERT: 1
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: sudo apt-get install -y socat run: sudo apt-get install -y socat
- name: Run Pebble - name: Run Pebble
@ -67,6 +67,6 @@ jobs:
-e PEBBLE_VA_ALWAYS_VALID=1 \ -e PEBBLE_VA_ALWAYS_VALID=1 \
-p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict -p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict
- name: Clone acmetest - 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 - name: Run acmetest
run: cd ../acmetest && ./letest.sh run: cd ../acmetest && ./letest.sh

17
.github/workflows/Solaris.yml

@ -14,12 +14,12 @@ on:
- '*.sh' - '*.sh'
- '.github/workflows/Solaris.yml' - '.github/workflows/Solaris.yml'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
Solaris: Solaris:
strategy: strategy:
@ -41,7 +41,7 @@ jobs:
# CA: "ZeroSSL RSA Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA"
# CA_EMAIL: "githubtest@acme.sh" # CA_EMAIL: "githubtest@acme.sh"
# TEST_PREFERRED_CHAIN: "" # TEST_PREFERRED_CHAIN: ""
runs-on: macos-12
runs-on: ubuntu-latest
env: env:
TEST_LOCAL: 1 TEST_LOCAL: 1
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@ -51,8 +51,8 @@ jobs:
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps: steps:
- uses: actions/checkout@v2
- uses: vmactions/cf-tunnel@v0.0.3
- uses: actions/checkout@v4
- uses: vmactions/cf-tunnel@v0
id: tunnel id: tunnel
with: with:
protocol: http protocol: http
@ -60,15 +60,16 @@ jobs:
- name: Set envs - name: Set envs
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
- name: Clone acmetest - name: Clone acmetest
run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v0
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
- uses: vmactions/solaris-vm@v1
with: with:
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
copyback: "false"
nat: | nat: |
"8080": "80" "8080": "80"
prepare: pkgutil -y -i socat curl wget prepare: pkgutil -y -i socat curl wget
copyback: false
run: | run: |
cd ../acmetest \ cd ../acmetest \
&& ./letest.sh && ./letest.sh

13
.github/workflows/Ubuntu.yml

@ -70,7 +70,7 @@ jobs:
TestingDomain: ${{ matrix.TestingDomain }} TestingDomain: ${{ matrix.TestingDomain }}
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: sudo apt-get install -y socat wget run: sudo apt-get install -y socat wget
- name: Start StepCA - name: Start StepCA
@ -80,15 +80,20 @@ jobs:
-p 9000:9000 \ -p 9000:9000 \
-e "DOCKER_STEPCA_INIT_NAME=Smallstep" \ -e "DOCKER_STEPCA_INIT_NAME=Smallstep" \
-e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \ -e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \
-e "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true" \
-e "DOCKER_STEPCA_INIT_PASSWORD=test" \
--name stepca \ --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 kill -1 1 \
&& docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt" && docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt"
- name: Clone acmetest - name: Clone acmetest
run: | run: |
cd .. \ 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/ && cp -r acme.sh acmetest/
- name: Run acmetest - name: Run acmetest
run: | run: |

6
.github/workflows/Windows.yml

@ -49,7 +49,7 @@ jobs:
- name: Set git to use LF - name: Set git to use LF
run: | run: |
git config --global core.autocrlf false git config --global core.autocrlf false
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install cygwin base packages with chocolatey - name: Install cygwin base packages with chocolatey
run: | run: |
choco config get cacheLocation choco config get cacheLocation
@ -57,7 +57,7 @@ jobs:
shell: cmd shell: cmd
- name: Install cygwin additional packages - name: Install cygwin additional packages
run: | 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 shell: cmd
- name: Set ENV - name: Set ENV
shell: cmd shell: cmd
@ -69,7 +69,7 @@ jobs:
echo "PATH=%PATH%" echo "PATH=%PATH%"
- name: Clone acmetest - name: Clone acmetest
shell: cmd 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 - name: Run acmetest
shell: cmd shell: cmd
run: cd ../acmetest && bash.exe -c ./letest.sh run: cd ../acmetest && bash.exe -c ./letest.sh

10
.github/workflows/dockerhub.yml

@ -28,9 +28,9 @@ jobs:
id: step_one id: step_one
run: | run: |
if [ "$DOCKER_PASSWORD" ] ; then if [ "$DOCKER_PASSWORD" ] ; then
echo "::set-output name=hasToken::true"
echo "hasToken=true" >>$GITHUB_OUTPUT
else else
echo "::set-output name=hasToken::false"
echo "hasToken=false" >>$GITHUB_OUTPUT
fi fi
- name: Check the value - name: Check the value
run: echo ${{ steps.step_one.outputs.hasToken }} run: echo ${{ steps.step_one.outputs.hasToken }}
@ -41,11 +41,11 @@ jobs:
if: "contains(needs.CheckToken.outputs.hasToken, 'true')" if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
steps: steps:
- name: checkout code - name: checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: login to docker hub - name: login to docker hub
run: | run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

6
.github/workflows/pr_dns.yml

@ -4,8 +4,6 @@ on:
pull_request_target: pull_request_target:
types: types:
- opened - opened
branches:
- 'dev'
paths: paths:
- 'dnsapi/*.sh' - 'dnsapi/*.sh'
@ -22,9 +20,11 @@ jobs:
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: `**Welcome** 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).
First thing: don't send PR to the master branch, please send to the dev branch instead.
Please make sure you've 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. Then reply on this message, otherwise, your code will not be reviewed or merged.
We look forward to reviewing your Pull request shortly ✨ We look forward to reviewing your Pull request shortly ✨
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
` `
}) })

2
.github/workflows/pr_notify.yml

@ -22,7 +22,7 @@ jobs:
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: `**Welcome** body: `**Welcome**
Please make sure you're read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify).
Please make sure you've 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. Then reply on this message, otherwise, your code will not be reviewed or merged.
We look forward to reviewing your Pull request shortly ✨ We look forward to reviewing your Pull request shortly ✨
` `

6
.github/workflows/shellcheck.yml

@ -22,16 +22,16 @@ jobs:
ShellCheck: ShellCheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Shellcheck - name: Install Shellcheck
run: sudo apt-get install -y shellcheck run: sudo apt-get install -y shellcheck
- name: DoShellcheck - 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: shfmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install shfmt - 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 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 - name: shfmt

13
Dockerfile

@ -1,4 +1,4 @@
FROM alpine:3.15
FROM alpine:3.17
RUN apk --no-cache add -f \ RUN apk --no-cache add -f \
openssl \ openssl \
@ -12,7 +12,8 @@ RUN apk --no-cache add -f \
oath-toolkit-oathtool \ oath-toolkit-oathtool \
tar \ tar \
libidn \ libidn \
jq
jq \
cronie
ENV LE_CONFIG_HOME /acme.sh 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 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 \ RUN for verb in help \
version \ version \
@ -64,12 +65,10 @@ RUN for verb in help \
RUN printf "%b" '#!'"/usr/bin/env sh\n \ RUN printf "%b" '#!'"/usr/bin/env sh\n \
if [ \"\$1\" = \"daemon\" ]; then \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 \ else \n \
exec -- \"\$@\"\n \ exec -- \"\$@\"\n \
fi" >/entry.sh && chmod +x /entry.sh
fi\n" >/entry.sh && chmod +x /entry.sh
VOLUME /acme.sh VOLUME /acme.sh

49
README.md

@ -8,7 +8,7 @@
[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml) [![Windows](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) [![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) [![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)
[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)
![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg) ![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg)
![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg) ![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg)
@ -51,14 +51,12 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
- [ruby-china.org](https://ruby-china.org/topics/31983) - [ruby-china.org](https://ruby-china.org/topics/31983)
- [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management) - [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management)
- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) - [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89)
- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt)
- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)
- [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)
- [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html)
- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh)
- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
- [CentOS Web Panel](http://centos-webpanel.com/)
- [CentOS Web Panel](https://control-webpanel.com)
- [lnmp.org](https://lnmp.org/) - [lnmp.org](https://lnmp.org/)
- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials) - [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)
@ -75,20 +73,21 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
|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 |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 |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 |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)
|10|[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios
|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)| Debian
|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)|CentOS
|13|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
|14|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|15|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
|16|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
|17|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
|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)|Gentoo Linux
|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)|ClearLinux
|22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|24|[![](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): Check our [testing project](https://github.com/acmesh-official/acmetest):
@ -361,10 +360,6 @@ Ok, it's done.
# 10. Issue ECC certificates # 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-`. Just set the `keylength` parameter with a prefix `ec-`.
For example: For example:
@ -385,10 +380,12 @@ Please look at the `keylength` parameter above.
Valid values are: 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")** 2. **ec-384 (secp384r1, "ECDSA P-384")**
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** 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 # 11. Issue Wildcard certificates
@ -510,10 +507,6 @@ Support this project with your organization. Your logo will show up here with a
<a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/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 # 19. License & Others

429
acme.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh #!/usr/bin/env sh
VER=3.0.5
VER=3.0.8
PROJECT_NAME="acme.sh" 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_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" DEFAULT_OPENSSL_BIN="openssl"
@ -102,12 +102,12 @@ ECC_SUFFIX="${ECC_SEP}ecc"
LOG_LEVEL_1=1 LOG_LEVEL_1=1
LOG_LEVEL_2=2 LOG_LEVEL_2=2
LOG_LEVEL_3=3 LOG_LEVEL_3=3
DEFAULT_LOG_LEVEL="$LOG_LEVEL_1"
DEFAULT_LOG_LEVEL="$LOG_LEVEL_2"
DEBUG_LEVEL_1=1 DEBUG_LEVEL_1=1
DEBUG_LEVEL_2=2 DEBUG_LEVEL_2=2
DEBUG_LEVEL_3=3 DEBUG_LEVEL_3=3
DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_1
DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_2
DEBUG_LEVEL_NONE=0 DEBUG_LEVEL_NONE=0
DOH_CLOUDFLARE=1 DOH_CLOUDFLARE=1
@ -923,8 +923,16 @@ _sed_i() {
fi fi
} }
if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then
__USE_EGREP=1
else
__USE_EGREP=""
fi
_egrep_o() { _egrep_o() {
if ! egrep -o "$1" 2>/dev/null; then
if [ "$__USE_EGREP" ]; then
egrep -o -- "$1" 2>/dev/null
else
sed -n 's/.*\('"$1"'\).*/\1/p' sed -n 's/.*\('"$1"'\).*/\1/p'
fi fi
} }
@ -1553,7 +1561,7 @@ createDomainKey() {
createCSR() { createCSR() {
_info "Creating csr" _info "Creating csr"
if [ -z "$1" ]; then if [ -z "$1" ]; then
_usage "Usage: $PROJECT_ENTRY --create-csr --domain <domain.tld> [--domain <domain2.tld> ...]"
_usage "Usage: $PROJECT_ENTRY --create-csr --domain <domain.tld> [--domain <domain2.tld> ...] [--ecc]"
return return
fi fi
@ -1637,7 +1645,7 @@ _stat() {
#keyfile #keyfile
_isRSA() { _isRSA() {
keyfile=$1 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 return 0
fi fi
return 1 return 1
@ -1646,7 +1654,7 @@ _isRSA() {
#keyfile #keyfile
_isEcc() { _isEcc() {
keyfile=$1 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 return 0
fi fi
return 1 return 1
@ -1744,7 +1752,7 @@ _calcjwk() {
_debug3 x64 "$x64" _debug3 x64 "$x64"
xend=$(_math "$xend" + 1) 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" _debug3 y "$y"
y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)" y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)"
@ -1787,6 +1795,10 @@ _date2time() {
if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
return return
fi fi
#Omnios
if da="$(echo "$1" | tr -d "Z" | tr "T" ' ')" perl -MTime::Piece -e 'print Time::Piece->strptime($ENV{da}, "%Y-%m-%d %H:%M:%S")->epoch, "\n";' 2>/dev/null; then
return
fi
_err "Can not parse _date2time $1" _err "Can not parse _date2time $1"
return 1 return 1
} }
@ -1852,9 +1864,15 @@ _inithttp() {
_ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE " _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
fi 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 " _ACME_CURL="$_ACME_CURL -g "
fi 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 fi
if [ -z "$_ACME_WGET" ] && _exists "wget"; then if [ -z "$_ACME_WGET" ] && _exists "wget"; then
@ -1872,11 +1890,11 @@ _inithttp() {
elif [ "$CA_BUNDLE" ]; then elif [ "$CA_BUNDLE" ]; then
_ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE " _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
fi 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 fi
__HTTP_INITIALIZED=1 __HTTP_INITIALIZED=1
@ -2058,7 +2076,7 @@ _get() {
fi fi
_debug "_WGET" "$_WGET" _debug "_WGET" "$_WGET"
if [ "$onlyheader" ]; then if [ "$onlyheader" ]; then
_wget_out = "$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)"
_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 if _contains "$_WGET" " -d "; then
# Demultiplex wget debug output # Demultiplex wget debug output
echo "$_wget_out" >&2 echo "$_wget_out" >&2
@ -2095,12 +2113,18 @@ _head_n() {
} }
_tail_n() { _tail_n() {
if ! tail -n "$1" 2>/dev/null; then
if _is_solaris; then
#fix for solaris #fix for solaris
tail -"$1" tail -"$1"
else
tail -n "$1"
fi fi
} }
_tail_c() {
tail -c "$1" 2>/dev/null || tail -"$1"c
}
# url payload needbase64 keyfile # url payload needbase64 keyfile
_send_signed_request() { _send_signed_request() {
url=$1 url=$1
@ -2110,6 +2134,7 @@ _send_signed_request() {
if [ -z "$keyfile" ]; then if [ -z "$keyfile" ]; then
keyfile="$ACCOUNT_KEY_PATH" keyfile="$ACCOUNT_KEY_PATH"
fi fi
_debug "=======Begin Send Signed Request======="
_debug url "$url" _debug url "$url"
_debug payload "$payload" _debug payload "$payload"
@ -2223,6 +2248,20 @@ _send_signed_request() {
_debug3 _body "$_body" _debug3 _body "$_body"
fi 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 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." _info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds."
_CACHED_NONCE="" _CACHED_NONCE=""
@ -2257,12 +2296,18 @@ _setopt() {
if [ ! -f "$__conf" ]; then if [ ! -f "$__conf" ]; then
touch "$__conf" touch "$__conf"
fi fi
if [ -n "$(_tail_c 1 <"$__conf")" ]; then
echo >>"$__conf"
fi
if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
_debug3 OK _debug3 OK
if _contains "$__val" "&"; then if _contains "$__val" "&"; then
__val="$(echo "$__val" | sed 's/&/\\&/g')" __val="$(echo "$__val" | sed 's/&/\\&/g')"
fi fi
if _contains "$__val" "|"; then
__val="$(echo "$__val" | sed 's/|/\\|/g')"
fi
text="$(cat "$__conf")" text="$(cat "$__conf")"
printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf" printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
@ -2270,6 +2315,9 @@ _setopt() {
if _contains "$__val" "&"; then if _contains "$__val" "&"; then
__val="$(echo "$__val" | sed 's/&/\\&/g')" __val="$(echo "$__val" | sed 's/&/\\&/g')"
fi fi
if _contains "$__val" "|"; then
__val="$(echo "$__val" | sed 's/|/\\|/g')"
fi
text="$(cat "$__conf")" text="$(cat "$__conf")"
printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf" printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
@ -2343,6 +2391,26 @@ _readdomainconf() {
_read_conf "$DOMAIN_CONF" "$1" _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 #key value base64encode
_savedeployconf() { _savedeployconf() {
_savedomainconf "SAVED_$1" "$2" "$3" _savedomainconf "SAVED_$1" "$2" "$3"
@ -2357,12 +2425,14 @@ _getdeployconf() {
if [ "$_rac_value" ]; then if [ "$_rac_value" ]; then
if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
_debug2 "trim quotation marks" _debug2 "trim quotation marks"
eval "export $_rac_key=$_rac_value"
eval $_rac_key=$_rac_value
export $_rac_key
fi fi
return 0 # do nothing return 0 # do nothing
fi 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 #_saveaccountconf key value base64encode
@ -2429,10 +2499,10 @@ _startserver() {
_debug Le_Listen_V6 "$Le_Listen_V6" _debug Le_Listen_V6 "$Le_Listen_V6"
_NC="socat" _NC="socat"
if [ "$Le_Listen_V4" ]; then
_NC="$_NC -4"
elif [ "$Le_Listen_V6" ]; then
if [ "$Le_Listen_V6" ]; then
_NC="$_NC -6" _NC="$_NC -6"
else
_NC="$_NC -4"
fi fi
if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
@ -2449,22 +2519,34 @@ _startserver() {
_content_len="$(printf "%s" "$content" | wc -c)" _content_len="$(printf "%s" "$content" | wc -c)"
_debug _content_len "$_content_len" _debug _content_len "$_content_len"
_debug "_NC" "$_NC $SOCAT_OPTIONS" _debug "_NC" "$_NC $SOCAT_OPTIONS"
export _SOCAT_ERR="$(_mktemp)"
$_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \ $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \
echo 'HTTP/1.0 200 OK'; \ echo 'HTTP/1.0 200 OK'; \
echo 'Content-Length\: $_content_len'; \ echo 'Content-Length\: $_content_len'; \
echo ''; \ echo ''; \
printf '%s' '$content';" &
printf '%s' '$content';" 2>"$_SOCAT_ERR" &
serverproc="$!" serverproc="$!"
if [ -f "$_SOCAT_ERR" ]; then
if grep "Permission denied" "$_SOCAT_ERR" >/dev/null; then
_err "socat: $(cat $_SOCAT_ERR)"
_err "Can not listen for user: $(whoami)"
_err "Maybe try with root again?"
rm -f "$_SOCAT_ERR"
return 1
fi
fi
} }
_stopserver() { _stopserver() {
pid="$1" pid="$1"
_debug "pid" "$pid" _debug "pid" "$pid"
if [ -z "$pid" ]; then if [ -z "$pid" ]; then
rm -f "$_SOCAT_ERR"
return return
fi fi
kill $pid kill $pid
rm -f "$_SOCAT_ERR"
} }
@ -2826,12 +2908,14 @@ _initpath() {
if _isEccKey "$_ilength"; then if _isEccKey "$_ilength"; then
DOMAIN_PATH="$domainhomeecc" DOMAIN_PATH="$domainhomeecc"
else
elif [ -z "$__SELECTED_RSA_KEY" ]; then
if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; 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
fi fi
_debug DOMAIN_PATH "$DOMAIN_PATH" _debug DOMAIN_PATH "$DOMAIN_PATH"
export DOMAIN_PATH
fi fi
if [ -z "$DOMAIN_BACKUP_PATH" ]; then if [ -z "$DOMAIN_BACKUP_PATH" ]; then
@ -2883,22 +2967,6 @@ _initpath() {
} }
_exec() {
if [ -z "$_EXEC_TEMP_ERR" ]; then
_EXEC_TEMP_ERR="$(_mktemp)"
fi
if [ "$_EXEC_TEMP_ERR" ]; then
eval "$@ 2>>$_EXEC_TEMP_ERR"
else
eval "$@"
fi
}
_exec_err() {
[ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR"
}
_apachePath() { _apachePath() {
_APACHECTL="apachectl" _APACHECTL="apachectl"
if ! _exists apachectl; then if ! _exists apachectl; then
@ -2911,8 +2979,7 @@ _apachePath() {
fi fi
fi fi
if ! _exec $_APACHECTL -V >/dev/null; then
_exec_err
if ! $_APACHECTL -V >/dev/null; then
return 1 return 1
fi fi
@ -2964,8 +3031,7 @@ _restoreApache() {
cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf" cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf"
_debug "Restored: $httpdconf." _debug "Restored: $httpdconf."
if ! _exec $_APACHECTL -t; then
_exec_err
if ! $_APACHECTL -t; then
_err "Sorry, restore apache config error, please contact me." _err "Sorry, restore apache config error, please contact me."
return 1 return 1
fi fi
@ -2983,8 +3049,7 @@ _setApache() {
#test the conf first #test the conf first
_info "Checking if there is an error in the apache config file before starting." _info "Checking if there is an error in the apache config file before starting."
if ! _exec "$_APACHECTL" -t >/dev/null; then
_exec_err
if ! $_APACHECTL -t >/dev/null; then
_err "The apache config file has error, please fix it first, then try again." _err "The apache config file has error, please fix it first, then try again."
_err "Don't worry, there is nothing changed to your system." _err "Don't worry, there is nothing changed to your system."
return 1 return 1
@ -3045,8 +3110,7 @@ Allow from all
chmod 755 "$ACME_DIR" chmod 755 "$ACME_DIR"
fi fi
if ! _exec "$_APACHECTL" graceful; then
_exec_err
if ! $_APACHECTL graceful; then
_err "$_APACHECTL graceful error, please contact me." _err "$_APACHECTL graceful error, please contact me."
_restoreApache _restoreApache
return 1 return 1
@ -3077,7 +3141,7 @@ _setNginx() {
_err "nginx command is not found." _err "nginx command is not found."
return 1 return 1
fi fi
NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")"
NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "\-\-conf-path=[^ ]* " | tr -d " ")"
_debug NGINX_CONF "$NGINX_CONF" _debug NGINX_CONF "$NGINX_CONF"
NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)" NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)"
_debug NGINX_CONF "$NGINX_CONF" _debug NGINX_CONF "$NGINX_CONF"
@ -3131,8 +3195,8 @@ _setNginx() {
return 1 return 1
fi fi
_info "Check the nginx conf before setting up." _info "Check the nginx conf before setting up."
if ! _exec "nginx -t" >/dev/null; then
_exec_err
if ! nginx -t >/dev/null 2>&1; then
_err "It seems that nginx conf is not correct, cannot continue."
return 1 return 1
fi fi
@ -3159,16 +3223,14 @@ location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
fi fi
_debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)" _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)"
_info "nginx conf is done, let's check it again." _info "nginx conf is done, let's check it again."
if ! _exec "nginx -t" >/dev/null; then
_exec_err
if ! nginx -t >/dev/null 2>&1; then
_err "It seems that nginx conf was broken, let's restore." _err "It seems that nginx conf was broken, let's restore."
cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
return 1 return 1
fi fi
_info "Reload nginx" _info "Reload nginx"
if ! _exec "nginx -s reload" >/dev/null; then
_exec_err
if ! nginx -s reload >/dev/null 2>&1; then
_err "It seems that nginx reload error, let's restore." _err "It seems that nginx reload error, let's restore."
cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
return 1 return 1
@ -3293,8 +3355,7 @@ _restoreNginx() {
done done
_info "Reload nginx" _info "Reload nginx"
if ! _exec "nginx -s reload" >/dev/null; then
_exec_err
if ! nginx -s reload >/dev/null; then
_err "It seems that nginx reload error, please report bug." _err "It seems that nginx reload error, please report bug."
return 1 return 1
fi fi
@ -3986,7 +4047,7 @@ _ns_purge_cf() {
#checks if cf server is available #checks if cf server is available
_ns_is_available_cf() { _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 return 0
else else
return 1 return 1
@ -3994,7 +4055,7 @@ _ns_is_available_cf() {
} }
_ns_is_available_google() { _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 return 0
else else
return 1 return 1
@ -4010,7 +4071,7 @@ _ns_lookup_google() {
} }
_ns_is_available_ali() { _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 return 0
else else
return 1 return 1
@ -4026,7 +4087,7 @@ _ns_lookup_ali() {
} }
_ns_is_available_dp() { _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 return 0
else else
return 1 return 1
@ -4041,8 +4102,7 @@ _ns_lookup_dp() {
_ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
} }
#domain, type
_ns_lookup() {
_ns_select_doh() {
if [ -z "$DOH_USE" ]; then if [ -z "$DOH_USE" ]; then
_debug "Detect dns server first." _debug "Detect dns server first."
if _ns_is_available_cf; then if _ns_is_available_cf; then
@ -4061,7 +4121,11 @@ _ns_lookup() {
_err "No doh" _err "No doh"
fi fi
fi fi
}
#domain, type
_ns_lookup() {
_ns_select_doh
if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
_ns_lookup_cf "$@" _ns_lookup_cf "$@"
elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then
@ -4084,6 +4148,7 @@ __check_txt() {
_debug "_c_txtdomain" "$_c_txtdomain" _debug "_c_txtdomain" "$_c_txtdomain"
_debug "_c_aliasdomain" "$_c_aliasdomain" _debug "_c_aliasdomain" "$_c_aliasdomain"
_debug "_c_txt" "$_c_txt" _debug "_c_txt" "$_c_txt"
_ns_select_doh
_answers="$(_ns_lookup "$_c_aliasdomain" TXT)" _answers="$(_ns_lookup "$_c_aliasdomain" TXT)"
_contains "$_answers" "$_c_txt" _contains "$_answers" "$_c_txt"
@ -4414,6 +4479,7 @@ issue() {
_debug "_saved_account_key_hash is not changed, skip register account." _debug "_saved_account_key_hash is not changed, skip register account."
fi fi
export Le_Next_Domain_Key="$CERT_KEY_PATH.next"
if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then
_info "Signing from existing CSR." _info "Signing from existing CSR."
else else
@ -4426,14 +4492,30 @@ issue() {
fi fi
_debug "Read key length:$_key" _debug "Read key length:$_key"
if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then 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 return 1
fi fi
fi fi
if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then
_err "Create CSR error." _err "Create CSR error."
_clearup _clearup
@ -4446,7 +4528,7 @@ issue() {
vlist="$Le_Vlist" vlist="$Le_Vlist"
_cleardomainconf "Le_Vlist" _cleardomainconf "Le_Vlist"
_info "Getting domain auth token for each domain"
_debug "Getting domain auth token for each domain"
sep='#' sep='#'
dvsep=',' dvsep=','
if [ -z "$vlist" ]; then if [ -z "$vlist" ]; then
@ -4502,12 +4584,22 @@ issue() {
if [ "$_notAfter" ]; then if [ "$_notAfter" ]; then
_newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\""
fi fi
_debug "STEP 1, Ordering a Certificate"
if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then
_err "Create new order error." _err "Create new order error."
_clearup _clearup
_on_issue_err "$_post_hook" _on_issue_err "$_post_hook"
return 1 return 1
fi fi
if _contains "$response" "invalid"; then
if echo "$response" | _normalizeJson | grep '"status":"invalid"' >/dev/null 2>&1; then
_err "Create new order with invalid status."
_err "$response"
_clearup
_on_issue_err "$_post_hook"
return 1
fi
fi
Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)" Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)"
_debug Le_LinkOrder "$Le_LinkOrder" _debug Le_LinkOrder "$Le_LinkOrder"
@ -4532,6 +4624,7 @@ issue() {
return 1 return 1
fi fi
_debug "STEP 2, Get the authorizations of each domain"
#domain and authz map #domain and authz map
_authorizations_map="" _authorizations_map=""
for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
@ -4540,6 +4633,7 @@ issue() {
_err "get to authz error." _err "get to authz error."
_err "_authorizations_seg" "$_authorizations_seg" _err "_authorizations_seg" "$_authorizations_seg"
_err "_authz_url" "$_authz_url" _err "_authz_url" "$_authz_url"
_err "$response"
_clearup _clearup
_on_issue_err "$_post_hook" _on_issue_err "$_post_hook"
return 1 return 1
@ -4547,14 +4641,23 @@ issue() {
response="$(echo "$response" | _normalizeJson)" response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response" _debug2 response "$response"
if echo "$response" | grep '"status":"invalid"' >/dev/null 2>&1; then
_err "get authz objec with invalid status, please try again later."
_err "_authorizations_seg" "$_authorizations_seg"
_err "$response"
_clearup
_on_issue_err "$_post_hook"
return 1
fi
_d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2- | tr -d ' "')" _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2- | tr -d ' "')"
if _contains "$response" "\"wildcard\" *: *true"; then if _contains "$response" "\"wildcard\" *: *true"; then
_d="*.$_d" _d="*.$_d"
fi fi
_debug2 _d "$_d" _debug2 _d "$_d"
_authorizations_map="$_d,$response
_authorizations_map="$_d,$response#$_authz_url
$_authorizations_map" $_authorizations_map"
done done
_debug2 _authorizations_map "$_authorizations_map" _debug2 _authorizations_map "$_authorizations_map"
_index=0 _index=0
@ -4606,33 +4709,32 @@ $_authorizations_map"
_on_issue_err "$_post_hook" _on_issue_err "$_post_hook"
return 1 return 1
fi fi
_authz_url="$(echo "$_candidates" | sed "s/$_idn_d,//" | _egrep_o "#.*" | sed "s/^#//")"
_debug _authz_url "$_authz_url"
if [ -z "$thumbprint" ]; then if [ -z "$thumbprint" ]; then
thumbprint="$(__calc_account_thumbprint)" thumbprint="$(__calc_account_thumbprint)"
fi fi
keyauthorization=""
if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
_debug "$d is already valid."
keyauthorization="$STATE_VERIFIED"
_debug keyauthorization "$keyauthorization"
fi
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
_debug entry "$entry" _debug entry "$entry"
keyauthorization=""
if [ -z "$entry" ]; then
if ! _startswith "$d" '*.'; then
_debug "Not a wildcard domain, lets check whether the validation is already valid."
if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
_debug "$d is already valid."
keyauthorization="$STATE_VERIFIED"
_debug keyauthorization "$keyauthorization"
fi
fi
if [ -z "$keyauthorization" ]; then
_err "Error, can not get domain token entry $d for $vtype"
_supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
if [ "$_supported_vtypes" ]; then
_err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
fi
_clearup
_on_issue_err "$_post_hook"
return 1
if [ -z "$keyauthorization" -a -z "$entry" ]; then
_err "Error, can not get domain token entry $d for $vtype"
_supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
if [ "$_supported_vtypes" ]; then
_err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
fi fi
_clearup
_on_issue_err "$_post_hook"
return 1
fi fi
if [ -z "$keyauthorization" ]; then if [ -z "$keyauthorization" ]; then
@ -4658,15 +4760,9 @@ $_authorizations_map"
fi fi
keyauthorization="$token.$thumbprint" keyauthorization="$token.$thumbprint"
_debug keyauthorization "$keyauthorization" _debug keyauthorization "$keyauthorization"
if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
_debug "$d is already verified."
keyauthorization="$STATE_VERIFIED"
_debug keyauthorization "$keyauthorization"
fi
fi fi
dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url"
_debug dvlist "$dvlist" _debug dvlist "$dvlist"
vlist="$vlist$dvlist$dvsep" vlist="$vlist$dvlist$dvsep"
@ -4683,6 +4779,7 @@ $_authorizations_map"
keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
vtype=$(echo "$ventry" | cut -d "$sep" -f 4) vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
_currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
_authz_url=$(echo "$ventry" | cut -d "$sep" -f 6)
_debug d "$d" _debug d "$d"
if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
_debug "$d is already verified, skip $vtype." _debug "$d is already verified, skip $vtype."
@ -4808,7 +4905,7 @@ $_authorizations_map"
uri=$(echo "$ventry" | cut -d "$sep" -f 3) uri=$(echo "$ventry" | cut -d "$sep" -f 3)
vtype=$(echo "$ventry" | cut -d "$sep" -f 4) vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
_currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
_authz_url=$(echo "$ventry" | cut -d "$sep" -f 6)
if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
_info "$d is already verified, skip $vtype." _info "$d is already verified, skip $vtype."
continue continue
@ -4818,6 +4915,7 @@ $_authorizations_map"
_debug "d" "$d" _debug "d" "$d"
_debug "keyauthorization" "$keyauthorization" _debug "keyauthorization" "$keyauthorization"
_debug "uri" "$uri" _debug "uri" "$uri"
_debug "_authz_url" "$_authz_url"
removelevel="" removelevel=""
token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)" token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)"
@ -4887,18 +4985,6 @@ $_authorizations_map"
if ! chmod a+r "$wellknown_path/$token"; then if ! chmod a+r "$wellknown_path/$token"; then
_debug "chmod failed, but we just continue." _debug "chmod failed, but we just continue."
fi fi
if [ ! "$usingApache" ]; then
if webroot_owner=$(_stat "$_currentRoot"); then
_debug "Changing owner/group of .well-known to $webroot_owner"
if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then
_debug "$(cat "$_EXEC_TEMP_ERR")"
_exec_err >/dev/null 2>&1
fi
else
_debug "not changing owner/group of webroot"
fi
fi
fi fi
elif [ "$vtype" = "$VTYPE_ALPN" ]; then elif [ "$vtype" = "$VTYPE_ALPN" ]; then
acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")" acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
@ -4937,6 +5023,7 @@ $_authorizations_map"
MAX_RETRY_TIMES=30 MAX_RETRY_TIMES=30
fi fi
_debug "Lets check the status of the authz"
while true; do while true; do
waittimes=$(_math "$waittimes" + 1) waittimes=$(_math "$waittimes" + 1)
if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then
@ -4960,9 +5047,9 @@ $_authorizations_map"
errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)" errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
_debug2 errordetail "$errordetail" _debug2 errordetail "$errordetail"
if [ "$errordetail" ]; then if [ "$errordetail" ]; then
_err "$d:Verify error:$errordetail"
_err "Invalid status, $d:Verify error detail:$errordetail"
else else
_err "$d:Verify error:$error"
_err "Invalid status, $d:Verify error:$error"
fi fi
if [ "$DEBUG" ]; then if [ "$DEBUG" ]; then
if [ "$vtype" = "$VTYPE_HTTP" ]; then if [ "$vtype" = "$VTYPE_HTTP" ]; then
@ -4984,12 +5071,12 @@ $_authorizations_map"
break break
fi fi
if [ "$status" = "pending" ]; then
if _contains "$status" "pending"; then
_info "Pending, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" _info "Pending, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)"
elif [ "$status" = "processing" ]; then
elif _contains "$status" "processing"; then
_info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" _info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)"
else else
_err "$d:Verify error:$response"
_err "Unknown status: $status, $d:Verify error:$response"
_clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
_clearup _clearup
_on_issue_err "$_post_hook" "$vlist" _on_issue_err "$_post_hook" "$vlist"
@ -4999,10 +5086,10 @@ $_authorizations_map"
_sleep 2 _sleep 2
_debug "checking" _debug "checking"
_send_signed_request "$uri"
_send_signed_request "$_authz_url"
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
_err "$d:Verify error:$response"
_err "Invalid code, $d:Verify error:$response"
_clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
_clearup _clearup
_on_issue_err "$_post_hook" "$vlist" _on_issue_err "$_post_hook" "$vlist"
@ -5169,6 +5256,9 @@ $_authorizations_map"
[ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in: $(__green "$CA_CERT_PATH")" [ -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")" [ -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) Le_CertCreateTime=$(_time)
_savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime" _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime"
@ -5710,6 +5800,7 @@ deploy() {
return 1 return 1
fi fi
_debug2 DOMAIN_CONF "$DOMAIN_CONF"
. "$DOMAIN_CONF" . "$DOMAIN_CONF"
_savedomainconf Le_DeployHook "$_hooks" _savedomainconf Le_DeployHook "$_hooks"
@ -5743,7 +5834,8 @@ installcert() {
_savedomainconf "Le_RealKeyPath" "$_real_key" _savedomainconf "Le_RealKeyPath" "$_real_key"
_savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64" _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
_savedomainconf "Le_RealFullChainPath" "$_real_fullchain" _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" _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"
} }
@ -5835,6 +5927,8 @@ _installcert() {
export CA_CERT_PATH export CA_CERT_PATH
export CERT_FULLCHAIN_PATH export CERT_FULLCHAIN_PATH
export Le_Domain="$_main_domain" export Le_Domain="$_main_domain"
export Le_ForceNewDomainKey
export Le_Next_Domain_Key
cd "$DOMAIN_PATH" && eval "$_reload_cmd" cd "$DOMAIN_PATH" && eval "$_reload_cmd"
); then ); then
_info "$(__green "Reload success")" _info "$(__green "Reload success")"
@ -5931,6 +6025,7 @@ installcronjob() {
fi fi
_t=$(_time) _t=$(_time)
random_minute=$(_math $_t % 60) random_minute=$(_math $_t % 60)
random_hour=$(_math $_t / 60 % 24)
if ! _exists "$_CRONTAB" && _exists "fcrontab"; then if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
_CRONTAB="fcrontab" _CRONTAB="fcrontab"
@ -5955,16 +6050,14 @@ installcronjob() {
_info "Installing cron job" _info "Installing cron job"
if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then
if _exists uname && uname -a | grep SunOS >/dev/null; then if _exists uname && uname -a | grep SunOS >/dev/null; then
$_CRONTAB -l | {
cat
echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
} | $_CRONTAB --
_CRONTAB_STDIN="$_CRONTAB --"
else else
$_CRONTAB -l | {
cat
echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
} | $_CRONTAB -
_CRONTAB_STDIN="$_CRONTAB -"
fi fi
$_CRONTAB -l | {
cat
echo "$random_minute $random_hour * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
} | $_CRONTAB_STDIN
fi fi
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
_err "Install cron job failed. You need to manually renew your certs." _err "Install cron job failed. You need to manually renew your certs."
@ -6066,8 +6159,22 @@ revoke() {
uri="${ACME_REVOKE_CERT}" 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 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 _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then
if [ -z "$response" ]; then if [ -z "$response" ]; then
_info "Revoke success." _info "Revoke success."
@ -6083,21 +6190,6 @@ revoke() {
else else
_info "Domain key file doesn't exist." _info "Domain key file doesn't exist."
fi 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"
cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked"
cat "$CSR_PATH" >"$CSR_PATH.revoked"
return 0
else
_err "Revoke error."
_debug "$response"
fi
fi
return 1 return 1
} }
@ -6671,6 +6763,13 @@ _send_notify() {
return 0 return 0
fi fi
_nsource="$NOTIFY_SOURCE"
if [ -z "$_nsource" ]; then
_nsource="$(hostname)"
fi
_nsubject="$_nsubject by $_nsource"
_send_err=0 _send_err=0
for _n_hook in $(echo "$_nhooks" | tr ',' " "); do for _n_hook in $(echo "$_nhooks" | tr ',' " "); do
_n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")" _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")"
@ -6725,11 +6824,12 @@ setnotify() {
_nhook="$1" _nhook="$1"
_nlevel="$2" _nlevel="$2"
_nmode="$3" _nmode="$3"
_nsource="$4"
_initpath _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" _usage "$_NOTIFY_WIKI"
return 1 return 1
fi fi
@ -6746,6 +6846,12 @@ setnotify() {
_saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE" _saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE"
fi fi
if [ "$_nsource" ]; then
_info "Set notify source to: $_nsource"
export "NOTIFY_SOURCE=$_nsource"
_saveaccountconf "NOTIFY_SOURCE" "$NOTIFY_SOURCE"
fi
if [ "$_nhook" ]; then if [ "$_nhook" ]; then
_info "Set notify hook to: $_nhook" _info "Set notify hook to: $_nhook"
if [ "$_nhook" = "$NO_VALUE" ]; then if [ "$_nhook" = "$NO_VALUE" ]; then
@ -6824,7 +6930,7 @@ Parameters:
-f, --force Force install, force cert renewal or override sudo restrictions. -f, --force Force install, force cert renewal or override sudo restrictions.
--staging, --test Use staging server, for testing. --staging, --test Use staging server, for testing.
--debug [0|1|2|3] Output debug info. Defaults to 1 if argument is omitted.
--debug [0|1|2|3] Output debug info. Defaults to $DEBUG_LEVEL_DEFAULT if argument is omitted.
--output-insecure Output all the sensitive messages. --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. -w, --webroot <directory> Specifies the web root folder for web root mode.
@ -6842,7 +6948,7 @@ Parameters:
-k, --keylength <bits> Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521. -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 -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. --log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted.
--log-level <1|2> Specifies the log level, default is 1.
--log-level <1|2> Specifies the log level, default is $DEFAULT_LOG_LEVEL.
--syslog <0|3|6|7> Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug. --syslog <0|3|6|7> Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
--eab-kid <eab_key_id> Key Identifier for External Account Binding. --eab-kid <eab_key_id> Key Identifier for External Account Binding.
--eab-hmac-key <eab_hmac_key> HMAC key for External Account Binding. --eab-hmac-key <eab_hmac_key> HMAC key for External Account Binding.
@ -6850,7 +6956,7 @@ Parameters:
These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert: These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert:
--cert-file <file> Path to copy the cert file to after issue/renew..
--cert-file <file> Path to copy the cert file to after issue/renew.
--key-file <file> Path to copy the key file to after issue/renew. --key-file <file> Path to copy the key file to after issue/renew.
--ca-file <file> Path to copy the intermediate cert file to after issue/renew. --ca-file <file> Path to copy the intermediate cert file to after issue/renew.
--fullchain-file <file> Path to copy the fullchain cert file to after issue/renew. --fullchain-file <file> Path to copy the fullchain cert file to after issue/renew.
@ -6880,7 +6986,8 @@ Parameters:
--no-profile Only valid for '--install' command, which means: do not install aliases to user profile. --no-profile Only valid for '--install' command, which means: do not install aliases to user profile.
--no-color Do not output color text. --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. --force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.
--ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--to-pkcs12' and '--create-csr'
--ecc Specifies use of the ECC cert. Only valid for '--install-cert', '--renew', '--remove ', '--revoke',
'--deploy', '--to-pkcs8', '--to-pkcs12' and '--create-csr'.
--csr <file> Specifies the input csr. --csr <file> Specifies the input csr.
--pre-hook <command> Command to be run before obtaining any certificates. --pre-hook <command> Command to be run before obtaining any certificates.
--post-hook <command> Command to be run after attempting to obtain/renew certificates. Runs regardless of whether obtain/renew succeeded or failed. --post-hook <command> Command to be run after attempting to obtain/renew certificates. Runs regardless of whether obtain/renew succeeded or failed.
@ -6906,6 +7013,7 @@ Parameters:
0: Bulk mode. Send all the domain's notifications in one message(mail). 0: Bulk mode. Send all the domain's notifications in one message(mail).
1: Cert mode. Send a message for every single cert. 1: Cert mode. Send a message for every single cert.
--notify-hook <hookname> Set the notify hook --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. --revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command.
See: $_REVOKE_WIKI See: $_REVOKE_WIKI
@ -7063,7 +7171,9 @@ _selectServer() {
_getCAShortName() { _getCAShortName() {
caurl="$1" caurl="$1"
if [ -z "$caurl" ]; then 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 fi
if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then
caurl="$CA_SSLCOM_RSA" #just hack to get the short name caurl="$CA_SSLCOM_RSA" #just hack to get the short name
@ -7180,6 +7290,7 @@ _process() {
_notify_hook="" _notify_hook=""
_notify_level="" _notify_level=""
_notify_mode="" _notify_mode=""
_notify_source=""
_revoke_reason="" _revoke_reason=""
_eab_kid="" _eab_kid=""
_eab_hmac_key="" _eab_hmac_key=""
@ -7425,6 +7536,9 @@ _process() {
--keylength | -k) --keylength | -k)
_keylength="$2" _keylength="$2"
shift shift
if [ "$_keylength" ] && ! _isEccKey "$_keylength"; then
export __SELECTED_RSA_KEY=1
fi
;; ;;
-ak | --accountkeylength) -ak | --accountkeylength)
_accountkeylength="$2" _accountkeylength="$2"
@ -7460,7 +7574,7 @@ _process() {
shift shift
;; ;;
--home) --home)
export LE_WORKING_DIR="$2"
export LE_WORKING_DIR="$(echo "$2" | sed 's|/$||')"
shift shift
;; ;;
--cert-home | --certhome) --cert-home | --certhome)
@ -7672,6 +7786,15 @@ _process() {
_notify_mode="$_nmode" _notify_mode="$_nmode"
shift 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)
_revoke_reason="$2" _revoke_reason="$2"
if _startswith "$_revoke_reason" "-"; then if _startswith "$_revoke_reason" "-"; then
@ -7826,7 +7949,7 @@ _process() {
createCSR "$_domain" "$_altdomains" "$_ecc" createCSR "$_domain" "$_altdomains" "$_ecc"
;; ;;
setnotify) setnotify)
setnotify "$_notify_hook" "$_notify_level" "$_notify_mode"
setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source"
;; ;;
setdefaultca) setdefaultca)
setdefaultca setdefaultca

27
deploy/docker.sh

@ -273,16 +273,27 @@ _check_curl_version() {
_minor="$(_getfield "$_cversion" 2 '.')" _minor="$(_getfield "$_cversion" 2 '.')"
_debug2 "_minor" "$_minor" _debug2 "_minor" "$_minor"
if [ "$_major$_minor" -lt "740" ]; then
if [ "$_major" -ge "8" ]; then
#ok
return 0
fi
if [ "$_major" = "7" ]; then
if [ "$_minor" -lt "40" ]; then
_err "curl v$_cversion doesn't support unit socket"
_err "Please upgrade to curl 7.40 or later."
return 1
fi
if [ "$_minor" -lt "50" ]; then
_debug "Use short host name"
export _CURL_NO_HOST=1
else
export _CURL_NO_HOST=
fi
return 0
else
_err "curl v$_cversion doesn't support unit socket" _err "curl v$_cversion doesn't support unit socket"
_err "Please upgrade to curl 7.40 or later." _err "Please upgrade to curl 7.40 or later."
return 1 return 1
fi fi
if [ "$_major$_minor" -lt "750" ]; then
_debug "Use short host name"
export _CURL_NO_HOST=1
else
export _CURL_NO_HOST=
fi
return 0
} }

15
deploy/gcore_cdn.sh

@ -1,10 +1,11 @@
#!/usr/bin/env sh #!/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. # Returns 0 when success.
# #
# Written by temoffey <temofffey@gmail.com> # Written by temoffey <temofffey@gmail.com>
# Public domain, 2019 # Public domain, 2019
# Update by DreamOfIce <admin@dreamofice.cn> in 2023
#export DEPLOY_GCORE_CDN_USERNAME=myusername #export DEPLOY_GCORE_CDN_USERNAME=myusername
#export DEPLOY_GCORE_CDN_PASSWORD=mypassword #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\"}" _request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
_debug _request "$_request" _debug _request "$_request"
export _H1="Content-Type:application/json" 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" _debug _response "$_response"
_regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$" _regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
_debug _regex "$_regex" _debug _regex "$_regex"
@ -69,8 +70,8 @@ gcore_cdn_deploy() {
fi fi
_info "Find CDN resource with cname $_cdomain" _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" _debug _response "$_response"
_regex="\"primary_resource\":null}," _regex="\"primary_resource\":null},"
_debug _regex "$_regex" _debug _regex "$_regex"
@ -102,7 +103,7 @@ gcore_cdn_deploy() {
_date=$(date "+%d.%m.%Y %H:%M:%S") _date=$(date "+%d.%m.%Y %H:%M:%S")
_request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}" _request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}"
_debug _request "$_request" _debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/sslData")
_response=$(_post "$_request" "https://api.gcore.com/cdn/sslData")
_debug _response "$_response" _debug _response "$_response"
_regex=".*\"id\":\([0-9]*\).*$" _regex=".*\"id\":\([0-9]*\).*$"
_debug _regex "$_regex" _debug _regex "$_regex"
@ -117,7 +118,7 @@ gcore_cdn_deploy() {
_info "Update CDN resource" _info "Update CDN resource"
_request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}" _request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}"
_debug _request "$_request" _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" _debug _response "$_response"
_regex=".*\"sslData\":\([0-9]*\).*$" _regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex" _debug _regex "$_regex"
@ -133,7 +134,7 @@ gcore_cdn_deploy() {
_info "Not found old SSL certificate" _info "Not found old SSL certificate"
else else
_info "Delete old SSL certificate" _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" _debug _response "$_response"
fi fi

2
deploy/gitlab.sh

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

2
deploy/haproxy.sh

@ -147,7 +147,7 @@ haproxy_deploy() {
# Create a temporary PEM file # Create a temporary PEM file
_temppem="$(_mktemp)" _temppem="$(_mktemp)"
_debug _temppem "${_temppem}" _debug _temppem "${_temppem}"
cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}"
cat "${_ccert}" "${_cca}" "${_ckey}" >"${_temppem}"
_ret="$?" _ret="$?"
# Check that we could create the temporary file # Check that we could create the temporary file

158
deploy/panos.sh

@ -7,11 +7,15 @@
# #
# Firewall admin with superuser and IP address is required. # Firewall admin with superuser and IP address is required.
# #
# export PANOS_USER="" # required
# export PANOS_PASS="" # required
# export PANOS_HOST="" # required
# REQURED:
# export PANOS_HOST=""
# export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role
# export PANOS_PASS=""
#
# The script will automatically generate a new API key if
# no key is found, or if a saved key has expired or is invalid.
# This function is to parse the XML
# This function is to parse the XML response from the firewall
parse_response() { parse_response() {
type=$2 type=$2
if [ "$type" = 'keygen' ]; then if [ "$type" = 'keygen' ]; then
@ -23,25 +27,46 @@ parse_response() {
message="PAN-OS Key could not be set." message="PAN-OS Key could not be set."
fi fi
else else
status=$(echo "$1" | sed 's/^.*"\([a-z]*\)".*/\1/g')
message=$(echo "$1" | sed 's/^.*<result>\(.*\)<\/result.*/\1/g')
status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g')
message=$(echo "$1" | tr -d '\n' | sed 's/.*\(<result>\|<msg>\|<line>\)\([^<]*\).*/\2/g')
_debug "Firewall message: $message"
if [ "$type" = 'keytest' ] && [ "$status" != "success" ]; then
_debug "**** API Key has EXPIRED or is INVALID ****"
unset _panos_key
fi
fi fi
return 0 return 0
} }
#This function is used to deploy to the firewall
deployer() { deployer() {
content="" content=""
type=$1 # Types are keygen, cert, key, commit
_debug "**** Deploying $type *****"
type=$1 # Types are keytest, keygen, cert, key, commit
panos_url="https://$_panos_host/api/" panos_url="https://$_panos_host/api/"
#Test API Key by performing a lookup
if [ "$type" = 'keytest' ]; then
_debug "**** Testing saved API Key ****"
_H1="Content-Type: application/x-www-form-urlencoded"
# Get Version Info to test key
content="type=version&key=$_panos_key"
## Exclude all scopes for the empty commit
#_exclude_scope="<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network><shared-object>exclude</shared-object>"
#content="type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>acmekeytest</member></admin></partial></commit>"
fi
# Generate API Key
if [ "$type" = 'keygen' ]; then if [ "$type" = 'keygen' ]; then
_debug "**** Generating new API Key ****"
_H1="Content-Type: application/x-www-form-urlencoded" _H1="Content-Type: application/x-www-form-urlencoded"
content="type=keygen&user=$_panos_user&password=$_panos_pass" content="type=keygen&user=$_panos_user&password=$_panos_pass"
# content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}" # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
fi fi
# Deploy Cert or Key
if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then
#Generate DEIM
_debug "**** Deploying $type ****"
#Generate DELIM
delim="-----MultipartDelimiter$(date "+%s%N")" delim="-----MultipartDelimiter$(date "+%s%N")"
nl="\015\012" nl="\015\012"
#Set Header #Set Header
@ -61,7 +86,7 @@ deployer() {
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
fi fi
#Close multipart #Close multipart
content="$content${nl}--$delim--${nl}${nl}" content="$content${nl}--$delim--${nl}${nl}"
@ -69,16 +94,25 @@ deployer() {
content=$(printf %b "$content") content=$(printf %b "$content")
fi fi
# Commit changes
if [ "$type" = 'commit' ]; then if [ "$type" = 'commit' ]; then
_debug "**** Committing changes ****"
export _H1="Content-Type: application/x-www-form-urlencoded" export _H1="Content-Type: application/x-www-form-urlencoded"
cmd=$(printf "%s" "<commit><partial><$_panos_user></$_panos_user></partial></commit>" | _url_encode)
content="type=commit&key=$_panos_key&cmd=$cmd"
#Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution!
if [ "$FORCE" ]; then
_debug "Force switch detected. Committing ALL changes to the firewall."
cmd=$(printf "%s" "<commit><partial><force><admin><member>$_panos_user</member></admin></force></partial></commit>" | _url_encode)
else
_exclude_scope="<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network>"
cmd=$(printf "%s" "<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>" | _url_encode)
fi
content="type=commit&action=partial&key=$_panos_key&cmd=$cmd"
fi fi
response=$(_post "$content" "$panos_url" "" "POST") response=$(_post "$content" "$panos_url" "" "POST")
parse_response "$response" "$type" parse_response "$response" "$type"
# Saving response to variables # Saving response to variables
response_status=$status response_status=$status
#DEBUG
_debug response_status "$response_status" _debug response_status "$response_status"
if [ "$response_status" = "success" ]; then if [ "$response_status" = "success" ]; then
_debug "Successfully deployed $type" _debug "Successfully deployed $type"
@ -92,43 +126,85 @@ deployer() {
# This is the main function that will call the other functions to deploy everything. # This is the main function that will call the other functions to deploy everything.
panos_deploy() { panos_deploy() {
_cdomain="$1"
_cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename
_ckey="$2" _ckey="$2"
_cfullchain="$5" _cfullchain="$5"
# PANOS ENV VAR check
if [ -z "$PANOS_USER" ] || [ -z "$PANOS_PASS" ] || [ -z "$PANOS_HOST" ]; then
_debug "No ENV variables found lets check for saved variables"
_getdeployconf PANOS_USER
_getdeployconf PANOS_PASS
_getdeployconf PANOS_HOST
_panos_user=$PANOS_USER
_panos_pass=$PANOS_PASS
_panos_host=$PANOS_HOST
if [ -z "$_panos_user" ] && [ -z "$_panos_pass" ] && [ -z "$_panos_host" ]; then
_err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs."
return 1
else
_debug "Using saved env variables."
fi
# VALID FILE CHECK
if [ ! -f "$_ckey" ] || [ ! -f "$_cfullchain" ]; then
_err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying."
return 1
fi
# PANOS_HOST
if [ "$PANOS_HOST" ]; then
_debug "Detected ENV variable PANOS_HOST. Saving to file."
_savedeployconf PANOS_HOST "$PANOS_HOST" 1
else else
_debug "Detected ENV variables to be saved to the deploy conf."
# Encrypt and save user
_debug "Attempting to load variable PANOS_HOST from file."
_getdeployconf PANOS_HOST
fi
# PANOS USER
if [ "$PANOS_USER" ]; then
_debug "Detected ENV variable PANOS_USER. Saving to file."
_savedeployconf PANOS_USER "$PANOS_USER" 1 _savedeployconf PANOS_USER "$PANOS_USER" 1
else
_debug "Attempting to load variable PANOS_USER from file."
_getdeployconf PANOS_USER
fi
# PANOS_PASS
if [ "$PANOS_PASS" ]; then
_debug "Detected ENV variable PANOS_PASS. Saving to file."
_savedeployconf PANOS_PASS "$PANOS_PASS" 1 _savedeployconf PANOS_PASS "$PANOS_PASS" 1
_savedeployconf PANOS_HOST "$PANOS_HOST" 1
_panos_user="$PANOS_USER"
_panos_pass="$PANOS_PASS"
_panos_host="$PANOS_HOST"
else
_debug "Attempting to load variable PANOS_PASS from file."
_getdeployconf PANOS_PASS
fi fi
_debug "Let's use username and pass to generate token."
if [ -z "$_panos_user" ] || [ -z "$_panos_pass" ] || [ -z "$_panos_host" ]; then
_err "Please pass username and password and host as env variables PANOS_USER, PANOS_PASS and PANOS_HOST"
# PANOS_KEY
_getdeployconf PANOS_KEY
if [ "$PANOS_KEY" ]; then
_debug "Detected saved key."
_panos_key=$PANOS_KEY
else
_debug "No key detected"
unset _panos_key
fi
#Store variables
_panos_host=$PANOS_HOST
_panos_user=$PANOS_USER
_panos_pass=$PANOS_PASS
#Test API Key if found. If the key is invalid, the variable _panos_key will be unset.
if [ "$_panos_host" ] && [ "$_panos_key" ]; then
_debug "**** Testing API KEY ****"
deployer keytest
fi
# Check for valid variables
if [ -z "$_panos_host" ]; then
_err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs."
return 1
elif [ -z "$_panos_user" ]; then
_err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs."
return 1
elif [ -z "$_panos_pass" ]; then
_err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs."
return 1 return 1
else else
_debug "Getting PANOS KEY"
deployer keygen
# Generate a new API key if no valid API key is found
if [ -z "$_panos_key" ]; then
_debug "**** Generating new PANOS API KEY ****"
deployer keygen
_savedeployconf PANOS_KEY "$_panos_key" 1
fi
# Confirm that a valid key was generated
if [ -z "$_panos_key" ]; then if [ -z "$_panos_key" ]; then
_err "Missing apikey."
_err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
return 1 return 1
else else
deployer cert deployer cert

8
deploy/proxmoxve.sh

@ -99,11 +99,11 @@ proxmoxve_deploy() {
_proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY" _proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
_savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY" _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
fi fi
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY _proxmoxve_api_token_key
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY "$_proxmoxve_api_token_key"
# PVE API Token header value. Used in "Authorization: PVEAPIToken". # 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}" _proxmoxve_header_api_token="${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}"
_debug2 "Auth Header" _proxmoxve_header_api_token
_debug2 "Auth Header" "$_proxmoxve_header_api_token"
# Ugly. I hate putting heredocs inside functions because heredocs don't # Ugly. I hate putting heredocs inside functions because heredocs don't
# account for whitespace correctly but it _does_ work and is several times # account for whitespace correctly but it _does_ work and is several times
@ -124,8 +124,8 @@ HEREDOC
) )
_debug2 Payload "$_json_payload" _debug2 Payload "$_json_payload"
# Push certificates to server.
export _HTTPS_INSECURE=1
_info "Push certificates to server"
export HTTPS_INSECURE=1
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}" export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
_post "$_json_payload" "$_target_url" "" POST "application/json" _post "$_json_payload" "$_target_url" "" POST "application/json"

6
deploy/routeros.sh

@ -137,7 +137,7 @@ routeros_deploy() {
return $_err_code return $_err_code
fi fi
DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=$ROUTER_OS_USERNAME \
DEPLOY_SCRIPT_CMD="/system script add name=\"LECertDeploy-$_cdomain\" owner=$ROUTER_OS_USERNAME \
comment=\"generated by routeros deploy script in acme.sh\" \ comment=\"generated by routeros deploy script in acme.sh\" \
source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\ source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
\n/certificate remove [ find name=$_cdomain.cer_1 ];\ \n/certificate remove [ find name=$_cdomain.cer_1 ];\
@ -158,11 +158,11 @@ source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
return $_err_code return $_err_code
fi fi
if ! _ssh_remote_cmd "/system script run \"LE Cert Deploy - $_cdomain\""; then
if ! _ssh_remote_cmd "/system script run \"LECertDeploy-$_cdomain\""; then
return $_err_code return $_err_code
fi fi
if ! _ssh_remote_cmd "/system script remove \"LE Cert Deploy - $_cdomain\""; then
if ! _ssh_remote_cmd "/system script remove \"LECertDeploy-$_cdomain\""; then
return $_err_code return $_err_code
fi fi

409
deploy/ssh.sh

@ -14,7 +14,7 @@
# The following examples are for QNAP NAS running QTS 4.2 # The following examples are for QNAP NAS running QTS 4.2
# export DEPLOY_SSH_CMD="" # defaults to "ssh -T" # export DEPLOY_SSH_CMD="" # defaults to "ssh -T"
# export DEPLOY_SSH_USER="admin" # required # 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_KEYFILE="/etc/stunnel/stunnel.pem"
# export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" # export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem"
# export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.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="" # 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_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_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 ##################### ######## Public functions #####################
@ -42,72 +44,134 @@ ssh_deploy() {
_debug _cfullchain "$_cfullchain" _debug _cfullchain "$_cfullchain"
# USER is required to login by SSH to remote host. # USER is required to login by SSH to remote host.
_migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER
_getdeployconf DEPLOY_SSH_USER _getdeployconf DEPLOY_SSH_USER
_debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER" _debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
if [ -z "$DEPLOY_SSH_USER" ]; then 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 fi
_savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
# SERVER is optional. If not provided then use _cdomain # SERVER is optional. If not provided then use _cdomain
_migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER
_getdeployconf DEPLOY_SSH_SERVER _getdeployconf DEPLOY_SSH_SERVER
_debug2 DEPLOY_SSH_SERVER "$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 fi
_savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
# CMD is optional. If not provided then use ssh # CMD is optional. If not provided then use ssh
_migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD
_getdeployconf DEPLOY_SSH_CMD _getdeployconf DEPLOY_SSH_CMD
_debug2 DEPLOY_SSH_CMD "$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 fi
_savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
# BACKUP is optional. If not provided then default to previously saved value or yes. # 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 _getdeployconf DEPLOY_SSH_BACKUP
_debug2 DEPLOY_SSH_BACKUP "$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 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 # 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 _getdeployconf DEPLOY_SSH_BACKUP_PATH
_debug2 DEPLOY_SSH_BACKUP_PATH "$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 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 # MULTI_CALL is optional. If not provided then default to previously saved
# value (which may be undefined... equivalent to "no"). # value (which may be undefined... equivalent to "no").
_migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL
_getdeployconf DEPLOY_SSH_MULTI_CALL _getdeployconf DEPLOY_SSH_MULTI_CALL
_debug2 DEPLOY_SSH_MULTI_CALL "$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 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 _ssh_deploy
done done
} }
@ -117,16 +181,25 @@ _ssh_deploy() {
_cmdstr="" _cmdstr=""
_backupprefix="" _backupprefix=""
_backupdir="" _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 ' ' '-')" _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
# run cleanup on the backup directory, erase all older # run cleanup on the backup directory, erase all older
# than 180 days (15552000 seconds). # 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" _cmdstr="mkdir -p $_backupdir; $_cmdstr"
_info "Backup of old certificate files will be placed in remote directory $_backupdir" _info "Backup of old certificate files will be placed in remote directory $_backupdir"
_info "Backup directories erased after 180 days." _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 if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code return $_err_code
fi fi
@ -146,129 +219,184 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
fi fi
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 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. # 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 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 return $_err_code
fi 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
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 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=">" _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. # if filename is same as previous file then append.
_pipe=">>" _pipe=">>"
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite. # 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 fi
# copy new certificate into file. # 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 return $_err_code
fi 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
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 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=">" _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. # if filename is same as previous file then append.
_pipe=">>" _pipe=">>"
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite. # 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 fi
# copy new certificate into file. # 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 return $_err_code
fi 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
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 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=">" _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. # if filename is same as previous file then append.
_pipe=">>" _pipe=">>"
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
# backup file we are about to overwrite. # 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 fi
# copy new certificate into file. # 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 return $_err_code
fi 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
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 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 if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code return $_err_code
fi fi
@ -282,17 +410,25 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
return $_err_code return $_err_code
fi fi
fi fi
# cleanup in case all is ok
return 0 return 0
} }
#cmd #cmd
_ssh_remote_cmd() { _ssh_remote_cmd() {
_cmd="$1" _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" _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 # quotations in bash cmd below intended. Squash travis spellcheck error
# shellcheck disable=SC2029 # 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="$?" _err_code="$?"
if [ "$_err_code" != "0" ]; then if [ "$_err_code" != "0" ]; then
@ -301,3 +437,26 @@ _ssh_remote_cmd() {
return $_err_code 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
}

216
deploy/synology_dsm.sh

@ -1,34 +1,40 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to Synology DSM
#
# It requires following environment variables:
#
# SYNO_Username - Synology Username to login (must be an administrator)
# SYNO_Password - Synology Password to login
# SYNO_Certificate - Certificate description to target for replacement
#
# The following environmental variables may be set if you don't like their
# default values:
#
# SYNO_Scheme - defaults to http
# SYNO_Hostname - defaults to localhost
# SYNO_Port - defaults to 5000
# SYNO_DID - device ID to skip OTP - defaults to empty
# SYNO_TOTP_SECRET - TOTP secret to generate OTP - defaults to empty
#
# Dependencies:
# -------------
# - jq and curl
# - oathtool (When using 2 Factor Authentication and SYNO_TOTP_SECRET is set)
#
#returns 0 means success, otherwise error.
#!/bin/bash
######## Public functions #####################
################################################################################
# ACME.sh 3rd party deploy plugin for Synology DSM
################################################################################
# Authors: Brian Hartvigsen (creator), https://github.com/tresni
# Martin Arndt (contributor), https://troublezone.net/
# Updated: 2023-07-03
# Issues: https://github.com/acmesh-official/acme.sh/issues/2727
################################################################################
# Usage:
# - Create temp admin user automatically:
# export SYNO_USE_TEMP_ADMIN=1
# - Or provide your own admin user credential:
# 1. export SYNO_Username="adminUser"
# 2. export SYNO_Password="adminPassword"
# Optional exports (shown values are the defaults):
# - export SYNO_Certificate="" - to replace a specific certificate via description
# - export SYNO_Scheme="http"
# - export SYNO_Hostname="localhost"
# - export SYNO_Port="5000"
# - export SYNO_Create=1 - to allow creating the certificate if it doesn't exist
# - export SYNO_Device_Name="CertRenewal" - required if 2FA-OTP enabled
# - export SYNO_Device_ID="" - required for skipping 2FA-OTP
# 3. acme.sh --deploy --deploy-hook synology_dsm -d example.com
################################################################################
# Dependencies:
# - jq & curl
# - synouser & synogroup (When available and SYNO_USE_TEMP_ADMIN is set)
################################################################################
# Return value:
# 0 means success, otherwise error.
################################################################################
########## Public functions ####################################################
#domain keyfile certfile cafile fullchain #domain keyfile certfile cafile fullchain
synology_dsm_deploy() { synology_dsm_deploy() {
_cdomain="$1" _cdomain="$1"
_ckey="$2" _ckey="$2"
_ccert="$3" _ccert="$3"
@ -36,39 +42,61 @@ synology_dsm_deploy() {
_debug _cdomain "$_cdomain" _debug _cdomain "$_cdomain"
# Get Username and Password, but don't save until we successfully authenticate
# Get username & password, but don't save until we authenticated successfully
_getdeployconf SYNO_USE_TEMP_ADMIN
_getdeployconf SYNO_Username _getdeployconf SYNO_Username
_getdeployconf SYNO_Password _getdeployconf SYNO_Password
_getdeployconf SYNO_Create _getdeployconf SYNO_Create
_getdeployconf SYNO_DID _getdeployconf SYNO_DID
_getdeployconf SYNO_TOTP_SECRET _getdeployconf SYNO_TOTP_SECRET
_getdeployconf SYNO_Device_Name
_getdeployconf SYNO_Device_ID
# Prepare temp admin user info if SYNO_USE_TEMP_ADMIN is set
if [ -n "${SYNO_USE_TEMP_ADMIN:-}" ]; then
if ! _exists synouser; then
if ! _exists synogroup; then
_err "Tools are missing for creating temp admin user, please set SYNO_Username & SYNO_Password instead."
return 1
fi
fi
_debug "Setting temp admin user credential..."
SYNO_Username=sc-acmesh-tmp
SYNO_Password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
# Ignore 2FA-OTP settings which won't be needed.
SYNO_Device_Name=
SYNO_Device_ID=
fi
if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then
_err "SYNO_Username & SYNO_Password must be set"
_err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_Username and SYNO_Password."
return 1 return 1
fi fi
_debug2 SYNO_Username "$SYNO_Username" _debug2 SYNO_Username "$SYNO_Username"
_secure_debug2 SYNO_Password "$SYNO_Password" _secure_debug2 SYNO_Password "$SYNO_Password"
_debug2 SYNO_Create "$SYNO_Create"
_debug2 SYNO_Device_Name "$SYNO_Device_Name"
_secure_debug2 SYNO_Device_ID "$SYNO_Device_ID"
# Optional scheme, hostname, and port for Synology DSM
# Optional scheme, hostname & port for Synology DSM
_getdeployconf SYNO_Scheme _getdeployconf SYNO_Scheme
_getdeployconf SYNO_Hostname _getdeployconf SYNO_Hostname
_getdeployconf SYNO_Port _getdeployconf SYNO_Port
# default vaules for scheme, hostname, and port
# defaulting to localhost and http because it's localhost...
# Default values for scheme, hostname & port
# Defaulting to localhost & http, because it's localhost…
[ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http" [ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http"
[ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost" [ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost"
[ -n "${SYNO_Port}" ] || SYNO_Port="5000" [ -n "${SYNO_Port}" ] || SYNO_Port="5000"
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
_savedeployconf SYNO_Scheme "$SYNO_Scheme" _savedeployconf SYNO_Scheme "$SYNO_Scheme"
_savedeployconf SYNO_Hostname "$SYNO_Hostname" _savedeployconf SYNO_Hostname "$SYNO_Hostname"
_savedeployconf SYNO_Port "$SYNO_Port" _savedeployconf SYNO_Port "$SYNO_Port"
_debug2 SYNO_Scheme "$SYNO_Scheme" _debug2 SYNO_Scheme "$SYNO_Scheme"
_debug2 SYNO_Hostname "$SYNO_Hostname" _debug2 SYNO_Hostname "$SYNO_Hostname"
_debug2 SYNO_Port "$SYNO_Port" _debug2 SYNO_Port "$SYNO_Port"
# Get the certificate description, but don't save it until we verfiy it's real
# Get the certificate description, but don't save it until we verify it's real
_getdeployconf SYNO_Certificate _getdeployconf SYNO_Certificate
_debug SYNO_Certificate "${SYNO_Certificate:-}" _debug SYNO_Certificate "${SYNO_Certificate:-}"
@ -83,53 +111,95 @@ synology_dsm_deploy() {
_debug "Getting API version" _debug "Getting API version"
response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth") response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth")
api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p')
api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p') api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p')
_debug3 response "$response" _debug3 response "$response"
_debug3 api_path "$api_path"
_debug3 api_version "$api_version" _debug3 api_version "$api_version"
# Login, get the token from JSON and session id from cookie
# Login, get the session ID & SynoToken from JSON
_info "Logging into $SYNO_Hostname:$SYNO_Port" _info "Logging into $SYNO_Hostname:$SYNO_Port"
encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)" encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)"
encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)"
otp_code="" otp_code=""
# START - DEPRECATED, only kept for legacy compatibility reasons
if [ -n "$SYNO_TOTP_SECRET" ]; then if [ -n "$SYNO_TOTP_SECRET" ]; then
if _exists oathtool; then
otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)"
else
_info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!"
_info " See synology_dsm.sh script or ACME.sh Wiki page for details:"
_info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide"
if ! _exists oathtool; then
_err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET" _err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET"
return 1 return 1
fi fi
fi
DEPRECATED_otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)"
if [ -n "$SYNO_DID" ]; then
_H1="Cookie: did=$SYNO_DID"
export _H1
_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=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes")
_debug3 response "$response"
# END - DEPRECATED, only kept for legacy compatibility reasons
# If SYNO_DeviceDevice_ID & SYNO_Device_Name both empty, just log in normally
elif [ -z "${SYNO_Device_ID:-}" ] && [ -z "${SYNO_Device_Name:-}" ]; then
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_debug "Creating temp admin user in Synology DSM"
synouser --del "$SYNO_Username" >/dev/null 2>/dev/null
synouser --add "$SYNO_Username" "$SYNO_Password" "" 0 "" 0 >/dev/null
synogroup --memberadd administrators "$SYNO_Username" >/dev/null
fi
response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes")
_debug3 response "$response"
# Get device ID if still empty first, otherwise log in right away
# If SYNO_Device_Name is set, we treat that account enabled two-factor authorization, consider SYNO_Device_ID is not set, so it won't be able to login without requiring the OTP code.
elif [ -n "${SYNO_Device_Name:-}" ] && [ -z "${SYNO_Device_ID:-}" ]; then
printf "Enter OTP code for user '%s': " "$SYNO_Username"
read -r otp_code
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&otp_code=$otp_code&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_Device_Name")
_secure_debug3 response "$response"
if [ -n "$SYNO_DID" ]; then
_H1="Cookie: did=$SYNO_DID"
export _H1
_debug3 H1 "${_H1}"
id_property='device_id'
[ "${api_version}" -gt '6' ] || id_property='did'
SYNO_Device_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p')
_secure_debug2 SYNO_Device_ID "$SYNO_Device_ID"
# Otherwise, if SYNO_Device_ID is set, we can just use it to login.
else
if [ -z "${SYNO_Device_Name:-}" ]; then
printf "Enter device name or leave empty for default (CertRenewal): "
read -r SYNO_Device_Name
[ -n "${SYNO_Device_Name}" ] || SYNO_Device_Name="CertRenewal"
fi
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_Device_Name&device_id=$SYNO_Device_ID")
_secure_debug3 response "$response"
fi 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")
sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p')
_debug3 response "$response"
_debug token "$token"
if [ -z "$token" ]; then
_err "Unable to authenticate to $SYNO_Hostname:$SYNO_Port using $SYNO_Scheme."
_err "Check your username and password."
_err "If two-factor authentication is enabled for the user, set SYNO_TOTP_SECRET."
_debug "Session ID" "$sid"
_debug SynoToken "$token"
if [ -z "$sid" ] || [ -z "$token" ]; then
_err "Unable to authenticate to $_base_url - check your username & password."
_err "If two-factor authentication is enabled for the user:"
_err "- set SYNO_Device_Name then input *correct* OTP-code manually"
_err "- get & set SYNO_Device_ID via your browser cookies"
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
return 1 return 1
fi fi
sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
_H1="X-SYNO-TOKEN: $token" _H1="X-SYNO-TOKEN: $token"
export _H1 export _H1
_debug2 H1 "${_H1}" _debug2 H1 "${_H1}"
# Now that we know the username and password are good, save them
# Now that we know the username & password are good, save them
_savedeployconf SYNO_Username "$SYNO_Username" _savedeployconf SYNO_Username "$SYNO_Username"
_savedeployconf SYNO_Password "$SYNO_Password" _savedeployconf SYNO_Password "$SYNO_Password"
_savedeployconf SYNO_DID "$SYNO_DID"
_savedeployconf SYNO_TOTP_SECRET "$SYNO_TOTP_SECRET"
if [ -z "${SYNO_USE_TEMP_ADMIN:-}" ]; then
_savedeployconf SYNO_Device_Name "$SYNO_Device_Name"
_savedeployconf SYNO_Device_ID "$SYNO_Device_ID"
fi
_info "Getting certificates in Synology DSM" _info "Getting certificates in Synology DSM"
response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi") response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi")
@ -140,11 +210,12 @@ synology_dsm_deploy() {
_debug2 id "$id" _debug2 id "$id"
if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then
_err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set"
_err "Unable to find certificate: $SYNO_Certificate & \$SYNO_Create is not set"
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
return 1 return 1
fi fi
# we've verified this certificate description is a thing, so save it
# We've verified this certificate description is a thing, so save it
_savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64" _savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64"
_info "Generate form POST request" _info "Generate form POST request"
@ -156,10 +227,10 @@ synology_dsm_deploy() {
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}"
if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
_debug2 default "this is the default certificate"
_debug2 default "This is the default certificate"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true"
else else
_debug2 default "this is NOT the default certificate"
_debug2 default "This is NOT the default certificate"
fi fi
content="$content${nl}--$delim--${nl}" content="$content${nl}--$delim--${nl}"
content="$(printf "%b_" "$content")" content="$(printf "%b_" "$content")"
@ -171,13 +242,34 @@ synology_dsm_deploy() {
if ! echo "$response" | grep '"error":' >/dev/null; then if ! echo "$response" | grep '"error":' >/dev/null; then
if echo "$response" | grep '"restart_httpd":true' >/dev/null; then if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
_info "http services were restarted"
_info "Restarting HTTP services succeeded"
else else
_info "http services were NOT restarted"
_info "Restarting HTTP services failed"
fi fi
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
_logout
return 0 return 0
else else
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
_err "Unable to update certificate, error code $response" _err "Unable to update certificate, error code $response"
_logout
return 1 return 1
fi fi
} }
#################### Private functions below ##################################
_logout() {
# Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables)
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid")
_debug3 response "$response"
}
_remove_temp_admin() {
flag=$1
username=$2
if [ -n "${flag}" ]; then
_debug "Removing temp admin user in Synology DSM"
synouser --del "$username" >/dev/null
fi
}

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" _info "S3 certificate is not configured or is not the same as TrueNAS web UI"
fi 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" _info "Deleting old certificate"
_delete_result="$(_post "" "$_api_url/certificate/id/$_active_cert_id" "" "DELETE" "application/json")" _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_PREFIX - this contains the prefix path in vault
# VAULT_ADDR - vault requires this to find your vault server # 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 # additionally, you need to ensure that VAULT_TOKEN is avialable
# to access the vault server # to access the vault server
#returns 0 means success, otherwise error. #returns 0 means success, otherwise error.
######## Public functions #####################
######## Public functions #####################
#domain keyfile certfile cafile fullchain #domain keyfile certfile cafile fullchain
vault_deploy() { vault_deploy() {
@ -45,6 +48,26 @@ vault_deploy() {
fi fi
_savedeployconf VAULT_ADDR "$VAULT_ADDR" _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. # JSON does not allow multiline strings.
# So replacing new-lines with "\n" here # So replacing new-lines with "\n" here
_ckey=$(sed -z 's/\n/\\n/g' <"$2") _ckey=$(sed -z 's/\n/\\n/g' <"$2")
@ -52,26 +75,56 @@ vault_deploy() {
_cca=$(sed -z 's/\n/\\n/g' <"$4") _cca=$(sed -z 's/\n/\\n/g' <"$4")
_cfullchain=$(sed -z 's/\n/\\n/g' <"$5") _cfullchain=$(sed -z 's/\n/\\n/g' <"$5")
URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain"
export _H1="X-Vault-Token: $VAULT_TOKEN" 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 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 else
_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL"
_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" >/dev/null || return 1
fi fi
else else
if [ -n "$VAULT_KV_V2" ]; then 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 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
fi fi

47
deploy/vault_cli.sh

@ -8,6 +8,8 @@
# #
# VAULT_PREFIX - this contains the prefix path in vault # VAULT_PREFIX - this contains the prefix path in vault
# VAULT_ADDR - vault requires this to find your vault server # 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 # additionally, you need to ensure that VAULT_TOKEN is avialable or
# `vault auth` has applied the appropriate authorization for the vault binary # `vault auth` has applied the appropriate authorization for the vault binary
@ -33,15 +35,36 @@ vault_cli_deploy() {
_debug _cfullchain "$_cfullchain" _debug _cfullchain "$_cfullchain"
# validate required env vars # validate required env vars
_getdeployconf VAULT_PREFIX
if [ -z "$VAULT_PREFIX" ]; then if [ -z "$VAULT_PREFIX" ]; then
_err "VAULT_PREFIX needs to be defined (contains prefix path in vault)" _err "VAULT_PREFIX needs to be defined (contains prefix path in vault)"
return 1 return 1
fi fi
_savedeployconf VAULT_PREFIX "$VAULT_PREFIX"
_getdeployconf VAULT_ADDR
if [ -z "$VAULT_ADDR" ]; then if [ -z "$VAULT_ADDR" ]; then
_err "VAULT_ADDR needs to be defined (contains vault connection address)" _err "VAULT_ADDR needs to be defined (contains vault connection address)"
return 1 return 1
fi 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) VAULT_CMD=$(command -v vault)
if [ ! $? ]; then if [ ! $? ]; then
@ -49,13 +72,33 @@ vault_cli_deploy() {
return 1 return 1
fi 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 $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
else else
_info "Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem"
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 $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}/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 $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 fi
} }

160
dnsapi/dns_1984hosting.sh

@ -1,46 +1,46 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#This file name is "dns_1984hosting.sh"
#So, here must be a method dns_1984hosting_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
# This file name is "dns_1984hosting.sh"
# So, here must be a method dns_1984hosting_add()
# Which will be called by acme.sh to add the txt record to your api system.
# returns 0 means success, otherwise error.
#Author: Adrian Fedoreanu
#Report Bugs here: https://github.com/acmesh-official/acme.sh
# Author: Adrian Fedoreanu
# Report Bugs here: https://github.com/acmesh-official/acme.sh
# or here... https://github.com/acmesh-official/acme.sh/issues/2851 # or here... https://github.com/acmesh-official/acme.sh/issues/2851
#
######## Public functions #####################
######## Public functions #####################
# Export 1984HOSTING username and password in following variables # Export 1984HOSTING username and password in following variables
# #
# One984HOSTING_Username=username # One984HOSTING_Username=username
# One984HOSTING_Password=password # One984HOSTING_Password=password
# #
# sessionid cookie is saved in ~/.acme.sh/account.conf
# username/password need to be set only when changed.
# username/password and csrftoken/sessionid cookies are saved in ~/.acme.sh/account.conf
#Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Add a text record.
dns_1984hosting_add() { dns_1984hosting_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
_info "Add TXT record using 1984Hosting"
_info "Add TXT record using 1984Hosting."
_debug fulldomain "$fulldomain" _debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue" _debug txtvalue "$txtvalue"
if ! _1984hosting_login; then if ! _1984hosting_login; then
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file."
return 1 return 1
fi fi
_debug "First detect the root zone"
_debug "First detect the root zone."
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
_err "Invalid domain '$fulldomain'."
return 1 return 1
fi fi
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_debug "Add TXT record $fulldomain with value '$txtvalue'"
_debug "Add TXT record $fulldomain with value '$txtvalue'."
value="$(printf '%s' "$txtvalue" | _url_encode)" value="$(printf '%s' "$txtvalue" | _url_encode)"
url="https://1984.hosting/domains/entry/" url="https://1984.hosting/domains/entry/"
@ -53,92 +53,96 @@ dns_1984hosting_add() {
_debug2 postdata "$postdata" _debug2 postdata "$postdata"
_authpost "$postdata" "$url" _authpost "$postdata" "$url"
response="$(echo "$_response" | _normalizeJson)"
_debug2 response "$response"
if _contains "$response" '"haserrors": true'; then
_err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post"
if _contains "$_response" '"haserrors": true'; then
_err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post."
return 1 return 1
elif _contains "$response" "html>"; then
_err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file"
elif _contains "$_response" "html>"; then
_err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file."
return 1 return 1
elif _contains "$response" '"auth": false'; then
_err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie"
elif _contains "$_response" '"auth": false'; then
_err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie."
return 1 return 1
fi fi
_info "Added acme challenge TXT record for $fulldomain at 1984Hosting"
_info "Added acme challenge TXT record for $fulldomain at 1984Hosting."
return 0 return 0
} }
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
# Usage: fulldomain txtvalue
# Remove the txt record after validation.
dns_1984hosting_rm() { dns_1984hosting_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
_info "Delete TXT record using 1984Hosting"
_info "Delete TXT record using 1984Hosting."
_debug fulldomain "$fulldomain" _debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue" _debug txtvalue "$txtvalue"
if ! _1984hosting_login; then if ! _1984hosting_login; then
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file."
return 1 return 1
fi fi
_debug "First detect the root zone"
_debug "First detect the root zone."
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
_err "Invalid domain '$fulldomain'."
return 1 return 1
fi fi
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_debug "Delete $fulldomain TXT record"
_debug "Delete $fulldomain TXT record."
url="https://1984.hosting/domains" url="https://1984.hosting/domains"
if ! _get_zone_id "$url" "$_domain"; then if ! _get_zone_id "$url" "$_domain"; then
_err "invalid zone" "$_domain"
_err "Invalid zone '$_domain'."
return 1 return 1
fi fi
_htmlget "$url/$_zone_id" "$txtvalue" _htmlget "$url/$_zone_id" "$txtvalue"
_debug2 _response "$_response"
entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')" entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')"
_debug2 entry_id "$entry_id" _debug2 entry_id "$entry_id"
if [ -z "$entry_id" ]; then if [ -z "$entry_id" ]; then
_err "Error getting TXT entry_id for $1"
_err "Error getting TXT entry_id for $1."
return 1 return 1
fi fi
_authpost "entry=$entry_id" "$url/delentry/" _authpost "entry=$entry_id" "$url/delentry/"
response="$(echo "$_response" | _normalizeJson)"
_debug2 response "$response"
if ! _contains "$response" '"ok": true'; then
_err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post"
if ! _contains "$_response" '"ok": true'; then
_err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post."
return 1 return 1
fi fi
_info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting"
_info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting."
return 0 return 0
} }
#################### Private functions below ################################## #################### Private functions below ##################################
# usage: _1984hosting_login username password
# returns 0 success
_1984hosting_login() { _1984hosting_login() {
if ! _check_credentials; then return 1; fi if ! _check_credentials; then return 1; fi
if _check_cookies; then if _check_cookies; then
_debug "Already logged in"
_debug "Already logged in."
return 0 return 0
fi fi
_debug "Login to 1984Hosting as user $One984HOSTING_Username"
_debug "Login to 1984Hosting as user $One984HOSTING_Username."
username=$(printf '%s' "$One984HOSTING_Username" | _url_encode) username=$(printf '%s' "$One984HOSTING_Username" | _url_encode)
password=$(printf '%s' "$One984HOSTING_Password" | _url_encode) password=$(printf '%s' "$One984HOSTING_Password" | _url_encode)
url="https://1984.hosting/accounts/checkuserauth/"
url="https://1984.hosting/api/auth/"
_get "https://1984.hosting/accounts/login/" | grep "csrfmiddlewaretoken"
csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then
_err "One or more cookies are empty: '$csrftoken', '$sessionid'."
return 1
fi
export _H1="Cookie: $csrftoken; $sessionid"
export _H2="Referer: https://1984.hosting/accounts/login/"
csrf_header=$(echo "$csrftoken" | sed 's/csrftoken=//' | _head_n 1)
export _H3="X-CSRFToken: $csrf_header"
response="$(_post "username=$username&password=$password&otpkey=" $url)" response="$(_post "username=$username&password=$password&otpkey=" $url)"
response="$(echo "$response" | _normalizeJson)" response="$(echo "$response" | _normalizeJson)"
@ -149,6 +153,8 @@ _1984hosting_login() {
One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')" One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
export One984HOSTING_SESSIONID_COOKIE export One984HOSTING_SESSIONID_COOKIE
export One984HOSTING_CSRFTOKEN_COOKIE export One984HOSTING_CSRFTOKEN_COOKIE
_saveaccountconf_mutable One984HOSTING_Username "$One984HOSTING_Username"
_saveaccountconf_mutable One984HOSTING_Password "$One984HOSTING_Password"
_saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE" _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
_saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE" _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
return 0 return 0
@ -157,9 +163,13 @@ _1984hosting_login() {
} }
_check_credentials() { _check_credentials() {
One984HOSTING_Username="${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}"
One984HOSTING_Password="${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}"
if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then
One984HOSTING_Username="" One984HOSTING_Username=""
One984HOSTING_Password="" One984HOSTING_Password=""
_clearaccountconf_mutable One984HOSTING_Username
_clearaccountconf_mutable One984HOSTING_Password
_err "You haven't specified 1984Hosting username or password yet." _err "You haven't specified 1984Hosting username or password yet."
_err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again." _err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again."
return 1 return 1
@ -171,42 +181,43 @@ _check_cookies() {
One984HOSTING_SESSIONID_COOKIE="${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}" One984HOSTING_SESSIONID_COOKIE="${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}"
One984HOSTING_CSRFTOKEN_COOKIE="${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}" One984HOSTING_CSRFTOKEN_COOKIE="${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}"
if [ -z "$One984HOSTING_SESSIONID_COOKIE" ] || [ -z "$One984HOSTING_CSRFTOKEN_COOKIE" ]; then if [ -z "$One984HOSTING_SESSIONID_COOKIE" ] || [ -z "$One984HOSTING_CSRFTOKEN_COOKIE" ]; then
_debug "No cached cookie(s) found"
_debug "No cached cookie(s) found."
return 1 return 1
fi fi
_authget "https://1984.hosting/accounts/loginstatus/"
if _contains "$response" '"ok": true'; then
_debug "Cached cookies still valid"
_authget "https://1984.hosting/api/auth/"
if _contains "$_response" '"ok": true'; then
_debug "Cached cookies still valid."
return 0 return 0
fi fi
_debug "Cached cookies no longer valid"
_debug "Cached cookies no longer valid. Clearing cookies."
One984HOSTING_SESSIONID_COOKIE="" One984HOSTING_SESSIONID_COOKIE=""
One984HOSTING_CSRFTOKEN_COOKIE="" One984HOSTING_CSRFTOKEN_COOKIE=""
_saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
_saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
_clearaccountconf_mutable One984HOSTING_SESSIONID_COOKIE
_clearaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE
return 1 return 1
} }
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _acme-challenge.www.domain.com
# Returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() { _get_root() {
domain="$1" domain="$1"
i=1 i=1
p=1 p=1
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
# not valid
if [ -z "$h" ]; then if [ -z "$h" ]; then
#not valid
return 1 return 1
fi fi
_authget "https://1984.hosting/domains/soacheck/?zone=$h&nameserver=ns0.1984.is." _authget "https://1984.hosting/domains/soacheck/?zone=$h&nameserver=ns0.1984.is."
if _contains "$_response" "serial" && ! _contains "$_response" "null"; then if _contains "$_response" "serial" && ! _contains "$_response" "null"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h" _domain="$h"
return 0 return 0
fi fi
@ -216,46 +227,47 @@ _get_root() {
return 1 return 1
} }
#usage: _get_zone_id url domain.com
#returns zone id for domain.com
# Usage: _get_zone_id url domain.com
# Returns zone id for domain.com
_get_zone_id() { _get_zone_id() {
url=$1 url=$1
domain=$2 domain=$2
_htmlget "$url" "$domain" _htmlget "$url" "$domain"
_debug2 _response "$_response"
_zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)" _zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)"
_debug2 _zone_id "$_zone_id" _debug2 _zone_id "$_zone_id"
if [ -z "$_zone_id" ]; then if [ -z "$_zone_id" ]; then
_err "Error getting _zone_id for $2"
_err "Error getting _zone_id for $2."
return 1 return 1
fi fi
return 0 return 0
} }
# add extra headers to request
# Add extra headers to request
_authget() { _authget() {
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
_response=$(_get "$1" | _normalizeJson) _response=$(_get "$1" | _normalizeJson)
_debug2 _response "$_response" _debug2 _response "$_response"
} }
# truncate huge HTML response
# echo: Argument list too long
# Truncate huge HTML response
# Echo: Argument list too long
_htmlget() { _htmlget() {
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
_response=$(_get "$1" | grep "$2") _response=$(_get "$1" | grep "$2")
if _contains "$_response" "@$2"; then if _contains "$_response" "@$2"; then
_response=$(echo "$_response" | grep -v "[@]" | _head_n 1) _response=$(echo "$_response" | grep -v "[@]" | _head_n 1)
fi fi
_debug2 _response "$_response"
} }
# add extra headers to request
# Add extra headers to request
_authpost() { _authpost() {
url="https://1984.hosting/domains" url="https://1984.hosting/domains"
_get_zone_id "$url" "$_domain" _get_zone_id "$url" "$_domain"
csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")" csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")"
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
export _H2="Referer: https://1984.hosting/domains/$_zone_id" export _H2="Referer: https://1984.hosting/domains/$_zone_id"
export _H3="X-CSRFToken: $csrf_header" export _H3="X-CSRFToken: $csrf_header"
_response=$(_post "$1" "$2")
_response="$(_post "$1" "$2" | _normalizeJson)"
_debug2 _response "$_response"
} }

2
dnsapi/dns_acmeproxy.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh #!/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 ## API integration by Maarten den Braber
## ##
## Report any bugs via https://github.com/mdbraber/acme.sh ## Report any bugs via https://github.com/mdbraber/acme.sh

2
dnsapi/dns_ali.sh

@ -117,7 +117,7 @@ _ali_urlencode() {
_ali_nonce() { _ali_nonce() {
#_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31 #_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
#Not so good... #Not so good...
date +"%s%N"
date +"%s%N" | sed 's/%N//g'
} }
_check_exist_query() { _check_exist_query() {

180
dnsapi/dns_artfiles.sh

@ -0,0 +1,180 @@
#!/usr/bin/env sh
################################################################################
# ACME.sh 3rd party DNS API plugin for ArtFiles.de
################################################################################
# Author: Martin Arndt, https://troublezone.net/
# Released: 2022-02-27
# Issues: https://github.com/acmesh-official/acme.sh/issues/4718
################################################################################
# Usage:
# 1. export AF_API_USERNAME='api12345678'
# 2. export AF_API_PASSWORD='apiPassword'
# 3. acme.sh --issue -d example.com --dns dns_artfiles
################################################################################
########## API configuration ###################################################
AF_API_SUCCESS='status":"OK'
AF_URL_DCP='https://dcp.c.artfiles.de/api/'
AF_URL_DNS=${AF_URL_DCP}'dns/{*}_dns.html?domain='
AF_URL_DOMAINS=${AF_URL_DCP}'domain/get_domains.html'
########## Public functions ####################################################
# Adds a new TXT record for given ACME challenge value & domain.
# Usage: dns_artfiles_add _acme-challenge.www.example.com "ACME challenge value"
dns_artfiles_add() {
domain="$1"
txtValue="$2"
_info 'Using ArtFiles.de DNS addition API…'
_debug 'Domain' "$domain"
_debug 'txtValue' "$txtValue"
_set_credentials
_saveaccountconf_mutable 'AF_API_USERNAME' "$AF_API_USERNAME"
_saveaccountconf_mutable 'AF_API_PASSWORD' "$AF_API_PASSWORD"
_set_headers
_get_zone "$domain"
_dns 'GET'
if ! _contains "$response" 'TXT'; then
_err 'Retrieving TXT records failed.'
return 1
fi
_clean_records
_dns 'SET' "$(printf -- '%s\n_acme-challenge "%s"' "$response" "$txtValue")"
if ! _contains "$response" "$AF_API_SUCCESS"; then
_err 'Adding ACME challenge value failed.'
return 1
fi
}
# Removes the existing TXT record for given ACME challenge value & domain.
# Usage: dns_artfiles_rm _acme-challenge.www.example.com "ACME challenge value"
dns_artfiles_rm() {
domain="$1"
txtValue="$2"
_info 'Using ArtFiles.de DNS removal API…'
_debug 'Domain' "$domain"
_debug 'txtValue' "$txtValue"
_set_credentials
_set_headers
_get_zone "$domain"
if ! _dns 'GET'; then
return 1
fi
if ! _contains "$response" "$txtValue"; then
_err 'Retrieved TXT records are missing given ACME challenge value.'
return 1
fi
_clean_records
response="$(printf -- '%s' "$response" | sed '/_acme-challenge "'"$txtValue"'"/d')"
_dns 'SET' "$response"
if ! _contains "$response" "$AF_API_SUCCESS"; then
_err 'Removing ACME challenge value failed.'
return 1
fi
}
########## Private functions ###################################################
# Cleans awful TXT records response of ArtFiles's API & pretty prints it.
# Usage: _clean_records
_clean_records() {
_info 'Cleaning TXT records…'
# Extract TXT part, strip trailing quote sign (ACME.sh API guidelines forbid
# usage of SED's GNU extensions, hence couldn't omit it via regex), strip '\'
# from '\"' & turn '\n' into real LF characters.
# Yup, awful API to use - but that's all we got to get this working, so… ;)
_debug2 'Raw ' "$response"
response="$(printf -- '%s' "$response" | sed 's/^.*TXT":"\([^}]*\).*$/\1/;s/,".*$//;s/.$//;s/\\"/"/g;s/\\n/\n/g')"
_debug2 'Clean' "$response"
}
# Executes an HTTP GET or POST request for getting or setting DNS records,
# containing given payload upon POST.
# Usage: _dns [GET | SET] [payload]
_dns() {
_info 'Executing HTTP request…'
action="$1"
payload="$(printf -- '%s' "$2" | _url_encode)"
url="$(printf -- '%s%s' "$AF_URL_DNS" "$domain" | sed 's/{\*}/'"$(printf -- '%s' "$action" | _lower_case)"'/')"
if [ "$action" = 'SET' ]; then
_debug2 'Payload' "$payload"
response="$(_post '' "$url&TXT=$payload" '' 'POST' 'application/x-www-form-urlencoded')"
else
response="$(_get "$url" '' 10)"
fi
if ! _contains "$response" "$AF_API_SUCCESS"; then
_err "DNS API error: $response"
return 1
fi
_debug 'Response' "$response"
return 0
}
# Gets the root domain zone for given domain.
# Usage: _get_zone _acme-challenge.www.example.com
_get_zone() {
fqdn="$1"
domains="$(_get "$AF_URL_DOMAINS" '' 10)"
_info 'Getting domain zone…'
_debug2 'FQDN' "$fqdn"
_debug2 'Domains' "$domains"
while _contains "$fqdn" "."; do
if _contains "$domains" "$fqdn"; then
domain="$fqdn"
_info "Found root domain zone: $domain"
break
else
fqdn="${fqdn#*.}"
_debug2 'FQDN' "$fqdn"
fi
done
if [ "$domain" = "$fqdn" ]; then
return 0
fi
_err 'Couldn'\''t find root domain zone.'
return 1
}
# Sets the credentials for accessing ArtFiles's API
# Usage: _set_credentials
_set_credentials() {
_info 'Setting credentials…'
AF_API_USERNAME="${AF_API_USERNAME:-$(_readaccountconf_mutable AF_API_USERNAME)}"
AF_API_PASSWORD="${AF_API_PASSWORD:-$(_readaccountconf_mutable AF_API_PASSWORD)}"
if [ -z "$AF_API_USERNAME" ] || [ -z "$AF_API_PASSWORD" ]; then
_err 'Missing ArtFiles.de username and/or password.'
_err 'Please ensure both are set via export command & try again.'
return 1
fi
}
# Adds the HTTP Authorization & Content-Type headers to a follow-up request.
# Usage: _set_headers
_set_headers() {
_info 'Setting headers…'
encoded="$(printf -- '%s:%s' "$AF_API_USERNAME" "$AF_API_PASSWORD" | _base64)"
export _H1="Authorization: Basic $encoded"
export _H2='Content-Type: application/json'
}

13
dnsapi/dns_arvan.sh

@ -1,10 +1,10 @@
#!/usr/bin/env sh #!/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 ##################### ######## Public functions #####################
@ -18,7 +18,7 @@ dns_arvan_add() {
if [ -z "$Arvan_Token" ]; then if [ -z "$Arvan_Token" ]; then
_err "You didn't specify \"Arvan_Token\" token yet." _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 return 1
fi fi
#save the api token to the account conf file. #save the api token to the account conf file.
@ -40,7 +40,7 @@ dns_arvan_add() {
_info "response id is $response" _info "response id is $response"
_info "Added, OK" _info "Added, OK"
return 0 return 0
elif _contains "$response" "Record Data is Duplicated"; then
elif _contains "$response" "Record Data is duplicate"; then
_info "Already exists, OK" _info "Already exists, OK"
return 0 return 0
else else
@ -141,6 +141,7 @@ _arvan_rest() {
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")" response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
elif [ "$mtd" = "POST" ]; then elif [ "$mtd" = "POST" ]; then
export _H2="Content-Type: application/json" export _H2="Content-Type: application/json"
export _H3="Accept: application/json"
_debug data "$data" _debug data "$data"
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")" response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
else else

2
dnsapi/dns_aws.sh

@ -157,7 +157,7 @@ _get_root() {
# iterate over names (a.b.c.d -> b.c.d -> c.d -> d) # iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f $i-100 | sed 's/\./\\./g')
_debug "Checking domain: $h" _debug "Checking domain: $h"
if [ -z "$h" ]; then if [ -z "$h" ]; then
_error "invalid domain" _error "invalid domain"

89
dnsapi/dns_bookmyname.sh

@ -0,0 +1,89 @@
#!/usr/bin/env sh
#Here is a sample custom api script.
#This file name is "dns_bookmyname.sh"
#So, here must be a method dns_bookmyname_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
#
#Author: Neilpang
#Report Bugs here: https://github.com/acmesh-official/acme.sh
#
######## Public functions #####################
# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
# BookMyName urls:
# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=add&value="XXXXXXXX"'
# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=remove&value="XXXXXXXX"'
# Output:
#good: update done, cid 123456, domain id 456789, type txt, ip XXXXXXXX
#good: remove done 1, cid 123456, domain id 456789, ttl 300, type txt, ip XXXXXXXX
# Be careful, BMN DNS servers can be slow to pick up changes; using dnssleep is thus advised.
# Usage:
# export BOOKMYNAME_USERNAME="ABCDE-FREE"
# export BOOKMYNAME_PASSWORD="MyPassword"
# /usr/local/ssl/acme.sh/acme.sh --dns dns_bookmyname --dnssleep 600 --issue -d domain.tld
#Usage: dns_bookmyname_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_bookmyname_add() {
fulldomain=$1
txtvalue=$2
_info "Using bookmyname"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}"
BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}"
if [ -z "$BOOKMYNAME_USERNAME" ] || [ -z "$BOOKMYNAME_PASSWORD" ]; then
BOOKMYNAME_USERNAME=""
BOOKMYNAME_PASSWORD=""
_err "You didn't specify BookMyName username and password yet."
_err "Please specify them and try again."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable BOOKMYNAME_USERNAME "$BOOKMYNAME_USERNAME"
_saveaccountconf_mutable BOOKMYNAME_PASSWORD "$BOOKMYNAME_PASSWORD"
uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/"
data="?hostname=${fulldomain}&type=TXT&ttl=300&do=add&value=${txtvalue}"
result="$(_get "${uri}${data}")"
_debug "Result: $result"
if ! _startswith "$result" 'good: update done, cid '; then
_err "Can't add $fulldomain"
return 1
fi
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_bookmyname_rm() {
fulldomain=$1
txtvalue=$2
_info "Using bookmyname"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}"
BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}"
uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/"
data="?hostname=${fulldomain}&type=TXT&ttl=300&do=remove&value=${txtvalue}"
result="$(_get "${uri}${data}")"
_debug "Result: $result"
if ! _startswith "$result" 'good: remove done 1, cid '; then
_info "Can't remove $fulldomain"
fi
}
#################### Private functions below ##################################

2
dnsapi/dns_cloudns.sh

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

185
dnsapi/dns_dnsexit.sh

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#use dns-01 at DNSExit.com
#Author: Samuel Jimenez
#Report Bugs here: https://github.com/acmesh-official/acme.sh
#DNSEXIT_API_KEY=ABCDEFGHIJ0123456789abcdefghij
#DNSEXIT_AUTH_USER=login@email.address
#DNSEXIT_AUTH_PASS=aStrongPassword
DNSEXIT_API_URL="https://api.dnsexit.com/dns/"
DNSEXIT_HOSTS_URL="https://update.dnsexit.com/ipupdate/hosts.jsp"
######## Public functions #####################
#Usage: dns_dnsexit_add _acme-challenge.*.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dnsexit_add() {
fulldomain=$1
txtvalue=$2
_info "Using DNSExit.com"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug 'Load account auth'
if ! get_account_info; then
return 1
fi
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"add\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":0,\"overwrite\":false}}"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_dnsexit_rm() {
fulldomain=$1
txtvalue=$2
_info "Using DNSExit.com"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug 'Load account auth'
if ! get_account_info; then
return 1
fi
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
_err "$response"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"delete\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"}}"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
i=1
while true; do
_domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$_domain"
if [ -z "$_domain" ]; then
return 1
fi
_debug login "$DNSEXIT_AUTH_USER"
_debug password "$DNSEXIT_AUTH_PASS"
_debug domain "$_domain"
_dnsexit_http "login=$DNSEXIT_AUTH_USER&password=$DNSEXIT_AUTH_PASS&domain=$_domain"
if _contains "$response" "0=$_domain"; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
return 0
else
_debug "Go to next level of $_domain"
fi
i=$(_math "$i" + 1)
done
return 1
}
_dnsexit_rest() {
m=POST
ep=""
data="$1"
_debug _dnsexit_rest "$ep"
_debug data "$data"
api_key_trimmed=$(echo "$DNSEXIT_API_KEY" | tr -d '"')
export _H1="apikey: $api_key_trimmed"
export _H2='Content-Type: application/json'
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$DNSEXIT_API_URL/$ep" "" "$m")"
else
response="$(_get "$DNSEXIT_API_URL/$ep")"
fi
if [ "$?" != "0" ]; then
_err "Error $ep"
return 1
fi
_debug2 response "$response"
return 0
}
_dnsexit_http() {
m=GET
param="$1"
_debug param "$param"
_debug get "$DNSEXIT_HOSTS_URL?$param"
response="$(_get "$DNSEXIT_HOSTS_URL?$param")"
_debug response "$response"
if [ "$?" != "0" ]; then
_err "Error $param"
return 1
fi
_debug2 response "$response"
return 0
}
get_account_info() {
DNSEXIT_API_KEY="${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}"
if test -z "$DNSEXIT_API_KEY"; then
DNSEXIT_API_KEY=''
_err 'DNSEXIT_API_KEY was not exported'
return 1
fi
_saveaccountconf_mutable DNSEXIT_API_KEY "$DNSEXIT_API_KEY"
DNSEXIT_AUTH_USER="${DNSEXIT_AUTH_USER:-$(_readaccountconf_mutable DNSEXIT_AUTH_USER)}"
if test -z "$DNSEXIT_AUTH_USER"; then
DNSEXIT_AUTH_USER=""
_err 'DNSEXIT_AUTH_USER was not exported'
return 1
fi
_saveaccountconf_mutable DNSEXIT_AUTH_USER "$DNSEXIT_AUTH_USER"
DNSEXIT_AUTH_PASS="${DNSEXIT_AUTH_PASS:-$(_readaccountconf_mutable DNSEXIT_AUTH_PASS)}"
if test -z "$DNSEXIT_AUTH_PASS"; then
DNSEXIT_AUTH_PASS=""
_err 'DNSEXIT_AUTH_PASS was not exported'
return 1
fi
_saveaccountconf_mutable DNSEXIT_AUTH_PASS "$DNSEXIT_AUTH_PASS"
return 0
}

148
dnsapi/dns_do.sh

@ -1,148 +0,0 @@
#!/usr/bin/env sh
# DNS API for Domain-Offensive / Resellerinterface / Domainrobot
# Report bugs at https://github.com/seidler2547/acme.sh/issues
# set these environment variables to match your customer ID and password:
# DO_PID="KD-1234567"
# DO_PW="cdfkjl3n2"
DO_URL="https://soap.resellerinterface.de/"
######## Public functions #####################
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_do_add() {
fulldomain=$1
txtvalue=$2
if _dns_do_authenticate; then
_info "Adding TXT record to ${_domain} as ${fulldomain}"
_dns_do_soap createRR origin "${_domain}" name "${fulldomain}" type TXT data "${txtvalue}" ttl 300
if _contains "${response}" '>success<'; then
return 0
fi
_err "Could not create resource record, check logs"
fi
return 1
}
#fulldomain
dns_do_rm() {
fulldomain=$1
if _dns_do_authenticate; then
if _dns_do_list_rrs; then
_dns_do_had_error=0
for _rrid in ${_rr_list}; do
_info "Deleting resource record $_rrid for $_domain"
_dns_do_soap deleteRR origin "${_domain}" rrid "${_rrid}"
if ! _contains "${response}" '>success<'; then
_dns_do_had_error=1
_err "Could not delete resource record for ${_domain}, id ${_rrid}"
fi
done
return $_dns_do_had_error
fi
fi
return 1
}
#################### Private functions below ##################################
_dns_do_authenticate() {
_info "Authenticating as ${DO_PID}"
_dns_do_soap authPartner partner "${DO_PID}" password "${DO_PW}"
if _contains "${response}" '>success<'; then
_get_root "$fulldomain"
_debug "_domain $_domain"
return 0
else
_err "Authentication failed, are DO_PID and DO_PW set correctly?"
fi
return 1
}
_dns_do_list_rrs() {
_dns_do_soap getRRList origin "${_domain}"
if ! _contains "${response}" 'SOAP-ENC:Array'; then
_err "getRRList origin ${_domain} failed"
return 1
fi
_rr_list="$(echo "${response}" |
tr -d "\n\r\t" |
sed -e 's/<item xsi:type="ns2:Map">/\n/g' |
grep ">$(_regexcape "$fulldomain")</value>" |
sed -e 's/<\/item>/\n/g' |
grep '>id</key><value' |
_egrep_o '>[0-9]{1,16}<' |
tr -d '><')"
[ "${_rr_list}" ]
}
_dns_do_soap() {
func="$1"
shift
# put the parameters to xml
body="<tns:${func} xmlns:tns=\"${DO_URL}\">"
while [ "$1" ]; do
_k="$1"
shift
_v="$1"
shift
body="$body<$_k>$_v</$_k>"
done
body="$body</tns:${func}>"
_debug2 "SOAP request ${body}"
# build SOAP XML
_xml='<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>'"$body"'</env:Body>
</env:Envelope>'
# set SOAP headers
export _H1="SOAPAction: ${DO_URL}#${func}"
if ! response="$(_post "${_xml}" "${DO_URL}")"; then
_err "Error <$1>"
return 1
fi
_debug2 "SOAP response $response"
# retrieve cookie header
_H2="$(_egrep_o 'Cookie: [^;]+' <"$HTTP_HEADER" | _head_n 1)"
export _H2
return 0
}
_get_root() {
domain=$1
i=1
_dns_do_soap getDomainList
_all_domains="$(echo "${response}" |
tr -d "\n\r\t " |
_egrep_o 'domain</key><value[^>]+>[^<]+' |
sed -e 's/^domain<\/key><value[^>]*>//g')"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then
return 1
fi
if _contains "${_all_domains}" "^$(_regexcape "$h")\$"; then
_domain="$h"
return 0
fi
i=$(_math $i + 1)
done
_debug "$domain not found"
return 1
}
_regexcape() {
echo "$1" | sed -e 's/\([]\.$*^[]\)/\\\1/g'
}

8
dnsapi/dns_dynv6.sh

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

2
dnsapi/dns_edgedns.sh

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

35
dnsapi/dns_gandi_livedns.sh

@ -1,7 +1,8 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# Gandi LiveDNS v5 API # Gandi LiveDNS v5 API
# http://doc.livedns.gandi.net/
# https://api.gandi.net/docs/livedns/
# https://api.gandi.net/docs/authentication/ for token + apikey (deprecated) authentication
# currently under beta # currently under beta
# #
# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable # Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable
@ -12,20 +13,27 @@
# #
######## Public functions ##################### ######## Public functions #####################
GANDI_LIVEDNS_API="https://dns.api.gandi.net/api/v5"
GANDI_LIVEDNS_API="https://api.gandi.net/v5/livedns"
#Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" #Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_gandi_livedns_add() { dns_gandi_livedns_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
if [ -z "$GANDI_LIVEDNS_KEY" ]; then
_err "No API key specified for Gandi LiveDNS."
_err "Create your key and export it as GANDI_LIVEDNS_KEY"
if [ -z "$GANDI_LIVEDNS_KEY" ] && [ -z "$GANDI_LIVEDNS_TOKEN" ]; then
_err "No Token or API key (deprecated) specified for Gandi LiveDNS."
_err "Create your token or key and export it as GANDI_LIVEDNS_KEY or GANDI_LIVEDNS_TOKEN respectively"
return 1 return 1
fi fi
_saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY"
# Keep only one secret in configuration
if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then
_saveaccountconf GANDI_LIVEDNS_TOKEN "$GANDI_LIVEDNS_TOKEN"
_clearaccountconf GANDI_LIVEDNS_KEY
elif [ -n "$GANDI_LIVEDNS_KEY" ]; then
_saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY"
_clearaccountconf GANDI_LIVEDNS_TOKEN
fi
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -70,7 +78,7 @@ dns_gandi_livedns_rm() {
_gandi_livedns_rest PUT \ _gandi_livedns_rest PUT \
"domains/$_domain/records/$_sub_domain/TXT" \ "domains/$_domain/records/$_sub_domain/TXT" \
"{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" && "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" &&
_contains "$response" '{"message": "DNS Record Created"}' &&
_contains "$response" '{"message":"DNS Record Created"}' &&
_info "Removing record $(__green "success")" _info "Removing record $(__green "success")"
} }
@ -126,7 +134,7 @@ _dns_gandi_append_record() {
_debug new_rrset_values "$_rrset_values" _debug new_rrset_values "$_rrset_values"
_gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \ _gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \
"{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" && "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" &&
_contains "$response" '{"message": "DNS Record Created"}' &&
_contains "$response" '{"message":"DNS Record Created"}' &&
_info "Adding record $(__green "success")" _info "Adding record $(__green "success")"
} }
@ -136,11 +144,11 @@ _dns_gandi_existing_rrset_values() {
if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then
return 1 return 1
fi fi
if ! _contains "$response" '"rrset_type": "TXT"'; then
if ! _contains "$response" '"rrset_type":"TXT"'; then
_debug "Does not have a _acme-challenge TXT record yet." _debug "Does not have a _acme-challenge TXT record yet."
return 1 return 1
fi fi
if _contains "$response" '"rrset_values": \[\]'; then
if _contains "$response" '"rrset_values":\[\]'; then
_debug "Empty rrset_values for TXT record, no previous TXT record." _debug "Empty rrset_values for TXT record, no previous TXT record."
return 1 return 1
fi fi
@ -157,7 +165,12 @@ _gandi_livedns_rest() {
_debug "$ep" _debug "$ep"
export _H1="Content-Type: application/json" export _H1="Content-Type: application/json"
export _H2="X-Api-Key: $GANDI_LIVEDNS_KEY"
if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then
export _H2="Authorization: Bearer $GANDI_LIVEDNS_TOKEN"
else
export _H2="Authorization: Apikey $GANDI_LIVEDNS_KEY"
fi
if [ "$m" = "GET" ]; then if [ "$m" = "GET" ]; then
response="$(_get "$GANDI_LIVEDNS_API/$ep")" response="$(_get "$GANDI_LIVEDNS_API/$ep")"

2
dnsapi/dns_gcloud.sh

@ -42,7 +42,7 @@ dns_gcloud_rm() {
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 $? _dns_gcloud_execute_tr || return $?
_info "$fulldomain record added"
_info "$fulldomain record removed"
} }
#################### Private functions below ################################## #################### Private functions below ##################################

187
dnsapi/dns_gcore.sh

@ -0,0 +1,187 @@
#!/usr/bin/env sh
#
#GCORE_Key='773$7b7adaf2a2b32bfb1b83787b4ff32a67eb178e3ada1af733e47b1411f2461f7f4fa7ed7138e2772a46124377bad7384b3bb8d87748f87b3f23db4b8bbe41b2bb'
#
GCORE_Api="https://api.gcore.com/dns/v2"
GCORE_Doc="https://api.gcore.com/docs/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
}

6
dnsapi/dns_gd.sh

@ -22,8 +22,8 @@ dns_gd_add() {
if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then
GD_Key="" GD_Key=""
GD_Secret="" 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 return 1
fi fi
@ -46,7 +46,7 @@ dns_gd_add() {
fi fi
if _contains "$response" "$txtvalue"; then if _contains "$response" "$txtvalue"; then
_info "The record is existing, skip"
_info "This record already exists, skipping"
return 0 return 0
fi fi

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
}

78
dnsapi/dns_huaweicloud.sh

@ -23,7 +23,7 @@ dns_huaweicloud_add() {
HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}" HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
# Check information # Check information
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
@ -74,7 +74,7 @@ dns_huaweicloud_rm() {
HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}" HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
# Check information # Check information
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
@ -98,19 +98,59 @@ dns_huaweicloud_rm() {
fi fi
_debug "Zone ID is:" "${zoneid}" _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 # Remove all records
# Therotically HuaweiCloud does not allow more than one record set # Therotically HuaweiCloud does not allow more than one record set
# But remove them recurringly to increase robusty # But remove them recurringly to increase robusty
while [ "${record_id}" != "0" ]; do
while [ "${_record_id}" != "0" ] && [ "${_retry_cnt}" != "0" ]; do
_debug "Removing Record" _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 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 return 0
} }
################### Private functions below ##################################
# _get_zoneid # _get_zoneid
# #
# _token=$1 # _token=$1
@ -124,7 +164,7 @@ _get_zoneid() {
i=1 i=1
while true; do 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 if [ -z "$h" ]; then
#not valid #not valid
return 1 return 1
@ -135,11 +175,11 @@ _get_zoneid() {
if _contains "${response}" '"id"'; then if _contains "${response}" '"id"'; then
zoneidlist=$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ") 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 " ") 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 zoneidnum=0
zoneidcount=$(echo "${zoneidlist}" | grep -c '^') zoneidcount=$(echo "${zoneidlist}" | grep -c '^')
_debug "Retund Zone ID(s) Count:" "${zoneidcount}"
_debug "Returned Zone ID(s) Count:" "${zoneidcount}"
while [ "${zoneidnum}" -lt "${zoneidcount}" ]; do while [ "${zoneidnum}" -lt "${zoneidcount}" ]; do
zoneidnum=$(_math "$zoneidnum" + 1) zoneidnum=$(_math "$zoneidnum" + 1)
_zoneid=$(echo "${zoneidlist}" | sed -n "${zoneidnum}p") _zoneid=$(echo "${zoneidlist}" | sed -n "${zoneidnum}p")
@ -206,8 +246,7 @@ _add_record() {
\"type\": \"TXT\", \"type\": \"TXT\",
\"ttl\": 1, \"ttl\": 1,
\"records\": [ \"records\": [
${_exist_record},
\"\\\"${_txtvalue}\\\"\"
${_exist_record},\"\\\"${_txtvalue}\\\"\"
] ]
}" }"
fi fi
@ -215,19 +254,16 @@ _add_record() {
_record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")" _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")"
_debug "Record Set ID is:" "${_record_id}" _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 # Add brand new records with all old and new records
export _H2="Content-Type: application/json" export _H2="Content-Type: application/json"
export _H1="X-Auth-Token: ${_token}" export _H1="X-Auth-Token: ${_token}"
_debug2 "${_post_body}" _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")" _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
if [ "$_code" != "202" ]; then if [ "$_code" != "202" ]; then
_err "dns_huaweicloud: http code ${_code}" _err "dns_huaweicloud: http code ${_code}"

4
dnsapi/dns_infomaniak.sh

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

2
dnsapi/dns_inwx.sh

@ -194,7 +194,7 @@ _inwx_login() {
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
_H1=$INWX_Cookie _H1=$INWX_Cookie
export _H1 export _H1
export INWX_Cookie export INWX_Cookie

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
}

4
dnsapi/dns_kappernet.sh

@ -45,8 +45,8 @@ dns_kappernet_add() {
if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then
if _contains "$response" "{\"OK\":true"; then if _contains "$response" "{\"OK\":true"; then
_info "Waiting 120 seconds for DNS to spread the new record"
_sleep 120
_info "Waiting 1 second for DNS to spread the new record"
_sleep 1
return 0 return 0
else else
_err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue" _err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue"

2
dnsapi/dns_kas.sh

@ -215,7 +215,7 @@ _get_record_id() {
return 1 return 1
fi 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" | sed "s/<\/value><\/item>/\n/g" | grep "=>" | sed "s/=>//g")"
_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" _debug "[KAS] -> Record Id: " "$_record_id"
return 0 return 0
} }

2
dnsapi/dns_kinghost.sh

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

4
dnsapi/dns_leaseweb.sh

@ -3,10 +3,10 @@
#Author: Rolph Haspers <r.haspers@global.leaseweb.com> #Author: Rolph Haspers <r.haspers@global.leaseweb.com>
#Utilize leaseweb.com API to finish dns-01 verifications. #Utilize leaseweb.com API to finish dns-01 verifications.
#Requires a Leaseweb API Key (export LSW_Key="Your Key") #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 ##################### ######## 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 #Usage: dns_leaseweb_add _acme-challenge.www.domain.com
dns_leaseweb_add() { dns_leaseweb_add() {

2
dnsapi/dns_loopia.sh

@ -107,7 +107,7 @@ _loopia_load_config() {
fi fi
if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then
_err "Password contains quoute or double quoute and this is not supported by dns_loopia.sh"
_err "Password contains a quotation mark or double quotation marks and this is not supported by dns_loopia.sh"
return 1 return 1
fi fi

2
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." _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 # 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. # 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 if ! _get_root_by_getHosts "$fulldomain"; then
return 1 return 1
fi fi

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
}

1
dnsapi/dns_oci.sh

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

12
dnsapi/dns_openstack.sh

@ -57,16 +57,16 @@ _dns_openstack_create_recordset() {
if [ -z "$_recordset_id" ]; then if [ -z "$_recordset_id" ]; then
_info "Creating a new recordset" _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" _err "No recordset ID found after create"
return 1 return 1
fi fi
else else
_info "Updating existing recordset" _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 for _rec in $_records; do
_record_args="$_record_args --record $_rec"
_record_args="$_record_args --record=$_rec"
done done
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then 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 fi
else else
_info "Found existing records, updating recordset" _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="" _record_args=""
for _rec in $_records; do for _rec in $_records; do
if [ "$_rec" = "$txtvalue" ]; then if [ "$_rec" = "$txtvalue" ]; then
continue continue
fi fi
_record_args="$_record_args --record $_rec"
_record_args="$_record_args --record=$_rec"
done done
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then 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 domain=$1
i=2 i=2
p=1 p=1
if _opns_rest "GET" "/domain/searchMasterDomain"; then
if _opns_rest "GET" "/domain/searchPrimaryDomain"; then
_domain_response="$response" _domain_response="$response"
else else
return 1 return 1
@ -150,7 +150,7 @@ _get_root() {
return 1 return 1
fi fi
_debug h "$h" _debug h "$h"
id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"master\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"primary\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
if [ -n "$id" ]; then if [ -n "$id" ]; then
_debug id "$id" _debug id "$id"
_host=$(printf "%s" "$domain" | cut -d . -f 1-$p) _host=$(printf "%s" "$domain" | cut -d . -f 1-$p)

17
dnsapi/dns_ovh.sh

@ -14,6 +14,9 @@
#'ovh-eu' #'ovh-eu'
OVH_EU='https://eu.api.ovh.com/1.0' OVH_EU='https://eu.api.ovh.com/1.0'
#'ovh-us'
OVH_US='https://api.us.ovhcloud.com/1.0'
#'ovh-ca': #'ovh-ca':
OVH_CA='https://ca.api.ovh.com/1.0' OVH_CA='https://ca.api.ovh.com/1.0'
@ -29,9 +32,6 @@ SYS_EU='https://eu.api.soyoustart.com/1.0'
#'soyoustart-ca' #'soyoustart-ca'
SYS_CA='https://ca.api.soyoustart.com/1.0' SYS_CA='https://ca.api.soyoustart.com/1.0'
#'runabove-ca'
RAV_CA='https://api.runabove.com/1.0'
wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api" wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api"
ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success" ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success"
@ -45,6 +45,10 @@ _ovh_get_api() {
printf "%s" $OVH_EU printf "%s" $OVH_EU
return return
;; ;;
ovh-us | ovhus)
printf "%s" $OVH_US
return
;;
ovh-ca | ovhca) ovh-ca | ovhca)
printf "%s" $OVH_CA printf "%s" $OVH_CA
return return
@ -65,14 +69,15 @@ _ovh_get_api() {
printf "%s" $SYS_CA printf "%s" $SYS_CA
return return
;; ;;
runabove-ca | runaboveca)
printf "%s" $RAV_CA
# raw API url starts with https://
https*)
printf "%s" "$1"
return return
;; ;;
*) *)
_err "Unknown parameter : $1"
_err "Unknown endpoint : $1"
return 1 return 1
;; ;;
esac esac

60
dnsapi/dns_pleskxml.sh

@ -41,11 +41,15 @@ pleskxml_init_checks_done=0
NEWLINE='\ NEWLINE='\
' '
pleskxml_tplt_get_domains="<packet><customer><get-domain-list><filter/></get-domain-list></customer></packet>"
pleskxml_tplt_get_domains="<packet><webspace><get><filter/><dataset><gen_info/></dataset></get></webspace></packet>"
# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh
# Also used to test credentials and URI. # Also used to test credentials and URI.
# No params. # No params.
pleskxml_tplt_get_additional_domains="<packet><site><get><filter/><dataset><gen_info/></dataset></get></site></packet>"
# Get a list of additional domains that PLESK can manage, so we can check root domain + host for acme.sh
# No params.
pleskxml_tplt_get_dns_records="<packet><dns><get_rec><filter><site-id>%s</site-id></filter></get_rec></dns></packet>" pleskxml_tplt_get_dns_records="<packet><dns><get_rec><filter><site-id>%s</site-id></filter></get_rec></dns></packet>"
# Get all DNS records for a Plesk domain ID. # Get all DNS records for a Plesk domain ID.
# PARAM = Plesk domain id to query # PARAM = Plesk domain id to query
@ -145,22 +149,25 @@ dns_pleskxml_rm() {
)" )"
if [ -z "$reclist" ]; then if [ -z "$reclist" ]; then
_err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting."
_err "No TXT records found for root domain $fulldomain (Plesk domain ID ${root_domain_id}). Exiting."
return 1 return 1
fi fi
_debug "Got list of DNS TXT records for root domain '$root_domain_name':"
_debug "Got list of DNS TXT records for root Plesk domain ID ${root_domain_id} of root domain $fulldomain:"
_debug "$reclist" _debug "$reclist"
# Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value
recid="$( recid="$(
_value "$reclist" | _value "$reclist" |
grep "<host>${fulldomain}.</host>" |
grep -i "<host>${fulldomain}.</host>" |
grep "<value>${txtvalue}</value>" | grep "<value>${txtvalue}</value>" |
sed 's/^.*<id>\([0-9]\{1,\}\)<\/id>.*$/\1/' sed 's/^.*<id>\([0-9]\{1,\}\)<\/id>.*$/\1/'
)" )"
_debug "Got id from line: $recid"
if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then
_err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
_err "DNS records for root domain '${fulldomain}.' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
_err "Cannot delete TXT record. Exiting." _err "Cannot delete TXT record. Exiting."
return 1 return 1
fi fi
@ -251,9 +258,12 @@ _call_api() {
# Detect any <status> that isn't "ok". None of the used calls should fail if the API is working correctly. # Detect any <status> that isn't "ok". None of the used calls should fail if the API is working correctly.
# Also detect if there simply aren't any status lines (null result?) and report that, as well. # Also detect if there simply aren't any status lines (null result?) and report that, as well.
# Remove <data></data> structure from result string, since it might contain <status> values that are related to the status of the domain and not to the API request
statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *<status>[^<]*</status> *$')"
statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *<status>ok</status> *$')"
statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | sed '/<data>/,/<\/data>/d' | grep -c '^ *<status>[^<]*</status> *$')"
statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | sed '/<data>/,/<\/data>/d' | grep -c '^ *<status>ok</status> *$')"
_debug "statuslines_count_total=$statuslines_count_total."
_debug "statuslines_count_okay=$statuslines_count_okay."
if [ -z "$statuslines_count_total" ]; then if [ -z "$statuslines_count_total" ]; then
@ -369,16 +379,44 @@ _pleskxml_get_root_domain() {
return 1 return 1
fi fi
# Generate a crude list of domains known to this Plesk account.
# Generate a crude list of domains known to this Plesk account based on subscriptions.
# We convert <ascii-name> tags to <name> so it'll flag on a hit with either <name> or <ascii-name> fields,
# for non-Western character sets.
# Output will be one line per known domain, containing 2 <name> tages and a single <id> tag
# We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.
output="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '<status>ok</status>' | sed 's/<ascii-name>/<name>/g;s/<\/ascii-name>/<\/name>/g' | grep '<name>' | grep '<id>')"
debug_output="$(printf "%s" "$output" | sed -n 's:.*<name>\(.*\)</name>.*:\1:p')"
_debug 'Domains managed by Plesk server are:'
_debug "$debug_output"
_debug "Querying Plesk server for list of additional managed domains..."
_call_api "$pleskxml_tplt_get_additional_domains"
if [ "$pleskxml_retcode" -ne 0 ]; then
return 1
fi
# Generate a crude list of additional domains known to this Plesk account based on sites.
# We convert <ascii-name> tags to <name> so it'll flag on a hit with either <name> or <ascii-name> fields, # We convert <ascii-name> tags to <name> so it'll flag on a hit with either <name> or <ascii-name> fields,
# for non-Western character sets. # for non-Western character sets.
# Output will be one line per known domain, containing 2 <name> tages and a single <id> tag # Output will be one line per known domain, containing 2 <name> tages and a single <id> tag
# We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.
output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' '<type>domain</type>' | sed 's/<ascii-name>/<name>/g;s/<\/ascii-name>/<\/name>/g' | grep '<name>' | grep '<id>')"
output_additional="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '<status>ok</status>' | sed 's/<ascii-name>/<name>/g;s/<\/ascii-name>/<\/name>/g' | grep '<name>' | grep '<id>')"
debug_additional="$(printf "%s" "$output_additional" | sed -n 's:.*<name>\(.*\)</name>.*:\1:p')"
_debug 'Additional domains managed by Plesk server are:'
_debug "$debug_additional"
# Concate the two outputs together.
output="$(printf "%s" "$output $NEWLINE $output_additional")"
debug_output="$(printf "%s" "$output" | sed -n 's:.*<name>\(.*\)</name>.*:\1:p')"
_debug 'Domains managed by Plesk server are (ignore the hacked output):'
_debug "$output"
_debug 'Domains (including additional) managed by Plesk server are:'
_debug "$debug_output"
# loop and test if domain, or any parent domain, is managed by Plesk # loop and test if domain, or any parent domain, is managed by Plesk
# Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain

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 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." _info "A txt record with the same name already exists."
# trim the string on the left # 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 # trim the string on the right
txtvalue_old=${txtvalue_old%%\"*} txtvalue_old=${txtvalue_old%%\"*}

211
dnsapi/dns_tencent.sh

@ -0,0 +1,211 @@
#!/usr/bin/env sh
Tencent_API="https://dnspod.tencentcloudapi.com"
#Tencent_SecretId="AKIDz81d2cd22cdcdc2dcd1cc1d1A"
#Tencent_SecretKey="Gu5t9abcabcaabcbabcbbbcbcbbccbbcb"
#Usage: dns_tencent_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_tencent_add() {
fulldomain=$1
txtvalue=$2
Tencent_SecretId="${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}"
Tencent_SecretKey="${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}"
if [ -z "$Tencent_SecretId" ] || [ -z "$Tencent_SecretKey" ]; then
Tencent_SecretId=""
Tencent_SecretKey=""
_err "You don't specify tencent api SecretId and SecretKey yet."
return 1
fi
#save the api SecretId and SecretKey to the account conf file.
_saveaccountconf_mutable Tencent_SecretId "$Tencent_SecretId"
_saveaccountconf_mutable Tencent_SecretKey "$Tencent_SecretKey"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
return 1
fi
_debug "Add record"
_add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _tencent_rest "CreateRecord"
}
dns_tencent_rm() {
fulldomain=$1
txtvalue=$2
Tencent_SecretId="${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}"
Tencent_SecretKey="${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
return 1
fi
_debug "Get record list"
attempt=1
max_attempts=5
while [ -z "$record_id" ] && [ "$attempt" -le $max_attempts ]; do
_check_exist_query "$_domain" "$_sub_domain" "$txtvalue" && _tencent_rest "DescribeRecordFilterList"
record_id="$(echo "$response" | _egrep_o "\"RecordId\":\s*[0-9]+" | _egrep_o "[0-9]+")"
_debug2 record_id "$record_id"
if [ -z "$record_id" ]; then
_debug "Due to TencentCloud API synchronization delay, record not found, waiting 10 seconds and retrying"
_sleep 10
attempt=$(_math "$attempt + 1")
fi
done
record_id="$(echo "$response" | _egrep_o "\"RecordId\":\s*[0-9]+" | _egrep_o "[0-9]+")"
_debug2 record_id "$record_id"
if [ -z "$record_id" ]; then
_debug "record not found after $max_attempts attempts, skip"
else
_debug "Delete record"
_delete_record_query "$record_id" && _tencent_rest "DeleteRecord"
fi
}
#################### Private functions below ##################################
_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
_describe_records_query "$h" "@"
if ! _tencent_rest "DescribeRecordList" "ignore"; then
return 1
fi
if _contains "$response" "\"TotalCount\":"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_debug _sub_domain "$_sub_domain"
_domain="$h"
_debug _domain "$_domain"
return 0
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
_tencent_rest() {
action=$1
service="dnspod"
payload="${query}"
timestamp=$(date -u +%s)
token=$(tencent_signature_v3 $service "$action" "$payload" "$timestamp")
version="2021-03-23"
if ! response="$(tencent_api_request $service $version "$action" "$payload" "$timestamp")"; then
_err "Error <$1>"
return 1
fi
_debug2 response "$response"
if [ -z "$2" ]; then
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
if [ "$message" ]; then
_err "$message"
return 1
fi
fi
}
_add_record_query() {
query="{\"Domain\":\"$1\",\"SubDomain\":\"$2\",\"RecordType\":\"TXT\",\"RecordLineId\":\"0\",\"RecordLine\":\"0\",\"Value\":\"$3\",\"TTL\":600}"
}
_describe_records_query() {
query="{\"Domain\":\"$1\",\"Limit\":3000}"
}
_delete_record_query() {
query="{\"Domain\":\"$_domain\",\"RecordId\":$1}"
}
_check_exist_query() {
_domain="$1"
_subdomain="$2"
_value="$3"
query="{\"Domain\":\"$_domain\",\"SubDomain\":\"$_subdomain\",\"RecordValue\":\"$_value\"}"
}
# shell client for tencent cloud api v3 | @author: rehiy
tencent_sha256() {
printf %b "$@" | _digest sha256 hex
}
tencent_hmac_sha256() {
k=$1
shift
hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ')
printf %b "$@" | _hmac sha256 "$hex_key" hex
}
tencent_hmac_sha256_hexkey() {
k=$1
shift
printf %b "$@" | _hmac sha256 "$k" hex
}
tencent_signature_v3() {
service=$1
action=$(echo "$2" | _lower_case)
payload=${3:-'{}'}
timestamp=${4:-$(date +%s)}
domain="$service.tencentcloudapi.com"
secretId=${Tencent_SecretId:-'tencent-cloud-secret-id'}
secretKey=${Tencent_SecretKey:-'tencent-cloud-secret-key'}
algorithm='TC3-HMAC-SHA256'
date=$(date -u -d "@$timestamp" +%Y-%m-%d 2>/dev/null)
[ -z "$date" ] && date=$(date -u -r "$timestamp" +%Y-%m-%d)
canonicalUri='/'
canonicalQuery=''
canonicalHeaders="content-type:application/json\nhost:$domain\nx-tc-action:$action\n"
signedHeaders='content-type;host;x-tc-action'
canonicalRequest="POST\n$canonicalUri\n$canonicalQuery\n$canonicalHeaders\n$signedHeaders\n$(tencent_sha256 "$payload")"
credentialScope="$date/$service/tc3_request"
stringToSign="$algorithm\n$timestamp\n$credentialScope\n$(tencent_sha256 "$canonicalRequest")"
secretDate=$(tencent_hmac_sha256 "TC3$secretKey" "$date")
secretService=$(tencent_hmac_sha256_hexkey "$secretDate" "$service")
secretSigning=$(tencent_hmac_sha256_hexkey "$secretService" 'tc3_request')
signature=$(tencent_hmac_sha256_hexkey "$secretSigning" "$stringToSign")
echo "$algorithm Credential=$secretId/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature"
}
tencent_api_request() {
service=$1
version=$2
action=$3
payload=${4:-'{}'}
timestamp=${5:-$(date +%s)}
token=$(tencent_signature_v3 "$service" "$action" "$payload" "$timestamp")
_H1="Content-Type: application/json"
_H2="Authorization: $token"
_H3="X-TC-Version: $version"
_H4="X-TC-Timestamp: $timestamp"
_H5="X-TC-Action: $action"
_post "$payload" "$Tencent_API" "" "POST" "application/json"
}

21
dnsapi/dns_variomedia.sh

@ -69,7 +69,7 @@ dns_variomedia_rm() {
return 1 return 1
fi fi
_record_id="$(echo "$response" | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
_record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
_debug _record_id "$_record_id" _debug _record_id "$_record_id"
if [ "$_record_id" ]; then if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge." _info "Successfully retrieved the record id for ACME challenge."
@ -93,11 +93,11 @@ dns_variomedia_rm() {
# _sub_domain=_acme-challenge.www # _sub_domain=_acme-challenge.www
# _domain=domain.com # _domain=domain.com
_get_root() { _get_root() {
fulldomain=$1
domain=$1
i=1 i=1
p=1
while true; do while true; do
h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
_debug h "$h"
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then if [ -z "$h" ]; then
return 1 return 1
fi fi
@ -106,17 +106,14 @@ _get_root() {
return 1 return 1
fi fi
if _startswith "$response" "\{\"data\":"; then
if _contains "$response" "\"id\":\"$h\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
_domain=$h
return 0
fi
if _contains "$response" "\"id\":\"$h\""; then
_sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-$p)
_domain="$h"
return 0
fi fi
p=$i
i=$(_math "$i" + 1) i=$(_math "$i" + 1)
done done
_debug "root domain not found"
return 1 return 1
} }

6
dnsapi/dns_vultr.sh

@ -78,7 +78,7 @@ dns_vultr_rm() {
return 1 return 1
fi fi
_record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'id' | 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" _debug _record_id "$_record_id"
if [ "$_record_id" ]; then if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge." _info "Successfully retrieved the record id for ACME challenge."
@ -116,7 +116,7 @@ _get_root() {
return 1 return 1
fi 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 if _contains "$response" "\"domain\":\"$_domain\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")" _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
return 0 return 0
@ -139,7 +139,7 @@ _vultr_rest() {
data="$3" data="$3"
_debug "$ep" _debug "$ep"
api_key_trimmed=$(echo $VULTR_API_KEY | tr -d '"')
api_key_trimmed=$(echo "$VULTR_API_KEY" | tr -d '"')
export _H1="Authorization: Bearer $api_key_trimmed" export _H1="Authorization: Bearer $api_key_trimmed"
export _H2='Content-Type: application/json' export _H2='Content-Type: application/json'

105
dnsapi/dns_west_cn.sh

@ -0,0 +1,105 @@
#!/usr/bin/env sh
# West.cn Domain api
#WEST_Username="username"
#WEST_Key="sADDsdasdgdsf"
#Set key at https://www.west.cn/manager/API/APIconfig.asp
REST_API="https://api.west.cn/API/v2"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_west_cn_add() {
fulldomain=$1
txtvalue=$2
WEST_Username="${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}"
WEST_Key="${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}"
if [ -z "$WEST_Username" ] || [ -z "$WEST_Key" ]; then
WEST_Username=""
WEST_Key=""
_err "You don't specify west api key and username yet."
_err "Please set you key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable WEST_Username "$WEST_Username"
_saveaccountconf_mutable WEST_Key "$WEST_Key"
add_record "$fulldomain" "$txtvalue"
}
#Usage: rm _acme-challenge.www.domain.com
dns_west_cn_rm() {
fulldomain=$1
txtvalue=$2
WEST_Username="${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}"
WEST_Key="${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}"
if ! _rest POST "domain/dns/" "act=dnsrec.list&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT"; then
_err "dnsrec.list error."
return 1
fi
if _contains "$response" 'no records'; then
_info "Don't need to remove."
return 0
fi
record_id=$(echo "$response" | tr "{" "\n" | grep -- "$txtvalue" | grep '^"record_id"' | cut -d : -f 2 | cut -d ',' -f 1)
_debug record_id "$record_id"
if [ -z "$record_id" ]; then
_err "Can not get record id."
return 1
fi
if ! _rest POST "domain/dns/" "act=dnsrec.remove&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_id=$record_id"; then
_err "dnsrec.remove error."
return 1
fi
_contains "$response" "success"
}
#add the txt record.
#usage: add fulldomain txtvalue
add_record() {
fulldomain=$1
txtvalue=$2
_info "Adding record"
if ! _rest POST "domain/dns/" "act=dnsrec.add&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT&record_value=$txtvalue"; then
return 1
fi
_contains "$response" "success"
}
#Usage: method URI data
_rest() {
m="$1"
ep="$2"
data="$3"
_debug "$ep"
url="$REST_API/$ep"
_debug url "$url"
if [ "$m" = "GET" ]; then
response="$(_get "$url" | tr -d '\r')"
else
_debug2 data "$data"
response="$(_post "$data" "$url" | tr -d '\r')"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

226
notify/aws_ses.sh

@ -0,0 +1,226 @@
#!/usr/bin/env sh
#
#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
#AWS_SECRET_ACCESS_KEY="xxxxxxx"
#
#AWS_SES_REGION="us-east-1"
#
#AWS_SES_TO="xxxx@xxx.com"
#
#AWS_SES_FROM="xxxx@cccc.com"
#
#AWS_SES_FROM_NAME="Something something"
#This is the Amazon SES api wrapper for acme.sh
AWS_WIKI="https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html"
aws_ses_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
AWS_SES_REGION="${AWS_SES_REGION:-$(_readaccountconf_mutable AWS_SES_REGION)}"
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
_use_container_role || _use_instance_role
fi
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
_err "You haven't specified the aws SES api key id and and api key secret yet."
_err "Please create your key and try again. see $(__green $AWS_WIKI)"
return 1
fi
if [ -z "$AWS_SES_REGION" ]; then
AWS_SES_REGION=""
_err "You haven't specified the aws SES api region yet."
_err "Please specify your region and try again. see https://docs.aws.amazon.com/general/latest/gr/ses.html"
return 1
fi
_saveaccountconf_mutable AWS_SES_REGION "$AWS_SES_REGION"
#save for future use, unless using a role which will be fetched as needed
if [ -z "$_using_role" ]; then
_saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
_saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
fi
AWS_SES_TO="${AWS_SES_TO:-$(_readaccountconf_mutable AWS_SES_TO)}"
if [ -z "$AWS_SES_TO" ]; then
AWS_SES_TO=""
_err "You didn't specify an email to AWS_SES_TO receive messages."
return 1
fi
_saveaccountconf_mutable AWS_SES_TO "$AWS_SES_TO"
AWS_SES_FROM="${AWS_SES_FROM:-$(_readaccountconf_mutable AWS_SES_FROM)}"
if [ -z "$AWS_SES_FROM" ]; then
AWS_SES_FROM=""
_err "You didn't specify an email to AWS_SES_FROM receive messages."
return 1
fi
_saveaccountconf_mutable AWS_SES_FROM "$AWS_SES_FROM"
AWS_SES_FROM_NAME="${AWS_SES_FROM_NAME:-$(_readaccountconf_mutable AWS_SES_FROM_NAME)}"
_saveaccountconf_mutable AWS_SES_FROM_NAME "$AWS_SES_FROM_NAME"
AWS_SES_SENDFROM="$AWS_SES_FROM_NAME <$AWS_SES_FROM>"
AWS_SES_ACTION="Action=SendEmail"
AWS_SES_SOURCE="Source=$AWS_SES_SENDFROM"
AWS_SES_TO="Destination.ToAddresses.member.1=$AWS_SES_TO"
AWS_SES_SUBJECT="Message.Subject.Data=$_subject"
AWS_SES_MESSAGE="Message.Body.Text.Data=$_content"
_data="${AWS_SES_ACTION}&${AWS_SES_SOURCE}&${AWS_SES_TO}&${AWS_SES_SUBJECT}&${AWS_SES_MESSAGE}"
response="$(aws_rest POST "" "" "$_data")"
}
_use_metadata() {
_aws_creds="$(
_get "$1" "" 1 |
_normalizeJson |
tr '{,}' '\n' |
while read -r _line; do
_key="$(echo "${_line%%:*}" | tr -d '"')"
_value="${_line#*:}"
_debug3 "_key" "$_key"
_secure_debug3 "_value" "$_value"
case "$_key" in
AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;;
SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;;
Token) echo "AWS_SESSION_TOKEN=$_value" ;;
esac
done |
paste -sd' ' -
)"
_secure_debug "_aws_creds" "$_aws_creds"
if [ -z "$_aws_creds" ]; then
return 1
fi
eval "$_aws_creds"
_using_role=true
}
#method uri qstr data
aws_rest() {
mtd="$1"
ep="$2"
qsr="$3"
data="$4"
_debug mtd "$mtd"
_debug ep "$ep"
_debug qsr "$qsr"
_debug data "$data"
CanonicalURI="/$ep"
_debug2 CanonicalURI "$CanonicalURI"
CanonicalQueryString="$qsr"
_debug2 CanonicalQueryString "$CanonicalQueryString"
RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")"
_debug2 RequestDate "$RequestDate"
#RequestDate="20161120T141056Z" ##############
export _H1="x-amz-date: $RequestDate"
aws_host="email.$AWS_SES_REGION.amazonaws.com"
CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n"
SignedHeaders="host;x-amz-date"
if [ -n "$AWS_SESSION_TOKEN" ]; then
export _H3="x-amz-security-token: $AWS_SESSION_TOKEN"
CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n"
SignedHeaders="${SignedHeaders};x-amz-security-token"
fi
_debug2 CanonicalHeaders "$CanonicalHeaders"
_debug2 SignedHeaders "$SignedHeaders"
RequestPayload="$data"
_debug2 RequestPayload "$RequestPayload"
Hash="sha256"
CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)"
_debug2 CanonicalRequest "$CanonicalRequest"
HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)"
_debug2 HashedCanonicalRequest "$HashedCanonicalRequest"
Algorithm="AWS4-HMAC-SHA256"
_debug2 Algorithm "$Algorithm"
RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)"
_debug2 RequestDateOnly "$RequestDateOnly"
Region="$AWS_SES_REGION"
Service="ses"
CredentialScope="$RequestDateOnly/$Region/$Service/aws4_request"
_debug2 CredentialScope "$CredentialScope"
StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest"
_debug2 StringToSign "$StringToSign"
kSecret="AWS4$AWS_SECRET_ACCESS_KEY"
#kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################
_secure_debug2 kSecret "$kSecret"
kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")"
_secure_debug2 kSecretH "$kSecretH"
kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)"
_debug2 kDateH "$kDateH"
kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)"
_debug2 kRegionH "$kRegionH"
kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)"
_debug2 kServiceH "$kServiceH"
kSigningH="$(printf "%s" "aws4_request" | _hmac "$Hash" "$kServiceH" hex)"
_debug2 kSigningH "$kSigningH"
signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)"
_debug2 signature "$signature"
Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
_debug2 Authorization "$Authorization"
_H2="Authorization: $Authorization"
_debug _H2 "$_H2"
url="https://$aws_host/$ep"
if [ "$qsr" ]; then
url="https://$aws_host/$ep?$qsr"
fi
if [ "$mtd" = "GET" ]; then
response="$(_get "$url")"
else
response="$(_post "$data" "$url")"
fi
_ret="$?"
_debug2 response "$response"
if [ "$_ret" = "0" ]; then
if _contains "$response" "<ErrorResponse"; then
_err "Response error:$response"
return 1
fi
fi
}

52
notify/mattermost.sh

@ -0,0 +1,52 @@
#!/usr/bin/env sh
# Support mattermost bots
#MATTERMOST_API_URL=""
#MATTERMOST_CHANNEL_ID=""
#MATTERMOST_BOT_TOKEN=""
mattermost_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
MATTERMOST_API_URL="${MATTERMOST_API_URL:-$(_readaccountconf_mutable MATTERMOST_API_URL)}"
if [ -z "$MATTERMOST_API_URL" ]; then
_err "You didn't specify a Mattermost API URL MATTERMOST_API_URL yet."
return 1
fi
_saveaccountconf_mutable MATTERMOST_API_URL "$MATTERMOST_API_URL"
MATTERMOST_CHANNEL_ID="${MATTERMOST_CHANNEL_ID:-$(_readaccountconf_mutable MATTERMOST_CHANNEL_ID)}"
if [ -z "$MATTERMOST_CHANNEL_ID" ]; then
_err "You didn't specify a Mattermost channel id MATTERMOST_CHANNEL_ID yet."
return 1
fi
_saveaccountconf_mutable MATTERMOST_CHANNEL_ID "$MATTERMOST_CHANNEL_ID"
MATTERMOST_BOT_TOKEN="${MATTERMOST_BOT_TOKEN:-$(_readaccountconf_mutable MATTERMOST_BOT_TOKEN)}"
if [ -z "$MATTERMOST_BOT_TOKEN" ]; then
_err "You didn't specify a Mattermost bot API token MATTERMOST_BOT_TOKEN yet."
return 1
fi
_saveaccountconf_mutable MATTERMOST_BOT_TOKEN "$MATTERMOST_BOT_TOKEN"
_content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)"
_data="{\"channel_id\": \"$MATTERMOST_CHANNEL_ID\", "
_data="$_data\"message\": \"$_content\"}"
export _H1="Authorization: Bearer $MATTERMOST_BOT_TOKEN"
response=""
if _post "$_data" "$MATTERMOST_API_URL" "" "POST" "application/json; charset=utf-8"; then
MATTERMOST_RESULT_OK=$(echo "$response" | _egrep_o 'create_at')
if [ "$?" = "0" ] && [ "$MATTERMOST_RESULT_OK" ]; then
_info "mattermost send success."
return 0
fi
fi
_err "mattermost send error."
_err "$response"
return 1
}

4
notify/smtp.sh

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

Loading…
Cancel
Save