mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-09-03 20:12:48 +08:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b5a902755 | ||
|
|
4a3f8b5dea | ||
|
|
08ac83459d | ||
|
|
2b7487ba72 | ||
|
|
19efdf269a | ||
|
|
cdca555cad | ||
|
|
cce4a28b99 | ||
|
|
4756183e30 | ||
|
|
6e1deacac0 | ||
|
|
c6cd844986 | ||
|
|
ef7b2d3c2e | ||
|
|
b481ffb81b | ||
|
|
b1a8eb1c95 | ||
|
|
e0c0297ba3 | ||
|
|
1cbd223386 | ||
|
|
5180911929 | ||
|
|
5ff7f0a4e7 | ||
|
|
41bdd4cd0e | ||
|
|
a89ba9c2e5 | ||
|
|
2e2782f0d8 | ||
|
|
4a3bc2c919 | ||
|
|
05367d3598 | ||
|
|
f1cbba05f6 | ||
|
|
f67be78ff4 | ||
|
|
9aad4dcbd5 | ||
|
|
603a126a7c | ||
|
|
5e6c263211 | ||
|
|
b4925052dd | ||
|
|
ea5e70564d | ||
|
|
bf90b845b2 | ||
|
|
0565443622 | ||
|
|
057c940895 | ||
|
|
7c12deb7ef | ||
|
|
830782fd1d | ||
|
|
698f6c7329 | ||
|
|
749fa2487e | ||
|
|
24d573b6d3 | ||
|
|
1774d838ca | ||
|
|
4c8a143086 | ||
|
|
6feb1df83c | ||
|
|
97c5aca136 | ||
|
|
7fa3018219 | ||
|
|
9bfcf2cf1a | ||
|
|
59a97d7f8b | ||
|
|
a836e747d1 | ||
|
|
8f98786d93 | ||
|
|
6d559ae69f | ||
|
|
24501ac0ca | ||
|
|
a82cf763cf | ||
|
|
7cc16cd09a | ||
|
|
d45b6fe8e6 | ||
|
|
237f2d9c3b | ||
|
|
3e64b5e6f5 | ||
|
|
347cc207cd | ||
|
|
1651a5a609 | ||
|
|
0c76c1f211 | ||
|
|
e00d3cfde3 | ||
|
|
4187ec23c1 | ||
|
|
9f0ef7abcd | ||
|
|
0e2659b768 | ||
|
|
f50401a342 | ||
|
|
992886c4eb | ||
|
|
d60c75b4e3 | ||
|
|
3c848adbb7 | ||
|
|
f265c304da |
274
.github/workflows/DNS.yml
vendored
274
.github/workflows/DNS.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
run: |
|
run: |
|
||||||
brew untap aws/tap || true
|
brew untap aws/tap || true
|
||||||
@@ -167,7 +167,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@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Install cygwin base packages with chocolatey
|
- name: Install cygwin base packages with chocolatey
|
||||||
run: |
|
run: |
|
||||||
choco config get cacheLocation
|
choco config get cacheLocation
|
||||||
@@ -231,12 +231,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/freebsd-vm@v1
|
- uses: vmactions/freebsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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
|
||||||
usesh: true
|
usesh: true
|
||||||
@@ -289,12 +290,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/ghostbsd-vm@v1
|
- uses: vmactions/ghostbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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
|
||||||
usesh: true
|
usesh: true
|
||||||
@@ -345,12 +347,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/openbsd-vm@v1
|
- uses: vmactions/openbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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 libiconv
|
prepare: pkg_add socat curl libiconv
|
||||||
usesh: true
|
usesh: true
|
||||||
@@ -401,12 +404,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/netbsd-vm@v1
|
- uses: vmactions/netbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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: |
|
||||||
/usr/sbin/pkg_add curl socat
|
/usr/sbin/pkg_add curl socat
|
||||||
@@ -458,12 +462,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/dragonflybsd-vm@v1
|
- uses: vmactions/dragonflybsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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 libnghttp2
|
pkg install -y libnghttp2
|
||||||
@@ -519,12 +524,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/midnightbsd-vm@v1
|
- uses: vmactions/midnightbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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: mport install socat curl || true
|
prepare: mport install socat curl || true
|
||||||
usesh: true
|
usesh: true
|
||||||
@@ -576,12 +582,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/solaris-vm@v1
|
- uses: vmactions/solaris-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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}}'
|
||||||
sync: nfs
|
sync: nfs
|
||||||
prepare: |
|
prepare: |
|
||||||
@@ -635,12 +642,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/omnios-vm@v1
|
- uses: vmactions/omnios-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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}}'
|
||||||
sync: nfs
|
sync: nfs
|
||||||
prepare: pkg install socat
|
prepare: pkg install socat
|
||||||
@@ -691,12 +699,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/openindiana-vm@v1
|
- uses: vmactions/openindiana-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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}}'
|
||||||
sync: nfs
|
sync: nfs
|
||||||
prepare: pkg install socat
|
prepare: pkg install socat
|
||||||
@@ -747,12 +756,13 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/tribblix-vm@v1
|
- uses: vmactions/tribblix-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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}}'
|
||||||
sync: nfs
|
sync: nfs
|
||||||
prepare: zap install socat
|
prepare: zap install socat
|
||||||
@@ -803,19 +813,20 @@ jobs:
|
|||||||
TokenName4: ${{ secrets.TokenName4}}
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
TokenName5: ${{ secrets.TokenName5}}
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Clone acmetest
|
- name: Clone acmetest
|
||||||
run: cd .. && git clone --depth=1 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/
|
||||||
- uses: vmactions/haiku-vm@v1
|
- uses: vmactions/haiku-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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}}'
|
||||||
sync: rsync
|
sync: rsync
|
||||||
copyback: false
|
copyback: false
|
||||||
prepare: |
|
prepare: |
|
||||||
mkdir -p /boot/home/.cache
|
mkdir -p /boot/home/.cache
|
||||||
pkgman install -y cronie
|
pkgman install -y cronie
|
||||||
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||||
@@ -842,3 +853,236 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Hurd:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: Haiku
|
||||||
|
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
|
||||||
|
TokenName1: ${{ secrets.TokenName1}}
|
||||||
|
TokenName2: ${{ secrets.TokenName2}}
|
||||||
|
TokenName3: ${{ secrets.TokenName3}}
|
||||||
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Clone acmetest
|
||||||
|
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||||
|
- uses: vmactions/hurd-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
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}}'
|
||||||
|
sync: rsync
|
||||||
|
copyback: false
|
||||||
|
usesh: true
|
||||||
|
prepare: |
|
||||||
|
apt-get update -y
|
||||||
|
apt-get install -y curl cron
|
||||||
|
run: |
|
||||||
|
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||||
|
fi
|
||||||
|
cd ../acmetest
|
||||||
|
./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OpenEuler:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: Hurd
|
||||||
|
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
|
||||||
|
TokenName1: ${{ secrets.TokenName1}}
|
||||||
|
TokenName2: ${{ secrets.TokenName2}}
|
||||||
|
TokenName3: ${{ secrets.TokenName3}}
|
||||||
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Clone acmetest
|
||||||
|
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||||
|
- uses: vmactions/openeuler-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
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}}'
|
||||||
|
sync: rsync
|
||||||
|
copyback: false
|
||||||
|
usesh: true
|
||||||
|
prepare: dnf install -y curl socat cronie tar gzip
|
||||||
|
run: |
|
||||||
|
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||||
|
fi
|
||||||
|
cd ../acmetest
|
||||||
|
./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HardenedBSD:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: OpenEuler
|
||||||
|
env:
|
||||||
|
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||||
|
TestingDomain: ${{ secrets.TestingDomain }}
|
||||||
|
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||||
|
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||||
|
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||||
|
CASE: le_test_dnsapi
|
||||||
|
TEST_LOCAL: 1
|
||||||
|
DEBUG: ${{ secrets.DEBUG }}
|
||||||
|
http_proxy: ${{ secrets.http_proxy }}
|
||||||
|
https_proxy: ${{ secrets.https_proxy }}
|
||||||
|
TokenName1: ${{ secrets.TokenName1}}
|
||||||
|
TokenName2: ${{ secrets.TokenName2}}
|
||||||
|
TokenName3: ${{ secrets.TokenName3}}
|
||||||
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Clone acmetest
|
||||||
|
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||||
|
- uses: vmactions/hardenedbsd-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||||
|
prepare: pkg install -y socat curl
|
||||||
|
usesh: true
|
||||||
|
sync: nfs
|
||||||
|
run: |
|
||||||
|
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||||
|
fi
|
||||||
|
cd ../acmetest
|
||||||
|
./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OPNsense:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: HardenedBSD
|
||||||
|
env:
|
||||||
|
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||||
|
TestingDomain: ${{ secrets.TestingDomain }}
|
||||||
|
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||||
|
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||||
|
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||||
|
CASE: le_test_dnsapi
|
||||||
|
TEST_LOCAL: 1
|
||||||
|
DEBUG: ${{ secrets.DEBUG }}
|
||||||
|
http_proxy: ${{ secrets.http_proxy }}
|
||||||
|
https_proxy: ${{ secrets.https_proxy }}
|
||||||
|
TokenName1: ${{ secrets.TokenName1}}
|
||||||
|
TokenName2: ${{ secrets.TokenName2}}
|
||||||
|
TokenName3: ${{ secrets.TokenName3}}
|
||||||
|
TokenName4: ${{ secrets.TokenName4}}
|
||||||
|
TokenName5: ${{ secrets.TokenName5}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Clone acmetest
|
||||||
|
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||||
|
- uses: vmactions/opnsense-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
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}}'
|
||||||
|
#The dns-01 cases need no inbound port, so the appliance's web GUI can
|
||||||
|
#keep the 80 port here, unlike the standalone workflow.
|
||||||
|
prepare: pkg install -y socat curl
|
||||||
|
usesh: true
|
||||||
|
sync: nfs
|
||||||
|
run: |
|
||||||
|
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||||
|
fi
|
||||||
|
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||||
|
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||||
|
fi
|
||||||
|
cd ../acmetest
|
||||||
|
./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
|
|||||||
1
.github/workflows/DragonFlyBSD.yml
vendored
1
.github/workflows/DragonFlyBSD.yml
vendored
@@ -58,6 +58,7 @@ jobs:
|
|||||||
- uses: vmactions/dragonflybsd-vm@v1
|
- uses: vmactions/dragonflybsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/FreeBSD.yml
vendored
1
.github/workflows/FreeBSD.yml
vendored
@@ -64,6 +64,7 @@ jobs:
|
|||||||
- uses: vmactions/freebsd-vm@v1
|
- uses: vmactions/freebsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/GhostBSD.yml
vendored
1
.github/workflows/GhostBSD.yml
vendored
@@ -66,6 +66,7 @@ jobs:
|
|||||||
- uses: vmactions/ghostbsd-vm@v1
|
- uses: vmactions/ghostbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/Haiku.yml
vendored
1
.github/workflows/Haiku.yml
vendored
@@ -65,6 +65,7 @@ jobs:
|
|||||||
- uses: vmactions/haiku-vm@v1
|
- uses: vmactions/haiku-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
76
.github/workflows/HardenedBSD.yml
vendored
Normal file
76
.github/workflows/HardenedBSD.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
name: HardenedBSD
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/HardenedBSD.yml'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/HardenedBSD.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
HardenedBSD:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||||
|
CA_ECDSA: ""
|
||||||
|
CA: ""
|
||||||
|
CA_EMAIL: ""
|
||||||
|
TEST_PREFERRED_CHAIN: (STAGING)
|
||||||
|
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||||
|
CA_ECDSA: ""
|
||||||
|
CA: ""
|
||||||
|
CA_EMAIL: ""
|
||||||
|
TEST_PREFERRED_CHAIN: (STAGING)
|
||||||
|
ACME_USE_WGET: 1
|
||||||
|
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@v7
|
||||||
|
- uses: anyvm-org/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/hardenedbsd-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||||
|
nat: |
|
||||||
|
"8080": "80"
|
||||||
|
prepare: pkg install -y socat curl wget
|
||||||
|
usesh: true
|
||||||
|
sync: nfs
|
||||||
|
run: |
|
||||||
|
cd ../acmetest \
|
||||||
|
&& ./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
76
.github/workflows/Hurd.yml
vendored
Normal file
76
.github/workflows/Hurd.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
name: Hurd
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/Hurd.yml'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/Hurd.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Hurd:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||||
|
CA_ECDSA: ""
|
||||||
|
CA: ""
|
||||||
|
CA_EMAIL: ""
|
||||||
|
TEST_PREFERRED_CHAIN: (STAGING)
|
||||||
|
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 }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: anyvm-org/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/hurd-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
|
||||||
|
nat: |
|
||||||
|
"8080": "80"
|
||||||
|
# Do NOT install socat: socat's SYSTEM: address is broken on GNU Hurd
|
||||||
|
# (the child shell output goes to socat's stdout instead of the socket,
|
||||||
|
# so clients get an empty reply). Without socat, acme.sh standalone
|
||||||
|
# mode falls back to its python3 server, which works on Hurd.
|
||||||
|
prepare: |
|
||||||
|
apt-get update -y
|
||||||
|
apt-get install -y curl cron
|
||||||
|
usesh: true
|
||||||
|
sync: rsync
|
||||||
|
copyback: false
|
||||||
|
run: |
|
||||||
|
cd ../acmetest \
|
||||||
|
&& ./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
1
.github/workflows/MidnightBSD.yml
vendored
1
.github/workflows/MidnightBSD.yml
vendored
@@ -58,6 +58,7 @@ jobs:
|
|||||||
- uses: vmactions/midnightbsd-vm@v1
|
- uses: vmactions/midnightbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/NetBSD.yml
vendored
1
.github/workflows/NetBSD.yml
vendored
@@ -58,6 +58,7 @@ jobs:
|
|||||||
- uses: vmactions/netbsd-vm@v1
|
- uses: vmactions/netbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
86
.github/workflows/OPNsense.yml
vendored
Normal file
86
.github/workflows/OPNsense.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
name: OPNsense
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/OPNsense.yml'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/OPNsense.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
OPNsense:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||||
|
CA_ECDSA: ""
|
||||||
|
CA: ""
|
||||||
|
CA_EMAIL: ""
|
||||||
|
TEST_PREFERRED_CHAIN: (STAGING)
|
||||||
|
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||||
|
CA_ECDSA: ""
|
||||||
|
CA: ""
|
||||||
|
CA_EMAIL: ""
|
||||||
|
TEST_PREFERRED_CHAIN: (STAGING)
|
||||||
|
ACME_USE_WGET: 1
|
||||||
|
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@v7
|
||||||
|
- uses: anyvm-org/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/opnsense-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||||
|
nat: |
|
||||||
|
"8080": "80"
|
||||||
|
prepare: pkg install -y socat curl wget
|
||||||
|
usesh: true
|
||||||
|
sync: nfs
|
||||||
|
run: |
|
||||||
|
#OPNsense is a firewall appliance whose web GUI holds the 80 port,
|
||||||
|
#where every --standalone case listens. configd has no "stop"
|
||||||
|
#action for it and the rc script cannot stop it either, so kill it.
|
||||||
|
#This belongs here and not in prepare: prepare runs before the
|
||||||
|
#cache-after-prepare reboot, which would bring the GUI back. And do
|
||||||
|
#NOT free the port by disabling the GUI's http redirect in
|
||||||
|
#config.xml: pf's automatic pass rule for the 80 port is generated
|
||||||
|
#from the web GUI settings, so dropping the redirect also drops the
|
||||||
|
#rule on the next boot, and the inbound challenge is filtered.
|
||||||
|
pkill lighttpd || true
|
||||||
|
cd ../acmetest \
|
||||||
|
&& ./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
1
.github/workflows/Omnios.yml
vendored
1
.github/workflows/Omnios.yml
vendored
@@ -64,6 +64,7 @@ jobs:
|
|||||||
- uses: vmactions/omnios-vm@v1
|
- uses: vmactions/omnios-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/OpenBSD.yml
vendored
1
.github/workflows/OpenBSD.yml
vendored
@@ -64,6 +64,7 @@ jobs:
|
|||||||
- uses: vmactions/openbsd-vm@v1
|
- uses: vmactions/openbsd-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
78
.github/workflows/OpenEuler.yml
vendored
Normal file
78
.github/workflows/OpenEuler.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: OpenEuler
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/OpenEuler.yml'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
paths:
|
||||||
|
- '*.sh'
|
||||||
|
- '.github/workflows/OpenEuler.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
OpenEuler:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||||
|
CA_ECDSA: ""
|
||||||
|
CA: ""
|
||||||
|
CA_EMAIL: ""
|
||||||
|
TEST_PREFERRED_CHAIN: (STAGING)
|
||||||
|
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 }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: anyvm-org/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/openeuler-vm@v1
|
||||||
|
with:
|
||||||
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
|
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
|
||||||
|
nat: |
|
||||||
|
"8080": "80"
|
||||||
|
prepare: |
|
||||||
|
# openEuler ships every repo with both a baseurl and a metalink.
|
||||||
|
# The metalink mirror pool is split-brain (dl-cdn.openeuler.openatom.cn
|
||||||
|
# froze at the 2026-08-20 snapshot while repo.openeuler.org moved on),
|
||||||
|
# so dnf takes repomd.xml from the stale mirror and then 404s fetching
|
||||||
|
# the checksummed metadata it names from the fresh ones. Keep only the
|
||||||
|
# vendor baseurl, which is self-consistent.
|
||||||
|
sed -i '/^metalink=/d' /etc/yum.repos.d/*.repo
|
||||||
|
dnf install -y curl socat cronie tar gzip
|
||||||
|
usesh: true
|
||||||
|
sync: rsync
|
||||||
|
copyback: false
|
||||||
|
run: |
|
||||||
|
cd ../acmetest \
|
||||||
|
&& ./letest.sh
|
||||||
|
- name: DebugOnError
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: |
|
||||||
|
echo "See how to debug in VM:"
|
||||||
|
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||||
1
.github/workflows/OpenIndiana.yml
vendored
1
.github/workflows/OpenIndiana.yml
vendored
@@ -64,6 +64,7 @@ jobs:
|
|||||||
- uses: vmactions/openindiana-vm@v1
|
- uses: vmactions/openindiana-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/Solaris.yml
vendored
1
.github/workflows/Solaris.yml
vendored
@@ -64,6 +64,7 @@ jobs:
|
|||||||
- uses: vmactions/solaris-vm@v1
|
- uses: vmactions/solaris-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
1
.github/workflows/Tribblix.yml
vendored
1
.github/workflows/Tribblix.yml
vendored
@@ -64,6 +64,7 @@ jobs:
|
|||||||
- uses: vmactions/tribblix-vm@v1
|
- uses: vmactions/tribblix-vm@v1
|
||||||
with:
|
with:
|
||||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||||
|
cache-after-prepare: true
|
||||||
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"
|
||||||
|
|||||||
44
.github/workflows/vtag.yml
vendored
Normal file
44
.github/workflows/vtag.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Mirror version tag
|
||||||
|
|
||||||
|
# Historical release tags are plain version numbers ("3.1.3") and cannot be
|
||||||
|
# renamed. When a plain version tag is pushed (including the tag created by
|
||||||
|
# publishing a GitHub release), mirror it as a "v"-prefixed tag ("v3.1.3")
|
||||||
|
# pointing to the same object, so both forms exist.
|
||||||
|
# No retrigger loop: the tag filter never matches a "v"-prefixed tag, and
|
||||||
|
# refs created with GITHUB_TOKEN do not fire workflows anyway.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '[0-9]*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
vtag:
|
||||||
|
if: github.repository == 'acmesh-official/acme.sh'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create the v-prefixed tag
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
if gh api "repos/$REPO/git/ref/tags/v$TAG" >/dev/null 2>&1; then
|
||||||
|
echo "Tag v$TAG already exists, nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Mirror the object the pushed tag actually points at: the commit
|
||||||
|
# for a lightweight tag, the tag object itself for an annotated or
|
||||||
|
# signed one. Pointing the mirror at the commit would strip the
|
||||||
|
# signature, so "git verify-tag v3.1.3" would fail while
|
||||||
|
# "git verify-tag 3.1.3" succeeds.
|
||||||
|
sha="$(gh api "repos/$REPO/git/ref/tags/$TAG" --jq .object.sha)"
|
||||||
|
if [ -z "$sha" ] || [ "$sha" = "null" ]; then
|
||||||
|
echo "Could not resolve refs/tags/$TAG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gh api "repos/$REPO/git/refs" -f ref="refs/tags/v$TAG" -f sha="$sha"
|
||||||
|
echo "Created tag v$TAG -> $sha"
|
||||||
33
README.md
33
README.md
@@ -36,6 +36,10 @@
|
|||||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg" alt="OpenIndiana"></a>
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg" alt="OpenIndiana"></a>
|
||||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg" alt="Tribblix"></a>
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg" alt="Tribblix"></a>
|
||||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg" alt="Haiku"></a>
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg" alt="Haiku"></a>
|
||||||
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml/badge.svg" alt="Hurd"></a>
|
||||||
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml/badge.svg" alt="OpenEuler"></a>
|
||||||
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/HardenedBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/HardenedBSD.yml/badge.svg" alt="HardenedBSD"></a>
|
||||||
|
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OPNsense.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OPNsense.yml/badge.svg" alt="OPNsense"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -130,6 +134,10 @@
|
|||||||
|25|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
|
|25|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
|
||||||
|26|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix
|
|26|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix
|
||||||
|27|[](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD
|
|27|[](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD
|
||||||
|
|28|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml)|GNU Hurd
|
||||||
|
|29|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml)|openEuler
|
||||||
|
|30|[](https://github.com/acmesh-official/acme.sh/actions/workflows/HardenedBSD.yml)|HardenedBSD
|
||||||
|
|31|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OPNsense.yml)|OPNsense
|
||||||
|
|
||||||
|
|
||||||
> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)
|
> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)
|
||||||
@@ -223,6 +231,31 @@ Cron entry example:
|
|||||||
acme.sh -h
|
acme.sh -h
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 🔏 Verify a Release
|
||||||
|
|
||||||
|
Release tags from `3.1.5` on are signed with the maintainer's SSH key. The
|
||||||
|
signing happens on the maintainer's machine, so the private key is never
|
||||||
|
available to CI. The public half is [`allowed_signers`](allowed_signers) in
|
||||||
|
this repository. From a clone:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config gpg.ssh.allowedSignersFile allowed_signers
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git verify-tag 3.1.5
|
||||||
|
```
|
||||||
|
|
||||||
|
The signature covers the tag object, which pins the commit and therefore the
|
||||||
|
whole tree, so a good signature verifies every file at that release and no
|
||||||
|
separate tarball checksum is needed. Build a tarball from the verified tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git archive --format=tar.gz --prefix=acme.sh-3.1.5/ 3.1.5 > acme.sh-3.1.5.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ Tags up to `3.1.4` predate the signing key and are unsigned.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2️⃣ Issue a Certificate
|
### 2️⃣ Issue a Certificate
|
||||||
|
|||||||
20
allowed_signers
Normal file
20
allowed_signers
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# acme.sh release signing key.
|
||||||
|
#
|
||||||
|
# Release tags are signed with this key. Its private half is held by the
|
||||||
|
# maintainer and is never available to CI, so a compromise of the build
|
||||||
|
# pipeline cannot produce a tag that verifies against this file.
|
||||||
|
#
|
||||||
|
# Fingerprint: SHA256:M60qVafm/NUywQHXAkoQcj2v6KgkfrdSXv6mPejUUeE
|
||||||
|
#
|
||||||
|
# To verify a release tag, from a clone of this repository:
|
||||||
|
#
|
||||||
|
# git config gpg.ssh.allowedSignersFile allowed_signers
|
||||||
|
# git verify-tag 3.1.5
|
||||||
|
#
|
||||||
|
# A good signature covers the tag object, which pins the commit, which pins
|
||||||
|
# the whole tree -- so verifying the tag verifies every file at that
|
||||||
|
# release. Build a tarball from the verified tag with:
|
||||||
|
#
|
||||||
|
# git archive --format=tar.gz --prefix=acme.sh-3.1.5/ 3.1.5 > acme.sh-3.1.5.tar.gz
|
||||||
|
#
|
||||||
|
github@neilpang.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBTjI0HBJn3uhfT2DsNcFybfAZi3ADbIacMpz1BItKdB
|
||||||
@@ -163,8 +163,8 @@ byteplus_alb_deploy() {
|
|||||||
# ── 3. Read cert and key ─────────────────────────────────────────────────────
|
# ── 3. Read cert and key ─────────────────────────────────────────────────────
|
||||||
# BytePlus requires NO blank lines between PEM blocks in the certificate chain
|
# BytePlus requires NO blank lines between PEM blocks in the certificate chain
|
||||||
|
|
||||||
_public_key=$(sed '/^[[:space:]]*$/d' "$_cfullchain" | tr -d '\r')
|
_public_key=$(_strip_blank_lines <"$_cfullchain" | tr -d '\r')
|
||||||
_private_key=$(sed '/^[[:space:]]*$/d' "$_ckey" | tr -d '\r')
|
_private_key=$(_strip_blank_lines <"$_ckey" | tr -d '\r')
|
||||||
|
|
||||||
if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then
|
if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then
|
||||||
_err "Failed to read certificate or key file."
|
_err "Failed to read certificate or key file."
|
||||||
|
|||||||
@@ -87,7 +87,11 @@ cpanel_uapi_deploy() {
|
|||||||
# Auto mode
|
# Auto mode
|
||||||
if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then
|
if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then
|
||||||
# call API for site config
|
# call API for site config
|
||||||
_response=$(uapi DomainInfo list_domains)
|
if [ -n "$_uapi_user" ]; then
|
||||||
|
_response=$(uapi --user="$_uapi_user" DomainInfo list_domains)
|
||||||
|
else
|
||||||
|
_response=$(uapi DomainInfo list_domains)
|
||||||
|
fi
|
||||||
# exit if error in response
|
# exit if error in response
|
||||||
if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
|
if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
|
||||||
_err "Error in deploying certificate - cannot retrieve sitelist:"
|
_err "Error in deploying certificate - cannot retrieve sitelist:"
|
||||||
|
|||||||
114
deploy/ikuai.sh
Normal file
114
deploy/ikuai.sh
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Here is a script to deploy cert to ikuai using curl
|
||||||
|
#
|
||||||
|
# it requires following environment variables:
|
||||||
|
#
|
||||||
|
# IKUAI_SCHEME="http" - http or https , defaults to "http"
|
||||||
|
# IKUAI_HOSTNAME="localhost" - host , defaults to "192.168.9.1"
|
||||||
|
# IKUAI_PORT="80" - port , defaults to "80"
|
||||||
|
# IKUAI_USERNAME="admin" - username , defaults to "admin"
|
||||||
|
# IKUAI_PASSWORD="yourPassword" - password
|
||||||
|
# IKUAI_CERT_ID=1 - ikuai cert id , defaults to 1, and only 1 is supported for now !!!
|
||||||
|
#
|
||||||
|
#returns 0 means success, otherwise error.
|
||||||
|
#
|
||||||
|
######## Public functions #####################
|
||||||
|
#
|
||||||
|
#domain keyfile certfile cafile fullchain
|
||||||
|
ikuai_deploy() {
|
||||||
|
_cdomain="$1"
|
||||||
|
_ckey="$2"
|
||||||
|
_ccert="$3"
|
||||||
|
_cca="$4"
|
||||||
|
_cfullchain="$5"
|
||||||
|
|
||||||
|
_debug _cdomain "$_cdomain"
|
||||||
|
_debug _ckey "$_ckey"
|
||||||
|
_debug _ccert "$_ccert"
|
||||||
|
_debug _cca "$_cca"
|
||||||
|
_debug _cfullchain "$_cfullchain"
|
||||||
|
|
||||||
|
# Get deploy conf
|
||||||
|
_getdeployconf IKUAI_SCHEME
|
||||||
|
_getdeployconf IKUAI_HOSTNAME
|
||||||
|
_getdeployconf IKUAI_PORT
|
||||||
|
_getdeployconf IKUAI_USERNAME
|
||||||
|
_getdeployconf IKUAI_PASSWORD
|
||||||
|
_getdeployconf IKUAI_CERT_ID
|
||||||
|
|
||||||
|
# Use default if not provided
|
||||||
|
[ -n "$IKUAI_SCHEME" ] || IKUAI_SCHEME="http"
|
||||||
|
[ -n "$IKUAI_HOSTNAME" ] || IKUAI_HOSTNAME="192.168.9.1"
|
||||||
|
[ -n "$IKUAI_PORT" ] || IKUAI_PORT=80
|
||||||
|
[ -n "$IKUAI_USERNAME" ] || IKUAI_USERNAME="admin"
|
||||||
|
[ -n "$IKUAI_CERT_ID" ] || IKUAI_CERT_ID=1
|
||||||
|
|
||||||
|
if [ -z "$IKUAI_PASSWORD" ]; then
|
||||||
|
_err "please define IKUAI_PASSWORD."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug2 IKUAI_SCHEME "$IKUAI_SCHEME"
|
||||||
|
_debug2 IKUAI_HOSTNAME "$IKUAI_HOSTNAME"
|
||||||
|
_debug2 IKUAI_PORT "$IKUAI_PORT"
|
||||||
|
_debug2 IKUAI_USERNAME "$IKUAI_USERNAME"
|
||||||
|
_secure_debug2 IKUAI_PASSWORD "$IKUAI_PASSWORD"
|
||||||
|
|
||||||
|
_info "Login to ikuai ..."
|
||||||
|
_ikuai_url="$IKUAI_SCHEME://$IKUAI_HOSTNAME:$IKUAI_PORT"
|
||||||
|
_pass_md5="$(printf "%s" "$IKUAI_PASSWORD" | _digest md5 hex | _lower_case)"
|
||||||
|
_pass_salt="$(printf "salt_11%s" "$IKUAI_PASSWORD" | _base64)"
|
||||||
|
_debug2 _ikuai_url "$_ikuai_url"
|
||||||
|
|
||||||
|
_login_req="{\"username\":\"$IKUAI_USERNAME\",\"passwd\":\"$_pass_md5\",\"pass\":\"$_pass_salt\",\"remember_password\":\"\"}"
|
||||||
|
_response=$(_post "$_login_req" "$_ikuai_url/Action/login" "" "POST" "application/json")
|
||||||
|
|
||||||
|
_err_msg="$(printf "%s" "$_response" | _normalizeJson | _egrep_o '"ErrMsg":"[^"]*"' | cut -d'"' -f 4)"
|
||||||
|
# check ErrMsg
|
||||||
|
if [ "$_err_msg" != "Success" ]; then
|
||||||
|
_err "Failed to login to ikuai: $_err_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# check cookie
|
||||||
|
_cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | sed 's/;.*//')"
|
||||||
|
if [ -z "$_cookie" ]; then
|
||||||
|
_err "Fail to get the cookie."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set cookie header
|
||||||
|
_H1="Cookie: $_cookie; username=$IKUAI_USERNAME; login=1"
|
||||||
|
|
||||||
|
_info "Deploy the cert to ikuai ... "
|
||||||
|
|
||||||
|
# Should replace \n to @ ," " to #
|
||||||
|
_cert_content_single_line="$(tr <"$_cfullchain" '\n' '@' | tr ' ' '#')"
|
||||||
|
_key_content_single_line="$(tr <"$_ckey" '\n' '@' | tr ' ' '#')"
|
||||||
|
|
||||||
|
_debug2 _cert_content_single_line "$_cert_content_single_line"
|
||||||
|
_secure_debug2 _key_content_single_line "$_key_content_single_line"
|
||||||
|
|
||||||
|
_key_manager_req="{\"func_name\":\"key_manager\",\"action\":\"save\",\"param\":{\"ca\":\"$_cert_content_single_line\",\"key\":\"$_key_content_single_line\",\"id\":$IKUAI_CERT_ID,\"enabled\":\"yes\",\"comment\":\"\"}}"
|
||||||
|
_response=$(_post "$_key_manager_req" "$_ikuai_url/Action/call" "" "POST" "application/json")
|
||||||
|
|
||||||
|
_err_msg="$(printf "%s" "$_response" | _normalizeJson | _egrep_o '"ErrMsg":"[^"]*"' | cut -d'"' -f 4)"
|
||||||
|
# check ErrMsg
|
||||||
|
if [ "$_err_msg" != "Success" ]; then
|
||||||
|
_err "Failed to deploy the cert to ikuai: $_err_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "Save the deploy config ... "
|
||||||
|
# Save the config
|
||||||
|
_savedeployconf IKUAI_SCHEME "$IKUAI_SCHEME"
|
||||||
|
_savedeployconf IKUAI_HOSTNAME "$IKUAI_HOSTNAME"
|
||||||
|
_savedeployconf IKUAI_PORT "$IKUAI_PORT"
|
||||||
|
_savedeployconf IKUAI_USERNAME "$IKUAI_USERNAME"
|
||||||
|
_savedeployconf IKUAI_PASSWORD "$IKUAI_PASSWORD"
|
||||||
|
_savedeployconf IKUAI_CERT_ID "$IKUAI_CERT_ID"
|
||||||
|
|
||||||
|
_info "Successfully deployed certificate to ikuai. Enjoy! :>"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -83,7 +83,7 @@ keyhelp_deploy() {
|
|||||||
_request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca"
|
_request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca"
|
||||||
_H1="Cookie: $_cookie"
|
_H1="Cookie: $_cookie"
|
||||||
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST")
|
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST")
|
||||||
_message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
_message=$(echo "$_response" | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
||||||
_info "_message" "$_message"
|
_info "_message" "$_message"
|
||||||
if [ -z "$_message" ]; then
|
if [ -z "$_message" ]; then
|
||||||
_err "Fail to upload certificate."
|
_err "Fail to upload certificate."
|
||||||
@@ -118,7 +118,7 @@ keyhelp_deploy() {
|
|||||||
|
|
||||||
_request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS"
|
_request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS"
|
||||||
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST")
|
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST")
|
||||||
_message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
_message=$(echo "$_response" | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
||||||
_info "_message" "$_message"
|
_info "_message" "$_message"
|
||||||
if [ -z "$_message" ]; then
|
if [ -z "$_message" ]; then
|
||||||
_err "Fail to apply certificate."
|
_err "Fail to apply certificate."
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
# Usage (shown values are the examples):
|
# Usage (shown values are the examples):
|
||||||
# 1. Set optional environment variables
|
# 1. Set optional environment variables
|
||||||
# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set"
|
# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set"
|
||||||
|
# A name without a leading '/' is looked up in the certificate directory
|
||||||
|
# of the domain. An absolute path is used as is, so a single deploy file
|
||||||
|
# can be shared by all domains, e.g.
|
||||||
|
# - export MULTIDEPLOY_FILENAME="/etc/acme/multideploy.yml"
|
||||||
#
|
#
|
||||||
# 2. Run command:
|
# 2. Run command:
|
||||||
# acme.sh --deploy --deploy-hook multideploy -d example.com
|
# acme.sh --deploy --deploy-hook multideploy -d example.com
|
||||||
@@ -49,7 +53,7 @@ multideploy_deploy() {
|
|||||||
_debug _cfullchain "$_cfullchain"
|
_debug _cfullchain "$_cfullchain"
|
||||||
_debug _cpfx "$_cpfx"
|
_debug _cpfx "$_cpfx"
|
||||||
|
|
||||||
MULTIDEPLOY_FILENAME="${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}"
|
_getdeployconf MULTIDEPLOY_FILENAME
|
||||||
if [ -z "$MULTIDEPLOY_FILENAME" ]; then
|
if [ -z "$MULTIDEPLOY_FILENAME" ]; then
|
||||||
MULTIDEPLOY_FILENAME="multideploy.yml"
|
MULTIDEPLOY_FILENAME="multideploy.yml"
|
||||||
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
|
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
|
||||||
@@ -75,7 +79,8 @@ multideploy_deploy() {
|
|||||||
# This function preprocesses the deploy file by checking if 'yq' is installed,
|
# This function preprocesses the deploy file by checking if 'yq' is installed,
|
||||||
# verifying the existence of the deploy file, and ensuring only one deploy file is present.
|
# verifying the existence of the deploy file, and ensuring only one deploy file is present.
|
||||||
# Arguments:
|
# Arguments:
|
||||||
# $@ - Posible deploy file names.
|
# $@ - Posible deploy file names. A name starting with '/' is treated as an
|
||||||
|
# absolute path, any other name is relative to the domain directory.
|
||||||
# Usage:
|
# Usage:
|
||||||
# _preprocess_deployfile "<deploy_file1>" "<deploy_file2>?"
|
# _preprocess_deployfile "<deploy_file1>" "<deploy_file2>?"
|
||||||
_preprocess_deployfile() {
|
_preprocess_deployfile() {
|
||||||
@@ -87,15 +92,21 @@ _preprocess_deployfile() {
|
|||||||
_debug3 "yq is installed."
|
_debug3 "yq is installed."
|
||||||
|
|
||||||
# Check if deploy file exists
|
# Check if deploy file exists
|
||||||
|
found_file=""
|
||||||
for file in "$@"; do
|
for file in "$@"; do
|
||||||
_debug3 "Checking file" "$DOMAIN_PATH/$file"
|
if _startswith "$file" "/"; then
|
||||||
if [ -f "$DOMAIN_PATH/$file" ]; then
|
_multideploy_path="$file"
|
||||||
|
else
|
||||||
|
_multideploy_path="$DOMAIN_PATH/$file"
|
||||||
|
fi
|
||||||
|
_debug3 "Checking file" "$_multideploy_path"
|
||||||
|
if [ -f "$_multideploy_path" ]; then
|
||||||
_debug3 "File found"
|
_debug3 "File found"
|
||||||
if [ -n "$found_file" ]; then
|
if [ -n "$found_file" ]; then
|
||||||
_err "Multiple deploy files found. Please keep only one deploy file."
|
_err "Multiple deploy files found. Please keep only one deploy file."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
found_file="$file"
|
found_file="$_multideploy_path"
|
||||||
else
|
else
|
||||||
_debug3 "File not found"
|
_debug3 "File not found"
|
||||||
fi
|
fi
|
||||||
@@ -105,12 +116,12 @@ _preprocess_deployfile() {
|
|||||||
_err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one."
|
_err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if ! _check_deployfile "$DOMAIN_PATH/$found_file"; then
|
if ! _check_deployfile "$found_file"; then
|
||||||
_err "Deploy file is not valid: $DOMAIN_PATH/$found_file"
|
_err "Deploy file is not valid: $found_file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$DOMAIN_PATH/$found_file"
|
echo "$found_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Description:
|
# Description:
|
||||||
|
|||||||
@@ -116,17 +116,24 @@ HEREDOC
|
|||||||
export HTTPS_INSECURE=1
|
export HTTPS_INSECURE=1
|
||||||
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}"
|
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}"
|
||||||
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
|
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
|
||||||
|
_retval=$?
|
||||||
|
# The API errors out with a non-2xx HTTP status and an empty body,
|
||||||
|
# so the status line is checked too, not only the response body.
|
||||||
|
_status_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||||
|
_debug2 "HTTP status" "$_status_code"
|
||||||
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
||||||
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||||
_retval=$?
|
case "$_status_code" in
|
||||||
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
|
2[0-9][0-9])
|
||||||
_debug3 response "$response"
|
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
|
||||||
_info "Certificate successfully deployed"
|
_debug3 response "$response"
|
||||||
return 0
|
_info "Certificate successfully deployed"
|
||||||
else
|
return 0
|
||||||
_err "Certificate deployment failed: $message"
|
fi
|
||||||
_debug "Response" "$response"
|
;;
|
||||||
return 1
|
esac
|
||||||
fi
|
_err "Certificate deployment failed (HTTP status $_status_code). $message"
|
||||||
|
_debug "Response" "$response"
|
||||||
|
return 1
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,17 +128,24 @@ HEREDOC
|
|||||||
export HTTPS_INSECURE=1
|
export HTTPS_INSECURE=1
|
||||||
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
|
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
|
||||||
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
|
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
|
||||||
|
_retval=$?
|
||||||
|
# The API errors out with a non-2xx HTTP status and an empty body,
|
||||||
|
# so the status line is checked too, not only the response body.
|
||||||
|
_status_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||||
|
_debug2 "HTTP status" "$_status_code"
|
||||||
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
||||||
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||||
_retval=$?
|
case "$_status_code" in
|
||||||
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
|
2[0-9][0-9])
|
||||||
_debug3 response "$response"
|
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
|
||||||
_info "Certificate successfully deployed"
|
_debug3 response "$response"
|
||||||
return 0
|
_info "Certificate successfully deployed"
|
||||||
else
|
return 0
|
||||||
_err "Certificate deployment failed: $message"
|
fi
|
||||||
_debug "Response" "$response"
|
;;
|
||||||
return 1
|
esac
|
||||||
fi
|
_err "Certificate deployment failed (HTTP status $_status_code). $message"
|
||||||
|
_debug "Response" "$response"
|
||||||
|
return 1
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
280
deploy/shelly.sh
Normal file
280
deploy/shelly.sh
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Here is a script to deploy cert to a Shelly Gen3+ device.
|
||||||
|
# Deploy the HTTPS server certificate to a Shelly device on the local network.
|
||||||
|
#
|
||||||
|
# ```sh
|
||||||
|
# export SHELLY_HOST=192.168.1.100
|
||||||
|
# export SHELLY_PASSWORD=mysecret # only if auth is enabled on the device
|
||||||
|
# acme.sh --deploy -d shelly.example.com --deploy-hook shelly
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Environment variables:
|
||||||
|
# SHELLY_HOST (required) IP or hostname of the Shelly device
|
||||||
|
# SHELLY_PASSWORD (optional) Admin password for digest authentication.
|
||||||
|
# Omit if auth is disabled on the device.
|
||||||
|
# SHELLY_USER (optional) Username for auth. Default: admin
|
||||||
|
# SHELLY_REBOOT (optional) Set to "0" to skip auto-reboot.
|
||||||
|
# Default: 1 (reboot after upload)
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Shelly Gen3+ device (Gen4 recommended)
|
||||||
|
# - Firmware 2.0.0+ for HTTPS server certificate support
|
||||||
|
# - curl or wget
|
||||||
|
# - openssl (for SHA-256 digest and random cnonce)
|
||||||
|
#
|
||||||
|
# The device must be reachable via HTTP on the local network.
|
||||||
|
# The hook uploads the fullchain.pem and private key,
|
||||||
|
# then reboots the device to apply the new certificate.
|
||||||
|
#
|
||||||
|
# Authentication uses standard RFC 7616 HTTP Digest (SHA-256) since
|
||||||
|
# firmware 2.0.0. The JSON-RPC auth object is not used for HTTP transport.
|
||||||
|
#
|
||||||
|
# returns 0 means success, otherwise error.
|
||||||
|
|
||||||
|
######## Public functions #####################
|
||||||
|
|
||||||
|
#domain keyfile certfile cafile fullchain
|
||||||
|
shelly_deploy() {
|
||||||
|
_cdomain="$1"
|
||||||
|
_ckey="$2"
|
||||||
|
_ccert="$3"
|
||||||
|
_cca="$4"
|
||||||
|
_cfullchain="$5"
|
||||||
|
|
||||||
|
_debug _cdomain "$_cdomain"
|
||||||
|
_debug _ckey "$_ckey"
|
||||||
|
_debug _ccert "$_ccert"
|
||||||
|
_debug _cca "$_cca"
|
||||||
|
_debug _cfullchain "$_cfullchain"
|
||||||
|
|
||||||
|
_getdeployconf SHELLY_HOST
|
||||||
|
_getdeployconf SHELLY_PASSWORD
|
||||||
|
_getdeployconf SHELLY_USER
|
||||||
|
_getdeployconf SHELLY_REBOOT
|
||||||
|
|
||||||
|
_debug SHELLY_HOST "$SHELLY_HOST"
|
||||||
|
_debug SHELLY_USER "$SHELLY_USER"
|
||||||
|
_secure_debug SHELLY_PASSWORD "$SHELLY_PASSWORD"
|
||||||
|
_debug SHELLY_REBOOT "$SHELLY_REBOOT"
|
||||||
|
|
||||||
|
if [ -z "$SHELLY_HOST" ]; then
|
||||||
|
_err "SHELLY_HOST is required. Please set the IP or hostname of your Shelly device."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHELLY_USER="${SHELLY_USER:-admin}"
|
||||||
|
SHELLY_REBOOT="${SHELLY_REBOOT:-1}"
|
||||||
|
|
||||||
|
_savedeployconf SHELLY_HOST "$SHELLY_HOST"
|
||||||
|
_savedeployconf SHELLY_PASSWORD "$SHELLY_PASSWORD"
|
||||||
|
_savedeployconf SHELLY_USER "$SHELLY_USER"
|
||||||
|
_savedeployconf SHELLY_REBOOT "$SHELLY_REBOOT"
|
||||||
|
|
||||||
|
# --- Auth handshake (only if password is set) ---
|
||||||
|
_shelly_auth_header=""
|
||||||
|
if [ -n "$SHELLY_PASSWORD" ]; then
|
||||||
|
_info "Authenticating to Shelly device at $SHELLY_HOST"
|
||||||
|
if ! _shelly_handshake; then
|
||||||
|
_err "Authentication handshake failed. Check SHELLY_PASSWORD and device accessibility."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_info "Authentication successful"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Upload certificate ---
|
||||||
|
_info "Uploading certificate to Shelly device at $SHELLY_HOST"
|
||||||
|
if ! _shelly_upload_cert; then
|
||||||
|
_err "Certificate upload failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Upload key ---
|
||||||
|
_info "Uploading private key to Shelly device"
|
||||||
|
if ! _shelly_upload_key; then
|
||||||
|
_err "Private key upload failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "Certificate and key uploaded successfully"
|
||||||
|
|
||||||
|
# --- Reboot ---
|
||||||
|
if [ "$SHELLY_REBOOT" != "0" ]; then
|
||||||
|
_info "Rebooting Shelly device to apply certificate"
|
||||||
|
# Reboot may close the connection before sending a response
|
||||||
|
_shelly_rpc "Shelly.Reboot" '{}' || _debug "Reboot may have closed connection (expected)"
|
||||||
|
_info "Reboot command sent. Device will restart shortly."
|
||||||
|
else
|
||||||
|
_info "Skipping reboot (SHELLY_REBOOT=0). Certificate will apply on next restart."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear auth header so it does not leak to other hooks
|
||||||
|
export _H1=""
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Helper functions ---
|
||||||
|
|
||||||
|
# Perform RFC 7616 HTTP Digest auth handshake.
|
||||||
|
# Sets _shelly_auth_header on success (the Authorization header value).
|
||||||
|
_shelly_handshake() {
|
||||||
|
_inithttp
|
||||||
|
|
||||||
|
_debug "Probing device for auth challenge"
|
||||||
|
|
||||||
|
# Use a protected method (Shelly.GetStatus) to trigger 401.
|
||||||
|
# Shelly.GetDeviceInfo is excluded from auth and would miss the challenge.
|
||||||
|
_post '{"id":1,"method":"Shelly.GetStatus"}' \
|
||||||
|
"http://${SHELLY_HOST}/rpc" "" "" "application/json"
|
||||||
|
|
||||||
|
# Detect auth from HTTP status line rather than response body
|
||||||
|
if ! _shelly_has_auth_challenge "$HTTP_HEADER"; then
|
||||||
|
# No auth challenge — device accepted the request without credentials
|
||||||
|
_debug "Device responded without auth challenge. Proceeding without auth."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_shelly_realm="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*realm="//;s/".*//')"
|
||||||
|
_shelly_nonce="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*nonce="//;s/".*//')"
|
||||||
|
_shelly_qop="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*qop="//;s/".*//')"
|
||||||
|
|
||||||
|
if [ -z "$_shelly_nonce" ]; then
|
||||||
|
_err "Failed to extract nonce from WWW-Authenticate header. Is SHELLY_PASSWORD correct?"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_shelly_qop="${_shelly_qop:-auth}"
|
||||||
|
|
||||||
|
_debug "Shelly realm: $_shelly_realm"
|
||||||
|
_debug "Shelly qop: $_shelly_qop"
|
||||||
|
_secure_debug "Shelly nonce" "$_shelly_nonce"
|
||||||
|
|
||||||
|
# ha1 = SHA256(username:realm:password)
|
||||||
|
_shelly_ha1="$(printf '%s' "${SHELLY_USER}:${_shelly_realm}:${SHELLY_PASSWORD}" | _digest sha256 hex)"
|
||||||
|
_secure_debug "Shelly ha1" "$_shelly_ha1"
|
||||||
|
|
||||||
|
# Generate client nonce (openssl is required for _digest, so always available)
|
||||||
|
_shelly_cnonce="$(${ACME_OPENSSL_BIN:-openssl} rand -hex 8 2>/dev/null)"
|
||||||
|
_debug "Shelly cnonce: $_shelly_cnonce"
|
||||||
|
|
||||||
|
# Build the digest Authorization header value (stored for reuse)
|
||||||
|
_shelly_nc=1
|
||||||
|
_shelly_build_auth_header
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check whether the HTTP response headers contain a digest auth challenge.
|
||||||
|
# Returns 0 (true) if a 401 with WWW-Authenticate is present.
|
||||||
|
_shelly_has_auth_challenge() {
|
||||||
|
_shelly_headers_file="$1"
|
||||||
|
_shelly_status="$(grep -i '^HTTP/' "$_shelly_headers_file" | _tail_n 1 | awk '{print $2}')"
|
||||||
|
[ "$_shelly_status" = "401" ] && grep -qi '^WWW-Authenticate:' "$_shelly_headers_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build or rebuild the RFC 7616 Authorization header.
|
||||||
|
# Uses: _shelly_ha1, _shelly_nonce, _shelly_cnonce, _shelly_qop, _shelly_realm, _shelly_nc
|
||||||
|
# Sets: _shelly_auth_header
|
||||||
|
_shelly_build_auth_header() {
|
||||||
|
_shelly_nc_hex="$(printf '%08x' "$_shelly_nc")"
|
||||||
|
|
||||||
|
# ha2 = SHA256(POST:/rpc)
|
||||||
|
_shelly_ha2="$(printf '%s' "POST:/rpc" | _digest sha256 hex)"
|
||||||
|
|
||||||
|
# response = SHA256(ha1:nonce:nc:cnonce:qop:ha2)
|
||||||
|
_shelly_digest_response="$(printf '%s' "${_shelly_ha1}:${_shelly_nonce}:${_shelly_nc_hex}:${_shelly_cnonce}:${_shelly_qop}:${_shelly_ha2}" | _digest sha256 hex)"
|
||||||
|
|
||||||
|
# Build the Authorization header value (without the "Authorization: " prefix)
|
||||||
|
_shelly_auth_header="Digest username=\"${SHELLY_USER}\", realm=\"${_shelly_realm}\", nonce=\"${_shelly_nonce}\", uri=\"/rpc\", qop=${_shelly_qop}, nc=${_shelly_nc_hex}, cnonce=\"${_shelly_cnonce}\", response=\"${_shelly_digest_response}\", algorithm=SHA-256"
|
||||||
|
|
||||||
|
_secure_debug "Authorization header" "$_shelly_auth_header"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make a Shelly JSON-RPC call.
|
||||||
|
# Usage: _shelly_rpc <method> <params_json>
|
||||||
|
# Returns 0 on success, 1 on error.
|
||||||
|
_shelly_rpc() {
|
||||||
|
_shelly_method="$1"
|
||||||
|
_shelly_params="$2"
|
||||||
|
|
||||||
|
_shelly_body='{"id":1,"method":"'"$_shelly_method"'","params":'"$_shelly_params"'}'
|
||||||
|
|
||||||
|
_debug "RPC method: $_shelly_method"
|
||||||
|
_debug2 "RPC body: $_shelly_body"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2090
|
||||||
|
if [ -n "$_shelly_auth_header" ]; then
|
||||||
|
export _H1="Authorization: $_shelly_auth_header"
|
||||||
|
else
|
||||||
|
export _H1=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
_post "$_shelly_body" "http://${SHELLY_HOST}/rpc" "" "" "application/json"
|
||||||
|
_shelly_ret=$?
|
||||||
|
|
||||||
|
if [ "$_shelly_ret" != "0" ]; then
|
||||||
|
_err "HTTP request failed for $_shelly_method (curl/wget error $_shelly_ret)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Empty response means something went wrong (auth required but not provided, etc.)
|
||||||
|
if [ -z "$response" ]; then
|
||||||
|
_err "Empty response from Shelly device. If authentication is enabled on the device, set SHELLY_PASSWORD."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate response looks like a Shelly JSON-RPC response.
|
||||||
|
# Catches non-JSON responses such as HTTP 429 "Too Many Requests" which
|
||||||
|
# would otherwise pass the empty and "error" checks below.
|
||||||
|
if ! _startswith "$response" '{' || ! _contains "$response" '"id"'; then
|
||||||
|
_err "Invalid response from Shelly device: $response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for JSON-RPC error in response
|
||||||
|
if _contains "$response" '"error"'; then
|
||||||
|
_err "RPC error from Shelly: $response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug "RPC response: $response"
|
||||||
|
|
||||||
|
# Increment nonce counter and rebuild auth header for next request
|
||||||
|
if [ -n "$_shelly_auth_header" ]; then
|
||||||
|
_shelly_nc=$((_shelly_nc + 1))
|
||||||
|
_shelly_build_auth_header
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload the certificate to the device.
|
||||||
|
# Note: We do NOT clear the existing certificate first, because the Shelly
|
||||||
|
# auto-removes all three files (cert, key, CA) when any one is cleared.
|
||||||
|
# Uploading overwrites in place — no clearing needed.
|
||||||
|
_shelly_upload_cert() {
|
||||||
|
_shelly_cert_data="$(_json_encode <"$_cfullchain")"
|
||||||
|
|
||||||
|
_debug "Uploading certificate"
|
||||||
|
if ! _shelly_rpc "Shelly.PutHTTPServerCert" '{"data":"'"$_shelly_cert_data"'"}'; then
|
||||||
|
_err "Failed to upload certificate to device"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload the private key to the device.
|
||||||
|
# Note: Do not clear first — see _shelly_upload_cert for rationale.
|
||||||
|
_shelly_upload_key() {
|
||||||
|
_shelly_key_data="$(_json_encode <"$_ckey")"
|
||||||
|
|
||||||
|
_debug "Uploading key"
|
||||||
|
if ! _shelly_rpc "Shelly.PutHTTPServerKey" '{"data":"'"$_shelly_key_data"'"}'; then
|
||||||
|
_err "Failed to upload key to device"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -344,6 +344,7 @@ synology_dsm_deploy() {
|
|||||||
else
|
else
|
||||||
_err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more."
|
_err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more."
|
||||||
fi
|
fi
|
||||||
|
_logout
|
||||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -354,6 +355,7 @@ synology_dsm_deploy() {
|
|||||||
|
|
||||||
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 and \$SYNO_CREATE is not set."
|
||||||
|
_logout
|
||||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -389,13 +391,13 @@ synology_dsm_deploy() {
|
|||||||
else
|
else
|
||||||
_info "Restart HTTP services not necessary."
|
_info "Restart HTTP services not necessary."
|
||||||
fi
|
fi
|
||||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
|
||||||
_logout
|
_logout
|
||||||
|
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
|
||||||
_err "Unable to update certificate, got error response: $response."
|
_err "Unable to update certificate, got error response: $response."
|
||||||
_logout
|
_logout
|
||||||
|
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -403,6 +405,8 @@ synology_dsm_deploy() {
|
|||||||
#################### Private functions below ##################################
|
#################### Private functions below ##################################
|
||||||
_logout() {
|
_logout() {
|
||||||
# Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables)
|
# Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables)
|
||||||
|
# Must be called before _temp_admin_cleanup: once the temp admin is deleted, its session can no longer be logged out.
|
||||||
|
# Note: this overwrites $response, so print any error message that needs it before calling.
|
||||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid")
|
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid")
|
||||||
_debug3 response "$response"
|
_debug3 response "$response"
|
||||||
}
|
}
|
||||||
|
|||||||
315
deploy/unifios.sh
Normal file
315
deploy/unifios.sh
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# Deploy hook for UniFi OS, via the certificate REST API.
|
||||||
|
#
|
||||||
|
# Works against any UniFi OS whose management UI exposes
|
||||||
|
# /api/userCertificates. Confirmed on:
|
||||||
|
# - UniFi OS Server (the separately-installed, self-hosted application)
|
||||||
|
# on macOS and on Linux. Windows should also work (it runs under
|
||||||
|
# WSL2), but has not been tested.
|
||||||
|
# Tested on: Ubuntu 26.04 (remote) and macOS 26.6 (local).
|
||||||
|
# - UniFi OS hardware: UDM Pro on UniFi OS 5.1.26, UCG Fiber on
|
||||||
|
# UniFi OS 5.0.16 (user reports, see issues 7184 and 6916).
|
||||||
|
# No lower version bound is claimed -- if the UI has a certificate
|
||||||
|
# manager, this hook should work.
|
||||||
|
#
|
||||||
|
# `unifios` vs `unifi`: the split is the access method, not the product
|
||||||
|
# line. `unifi` writes files / a Java keystore and needs local or SSH
|
||||||
|
# access on the device; this hook drives the same REST API the web UI
|
||||||
|
# uses and works remotely. Use `unifi` where acme.sh runs on the device
|
||||||
|
# itself, this hook where it does not.
|
||||||
|
#
|
||||||
|
# The API is served on the management port, which differs per install:
|
||||||
|
# UniFi OS Server listens on 11443 (hence the default below), while
|
||||||
|
# UniFi OS hardware serves it on 443 -- set DEPLOY_UNIFIOS_HOST to
|
||||||
|
# "https://<host>" there.
|
||||||
|
#
|
||||||
|
# Endpoints used, all as the web UI itself calls them:
|
||||||
|
# POST /api/auth/login - session login (cookie + JWT)
|
||||||
|
# GET /api/userCertificates - list uploaded certificates
|
||||||
|
# POST /api/userCertificates - upload a new certificate
|
||||||
|
# DELETE /api/userCertificates/{id} - remove a certificate
|
||||||
|
# PUT /api/userCertificates/{id}/status - activate/deactivate a certificate
|
||||||
|
#
|
||||||
|
# This was reverse-engineered from the browser's Network tab while using the
|
||||||
|
# real GUI upload/activate/delete flow -- it is undocumented but is the same
|
||||||
|
# code path the UI uses, so it's far more robust than editing settings.yaml,
|
||||||
|
# http/local-certs.conf, or the underlying Postgres user_certificates table
|
||||||
|
# directly (all of which are also touched by this API, but only as a result
|
||||||
|
# of the app's own internal logic, which handles cert parsing, active-cert
|
||||||
|
# bookkeeping, and nginx config regeneration correctly on its own).
|
||||||
|
#
|
||||||
|
# Auth: POST /api/auth/login returns a `TOKEN` cookie containing a JWT whose
|
||||||
|
# payload has a `csrfToken` claim. That value must be echoed back as the
|
||||||
|
# `x-csrf-token` header on every subsequent state-changing request (a classic
|
||||||
|
# double-submit CSRF pattern). No other cookies were found to be necessary.
|
||||||
|
#
|
||||||
|
# Uses core acme.sh helpers throughout (_post/_get, _json_encode,
|
||||||
|
# _durl_replace_base64, _dbase64, _egrep_o) rather than raw curl -k or
|
||||||
|
# python3, so the wget fallback, --debug tracing, and CA_BUNDLE are all
|
||||||
|
# honored the same as every other hook. The management API's cert may be
|
||||||
|
# self-signed -- it always is on a fresh install, and there is no reliable
|
||||||
|
# way to tell in advance whether an earlier run has already replaced it --
|
||||||
|
# so this hook sets HTTPS_INSECURE=1 itself, scoped to its own subshell (see
|
||||||
|
# acme.sh's per-hook sourcing in _deploy) -- it does not weaken TLS
|
||||||
|
# verification for the rest of the acme.sh run, e.g. the connection to the
|
||||||
|
# ACME CA.
|
||||||
|
#
|
||||||
|
# Design: This hook does not save a certificate ID between renewals. Each
|
||||||
|
# upload gets a name unique to that run: the domain name plus a timestamp.
|
||||||
|
# This name never collides with an entry from a previous deploy. This is
|
||||||
|
# true even if that entry is still active. The hook uploads and activates
|
||||||
|
# the new certificate before it removes any old entries. If a failure
|
||||||
|
# occurs during this process, the server still has a valid, active
|
||||||
|
# certificate. The hook removes old entries only after activation is
|
||||||
|
# complete. It removes only entries whose name starts with the domain name,
|
||||||
|
# because this is the hook's own naming convention. As a result, this step
|
||||||
|
# can only affect entries that this hook created for this domain. It can
|
||||||
|
# never affect a certificate that a user uploaded manually, and it can
|
||||||
|
# never affect a self-signed certificate.
|
||||||
|
#
|
||||||
|
# Settings:
|
||||||
|
# DEPLOY_UNIFIOS_HOST - base URL of the management API
|
||||||
|
# (default: "https://localhost:11443", i.e. a UniFi OS Server on the
|
||||||
|
# same machine as acme.sh; set it to "https://<host>" for UniFi OS
|
||||||
|
# hardware or any remote target)
|
||||||
|
# DEPLOY_UNIFIOS_USERNAME - UniFi OS admin username (required)
|
||||||
|
# DEPLOY_UNIFIOS_PASSWORD - UniFi OS admin password (required)
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# export DEPLOY_UNIFIOS_USERNAME="acmeuser"
|
||||||
|
# export DEPLOY_UNIFIOS_PASSWORD="xxxxx"
|
||||||
|
# acme.sh --deploy -d example.com --deploy-hook unifios
|
||||||
|
#
|
||||||
|
# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/7182
|
||||||
|
|
||||||
|
_uos_response_code() {
|
||||||
|
# tr strips the trailing newline along with form feeds; re-terminate
|
||||||
|
# before the second _egrep_o, whose sed fallback (used wherever egrep -o
|
||||||
|
# is unavailable) drops an unterminated final line on some platforms.
|
||||||
|
_uos_code="$(_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")"
|
||||||
|
printf '%s\n' "$_uos_code" | _egrep_o "^[0-9][0-9]*"
|
||||||
|
}
|
||||||
|
|
||||||
|
_uos_response_cookie() {
|
||||||
|
# $1 = cookie name
|
||||||
|
grep <"$HTTP_HEADER" -i "^Set-Cookie: *$1=" | _tail_n 1 | _egrep_o "$1=[^;]*" | _head_n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
unifios_deploy() {
|
||||||
|
_cdomain="$1"
|
||||||
|
_ckey="$2"
|
||||||
|
_ccert="$3"
|
||||||
|
_cca="$4"
|
||||||
|
_cfullchain="$5"
|
||||||
|
|
||||||
|
_debug _cdomain "$_cdomain"
|
||||||
|
_debug _ckey "$_ckey"
|
||||||
|
_debug _ccert "$_ccert"
|
||||||
|
_debug _cca "$_cca"
|
||||||
|
_debug _cfullchain "$_cfullchain"
|
||||||
|
|
||||||
|
# Scoped to this hook's own subshell -- does not affect the rest of the
|
||||||
|
# acme.sh run (e.g. the connection to the ACME CA).
|
||||||
|
export HTTPS_INSECURE=1
|
||||||
|
|
||||||
|
_getdeployconf DEPLOY_UNIFIOS_HOST
|
||||||
|
DEPLOY_UNIFIOS_HOST="${DEPLOY_UNIFIOS_HOST:-https://localhost:11443}"
|
||||||
|
_savedeployconf DEPLOY_UNIFIOS_HOST "$DEPLOY_UNIFIOS_HOST"
|
||||||
|
_debug DEPLOY_UNIFIOS_HOST "$DEPLOY_UNIFIOS_HOST"
|
||||||
|
|
||||||
|
_getdeployconf DEPLOY_UNIFIOS_USERNAME
|
||||||
|
_getdeployconf DEPLOY_UNIFIOS_PASSWORD
|
||||||
|
|
||||||
|
if [ -z "$DEPLOY_UNIFIOS_USERNAME" ] || [ -z "$DEPLOY_UNIFIOS_PASSWORD" ]; then
|
||||||
|
_err "DEPLOY_UNIFIOS_USERNAME and DEPLOY_UNIFIOS_PASSWORD must be set."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug DEPLOY_UNIFIOS_USERNAME "$DEPLOY_UNIFIOS_USERNAME"
|
||||||
|
_secure_debug DEPLOY_UNIFIOS_PASSWORD "$DEPLOY_UNIFIOS_PASSWORD"
|
||||||
|
|
||||||
|
_info "Logging in to UniFi OS Server API at $DEPLOY_UNIFIOS_HOST..."
|
||||||
|
|
||||||
|
# _json_encode always appends a trailing "\n" escape, even to input with
|
||||||
|
# no trailing newline (it normalizes via `echo`, unconditionally adding
|
||||||
|
# one). That's harmless for the key/cert file content below, which
|
||||||
|
# legitimately ends in a real newline anyway, but wrong for these plain
|
||||||
|
# strings -- strip the spurious escape it leaves behind.
|
||||||
|
_uos_user_json="$(printf '%s' "$DEPLOY_UNIFIOS_USERNAME" | _json_encode)"
|
||||||
|
_uos_user_json="${_uos_user_json%\\n}"
|
||||||
|
_uos_pass_json="$(printf '%s' "$DEPLOY_UNIFIOS_PASSWORD" | _json_encode)"
|
||||||
|
_uos_pass_json="${_uos_pass_json%\\n}"
|
||||||
|
_login_body="{\"username\":\"$_uos_user_json\",\"password\":\"$_uos_pass_json\",\"token\":\"\",\"rememberMe\":false}"
|
||||||
|
|
||||||
|
_login_json="$(_post "$_login_body" "$DEPLOY_UNIFIOS_HOST/api/auth/login" "" "POST" "application/json")"
|
||||||
|
_login_code="$(_uos_response_code)"
|
||||||
|
|
||||||
|
if [ "$_login_code" != "200" ]; then
|
||||||
|
_err "Login failed (HTTP $_login_code)."
|
||||||
|
_err "Response: $_login_json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Credentials are proven correct now -- save them, rather than only at the
|
||||||
|
# very end, so a later step failing doesn't discard a working login.
|
||||||
|
# base64-encoded: _save_conf wraps values in single quotes with no
|
||||||
|
# escaping, so a literal "'" in the password would otherwise corrupt the
|
||||||
|
# domain conf (see deploy/synology_dsm.sh for the same pattern).
|
||||||
|
_savedeployconf DEPLOY_UNIFIOS_USERNAME "$DEPLOY_UNIFIOS_USERNAME" "base64"
|
||||||
|
_savedeployconf DEPLOY_UNIFIOS_PASSWORD "$DEPLOY_UNIFIOS_PASSWORD" "base64"
|
||||||
|
|
||||||
|
_uos_token="$(_uos_response_cookie TOKEN)"
|
||||||
|
if [ -z "$_uos_token" ]; then
|
||||||
|
_err "Login succeeded but no TOKEN cookie was returned."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_H1="Cookie: $_uos_token"
|
||||||
|
export _H1
|
||||||
|
|
||||||
|
_uos_jwt_payload="$(echo "$_uos_token" | cut -d '=' -f 2- | cut -d '.' -f 2)"
|
||||||
|
_uos_csrf="$(_durl_replace_base64 "$_uos_jwt_payload" | _dbase64 | _egrep_o '"csrfToken":"[^"]*"' | cut -d '"' -f 4)"
|
||||||
|
if [ -z "$_uos_csrf" ]; then
|
||||||
|
_err "Could not extract csrfToken from session token."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_H2="x-csrf-token: $_uos_csrf"
|
||||||
|
export _H2
|
||||||
|
|
||||||
|
_info "Uploading new certificate..."
|
||||||
|
# "name" is a purely cosmetic label -- the server never validates it
|
||||||
|
# against the certificate's actual CN/SAN, and accepts arbitrary text
|
||||||
|
# including spaces (confirmed: a cert for example.com served correctly
|
||||||
|
# after being uploaded under the unrelated name "totally unrelated label").
|
||||||
|
# The only constraint that matters here is uniqueness: the server rejects
|
||||||
|
# a second entry with a name it already has, so a bare domain name would
|
||||||
|
# collide with the previous deploy's entry on every renewal after the
|
||||||
|
# first. A full human-readable timestamp would make that obvious in the
|
||||||
|
# UI, but the certificate list's name column is fixed-width and doesn't
|
||||||
|
# wrap (confirmed against the real UI: a long name overlaps the Expires
|
||||||
|
# column and makes both unreadable), so keep the suffix short instead --
|
||||||
|
# Unix epoch seconds are still unique enough for this purpose.
|
||||||
|
_uos_name="$_cdomain $(_time)"
|
||||||
|
_uos_key_json="$(_json_encode <"$_ckey")"
|
||||||
|
_uos_cert_json="$(_json_encode <"$_cfullchain")"
|
||||||
|
_create_body="{\"name\":\"$_uos_name\",\"key\":\"$_uos_key_json\",\"cert\":\"$_uos_cert_json\"}"
|
||||||
|
|
||||||
|
_create_json="$(_post "$_create_body" "$DEPLOY_UNIFIOS_HOST/api/userCertificates" "" "POST" "application/json")"
|
||||||
|
_create_code="$(_uos_response_code)"
|
||||||
|
|
||||||
|
if [ "$_create_code" = "201" ]; then
|
||||||
|
_new_id="$(echo "$_create_json" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||||
|
if [ -z "$_new_id" ]; then
|
||||||
|
_err "Could not determine new certificate ID from upload response."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
elif [ "$_create_code" = "400" ] && echo "$_create_json" | grep -q "USER_CERTIFICATE_DUPLICATE"; then
|
||||||
|
# HTTP 400 alone just means "bad request" -- it's the USER_CERTIFICATE_DUPLICATE
|
||||||
|
# code in the response body, checked above, that actually confirms this.
|
||||||
|
# The name above is unique to this run, so a duplicate here can only be
|
||||||
|
# the server's other uniqueness constraint: this exact certificate (by
|
||||||
|
# fingerprint) already exists as some other entry -- most likely a retry
|
||||||
|
# after a prior run already uploaded it (a real renewal always produces a
|
||||||
|
# new fingerprint, so this shouldn't happen in normal cron use). The
|
||||||
|
# response body doesn't include the existing entry's id, so look it up
|
||||||
|
# by fingerprint instead.
|
||||||
|
# The API's own fingerprint field is SHA-1 (20 bytes), not SHA-256 --
|
||||||
|
# confirmed against a real response, e.g.
|
||||||
|
# "fingerprint":"FC:02:50:9C:3B:3F:B7:79:9D:CA:4D:7C:AC:92:E7:D5:EA:F1:3A:29"
|
||||||
|
# (20 colon-separated groups). _fingerprint (core helper) strips the
|
||||||
|
# colons that field has, so re-insert them rather than stripping the
|
||||||
|
# JSON's own colons, which would also remove the ones separating every
|
||||||
|
# key from its value.
|
||||||
|
_uos_fingerprint="$(_fingerprint "$_cfullchain" sha1)"
|
||||||
|
if [ -z "$_uos_fingerprint" ]; then
|
||||||
|
_err "Could not compute the certificate's fingerprint."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_uos_fingerprint="$(echo "$_uos_fingerprint" | sed 's/\(..\)/\1:/g; s/:$//')"
|
||||||
|
|
||||||
|
_list_json="$(_get "$DEPLOY_UNIFIOS_HOST/api/userCertificates")"
|
||||||
|
_list_code="$(_uos_response_code)"
|
||||||
|
if [ "$_list_code" != "200" ]; then
|
||||||
|
_err "Failed to list existing certificates (HTTP $_list_code)."
|
||||||
|
_err "Response: $_list_json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# _normalizeJson collapses the response to one predictable line (no stray
|
||||||
|
# whitespace around colons, no embedded CR/LF the server might emit) but
|
||||||
|
# also strips the trailing newline entirely -- re-terminate before the
|
||||||
|
# split below, since some sed implementations drop an unterminated final
|
||||||
|
# line rather than processing it.
|
||||||
|
_list_json="$(echo "$_list_json" | _normalizeJson)"
|
||||||
|
# A literal embedded newline (not the two-character "\n", which GNU sed
|
||||||
|
# treats as a newline in the replacement but POSIX doesn't define and BSD
|
||||||
|
# sed emits literally) splits it one JSON object per line so grep can
|
||||||
|
# match a single certificate entry at a time.
|
||||||
|
_list_json="$(
|
||||||
|
printf '%s\n' "$_list_json" | sed 's/},{/},\
|
||||||
|
{/g'
|
||||||
|
)"
|
||||||
|
_new_id="$(echo "$_list_json" | grep -F "\"fingerprint\":\"$_uos_fingerprint\"" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||||
|
if [ -z "$_new_id" ]; then
|
||||||
|
_err "Certificate upload rejected as a duplicate (server reported USER_CERTIFICATE_DUPLICATE), but no existing entry matching this fingerprint was found."
|
||||||
|
_err "Response: $_create_json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# Reusing the existing entry rather than deleting it and re-uploading
|
||||||
|
# under today's name+timestamp: the served content is identical either
|
||||||
|
# way, so replacing it would only cost an extra delete+create round trip
|
||||||
|
# for no functional benefit. The tradeoff is cosmetic -- this entry keeps
|
||||||
|
# whatever name it was given whenever it was originally uploaded, so it
|
||||||
|
# won't reflect today's date in the UI.
|
||||||
|
_info "Certificate already present as entry $_new_id; reusing it."
|
||||||
|
else
|
||||||
|
_err "Certificate upload failed (HTTP $_create_code)."
|
||||||
|
_err "Response: $_create_json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "Activating certificate $_new_id..."
|
||||||
|
_activate_json="$(_post '{"active":true}' "$DEPLOY_UNIFIOS_HOST/api/userCertificates/$_new_id/status" "" "PUT" "application/json")"
|
||||||
|
_activate_code="$(_uos_response_code)"
|
||||||
|
|
||||||
|
if [ "$_activate_code" != "200" ]; then
|
||||||
|
_err "Failed to activate new certificate (HTTP $_activate_code)."
|
||||||
|
_err "Response: $_activate_json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# UniFi OS Server activation is exclusive server-wide. Tests against the
|
||||||
|
# real API confirm this: activation of one entry deactivates whichever
|
||||||
|
# other entry was active before, no matter its name or domain. As a
|
||||||
|
# result, the server serves the certificate that this hook just activated.
|
||||||
|
# This certificate is already live. If the removal of old entries below
|
||||||
|
# fails, the hook logs the failure. The deploy does not fail because of
|
||||||
|
# this.
|
||||||
|
_info "Checking for old certificate entries to remove..."
|
||||||
|
_list_json="$(_get "$DEPLOY_UNIFIOS_HOST/api/userCertificates")"
|
||||||
|
_list_code="$(_uos_response_code)"
|
||||||
|
if [ "$_list_code" != "200" ]; then
|
||||||
|
_err "Failed to list certificates for cleanup (HTTP $_list_code) -- leaving old entries in place."
|
||||||
|
else
|
||||||
|
_list_json="$(echo "$_list_json" | _normalizeJson)"
|
||||||
|
_list_json="$(
|
||||||
|
printf '%s\n' "$_list_json" | sed 's/},{/},\
|
||||||
|
{/g'
|
||||||
|
)"
|
||||||
|
# The pattern below matches the domain name followed by a space. If the
|
||||||
|
# space is missing, the pattern can also match a different domain that
|
||||||
|
# starts with the same text as this domain.
|
||||||
|
_old_ids="$(echo "$_list_json" | grep -F "\"name\":\"$_cdomain " | _egrep_o '"id":"[^"]*"' | cut -d '"' -f 4 | grep -v "^$_new_id$")"
|
||||||
|
for _old_id in $_old_ids; do
|
||||||
|
_info "Removing old certificate entry $_old_id..."
|
||||||
|
_del_json="$(_post "" "$DEPLOY_UNIFIOS_HOST/api/userCertificates/$_old_id" "" "DELETE")"
|
||||||
|
_del_code="$(_uos_response_code)"
|
||||||
|
if [ "$_del_code" != "204" ] && [ "$_del_code" != "200" ]; then
|
||||||
|
_err "Failed to delete old certificate $_old_id (HTTP $_del_code) -- leaving it in place."
|
||||||
|
_err "Response: $_del_json"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "UniFi OS Server certificate deployed and activated successfully."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ Options:
|
|||||||
AZUREDNS_APPID App ID. App ID of the service principal
|
AZUREDNS_APPID App ID. App ID of the service principal
|
||||||
AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal
|
AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal
|
||||||
AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false"
|
AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false"
|
||||||
AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Optional.
|
AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Not saved, provide it on every run. Optional.
|
||||||
'
|
'
|
||||||
|
|
||||||
wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS
|
wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS
|
||||||
@@ -47,13 +47,15 @@ dns_azure_add() {
|
|||||||
_saveaccountconf_mutable AZUREDNS_TENANTID ""
|
_saveaccountconf_mutable AZUREDNS_TENANTID ""
|
||||||
_saveaccountconf_mutable AZUREDNS_APPID ""
|
_saveaccountconf_mutable AZUREDNS_APPID ""
|
||||||
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET ""
|
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET ""
|
||||||
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN ""
|
_clearaccountconf_mutable AZUREDNS_BEARERTOKEN
|
||||||
else
|
else
|
||||||
_info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token"
|
_info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token"
|
||||||
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
|
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
|
||||||
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
|
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
|
||||||
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
|
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
|
||||||
AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
|
#AZUREDNS_BEARERTOKEN is short-lived, so it is taken from the environment only and never
|
||||||
|
#read from or saved to the account conf. Versions up to 3.0.9 cached their internal access
|
||||||
|
#token under the same name, which must not be replayed as a user token (#7218).
|
||||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||||
if [ -z "$AZUREDNS_TENANTID" ]; then
|
if [ -z "$AZUREDNS_TENANTID" ]; then
|
||||||
AZUREDNS_SUBSCRIPTIONID=""
|
AZUREDNS_SUBSCRIPTIONID=""
|
||||||
@@ -93,7 +95,7 @@ dns_azure_add() {
|
|||||||
_saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
|
_saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
|
||||||
_saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
|
_saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
|
||||||
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
|
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
|
||||||
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$AZUREDNS_BEARERTOKEN"
|
_clearaccountconf_mutable AZUREDNS_BEARERTOKEN
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||||
@@ -175,7 +177,7 @@ dns_azure_rm() {
|
|||||||
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
|
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
|
||||||
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
|
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
|
||||||
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
|
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
|
||||||
AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
|
#AZUREDNS_BEARERTOKEN comes from the environment only, see the note in dns_azure_add
|
||||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||||
if [ -z "$AZUREDNS_TENANTID" ]; then
|
if [ -z "$AZUREDNS_TENANTID" ]; then
|
||||||
AZUREDNS_SUBSCRIPTIONID=""
|
AZUREDNS_SUBSCRIPTIONID=""
|
||||||
|
|||||||
@@ -323,21 +323,21 @@ _bhosted_extract_id() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# JSON: "id":12345
|
# JSON: "id":12345
|
||||||
_id="$(printf "%s" "$_resp" | _egrep_o '"id"[[:space:]]*:[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
_id="$(printf "%s" "$_resp" | _egrep_o '"id"[ ]*:[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
||||||
if [ -n "$_id" ]; then
|
if [ -n "$_id" ]; then
|
||||||
printf "%s" "$_id"
|
printf "%s" "$_id"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# key=value: id=12345
|
# key=value: id=12345
|
||||||
_id="$(printf "%s" "$_resp" | _egrep_o '(^|[[:space:][:punct:]])id[[:space:]]*=[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
_id="$(printf "%s" "$_resp" | _egrep_o '(^|[^0-9a-zA-Z])id[ ]*=[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
||||||
if [ -n "$_id" ]; then
|
if [ -n "$_id" ]; then
|
||||||
printf "%s" "$_id"
|
printf "%s" "$_id"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# "record id 12345" / "recordid 12345"
|
# "record id 12345" / "recordid 12345"
|
||||||
_id="$(printf "%s" "$_resp" | _egrep_o '(record[[:space:]]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
_id="$(printf "%s" "$_resp" | _egrep_o '(record[ ]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
||||||
if [ -n "$_id" ]; then
|
if [ -n "$_id" ]; then
|
||||||
printf "%s" "$_id"
|
printf "%s" "$_id"
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ dns_creoline_rm() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
record_id=$(echo "$response" | _egrep_o "\"id\"[[:space:]]*:[[:space:]]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
record_id=$(echo "$response" | _egrep_o "\"id\"[ ]*:[ ]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
||||||
_debug "record_id" "$record_id"
|
_debug "record_id" "$record_id"
|
||||||
|
|
||||||
if [ -z "$record_id" ]; then
|
if [ -z "$record_id" ]; then
|
||||||
@@ -108,10 +108,10 @@ _get_root() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
_sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
||||||
_debug _sub_domain "$_sub_domain"
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
|
||||||
_domain=$(echo "$response" | _egrep_o "\"domain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
_domain=$(echo "$response" | _egrep_o "\"domain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
||||||
_debug _domain "$_domain"
|
_debug _domain "$_domain"
|
||||||
|
|
||||||
if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then
|
if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then
|
||||||
@@ -171,7 +171,7 @@ _creoline_rest() {
|
|||||||
_err "URI:$uri"
|
_err "URI:$uri"
|
||||||
return 1
|
return 1
|
||||||
elif _contains "$response" "message"; then
|
elif _contains "$response" "message"; then
|
||||||
message=$(echo "$response" | _egrep_o "\"message\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \")
|
message=$(echo "$response" | _egrep_o "\"message\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \")
|
||||||
_err "Error: $message"
|
_err "Error: $message"
|
||||||
_err "URI:$uri"
|
_err "URI:$uri"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -285,15 +285,15 @@ _cyon_delete_txt() {
|
|||||||
|
|
||||||
list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async"
|
list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async"
|
||||||
|
|
||||||
list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')"
|
list_txt_response="$(_get "${list_txt_url}")"
|
||||||
_debug list_txt_response "${list_txt_response}"
|
_debug list_txt_response "${list_txt_response}"
|
||||||
|
|
||||||
if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi
|
if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi
|
||||||
|
|
||||||
# Find and delete all acme challenge entries for the $fulldomain.
|
# Find and delete all acme challenge entries for the $fulldomain.
|
||||||
_dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')"
|
_dns_entries="$(printf "%s\n" "${list_txt_response}" | _egrep_o 'data-hash=\\"[^"]*\\" data-identifier=\\"[^"]*\\"' | sed 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\"/\1 \2/')"
|
||||||
|
|
||||||
printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do
|
printf "%s\n" "${_dns_entries}" | while read -r _hash _identifier; do
|
||||||
dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)"
|
dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)"
|
||||||
dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)"
|
dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)"
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ dns_czechia_add() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
|
_czechia_tab="$(printf '\t')"
|
||||||
_tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
_cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//")
|
||||||
|
_tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//")
|
||||||
|
|
||||||
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
|
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
|
||||||
_err "Missing zone or CZ_AuthorizationToken."
|
_err "Missing zone or CZ_AuthorizationToken."
|
||||||
@@ -108,8 +109,9 @@ dns_czechia_rm() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
|
_czechia_tab="$(printf '\t')"
|
||||||
_tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
_cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//")
|
||||||
|
_tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//")
|
||||||
|
|
||||||
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
|
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
|
||||||
_err "Missing zone or CZ_AuthorizationToken."
|
_err "Missing zone or CZ_AuthorizationToken."
|
||||||
@@ -180,12 +182,13 @@ _czechia_load_conf() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_czechia_pick_zone() {
|
_czechia_pick_zone() {
|
||||||
|
_czechia_pz_tab="$(printf '\t')"
|
||||||
_fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//')
|
_fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//')
|
||||||
_best_zone=""
|
_best_zone=""
|
||||||
|
|
||||||
_zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g')
|
_zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g')
|
||||||
for _z in $_zones_space; do
|
for _z in $_zones_space; do
|
||||||
_clean_z=$(printf "%s" "$_z" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
|
_clean_z=$(printf "%s" "$_z" | _lower_case | sed "s/[ $_czechia_pz_tab]//g; s/\.\$//")
|
||||||
[ -z "$_clean_z" ] && continue
|
[ -z "$_clean_z" ] && continue
|
||||||
|
|
||||||
case "$_fd" in
|
case "$_fd" in
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ dns_easydns_rm() {
|
|||||||
EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}"
|
EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}"
|
||||||
EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}"
|
EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}"
|
||||||
|
|
||||||
|
if [ -z "$EASYDNS_Token" ] || [ -z "$EASYDNS_Key" ]; then
|
||||||
|
_err "You didn't specify an easydns.net token or api key. Signup at https://cp.easydns.com/manage/security/api/signup.php"
|
||||||
|
return 1
|
||||||
|
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"
|
_err "invalid domain"
|
||||||
@@ -91,24 +96,21 @@ dns_easydns_rm() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
|
record_id=$(printf "%s\n" "$response" | tr '{' '\n' | grep "\"rdata\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
|
||||||
_debug count "$count"
|
_debug "record_id" "$record_id"
|
||||||
if [ "$count" = "0" ]; then
|
|
||||||
|
if [ -z "$record_id" ]; then
|
||||||
_info "Don't need to remove."
|
_info "Don't need to remove."
|
||||||
else
|
return 0
|
||||||
record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
|
|
||||||
_debug "record_id" "$record_id"
|
|
||||||
if [ -z "$record_id" ]; then
|
|
||||||
_err "Can not get record id to remove."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if ! _EASYDNS_rest DELETE "zones/records/$_domain/$record_id"; then
|
|
||||||
_err "Delete record error."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
_contains "$response" "\"status\":200"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! _EASYDNS_rest DELETE "zones/records/$_domain/$record_id"; then
|
||||||
|
_err "Delete record error."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_contains "$response" "\"status\":200"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#################### Private functions below ##################################
|
#################### Private functions below ##################################
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip
|
|||||||
Options:
|
Options:
|
||||||
FREEMYIP_Token API Token
|
FREEMYIP_Token API Token
|
||||||
Issues: github.com/acmesh-official/acme.sh/issues/6247
|
Issues: github.com/acmesh-official/acme.sh/issues/6247
|
||||||
Author: Recolic Keghart <root@recolic.net>, @Giova96
|
Author: Recolic Keghart <root@recolic.net>, @Giova96, ExtremeFiretop
|
||||||
'
|
'
|
||||||
|
|
||||||
FREEMYIP_DNS_API="https://freemyip.com/update?"
|
FREEMYIP_DNS_API="https://freemyip.com/update?"
|
||||||
@@ -68,22 +68,30 @@ dns_freemyip_rm() {
|
|||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
################ Private functions below ################
|
################ Private functions below ################
|
||||||
_get_root() {
|
_get_root() {
|
||||||
_fmi_d="$1"
|
_fmi_d="$1"
|
||||||
|
|
||||||
echo "$_fmi_d" | rev | cut -d '.' -f 1-3 | rev
|
echo "$_fmi_d" | sed 's/.*\.\([^.]*\.[^.]*\.[^.]*\)$/\1/'
|
||||||
}
|
}
|
||||||
|
|
||||||
# There is random failure while calling freemyip API too fast. This function automatically retry until success.
|
# There is random failure while calling freemyip API too fast. This function automatically retry until success.
|
||||||
_freemyip_get_until_ok() {
|
_freemyip_get_until_ok() {
|
||||||
_fmi_url="$1"
|
_fmi_url="$1"
|
||||||
for i in $(seq 1 8); do
|
_fmi_i=1
|
||||||
_debug "HTTP GET freemyip.com API '$_fmi_url', retry $i/8..."
|
while [ "$_fmi_i" -le 8 ]; do
|
||||||
_get "$_fmi_url" | tee /dev/fd/2 | grep OK && return 0
|
_debug "HTTP GET freemyip.com API '$_fmi_url', retry $_fmi_i/8..."
|
||||||
|
_fmi_response="$(_get "$_fmi_url")"
|
||||||
|
printf '%s\n' "$_fmi_response" >&2
|
||||||
|
|
||||||
|
if _contains "$_fmi_response" "OK"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
_sleep 1 # DO NOT send the request too fast
|
_sleep 1 # DO NOT send the request too fast
|
||||||
|
_fmi_i=$((_fmi_i + 1))
|
||||||
done
|
done
|
||||||
_err "Failed to request freemyip API: $_fmi_url . Server does not say 'OK'"
|
_err "Failed to request freemyip API. Server does not say 'OK'"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,13 +101,16 @@ _is_root_domain_published() {
|
|||||||
_webroot="$(_get_root "$_fmi_d")"
|
_webroot="$(_get_root "$_fmi_d")"
|
||||||
|
|
||||||
_info "Verifying '""$_fmi_d""' freemyip webroot (""$_webroot"") is not published yet"
|
_info "Verifying '""$_fmi_d""' freemyip webroot (""$_webroot"") is not published yet"
|
||||||
for i in $(seq 1 3); do
|
_fmi_i=1
|
||||||
_debug "'$_webroot' ns lookup, retry $i/3..."
|
while [ "$_fmi_i" -le 3 ]; do
|
||||||
|
_debug "'$_webroot' ns lookup, retry $_fmi_i/3..."
|
||||||
|
|
||||||
if [ "$(_ns_lookup "$_fmi_d" TXT)" ]; then
|
if [ "$(_ns_lookup "$_fmi_d" TXT)" ]; then
|
||||||
_debug "'$_webroot' already has a TXT record published!"
|
_debug "'$_webroot' already has a TXT record published!"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
_sleep 10 # Give it some time to propagate the TXT record
|
_sleep 10 # Give it some time to propagate the TXT record
|
||||||
|
_fmi_i=$((_fmi_i + 1))
|
||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
198
dnsapi/dns_hestiacp.sh
Normal file
198
dnsapi/dns_hestiacp.sh
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
dns_hestiacp_info='HestiaCP Server API
|
||||||
|
Site: hestiacp.com
|
||||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hestiacp
|
||||||
|
Options:
|
||||||
|
HESTIA_HOST Panel URL. E.g. "https://panel.example.com:8083"
|
||||||
|
HESTIA_ACCESS API access key
|
||||||
|
HESTIA_SECRET API secret key
|
||||||
|
HESTIA_USER Username owning the DNS zones. Default "admin". Optional.
|
||||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6251
|
||||||
|
Author: Radu Malica <radu.malica@gmail.com>
|
||||||
|
'
|
||||||
|
|
||||||
|
######## Public functions #####################
|
||||||
|
|
||||||
|
# Usage: dns_hestiacp_add fulldomain txtvalue
|
||||||
|
dns_hestiacp_add() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
if ! _hestia_init; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug "Detecting the root zone for $fulldomain"
|
||||||
|
if ! _hestia_get_root "$fulldomain"; then
|
||||||
|
_err "Cannot find a DNS zone for $fulldomain under user $HESTIA_USER"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug _hestia_domain "$_hestia_domain"
|
||||||
|
_debug _hestia_sub "$_hestia_sub"
|
||||||
|
|
||||||
|
# _hestia_get_root left the zone record listing in _hestia_response
|
||||||
|
if _hestia_find_records "$_hestia_sub" "TXT" | grep -F -- "$txtvalue" >/dev/null; then
|
||||||
|
_info "The TXT record already exists, skipping"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "Adding TXT record for $fulldomain"
|
||||||
|
if ! _hestia_rest "v-add-dns-record" "$HESTIA_USER" "$_hestia_domain" "$_hestia_sub" "TXT" "$txtvalue" "" "" "yes" "600"; then
|
||||||
|
_err "Error adding TXT record: $_hestia_response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_info "TXT record added successfully"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: dns_hestiacp_rm fulldomain txtvalue
|
||||||
|
dns_hestiacp_rm() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
if ! _hestia_init; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug "Detecting the root zone for $fulldomain"
|
||||||
|
if ! _hestia_get_root "$fulldomain"; then
|
||||||
|
_err "Cannot find a DNS zone for $fulldomain under user $HESTIA_USER"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug _hestia_domain "$_hestia_domain"
|
||||||
|
_debug _hestia_sub "$_hestia_sub"
|
||||||
|
|
||||||
|
_hestia_removed=0
|
||||||
|
_hestia_failed=0
|
||||||
|
while IFS='|' read -r _hestia_id _hestia_value || [ -n "$_hestia_id" ]; do
|
||||||
|
if [ -z "$_hestia_id" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! _contains "$_hestia_value" "$txtvalue"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
_info "Deleting TXT record $_hestia_id"
|
||||||
|
if ! _hestia_rest "v-delete-dns-record" "$HESTIA_USER" "$_hestia_domain" "$_hestia_id" "yes"; then
|
||||||
|
_err "Error deleting TXT record $_hestia_id: $_hestia_response"
|
||||||
|
_hestia_failed=$(_math "$_hestia_failed" + 1)
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
_hestia_removed=$(_math "$_hestia_removed" + 1)
|
||||||
|
done <<EOF
|
||||||
|
$(_hestia_find_records "$_hestia_sub" "TXT")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$_hestia_removed" = "0" ] && [ "$_hestia_failed" = "0" ]; then
|
||||||
|
_info "No matching TXT record found to remove"
|
||||||
|
else
|
||||||
|
_info "Removed $_hestia_removed TXT record(s)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$_hestia_failed" != "0" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#################### Private functions below ##################################
|
||||||
|
|
||||||
|
_hestia_init() {
|
||||||
|
HESTIA_HOST="${HESTIA_HOST:-$(_readaccountconf_mutable HESTIA_HOST)}"
|
||||||
|
HESTIA_ACCESS="${HESTIA_ACCESS:-$(_readaccountconf_mutable HESTIA_ACCESS)}"
|
||||||
|
HESTIA_SECRET="${HESTIA_SECRET:-$(_readaccountconf_mutable HESTIA_SECRET)}"
|
||||||
|
HESTIA_USER="${HESTIA_USER:-$(_readaccountconf_mutable HESTIA_USER)}"
|
||||||
|
|
||||||
|
if [ -z "$HESTIA_HOST" ] || [ -z "$HESTIA_ACCESS" ] || [ -z "$HESTIA_SECRET" ]; then
|
||||||
|
HESTIA_HOST=""
|
||||||
|
HESTIA_ACCESS=""
|
||||||
|
HESTIA_SECRET=""
|
||||||
|
_err "You must export HESTIA_HOST, HESTIA_ACCESS and HESTIA_SECRET first"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HESTIA_HOST="${HESTIA_HOST%/}"
|
||||||
|
if ! echo "$HESTIA_HOST" | grep -qE '^https?://[^/]+$'; then
|
||||||
|
_err "HESTIA_HOST must be a valid URL (e.g. https://panel.example.com:8083)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HESTIA_USER" ]; then
|
||||||
|
HESTIA_USER="admin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_saveaccountconf_mutable HESTIA_HOST "$HESTIA_HOST"
|
||||||
|
_saveaccountconf_mutable HESTIA_ACCESS "$HESTIA_ACCESS"
|
||||||
|
_saveaccountconf_mutable HESTIA_SECRET "$HESTIA_SECRET"
|
||||||
|
_saveaccountconf_mutable HESTIA_USER "$HESTIA_USER"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Walk up the domain labels until the API returns a DNS zone.
|
||||||
|
# Sets _hestia_domain to the zone and _hestia_sub to the record name
|
||||||
|
# relative to the zone. The zone record listing stays in _hestia_response.
|
||||||
|
_hestia_get_root() {
|
||||||
|
_hestia_fqdn="${1%.}"
|
||||||
|
_hestia_i=1
|
||||||
|
while true; do
|
||||||
|
_hestia_h=$(printf "%s" "$_hestia_fqdn" | cut -d . -f "$_hestia_i"-100)
|
||||||
|
_debug2 _hestia_h "$_hestia_h"
|
||||||
|
if [ -z "$_hestia_h" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if _hestia_rest "v-list-dns-records" "$HESTIA_USER" "$_hestia_h" "json"; then
|
||||||
|
_hestia_domain="$_hestia_h"
|
||||||
|
if [ "$_hestia_h" = "$_hestia_fqdn" ]; then
|
||||||
|
_hestia_sub="@"
|
||||||
|
else
|
||||||
|
_hestia_sub=$(printf "%s" "$_hestia_fqdn" | cut -d . -f 1-"$(_math "$_hestia_i" - 1)")
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
_hestia_i=$(_math "$_hestia_i" + 1)
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the HestiaCP API. Args: cmd [arg1 arg2 ...]
|
||||||
|
# The response body is stored in _hestia_response.
|
||||||
|
_hestia_rest() {
|
||||||
|
_hestia_cmd=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
_hestia_data="{\"access_key\":\"$HESTIA_ACCESS\",\"secret_key\":\"$HESTIA_SECRET\",\"cmd\":\"$_hestia_cmd\""
|
||||||
|
_hestia_argn=1
|
||||||
|
for _hestia_arg in "$@"; do
|
||||||
|
_hestia_data="$_hestia_data,\"arg$_hestia_argn\":\"$_hestia_arg\""
|
||||||
|
_hestia_argn=$(_math "$_hestia_argn" + 1)
|
||||||
|
done
|
||||||
|
_hestia_data="$_hestia_data}"
|
||||||
|
|
||||||
|
_debug2 "Calling $_hestia_cmd"
|
||||||
|
_hestia_response=$(_post "$_hestia_data" "$HESTIA_HOST/api/" "" "POST" "application/json")
|
||||||
|
_hestia_ret=$?
|
||||||
|
_debug2 _hestia_response "$_hestia_response"
|
||||||
|
if [ "$_hestia_ret" != "0" ]; then
|
||||||
|
_err "Error connecting to the HestiaCP API"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if _contains "$_hestia_response" "Error:"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract records matching name and type from the v-list-dns-records
|
||||||
|
# response in _hestia_response. Prints one "id|value" line per match.
|
||||||
|
_hestia_find_records() {
|
||||||
|
_hestia_fname=$1
|
||||||
|
_hestia_ftype=$2
|
||||||
|
|
||||||
|
echo "$_hestia_response" | tr -d '\n' | sed 's/},/}\
|
||||||
|
/g' | grep -F -- "\"RECORD\": \"$_hestia_fname\"" | grep -F -- "\"TYPE\": \"$_hestia_ftype\"" | while read -r _hestia_line; do
|
||||||
|
_hestia_id=$(echo "$_hestia_line" | _egrep_o '"ID": "[^"]*' | cut -d '"' -f 4)
|
||||||
|
_hestia_value=$(echo "$_hestia_line" | _egrep_o '"VALUE": "[^"]*' | cut -d '"' -f 4)
|
||||||
|
if [ -n "$_hestia_id" ]; then
|
||||||
|
echo "$_hestia_id|$_hestia_value"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
@@ -441,18 +441,18 @@ _hostup_json_extract() {
|
|||||||
input="${2:-$line}"
|
input="${2:-$line}"
|
||||||
|
|
||||||
# First try to extract quoted values (strings)
|
# First try to extract quoted values (strings)
|
||||||
quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | _head_n 1)"
|
quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[ ]*:[ ]*\"[^\"]*\"" | _head_n 1)"
|
||||||
if [ -n "$quoted_match" ]; then
|
if [ -n "$quoted_match" ]; then
|
||||||
printf "%s" "$quoted_match" |
|
printf "%s" "$quoted_match" |
|
||||||
cut -d : -f2- |
|
cut -d : -f2- |
|
||||||
sed 's/^[[:space:]]*"//' |
|
sed 's/^[ ]*"//' |
|
||||||
sed 's/"[[:space:]]*$//' |
|
sed 's/"[ ]*$//' |
|
||||||
sed 's/\\"/"/g'
|
sed 's/\\"/"/g'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback for unquoted values (e.g., numeric IDs)
|
# Fallback for unquoted values (e.g., numeric IDs)
|
||||||
unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[[:space:]]*:[[:space:]]*[^,}]*" | _head_n 1)"
|
unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\"[ ]*:[ ]*[^,}]*" | _head_n 1)"
|
||||||
if [ -n "$unquoted_match" ]; then
|
if [ -n "$unquoted_match" ]; then
|
||||||
printf "%s" "$unquoted_match" |
|
printf "%s" "$unquoted_match" |
|
||||||
cut -d : -f2- |
|
cut -d : -f2- |
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ dns_infoblox_uddi_rm() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
record_id=$(echo "$response" | _egrep_o '"id":[[:space:]]*"[^"]*"' | _head_n 1 | cut -d '"' -f 4)
|
record_id=$(echo "$response" | _egrep_o '"id":[ ]*"[^"]*"' | _head_n 1 | cut -d '"' -f 4)
|
||||||
_debug "record_id" "$record_id"
|
_debug "record_id" "$record_id"
|
||||||
|
|
||||||
if [ -z "$record_id" ]; then
|
if [ -z "$record_id" ]; then
|
||||||
@@ -178,7 +178,7 @@ _get_root() {
|
|||||||
# Check if response contains results (even if empty)
|
# Check if response contains results (even if empty)
|
||||||
if _contains "$response" '"results"'; then
|
if _contains "$response" '"results"'; then
|
||||||
# Extract zone ID - must match the pattern dns/auth_zone/...
|
# Extract zone ID - must match the pattern dns/auth_zone/...
|
||||||
zone_id=$(echo "$response" | _egrep_o '"id":[[:space:]]*"dns/auth_zone/[^"]*"' | _head_n 1 | cut -d '"' -f 4)
|
zone_id=$(echo "$response" | _egrep_o '"id":[ ]*"dns/auth_zone/[^"]*"' | _head_n 1 | cut -d '"' -f 4)
|
||||||
if [ -n "$zone_id" ]; then
|
if [ -n "$zone_id" ]; then
|
||||||
# Found the zone
|
# Found the zone
|
||||||
_domain="$h"
|
_domain="$h"
|
||||||
|
|||||||
@@ -7,22 +7,23 @@ Options:
|
|||||||
JD_ACCESS_KEY_ID Access key ID
|
JD_ACCESS_KEY_ID Access key ID
|
||||||
JD_ACCESS_KEY_SECRET Access key secret
|
JD_ACCESS_KEY_SECRET Access key secret
|
||||||
JD_REGION Region. E.g. "cn-north-1"
|
JD_REGION Region. E.g. "cn-north-1"
|
||||||
Issues: github.com/acmesh-official/acme.sh/issues/2388
|
Issues: github.com/acmesh-official/acme.sh/issues/7202
|
||||||
|
Author: @skysaint
|
||||||
'
|
'
|
||||||
|
|
||||||
_JD_ACCOUNT="https://uc.jdcloud.com/account/accesskey"
|
_JD_ACCOUNT="https://uc.jdcloud.com/account/accesskey"
|
||||||
|
|
||||||
_JD_PROD="clouddnsservice"
|
_JD_PROD="domainservice"
|
||||||
_JD_API="jdcloud-api.com"
|
_JD_API="jdcloud-api.com"
|
||||||
|
|
||||||
_JD_API_VERSION="v1"
|
_JD_API_VERSION="v2"
|
||||||
_JD_DEFAULT_REGION="cn-north-1"
|
_JD_DEFAULT_REGION="cn-north-1"
|
||||||
|
|
||||||
_JD_HOST="$_JD_PROD.$_JD_API"
|
_JD_HOST="$_JD_PROD.$_JD_API"
|
||||||
|
|
||||||
######## Public functions #####################
|
######## Public functions #####################
|
||||||
|
|
||||||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
#Usage: dns_jd_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
dns_jd_add() {
|
dns_jd_add() {
|
||||||
fulldomain=$1
|
fulldomain=$1
|
||||||
txtvalue=$2
|
txtvalue=$2
|
||||||
@@ -58,24 +59,14 @@ dns_jd_add() {
|
|||||||
_debug _sub_domain "$_sub_domain"
|
_debug _sub_domain "$_sub_domain"
|
||||||
_debug _domain "$_domain"
|
_debug _domain "$_domain"
|
||||||
|
|
||||||
#_debug "Getting getViewTree"
|
#_debug "Getting describeViewTree"
|
||||||
|
|
||||||
_debug "Adding records"
|
_debug "Adding records"
|
||||||
|
|
||||||
_addrr="{\"req\":{\"hostRecord\":\"$_sub_domain\",\"hostValue\":\"$txtvalue\",\"ttl\":300,\"type\":\"TXT\",\"viewValue\":-1},\"regionId\":\"$JD_REGION\",\"domainId\":\"$_domain_id\"}"
|
_addrr="{\"req\":{\"hostRecord\":\"$_sub_domain\",\"hostValue\":\"$txtvalue\",\"ttl\":300,\"type\":\"TXT\",\"viewValue\":-1}}"
|
||||||
#_addrr='{"req":{"hostRecord":"xx","hostValue":"\"value4\"","jcloudRes":false,"mxPriority":null,"port":null,"ttl":300,"type":"TXT","weight":null,"viewValue":-1},"regionId":"cn-north-1","domainId":"8824"}'
|
#_addrr='{"req":{"hostRecord":"_acme-challenge","hostValue":"XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs","ttl":300,"type":"TXT","viewValue":-1}}'
|
||||||
if jd_rest POST "domain/$_domain_id/RRAdd" "" "$_addrr"; then
|
if jd_rest POST "domain/$_domain_id/ResourceRecord" "" "$_addrr"; then
|
||||||
_rid="$(echo "$response" | tr '{},' '\n' | grep '"id":' | cut -d : -f 2)"
|
|
||||||
if [ -z "$_rid" ]; then
|
|
||||||
_err "Can not find record id from the result."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
_info "TXT record added successfully."
|
_info "TXT record added successfully."
|
||||||
_srid="$(_readdomainconf "JD_CLOUD_RIDS")"
|
|
||||||
if [ "$_srid" ]; then
|
|
||||||
_rid="$_srid,$_rid"
|
|
||||||
fi
|
|
||||||
_savedomainconf "JD_CLOUD_RIDS" "$_rid"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -97,14 +88,7 @@ dns_jd_rm() {
|
|||||||
|
|
||||||
_JD_BASE_URI="$_JD_API_VERSION/regions/$JD_REGION"
|
_JD_BASE_URI="$_JD_API_VERSION/regions/$JD_REGION"
|
||||||
|
|
||||||
_info "Getting existing records for $fulldomain"
|
_info "Removing TXT record for $fulldomain"
|
||||||
_srid="$(_readdomainconf "JD_CLOUD_RIDS")"
|
|
||||||
_debug _srid "$_srid"
|
|
||||||
|
|
||||||
if [ -z "$_srid" ]; then
|
|
||||||
_err "Not rid skip"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
_debug "First detect the root zone"
|
_debug "First detect the root zone"
|
||||||
if ! _get_root "$fulldomain"; then
|
if ! _get_root "$fulldomain"; then
|
||||||
@@ -115,16 +99,37 @@ dns_jd_rm() {
|
|||||||
_debug _sub_domain "$_sub_domain"
|
_debug _sub_domain "$_sub_domain"
|
||||||
_debug _domain "$_domain"
|
_debug _domain "$_domain"
|
||||||
|
|
||||||
_cleardomainconf JD_CLOUD_RIDS
|
# List records, filter by hostRecord and use a large pageSize so it isn't missed on record-heavy zones.
|
||||||
|
if ! jd_rest GET "domain/$_domain_id/ResourceRecord" "pageSize=50&search=$_sub_domain"; then
|
||||||
|
_err "Failed to list resource records"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
_aws_tmpl_xml="{\"ids\":[$_srid],\"action\":\"del\",\"regionId\":\"$JD_REGION\",\"domainId\":\"$_domain_id\"}"
|
# Match record by hostRecord + type TXT + hostValue
|
||||||
|
_record_id=""
|
||||||
|
_matched="$(echo "$response" | tr '{' '\n' | grep "\"hostRecord\":\"$_sub_domain\"" | grep "\"type\":\"TXT\"" | grep "\"hostValue\":\"$txtvalue\"")"
|
||||||
|
_debug2 _matched "$_matched"
|
||||||
|
|
||||||
if jd_rest POST "domain/$_domain_id/RROperate" "" "$_aws_tmpl_xml" && _contains "$response" "\"code\":\"OK\""; then
|
if [ -z "$_matched" ]; then
|
||||||
|
_info "TXT record not found, nothing to remove."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_record_id="$(echo "$_matched" | tr ',' '\n' | grep "\"id\":" | cut -d : -f 2 | tr -d '"' | _head_n 1)"
|
||||||
|
_debug _record_id "$_record_id"
|
||||||
|
|
||||||
|
if [ -z "$_record_id" ]; then
|
||||||
|
_info "Could not extract record id from response, nothing to remove."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if jd_rest DELETE "domain/$_domain_id/ResourceRecord/$_record_id"; then
|
||||||
_info "TXT record deleted successfully."
|
_info "TXT record deleted successfully."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
|
||||||
|
|
||||||
|
_err "Failed to delete TXT record."
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#################### Private functions below ##################################
|
#################### Private functions below ##################################
|
||||||
@@ -134,13 +139,14 @@ _get_root() {
|
|||||||
i=1
|
i=1
|
||||||
p=1
|
p=1
|
||||||
|
|
||||||
|
if ! jd_rest GET "domain"; then
|
||||||
|
_err "error get domain list"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||||
_debug2 "Checking domain: $h"
|
_debug2 "Checking domain: $h"
|
||||||
if ! jd_rest GET "domain"; then
|
|
||||||
_err "error get domain list"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$h" ]; then
|
if [ -z "$h" ]; then
|
||||||
#not valid
|
#not valid
|
||||||
_err "Invalid domain"
|
_err "Invalid domain"
|
||||||
@@ -168,6 +174,8 @@ _get_root() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Use '%b' with printf to expand \n escapes in CanonicalRequest and StringToSign.
|
||||||
|
# Use '%s' for plain values that contain no escapes to avoid unintended expansion.
|
||||||
#method uri qstr data
|
#method uri qstr data
|
||||||
jd_rest() {
|
jd_rest() {
|
||||||
mtd="$1"
|
mtd="$1"
|
||||||
@@ -220,7 +228,7 @@ jd_rest() {
|
|||||||
CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$RequestPayloadHash"
|
CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$RequestPayloadHash"
|
||||||
_debug2 CanonicalRequest "$CanonicalRequest"
|
_debug2 CanonicalRequest "$CanonicalRequest"
|
||||||
|
|
||||||
HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)"
|
HashedCanonicalRequest="$(printf '%b' "$CanonicalRequest" | _digest "$Hash" hex)"
|
||||||
_debug2 HashedCanonicalRequest "$HashedCanonicalRequest"
|
_debug2 HashedCanonicalRequest "$HashedCanonicalRequest"
|
||||||
|
|
||||||
Algorithm="JDCLOUD2-HMAC-SHA256"
|
Algorithm="JDCLOUD2-HMAC-SHA256"
|
||||||
@@ -246,19 +254,19 @@ jd_rest() {
|
|||||||
kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")"
|
kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")"
|
||||||
_secure_debug2 kSecretH "$kSecretH"
|
_secure_debug2 kSecretH "$kSecretH"
|
||||||
|
|
||||||
kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)"
|
kDateH="$(printf '%s' "$RequestDateOnly" | _hmac "$Hash" "$kSecretH" hex)"
|
||||||
_debug2 kDateH "$kDateH"
|
_debug2 kDateH "$kDateH"
|
||||||
|
|
||||||
kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)"
|
kRegionH="$(printf '%s' "$Region" | _hmac "$Hash" "$kDateH" hex)"
|
||||||
_debug2 kRegionH "$kRegionH"
|
_debug2 kRegionH "$kRegionH"
|
||||||
|
|
||||||
kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)"
|
kServiceH="$(printf '%s' "$Service" | _hmac "$Hash" "$kRegionH" hex)"
|
||||||
_debug2 kServiceH "$kServiceH"
|
_debug2 kServiceH "$kServiceH"
|
||||||
|
|
||||||
kSigningH="$(printf "%s" "jdcloud2_request" | _hmac "$Hash" "$kServiceH" hex)"
|
kSigningH="$(printf '%s' "jdcloud2_request" | _hmac "$Hash" "$kServiceH" hex)"
|
||||||
_debug2 kSigningH "$kSigningH"
|
_debug2 kSigningH "$kSigningH"
|
||||||
|
|
||||||
signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)"
|
signature="$(printf '%b' "$StringToSign" | _hmac "$Hash" "$kSigningH" hex)"
|
||||||
_debug2 signature "$signature"
|
_debug2 signature "$signature"
|
||||||
|
|
||||||
Authorization="$Algorithm Credential=$JD_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
|
Authorization="$Algorithm Credential=$JD_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
dns_linode_info='Linode.com (Old)
|
|
||||||
Deprecated. Use dns_linode_v4
|
|
||||||
Site: Linode.com
|
|
||||||
Options:
|
|
||||||
LINODE_API_KEY API Key
|
|
||||||
Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>
|
|
||||||
'
|
|
||||||
|
|
||||||
LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action="
|
|
||||||
|
|
||||||
######## Public functions #####################
|
|
||||||
|
|
||||||
#Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
|
||||||
dns_linode_add() {
|
|
||||||
fulldomain="${1}"
|
|
||||||
txtvalue="${2}"
|
|
||||||
|
|
||||||
if ! _Linode_API; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_info "Using Linode"
|
|
||||||
_debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'"
|
|
||||||
|
|
||||||
_debug "First detect the root zone"
|
|
||||||
if ! _get_root "$fulldomain"; then
|
|
||||||
_err "Domain does not exist."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
_debug _domain_id "$_domain_id"
|
|
||||||
_debug _sub_domain "$_sub_domain"
|
|
||||||
_debug _domain "$_domain"
|
|
||||||
|
|
||||||
_parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue"
|
|
||||||
|
|
||||||
if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then
|
|
||||||
_resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
|
|
||||||
_debug _resource_id "$_resource_id"
|
|
||||||
|
|
||||||
if [ -z "$_resource_id" ]; then
|
|
||||||
_err "Error adding the domain resource."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_info "Domain resource successfully added."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#Usage: dns_linode_rm _acme-challenge.www.domain.com
|
|
||||||
dns_linode_rm() {
|
|
||||||
fulldomain="${1}"
|
|
||||||
|
|
||||||
if ! _Linode_API; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_info "Using Linode"
|
|
||||||
_debug "Calling: dns_linode_rm() '${fulldomain}'"
|
|
||||||
|
|
||||||
_debug "First detect the root zone"
|
|
||||||
if ! _get_root "$fulldomain"; then
|
|
||||||
_err "Domain does not exist."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
_debug _domain_id "$_domain_id"
|
|
||||||
_debug _sub_domain "$_sub_domain"
|
|
||||||
_debug _domain "$_domain"
|
|
||||||
|
|
||||||
_parameters="&DomainID=$_domain_id"
|
|
||||||
|
|
||||||
if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then
|
|
||||||
response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
|
|
||||||
|
|
||||||
resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")"
|
|
||||||
if [ "$resource" ]; then
|
|
||||||
_resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
|
|
||||||
if [ "$_resource_id" ]; then
|
|
||||||
_debug _resource_id "$_resource_id"
|
|
||||||
|
|
||||||
_parameters="&DomainID=$_domain_id&ResourceID=$_resource_id"
|
|
||||||
|
|
||||||
if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then
|
|
||||||
_resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
|
|
||||||
_debug _resource_id "$_resource_id"
|
|
||||||
|
|
||||||
if [ -z "$_resource_id" ]; then
|
|
||||||
_err "Error deleting the domain resource."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_info "Domain resource successfully deleted."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#################### Private functions below ##################################
|
|
||||||
|
|
||||||
_Linode_API() {
|
|
||||||
if [ -z "$LINODE_API_KEY" ]; then
|
|
||||||
LINODE_API_KEY=""
|
|
||||||
|
|
||||||
_err "You didn't specify the Linode API key yet."
|
|
||||||
_err "Please create your key and try again."
|
|
||||||
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_saveaccountconf LINODE_API_KEY "$LINODE_API_KEY"
|
|
||||||
}
|
|
||||||
|
|
||||||
#################### Private functions below ##################################
|
|
||||||
#_acme-challenge.www.domain.com
|
|
||||||
#returns
|
|
||||||
# _sub_domain=_acme-challenge.www
|
|
||||||
# _domain=domain.com
|
|
||||||
# _domain_id=12345
|
|
||||||
_get_root() {
|
|
||||||
domain=$1
|
|
||||||
i=2
|
|
||||||
p=1
|
|
||||||
|
|
||||||
if _rest GET "domain.list"; then
|
|
||||||
response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
|
|
||||||
while true; do
|
|
||||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
|
||||||
_debug h "$h"
|
|
||||||
if [ -z "$h" ]; then
|
|
||||||
#not valid
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")"
|
|
||||||
if [ "$hostedzone" ]; then
|
|
||||||
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
|
|
||||||
if [ "$_domain_id" ]; then
|
|
||||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
|
||||||
_domain=$h
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
p=$i
|
|
||||||
i=$(_math "$i" + 1)
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#method method action data
|
|
||||||
_rest() {
|
|
||||||
mtd="$1"
|
|
||||||
ep="$2"
|
|
||||||
data="$3"
|
|
||||||
|
|
||||||
_debug mtd "$mtd"
|
|
||||||
_debug ep "$ep"
|
|
||||||
|
|
||||||
export _H1="Accept: application/json"
|
|
||||||
export _H2="Content-Type: application/json"
|
|
||||||
|
|
||||||
if [ "$mtd" != "GET" ]; then
|
|
||||||
# both POST and DELETE.
|
|
||||||
_debug data "$data"
|
|
||||||
response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")"
|
|
||||||
else
|
|
||||||
response="$(_get "$LINODE_API_URL$ep$data")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$?" != "0" ]; then
|
|
||||||
_err "error $ep"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
_debug2 response "$response"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
121
dnsapi/dns_myloc.sh
Executable file
121
dnsapi/dns_myloc.sh
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
dns_myloc_info='myloc.de
|
||||||
|
Site: myloc.de
|
||||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_myloc
|
||||||
|
Issues: github.com/acmesh-official/acme.sh/issues/5193
|
||||||
|
Options:
|
||||||
|
MYLOC_token API token
|
||||||
|
'
|
||||||
|
|
||||||
|
# updater for the (experimental) API of myloc.de / webtropia.com
|
||||||
|
# usage: acme.sh --issue -d example.com --dns dns_myloc --dnssleep 60
|
||||||
|
# API documentation at https://apidoc.myloc.de/
|
||||||
|
# As the API does not support quering available zones yet, the zone for a given
|
||||||
|
# fulldomain is searched recursively by removing prefixes one-by-one.
|
||||||
|
|
||||||
|
_myloc_api="https://zkm.myloc.de/api"
|
||||||
|
|
||||||
|
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_myloc_add() {
|
||||||
|
_myloc_fulldomain=$1
|
||||||
|
_myloc_txtvalue=$2
|
||||||
|
|
||||||
|
_myloc_token="${MYLOC_token:-$(_readaccountconf_mutable MYLOC_token)}"
|
||||||
|
if [ -z "$_myloc_token" ]; then
|
||||||
|
_err "You didn't specify MYLOC_token"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export _H1="Content-Type: application/json"
|
||||||
|
export _H2="Authorization: Bearer $_myloc_token"
|
||||||
|
|
||||||
|
_myloc_zone="$(_myloc_get_zone "$_myloc_fulldomain")"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# save token if the previous request was successful
|
||||||
|
_saveaccountconf_mutable MYLOC_token "$_myloc_token"
|
||||||
|
|
||||||
|
_info "Adding record"
|
||||||
|
_myloc_record="{\"type\":\"TXT\",\"name\":\"${_myloc_fulldomain}\",\"content\":\"\\\"${_myloc_txtvalue}\\\"\",\"ttl\":60}"
|
||||||
|
_debug "add record request $_myloc_record to ${_myloc_api}/dns/zone/${_myloc_zone}"
|
||||||
|
_myloc_response="$(_post "$_myloc_record" "${_myloc_api}/dns/zone/${_myloc_zone}" "" "PUT")"
|
||||||
|
_myloc_status=$?
|
||||||
|
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||||
|
_debug "add record response $_code $_myloc_response"
|
||||||
|
if [ $_myloc_status -ne 0 ]; then
|
||||||
|
_err "Add txt record curl error."
|
||||||
|
return 1
|
||||||
|
elif [ "$_code" = "204" ] && [ -z "$_myloc_response" ]; then
|
||||||
|
_info "Add txt record success"
|
||||||
|
return 0
|
||||||
|
elif _contains "$_myloc_response" "error" || _contains "$_myloc_response" "unexpected"; then
|
||||||
|
_err "Add txt record api error."
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
_err "Add txt record unknown response."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#_myloc_fulldomain _myloc_txtvalue
|
||||||
|
dns_myloc_rm() {
|
||||||
|
_myloc_fulldomain=$1
|
||||||
|
_myloc_txtvalue=$2
|
||||||
|
|
||||||
|
_myloc_token="${MYLOC_token:-$(_readaccountconf_mutable MYLOC_token)}"
|
||||||
|
if [ -z "$_myloc_token" ]; then
|
||||||
|
_err "You didn't specify MYLOC_token"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export _H1="Content-Type: application/json"
|
||||||
|
export _H2="Authorization: Bearer $_myloc_token"
|
||||||
|
|
||||||
|
_myloc_zone="$(_myloc_get_zone "$_myloc_fulldomain")"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# save token if the previous request was successful
|
||||||
|
_saveaccountconf_mutable MYLOC_token "$_myloc_token"
|
||||||
|
|
||||||
|
_info "Deleting record for $_myloc_fulldomain"
|
||||||
|
_myloc_record="{\"type\":\"TXT\",\"name\":\"${_myloc_fulldomain}\",\"content\":\"\\\"${_myloc_txtvalue}\\\"\"}"
|
||||||
|
_debug "delete record $_myloc_record"
|
||||||
|
_myloc_response="$(_post "$_myloc_record" "${_myloc_api}/dns/zone/${_myloc_zone}" "" "DELETE")"
|
||||||
|
_myloc_status=$?
|
||||||
|
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||||
|
_debug "delete response $_code $_myloc_response"
|
||||||
|
if [ $_myloc_status -ne 0 ] || [ "$_code" != "204" ]; then
|
||||||
|
_err "Failed to delete record"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: _myloc_get_zone "_acme-challenge.sub1.mydomain.com"
|
||||||
|
# Subdomains are walked until a zone is found or TLD is reached
|
||||||
|
_myloc_get_zone() {
|
||||||
|
_myloc_zone=$1
|
||||||
|
|
||||||
|
while [ "${_myloc_zone#*.}" != "$_myloc_zone" ]; do
|
||||||
|
_debug "Get zone trying $_myloc_zone"
|
||||||
|
_myloc_response="$(_get "${_myloc_api}/dns/zone/${_myloc_zone}")"
|
||||||
|
_myloc_status=$?
|
||||||
|
_debug "Get zone response $_myloc_response"
|
||||||
|
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||||
|
if [ $_myloc_status -eq 0 ] && [ "$_code" = "200" ]; then
|
||||||
|
_debug "Get zone success for $_myloc_zone"
|
||||||
|
echo "${_myloc_zone}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
_myloc_zone="${_myloc_zone#*.}"
|
||||||
|
done
|
||||||
|
|
||||||
|
_err "Get zone failed for all candidates"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -104,6 +104,9 @@ _get_root_by_getList() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
_namecheap_domain_list=$(echo "$response" | _egrep_o '<Domain [^>]*')
|
||||||
|
_debug2 domain_list "$_namecheap_domain_list"
|
||||||
|
|
||||||
i=2
|
i=2
|
||||||
p=1
|
p=1
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ _get_root_by_getList() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! _contains "$response" "$h"; then
|
if ! _namecheap_is_our_dns "$h"; then
|
||||||
_debug "$h not found"
|
_debug "$h not found"
|
||||||
else
|
else
|
||||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||||
@@ -133,6 +136,29 @@ _get_root_by_getList() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Usage: _namecheap_is_our_dns <domain>
|
||||||
|
#Succeeds only when domains.getList listed exactly <domain> AND that entry is
|
||||||
|
#served by Namecheap's own DNS. A domain parked on Namecheap's webhosting DNS
|
||||||
|
#is listed with IsOurDNS="false", and every dns.getHosts/setHosts call against
|
||||||
|
#it is refused with error 2030288 "not using proper DNS servers". Accepting
|
||||||
|
#such a domain as the root zone hides a subdomain that IS delegated to
|
||||||
|
#Namecheap DNS and that the getHosts probe below would have found.
|
||||||
|
#https://github.com/acmesh-official/acme.sh/issues/7178
|
||||||
|
_namecheap_is_our_dns() {
|
||||||
|
_namecheap_entry=$(echo "$_namecheap_domain_list" | grep -F " Name=\"$1\"" | _head_n 1)
|
||||||
|
if [ -z "$_namecheap_entry" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_namecheap_ourdns=$(echo "$_namecheap_entry" | _egrep_o ' IsOurDNS="[^"]*' | cut -d '"' -f 2)
|
||||||
|
_debug2 "$1 IsOurDNS" "$_namecheap_ourdns"
|
||||||
|
|
||||||
|
if [ "$_namecheap_ourdns" = "true" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
_get_root_by_getHosts() {
|
_get_root_by_getHosts() {
|
||||||
i=100
|
i=100
|
||||||
p=99
|
p=99
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ dns_netcup_add() {
|
|||||||
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
|
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
|
||||||
exit=$(_math "$exit" + 1)
|
exit=$(_math "$exit" + 1)
|
||||||
i=$exit
|
i=$exit
|
||||||
|
_nc_last=$(_nc_lastlevel "$i")
|
||||||
|
_nc_found=""
|
||||||
|
|
||||||
while
|
while
|
||||||
[ "$exit" -gt 0 ]
|
[ "$exit" -ge "$_nc_last" ]
|
||||||
do
|
do
|
||||||
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
|
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
|
||||||
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
|
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
|
||||||
@@ -51,12 +53,18 @@ dns_netcup_add() {
|
|||||||
_err "$msg"
|
_err "$msg"
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
|
_nc_found=1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
exit=$(_math "$exit" - 1)
|
exit=$(_math "$exit" - 1)
|
||||||
done
|
done
|
||||||
|
if [ -z "$_nc_found" ]; then
|
||||||
|
_err "$msg"
|
||||||
|
_nc_nozone "$fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
logout
|
logout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +78,11 @@ dns_netcup_rm() {
|
|||||||
exit=$(_math "$exit" + 1)
|
exit=$(_math "$exit" + 1)
|
||||||
i=$exit
|
i=$exit
|
||||||
rec=""
|
rec=""
|
||||||
|
_nc_last=$(_nc_lastlevel "$i")
|
||||||
|
_nc_found=""
|
||||||
|
|
||||||
while
|
while
|
||||||
[ "$exit" -gt 0 ]
|
[ "$exit" -ge "$_nc_last" ]
|
||||||
do
|
do
|
||||||
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
|
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
|
||||||
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
|
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
|
||||||
@@ -89,12 +99,18 @@ dns_netcup_rm() {
|
|||||||
_err "$msg"
|
_err "$msg"
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
|
_nc_found=1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
exit=$(_math "$exit" - 1)
|
exit=$(_math "$exit" - 1)
|
||||||
done
|
done
|
||||||
|
if [ -z "$_nc_found" ]; then
|
||||||
|
_err "$msg"
|
||||||
|
_nc_nozone "$fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
ida=0000
|
ida=0000
|
||||||
idv=0001
|
idv=0001
|
||||||
@@ -125,6 +141,27 @@ dns_netcup_rm() {
|
|||||||
logout
|
logout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The zone is looked up by walking the challenge name from the right, one
|
||||||
|
# label at a time. The leftmost label is the challenge prefix, so the full
|
||||||
|
# name itself can never be a zone: asking netcup for it only returns 4013
|
||||||
|
# "Validation Error", which would then mask the real 5028 "zone could not be
|
||||||
|
# found". Stop one label short, unless the name is too short to have a
|
||||||
|
# challenge prefix at all (manual invocation).
|
||||||
|
# levels
|
||||||
|
_nc_lastlevel() {
|
||||||
|
if [ "$1" -ge 3 ]; then
|
||||||
|
echo 2
|
||||||
|
else
|
||||||
|
echo 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# fulldomain
|
||||||
|
_nc_nozone() {
|
||||||
|
_err "No DNS zone for $1 was found at netcup."
|
||||||
|
_err "Check that the domain belongs to the account of the configured NC_CID and that its DNS is hosted at netcup."
|
||||||
|
}
|
||||||
|
|
||||||
_login() {
|
_login() {
|
||||||
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
|
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
|
||||||
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
|
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
|
||||||
|
|||||||
244
dnsapi/dns_nexdns.sh
Executable file
244
dnsapi/dns_nexdns.sh
Executable file
@@ -0,0 +1,244 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
dns_nexdns_info='NexDNS
|
||||||
|
Site: nexdns.tech
|
||||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nexdns
|
||||||
|
Options:
|
||||||
|
NEXDNS_Token API token. Can be created at https://nexdns.tech/settings/api-keys
|
||||||
|
NEXDNS_Api API base url. Default "https://api.nexdns.tech/v1". Optional.
|
||||||
|
Issues: github.com/acmesh-official/acme.sh/issues/7179
|
||||||
|
Author: NexDNS <https://github.com/nexdns>
|
||||||
|
'
|
||||||
|
|
||||||
|
NEXDNS_Api_Default="https://api.nexdns.tech/v1"
|
||||||
|
|
||||||
|
######## Public functions #####################
|
||||||
|
|
||||||
|
#Usage: dns_nexdns_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_nexdns_add() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
if ! _nexdns_init; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_saveaccountconf_mutable NEXDNS_Token "$NEXDNS_Token"
|
||||||
|
if [ "$NEXDNS_Api" != "$NEXDNS_Api_Default" ]; then
|
||||||
|
_saveaccountconf_mutable NEXDNS_Api "$NEXDNS_Api"
|
||||||
|
else
|
||||||
|
_clearaccountconf_mutable NEXDNS_Api
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug "First detect the root zone"
|
||||||
|
if ! _get_root "$fulldomain"; then
|
||||||
|
_err "Cannot find the zone of $fulldomain in this NexDNS account."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
_debug _domain "$_domain"
|
||||||
|
_debug _domain_id "$_domain_id"
|
||||||
|
|
||||||
|
_info "Adding the TXT record for $fulldomain"
|
||||||
|
if ! _nexdns_rest POST "zones/$_domain_id/records" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "The TXT record has been added."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#Usage: dns_nexdns_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_nexdns_rm() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
if ! _nexdns_init; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug "First detect the root zone"
|
||||||
|
if ! _get_root "$fulldomain"; then
|
||||||
|
_err "Cannot find the zone of $fulldomain in this NexDNS account."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
_debug _domain "$_domain"
|
||||||
|
_debug _domain_id "$_domain_id"
|
||||||
|
|
||||||
|
_info "Removing the TXT record for $fulldomain"
|
||||||
|
if ! _nexdns_rest GET "zones/$_domain_id/records?type=TXT&name=$_sub_domain"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#All the challenge records share one name and one type, so the value is the
|
||||||
|
#only thing that tells them apart. A certificate covering example.com and
|
||||||
|
#*.example.com puts two of them at the same name at the same time.
|
||||||
|
_record_id="$(echo "$response" | tr '{' "\n" | grep -- "$txtvalue" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||||
|
_debug _record_id "$_record_id"
|
||||||
|
|
||||||
|
if [ -z "$_record_id" ]; then
|
||||||
|
_info "The TXT record is already gone, nothing to remove."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _nexdns_rest DELETE "zones/$_domain_id/records/$_record_id"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "The TXT record has been removed."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#################### Private functions below ##################################
|
||||||
|
|
||||||
|
#Reads the token and the api url, and applies the default url.
|
||||||
|
_nexdns_init() {
|
||||||
|
NEXDNS_Token="${NEXDNS_Token:-$(_readaccountconf_mutable NEXDNS_Token)}"
|
||||||
|
NEXDNS_Api="${NEXDNS_Api:-$(_readaccountconf_mutable NEXDNS_Api)}"
|
||||||
|
|
||||||
|
if [ -z "$NEXDNS_Token" ]; then
|
||||||
|
_err "You have not set NEXDNS_Token yet."
|
||||||
|
_err "Create one at https://nexdns.tech/settings/api-keys, on a plan that includes API access, then:"
|
||||||
|
_err "export NEXDNS_Token=\"your-api-token\""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$NEXDNS_Api" ]; then
|
||||||
|
NEXDNS_Api="$NEXDNS_Api_Default"
|
||||||
|
fi
|
||||||
|
#A trailing slash would make every request path begin with a double slash.
|
||||||
|
NEXDNS_Api="$(echo "$NEXDNS_Api" | sed 's|/*$||')"
|
||||||
|
_debug NEXDNS_Api "$NEXDNS_Api"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#_acme-challenge.www.example.com
|
||||||
|
#returns
|
||||||
|
# _sub_domain=_acme-challenge.www
|
||||||
|
# _domain=example.com
|
||||||
|
# _domain_id=Zm9vYmFy
|
||||||
|
_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 ! _nexdns_rest GET "zones?search=$h&per_page=100"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#search matches on a substring, so the page can also hold zones that merely
|
||||||
|
#contain h. Take the id of the one whose name is exactly h.
|
||||||
|
_domain_id="$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||||
|
if [ "$_domain_id" ]; then
|
||||||
|
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||||
|
_domain=$h
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
p=$i
|
||||||
|
i=$(_math "$i" + 1)
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
#Usage: _nexdns_rest GET|POST|DELETE path [body] [attempt]
|
||||||
|
_nexdns_rest() {
|
||||||
|
m=$1
|
||||||
|
ep=$2
|
||||||
|
data=$3
|
||||||
|
attempt=${4:-1}
|
||||||
|
_debug "$ep"
|
||||||
|
|
||||||
|
export _H1="Authorization: Bearer $NEXDNS_Token"
|
||||||
|
export _H2="Content-Type: application/json"
|
||||||
|
export _H3="Accept: application/json"
|
||||||
|
|
||||||
|
if [ "$m" = "GET" ]; then
|
||||||
|
response="$(_get "$NEXDNS_Api/$ep")"
|
||||||
|
else
|
||||||
|
_debug2 data "$data"
|
||||||
|
response="$(_post "$data" "$NEXDNS_Api/$ep" "" "$m" "application/json")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$?" != "0" ]; then
|
||||||
|
_err "error $ep"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#A single certificate costs a handful of requests, but a renewal sweep over
|
||||||
|
#many of them meets the account's per-minute budget, and that run is
|
||||||
|
#unattended. Retry-After is treated as a floor: an api may report the time one
|
||||||
|
#token needs at an average rate and name a second when nothing frees for a
|
||||||
|
#minute, so the wait grows on its own across attempts.
|
||||||
|
if [ "$(grep "^HTTP" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" = "429" ]; then
|
||||||
|
if [ "$attempt" -ge 4 ]; then
|
||||||
|
_err "$m $ep failed: rate limited, and the wait budget is spent"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_retry_after="$(grep -i "^Retry-After" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d : -f 2 | tr -d " \r\n")"
|
||||||
|
_backoff="$(_math "$attempt" \* 15)"
|
||||||
|
#The header may also carry an http date. Anything but a plain count of
|
||||||
|
#seconds falls through to the backoff rather than being parsed: guessing
|
||||||
|
#wrong about a date is worse than waiting a known interval, and comparing a
|
||||||
|
#date numerically would abort the hook outright.
|
||||||
|
case "$_retry_after" in
|
||||||
|
"" | *[!0-9]*) _retry_after="$_backoff" ;;
|
||||||
|
*)
|
||||||
|
if [ "$_retry_after" -lt "$_backoff" ]; then
|
||||||
|
_retry_after="$_backoff"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#A wait longer than this is a refusal rather than a schedule, and sleeping
|
||||||
|
#it out would hold the hook for the length of the window. Hand the run back
|
||||||
|
#instead, so the next cron pass picks it up.
|
||||||
|
if [ "$_retry_after" -gt 120 ]; then
|
||||||
|
_err "$m $ep failed: rate limited for ${_retry_after}s, longer than this hook will wait"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "Rate limited by the NexDNS API; retrying in $_retry_after seconds."
|
||||||
|
_sleep "$_retry_after"
|
||||||
|
|
||||||
|
_nexdns_rest "$m" "$ep" "$data" "$(_math "$attempt" + 1)"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
#Whitespace between a key and its value would defeat every match made on the
|
||||||
|
#body, here and in the callers.
|
||||||
|
response="$(echo "$response" | _normalizeJson)"
|
||||||
|
_debug2 response "$response"
|
||||||
|
|
||||||
|
#The status line decides success, not the body: a delete answers 204 with no
|
||||||
|
#body at all, and a record whose own content contains "error": would otherwise
|
||||||
|
#turn a stored value into a reported failure. The body is read only for the
|
||||||
|
#message once the status says the request was rejected.
|
||||||
|
_code="$(grep "^HTTP" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||||
|
_debug2 _code "$_code"
|
||||||
|
case "$_code" in
|
||||||
|
"" | 2*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#A rejected request carries {"error":{"code":..,"message":..}}, so say what the
|
||||||
|
#api says went wrong.
|
||||||
|
_message="$(echo "$response" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||||
|
if [ -z "$_message" ]; then
|
||||||
|
_message="status $_code"
|
||||||
|
fi
|
||||||
|
_err "$m $ep failed: $_message"
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -227,7 +227,7 @@ _poweradmin_rest() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if printf '%s' "$response" | grep -q '"success"[[:space:]]*:[[:space:]]*false'; then
|
if printf '%s' "$response" | grep -q '"success"[ ]*:[ ]*false'; then
|
||||||
_err "API reported failure on $method $ep"
|
_err "API reported failure on $method $ep"
|
||||||
_debug "Response: $response"
|
_debug "Response: $response"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ dns_rage4_rm() {
|
|||||||
_debug "Getting txt records"
|
_debug "Getting txt records"
|
||||||
_rage4_rest "getrecords/?id=${_domain_id}"
|
_rage4_rest "getrecords/?id=${_domain_id}"
|
||||||
|
|
||||||
_record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p')
|
_record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -n 's/.*"id":\([0-9][0-9]*\),.*/\1/p')
|
||||||
if [ -z "$_record_id" ]; then
|
if [ -z "$_record_id" ]; then
|
||||||
_err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'."
|
_err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'."
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
145
dnsapi/dns_rltx.sh
Normal file
145
dnsapi/dns_rltx.sh
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
dns_rltx_info='Realtox Media Cloudpanel DNS API
|
||||||
|
Site: realtoxmedia.de
|
||||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rltx
|
||||||
|
Options:
|
||||||
|
RLTX_Key API Key
|
||||||
|
RLTX_OrganizationID Organization ID
|
||||||
|
'
|
||||||
|
|
||||||
|
######## Public functions #####################
|
||||||
|
|
||||||
|
#Usage: dns_rltx_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_rltx_add() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
_info "Using Realtox Media Cloudpanel DNS API"
|
||||||
|
_debug fulldomain "$fulldomain"
|
||||||
|
_debug txtvalue "$txtvalue"
|
||||||
|
|
||||||
|
if ! _rltx_init; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _get_root "$fulldomain"; then
|
||||||
|
_err "Could not find matching DNS zone for $fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug _domain_id "$_domain_id"
|
||||||
|
_debug _domain "$_domain"
|
||||||
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
|
||||||
|
data="{\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"ttl\":120}"
|
||||||
|
if ! _rltx_rest POST "domains/$_domain_id/dns/acme-txt" "$data"; then
|
||||||
|
_err "Add TXT record request failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if _contains "$response" '"status":"added"'; then
|
||||||
|
_info "Added TXT record, OK"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
_err "Add TXT record failed: $response"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#Usage: fulldomain txtvalue
|
||||||
|
#Remove the txt record after validation.
|
||||||
|
dns_rltx_rm() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
_info "Using Realtox Media Cloudpanel DNS API"
|
||||||
|
_debug fulldomain "$fulldomain"
|
||||||
|
_debug txtvalue "$txtvalue"
|
||||||
|
|
||||||
|
if ! _rltx_init; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _get_root "$fulldomain"; then
|
||||||
|
_err "Could not find matching DNS zone for $fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug _domain_id "$_domain_id"
|
||||||
|
_debug _domain "$_domain"
|
||||||
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
|
||||||
|
data="{\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"ttl\":120}"
|
||||||
|
if ! _rltx_rest DELETE "domains/$_domain_id/dns/acme-txt" "$data"; then
|
||||||
|
_err "Remove TXT record request failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if _contains "$response" '"status":"removed"'; then
|
||||||
|
_info "Removed TXT record, OK"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
_err "Remove TXT record failed: $response"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#################### Private functions below ##################################
|
||||||
|
|
||||||
|
_rltx_init() {
|
||||||
|
RLTX_Key="${RLTX_Key:-$(_readaccountconf_mutable RLTX_Key)}"
|
||||||
|
RLTX_OrganizationID="${RLTX_OrganizationID:-$(_readaccountconf_mutable RLTX_OrganizationID)}"
|
||||||
|
|
||||||
|
if [ -z "$RLTX_Key" ] || [ -z "$RLTX_OrganizationID" ]; then
|
||||||
|
RLTX_Key=""
|
||||||
|
RLTX_OrganizationID=""
|
||||||
|
_err "Please specify RLTX_Key and RLTX_OrganizationID."
|
||||||
|
_err "You can export them and retry: export RLTX_Key=... RLTX_OrganizationID=..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_saveaccountconf_mutable RLTX_Key "$RLTX_Key"
|
||||||
|
_saveaccountconf_mutable RLTX_OrganizationID "$RLTX_OrganizationID"
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_root() {
|
||||||
|
domain=$1
|
||||||
|
fqdn_encoded="$(printf "%s" "$domain" | _url_encode)"
|
||||||
|
if ! _rltx_rest GET "domains/dns/acme-zone?fqdn=$fqdn_encoded"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! _contains "$response" '"domain_id":"'; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_domain_id="$(printf "%s" "$response" | _egrep_o '"domain_id":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)"
|
||||||
|
_domain="$(printf "%s" "$response" | _egrep_o '"zone":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)"
|
||||||
|
_sub_domain="$(printf "%s" "$response" | _egrep_o '"record_name":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)"
|
||||||
|
|
||||||
|
if [ -z "$_domain_id" ] || [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_rltx_rest() {
|
||||||
|
m=$1
|
||||||
|
ep="$2"
|
||||||
|
data="$3"
|
||||||
|
_debug "$ep"
|
||||||
|
|
||||||
|
export _H1="X-API-Key: $RLTX_Key"
|
||||||
|
export _H2="X-Organization-ID: $RLTX_OrganizationID"
|
||||||
|
export _H3="Content-Type: application/json"
|
||||||
|
|
||||||
|
if [ "$m" = "GET" ]; then
|
||||||
|
response="$(_get "https://api.ccp.realtoxmedia.de/api/$ep")"
|
||||||
|
else
|
||||||
|
_debug2 data "$data"
|
||||||
|
response="$(_post "$data" "https://api.ccp.realtoxmedia.de/api/$ep" "" "$m")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$?" != "0" ]; then
|
||||||
|
_err "Realtox Media Cloudpanel API request failed: $ep"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug2 response "$response"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -368,7 +368,7 @@ _get_auth_token() {
|
|||||||
_data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}"
|
_data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}"
|
||||||
export _H1="Content-Type: application/json"
|
export _H1="Content-Type: application/json"
|
||||||
_result=$(_post "$_data_auth" "$auth_uri")
|
_result=$(_post "$_data_auth" "$auth_uri")
|
||||||
_token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p")
|
_token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | cut -d ':' -f 2- | tr -d ' \t\r')
|
||||||
_dt_curr=$(date +%s)
|
_dt_curr=$(date +%s)
|
||||||
SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}"
|
SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}"
|
||||||
_saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2"
|
_saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2"
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ dns_selfhost_add() {
|
|||||||
# only match full domains (at the beginning of the string or with a leading whitespace),
|
# only match full domains (at the beginning of the string or with a leading whitespace),
|
||||||
# e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
|
# e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
|
||||||
# if the domain is defined multiple times only the last occurance will be matched
|
# if the domain is defined multiple times only the last occurance will be matched
|
||||||
mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p")
|
# prepend a space to each line so "start of line" and "after whitespace"
|
||||||
|
# can both be matched as "after a space/tab" (portable BRE, no ERE (^|..))
|
||||||
|
_selfhost_tab="$(printf '\t')"
|
||||||
|
mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*:\{0,1\}[0-9]*\).*/\1/p")
|
||||||
_debug2 mapEntry "$mapEntry"
|
_debug2 mapEntry "$mapEntry"
|
||||||
if test -z "$mapEntry"; then
|
if test -z "$mapEntry"; then
|
||||||
_err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
|
_err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
|
||||||
@@ -54,7 +57,7 @@ dns_selfhost_add() {
|
|||||||
rid2=$(echo "$mapEntry" | cut -d: -f3)
|
rid2=$(echo "$mapEntry" | cut -d: -f3)
|
||||||
|
|
||||||
# read last used rid domain
|
# read last used rid domain
|
||||||
lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p")
|
lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*\).*/\1/p")
|
||||||
_debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
|
_debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
|
||||||
lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
|
lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
|
||||||
|
|
||||||
|
|||||||
@@ -145,8 +145,8 @@ _udr_rest() {
|
|||||||
_debug data "${data}"
|
_debug data "${data}"
|
||||||
response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")"
|
response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")"
|
||||||
|
|
||||||
_code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
_code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | tr -d ' \t\r')
|
||||||
_description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
_description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | tr -d '\r' | sed -e 's/^[ ]*//' -e 's/[ ]*$//')
|
||||||
|
|
||||||
_debug response_code "$_code"
|
_debug response_code "$_code"
|
||||||
_debug response_description "$_description"
|
_debug response_description "$_description"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ dns_world4you_add() {
|
|||||||
if _contains "$res" "successfully"; then
|
if _contains "$res" "successfully"; then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
msg=$(echo "$res" | grep -A 20 'alert-notification' | grep 'class="weak-title">[^<]' | sed 's/<[^>]*>//g;s/^\s*//g')
|
msg=$(_w4y_alert_msg "$res")
|
||||||
if [ "$msg" = '' ]; then
|
if [ "$msg" = '' ]; then
|
||||||
_err "Unable to add record: Unknown error"
|
_err "Unable to add record: Unknown error"
|
||||||
echo "$ret" >'error-01.html'
|
echo "$ret" >'error-01.html'
|
||||||
@@ -125,7 +125,7 @@ dns_world4you_rm() {
|
|||||||
if _contains "$res" "successfully"; then
|
if _contains "$res" "successfully"; then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
msg=$(echo "$res" | grep -A 20 'alert-notification' | grep 'class="weak-title">[^<]' | sed 's/<[^>]*>//g;s/^\s*//g')
|
msg=$(_w4y_alert_msg "$res")
|
||||||
if [ "$msg" = '' ]; then
|
if [ "$msg" = '' ]; then
|
||||||
_err "Unable to remove record: Unknown error"
|
_err "Unable to remove record: Unknown error"
|
||||||
echo "$ret" >'error-01.html'
|
echo "$ret" >'error-01.html'
|
||||||
@@ -145,6 +145,17 @@ dns_world4you_rm() {
|
|||||||
|
|
||||||
################ Private functions ################
|
################ Private functions ################
|
||||||
|
|
||||||
|
# Usage: _w4y_alert_msg <html>
|
||||||
|
# Extracts the error text out of the alert box of a DNS page.
|
||||||
|
# "grep -A" is not portable (Solaris /usr/bin/grep: "illegal option -- A"),
|
||||||
|
# so select from the alert to EOF and keep the same number of lines.
|
||||||
|
# "\s" is a GNU sed extension, use an explicit space/tab bracket instead.
|
||||||
|
_w4y_alert_msg() {
|
||||||
|
_w4y_tab=$(printf '\t')
|
||||||
|
echo "$1" | sed -n '/alert-notification/,$p' | _head_n 21 |
|
||||||
|
grep 'class="weak-title">[^<]' | sed "s/<[^>]*>//g;s/^[ $_w4y_tab]*//"
|
||||||
|
}
|
||||||
|
|
||||||
# Usage: _login
|
# Usage: _login
|
||||||
_login() {
|
_login() {
|
||||||
WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}"
|
WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}"
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ _check_variables() {
|
|||||||
org_response="$(echo "$org_response" | _normalizeJson)"
|
org_response="$(echo "$org_response" | _normalizeJson)"
|
||||||
YANDEX360_ORG_ID=$(
|
YANDEX360_ORG_ID=$(
|
||||||
echo "$org_response" |
|
echo "$org_response" |
|
||||||
_egrep_o '"id":[[:space:]]*[0-9]+' |
|
_egrep_o '"id":[ ]*[0-9]+' |
|
||||||
cut -d':' -f2
|
cut -d':' -f2
|
||||||
)
|
)
|
||||||
_debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID"
|
_debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID"
|
||||||
@@ -216,7 +216,7 @@ _get_token() {
|
|||||||
|
|
||||||
interval=$(
|
interval=$(
|
||||||
echo "$response" |
|
echo "$response" |
|
||||||
_egrep_o '"interval":[[:space:]]*[0-9]+' |
|
_egrep_o '"interval":[ ]*[0-9]+' |
|
||||||
cut -d':' -f2
|
cut -d':' -f2
|
||||||
)
|
)
|
||||||
_debug 'Polling interval' "$interval"
|
_debug 'Polling interval' "$interval"
|
||||||
|
|||||||
@@ -22,21 +22,32 @@ dns_yc_add() {
|
|||||||
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
|
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
|
||||||
txtvalue=$2
|
txtvalue=$2
|
||||||
|
|
||||||
|
# YC_SA_Key_File_PEM_b64/Path are always persisted to the domain conf below,
|
||||||
|
# so they must be recovered from there first (account conf is only a
|
||||||
|
# fallback for the YC_Folder_ID case, see the SA_ID/SA_Key_ID save below).
|
||||||
|
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readdomainconf YC_SA_Key_File_PEM_b64)}"
|
||||||
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
|
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
|
||||||
|
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readdomainconf YC_SA_Key_File_Path)}"
|
||||||
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
|
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
|
||||||
|
|
||||||
if [ "$YC_SA_Key_File_PEM_b64" ]; then
|
if [ "$YC_SA_Key_File_PEM_b64" ]; then
|
||||||
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
|
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
|
||||||
YC_SA_Key_File="private.key"
|
YC_SA_Key_File="private.key"
|
||||||
|
_yc_key_is_temp=1
|
||||||
_savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64"
|
_savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64"
|
||||||
else
|
else
|
||||||
YC_SA_Key_File="$YC_SA_Key_File_Path"
|
YC_SA_Key_File="$YC_SA_Key_File_Path"
|
||||||
|
_yc_key_is_temp=""
|
||||||
_savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path"
|
_savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
YC_Zone_ID="${YC_Zone_ID:-$(_readdomainconf YC_Zone_ID)}"
|
||||||
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
|
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
|
||||||
|
YC_Folder_ID="${YC_Folder_ID:-$(_readdomainconf YC_Folder_ID)}"
|
||||||
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
|
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
|
||||||
|
YC_SA_ID="${YC_SA_ID:-$(_readdomainconf YC_SA_ID)}"
|
||||||
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
|
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
|
||||||
|
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readdomainconf YC_SA_Key_ID)}"
|
||||||
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
|
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
|
||||||
|
|
||||||
if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
|
if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
|
||||||
@@ -65,11 +76,21 @@ dns_yc_add() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
# Clear both possible stores -- YC_Zone_ID/YC_Folder_ID/key material are
|
||||||
|
# persisted to the domain conf, while YC_SA_ID/YC_SA_Key_ID may have been
|
||||||
|
# saved account-wide (Folder_ID mode), so a plain _clearaccountconf alone
|
||||||
|
# would leave stale values behind in whichever store wasn't touched.
|
||||||
|
_cleardomainconf YC_Zone_ID
|
||||||
_clearaccountconf YC_Zone_ID
|
_clearaccountconf YC_Zone_ID
|
||||||
|
_cleardomainconf YC_Folder_ID
|
||||||
_clearaccountconf YC_Folder_ID
|
_clearaccountconf YC_Folder_ID
|
||||||
_clearaccountconf YC_SA_ID
|
_cleardomainconf YC_SA_ID
|
||||||
_clearaccountconf YC_SA_Key_ID
|
_clearaccountconf_mutable YC_SA_ID
|
||||||
|
_cleardomainconf YC_SA_Key_ID
|
||||||
|
_clearaccountconf_mutable YC_SA_Key_ID
|
||||||
|
_cleardomainconf YC_SA_Key_File_PEM_b64
|
||||||
_clearaccountconf YC_SA_Key_File_PEM_b64
|
_clearaccountconf YC_SA_Key_File_PEM_b64
|
||||||
|
_cleardomainconf YC_SA_Key_File_Path
|
||||||
_clearaccountconf YC_SA_Key_File_Path
|
_clearaccountconf YC_SA_Key_File_Path
|
||||||
_err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
|
_err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
|
||||||
return 1
|
return 1
|
||||||
@@ -110,11 +131,30 @@ dns_yc_rm() {
|
|||||||
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
|
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
|
||||||
txtvalue=$2
|
txtvalue=$2
|
||||||
|
|
||||||
|
YC_Zone_ID="${YC_Zone_ID:-$(_readdomainconf YC_Zone_ID)}"
|
||||||
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
|
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
|
||||||
|
YC_Folder_ID="${YC_Folder_ID:-$(_readdomainconf YC_Folder_ID)}"
|
||||||
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
|
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
|
||||||
|
YC_SA_ID="${YC_SA_ID:-$(_readdomainconf YC_SA_ID)}"
|
||||||
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
|
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
|
||||||
|
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readdomainconf YC_SA_Key_ID)}"
|
||||||
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
|
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
|
||||||
|
|
||||||
|
# See dns_yc_add() for why domain conf is checked before account conf.
|
||||||
|
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readdomainconf YC_SA_Key_File_PEM_b64)}"
|
||||||
|
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
|
||||||
|
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readdomainconf YC_SA_Key_File_Path)}"
|
||||||
|
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
|
||||||
|
|
||||||
|
if [ "$YC_SA_Key_File_PEM_b64" ]; then
|
||||||
|
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
|
||||||
|
YC_SA_Key_File="private.key"
|
||||||
|
_yc_key_is_temp=1
|
||||||
|
else
|
||||||
|
YC_SA_Key_File="$YC_SA_Key_File_Path"
|
||||||
|
_yc_key_is_temp=""
|
||||||
|
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"
|
_err "invalid domain"
|
||||||
@@ -124,16 +164,10 @@ dns_yc_rm() {
|
|||||||
_debug _sub_domain "$_sub_domain"
|
_debug _sub_domain "$_sub_domain"
|
||||||
_debug _domain "$_domain"
|
_debug _domain "$_domain"
|
||||||
|
|
||||||
_debug "Getting txt records"
|
# upsertRecordSets.deletions removes only the given value from the rrset,
|
||||||
if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
|
# leaving any other values at the same name (e.g. base + wildcard domain)
|
||||||
exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$")
|
# intact -- no need to read the current data set and recompute it.
|
||||||
_debug exists_txtvalue "$exists_txtvalue"
|
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
|
||||||
else
|
|
||||||
_err "Error: $response"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then
|
|
||||||
if _contains "$response" "\"done\": true"; then
|
if _contains "$response" "\"done\": true"; then
|
||||||
_info "Delete, OK"
|
_info "Delete, OK"
|
||||||
return 0
|
return 0
|
||||||
@@ -194,7 +228,7 @@ _get_root() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if _contains "$response" "\"zone\": \"$h\""; then
|
if _contains "$response" "\"zone\": \"$h\""; then
|
||||||
_domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')
|
_domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:][^:]*$" | tr -d '"')
|
||||||
_debug _domain_id "$_domain_id"
|
_debug _domain_id "$_domain_id"
|
||||||
if [ "$_domain_id" ]; then
|
if [ "$_domain_id" ]; then
|
||||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||||
@@ -255,7 +289,9 @@ _yc_login() {
|
|||||||
_signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
|
_signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
|
||||||
_debug2 _signature "$_signature"
|
_debug2 _signature "$_signature"
|
||||||
|
|
||||||
rm -rf "$YC_SA_Key_File"
|
if [ "$_yc_key_is_temp" ]; then
|
||||||
|
rm -f "$YC_SA_Key_File"
|
||||||
|
fi
|
||||||
|
|
||||||
_jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
|
_jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
|
||||||
_debug2 _jwt "$_jwt"
|
_debug2 _jwt "$_jwt"
|
||||||
@@ -264,7 +300,7 @@ _yc_login() {
|
|||||||
_iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
|
_iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
|
||||||
_debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
|
_debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
|
||||||
|
|
||||||
YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
|
YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:][^:]*$" | tr -d '"')"
|
||||||
_debug3 YC_Token
|
_debug3 YC_Token
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -57,9 +57,8 @@ waha_send() {
|
|||||||
|
|
||||||
_debug "_data" "$_data"
|
_debug "_data" "$_data"
|
||||||
|
|
||||||
export _H1="Content-Type: application/json"
|
|
||||||
if [ "$WAHA_API_KEY" ]; then
|
if [ "$WAHA_API_KEY" ]; then
|
||||||
export _H2="X-Api-Key: $WAHA_API_KEY"
|
export _H1="X-Api-Key: $WAHA_API_KEY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_waha_url="${WAHA_URL}/api/sendText"
|
_waha_url="${WAHA_URL}/api/sendText"
|
||||||
|
|||||||
Reference in New Issue
Block a user