diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 84a17470..5417068f 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -66,7 +66,7 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file @@ -114,7 +114,7 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Install tools run: | brew untap aws/tap || true @@ -167,7 +167,7 @@ jobs: - name: Set git to use LF run: | git config --global core.autocrlf false - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation @@ -231,13 +231,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/freebsd-vm@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 @@ -290,13 +289,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/ghostbsd-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 @@ -347,13 +345,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/openbsd-vm@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_add socat curl libiconv usesh: true @@ -404,13 +401,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/netbsd-vm@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: | /usr/sbin/pkg_add curl socat @@ -462,13 +458,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/dragonflybsd-vm@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 libnghttp2 @@ -524,13 +519,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/midnightbsd-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: mport install socat curl || true usesh: true @@ -582,13 +576,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/solaris-vm@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: nfs prepare: | @@ -642,13 +635,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/omnios-vm@v1 with: 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: nfs prepare: pkg install socat @@ -699,13 +691,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/openindiana-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: nfs prepare: pkg install socat @@ -756,13 +747,12 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/tribblix-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: nfs prepare: zap install socat @@ -813,140 +803,19 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/haiku-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 prepare: | mkdir -p /boot/home/.cache pkgman install -y cronie - - 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" - - - - 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}}" diff --git a/.github/workflows/DragonFlyBSD.yml b/.github/workflows/DragonFlyBSD.yml index 20c61dcc..c8cbc985 100644 --- a/.github/workflows/DragonFlyBSD.yml +++ b/.github/workflows/DragonFlyBSD.yml @@ -58,7 +58,6 @@ jobs: - uses: vmactions/dragonflybsd-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" diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml index 88ef0a6e..50fcab32 100644 --- a/.github/workflows/FreeBSD.yml +++ b/.github/workflows/FreeBSD.yml @@ -64,7 +64,6 @@ jobs: - uses: vmactions/freebsd-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" diff --git a/.github/workflows/GhostBSD.yml b/.github/workflows/GhostBSD.yml index 04510c15..c77fdf2e 100644 --- a/.github/workflows/GhostBSD.yml +++ b/.github/workflows/GhostBSD.yml @@ -66,7 +66,6 @@ jobs: - uses: vmactions/ghostbsd-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" diff --git a/.github/workflows/Haiku.yml b/.github/workflows/Haiku.yml index b133dd18..9884ebeb 100644 --- a/.github/workflows/Haiku.yml +++ b/.github/workflows/Haiku.yml @@ -65,7 +65,6 @@ jobs: - uses: vmactions/haiku-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" diff --git a/.github/workflows/Hurd.yml b/.github/workflows/Hurd.yml deleted file mode 100644 index fee80d29..00000000 --- a/.github/workflows/Hurd.yml +++ /dev/null @@ -1,76 +0,0 @@ -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" diff --git a/.github/workflows/MidnightBSD.yml b/.github/workflows/MidnightBSD.yml index ce499e4e..15024833 100644 --- a/.github/workflows/MidnightBSD.yml +++ b/.github/workflows/MidnightBSD.yml @@ -58,7 +58,6 @@ jobs: - uses: vmactions/midnightbsd-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" diff --git a/.github/workflows/NetBSD.yml b/.github/workflows/NetBSD.yml index 6695f71e..16d0ae2d 100644 --- a/.github/workflows/NetBSD.yml +++ b/.github/workflows/NetBSD.yml @@ -58,7 +58,6 @@ jobs: - uses: vmactions/netbsd-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" diff --git a/.github/workflows/Omnios.yml b/.github/workflows/Omnios.yml index aabb168b..eb486b35 100644 --- a/.github/workflows/Omnios.yml +++ b/.github/workflows/Omnios.yml @@ -64,7 +64,6 @@ jobs: - uses: vmactions/omnios-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" diff --git a/.github/workflows/OpenBSD.yml b/.github/workflows/OpenBSD.yml index 46318163..4fdb76c5 100644 --- a/.github/workflows/OpenBSD.yml +++ b/.github/workflows/OpenBSD.yml @@ -64,7 +64,6 @@ jobs: - uses: vmactions/openbsd-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" diff --git a/.github/workflows/OpenEuler.yml b/.github/workflows/OpenEuler.yml deleted file mode 100644 index 2b4bd0ab..00000000 --- a/.github/workflows/OpenEuler.yml +++ /dev/null @@ -1,70 +0,0 @@ -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: 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" diff --git a/.github/workflows/OpenIndiana.yml b/.github/workflows/OpenIndiana.yml index e3119f8e..b5061ba7 100644 --- a/.github/workflows/OpenIndiana.yml +++ b/.github/workflows/OpenIndiana.yml @@ -64,7 +64,6 @@ jobs: - uses: vmactions/openindiana-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" diff --git a/.github/workflows/Solaris.yml b/.github/workflows/Solaris.yml index 30e4e291..3393269a 100644 --- a/.github/workflows/Solaris.yml +++ b/.github/workflows/Solaris.yml @@ -64,7 +64,6 @@ jobs: - uses: vmactions/solaris-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" diff --git a/.github/workflows/Tribblix.yml b/.github/workflows/Tribblix.yml index 68e61dc8..cd43e0e3 100644 --- a/.github/workflows/Tribblix.yml +++ b/.github/workflows/Tribblix.yml @@ -64,7 +64,6 @@ jobs: - uses: vmactions/tribblix-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" diff --git a/.github/workflows/vtag.yml b/.github/workflows/vtag.yml deleted file mode 100644 index e9f7e8df..00000000 --- a/.github/workflows/vtag.yml +++ /dev/null @@ -1,32 +0,0 @@ -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 }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/v${{ github.ref_name }}" >/dev/null 2>&1; then - echo "Tag v${{ github.ref_name }} already exists, nothing to do." - exit 0 - fi - gh api "repos/${{ github.repository }}/git/refs" -f ref="refs/tags/v${{ github.ref_name }}" -f sha="${{ github.sha }}" - echo "Created tag v${{ github.ref_name }} -> ${{ github.sha }}" diff --git a/README.md b/README.md index 90280e94..b93a8a50 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,6 @@ OpenIndiana Tribblix Haiku - Hurd - OpenEuler

@@ -132,8 +130,6 @@ |25|[![Haiku](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS |26|[![Tribblix](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix |27|[![GhostBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD -|28|[![Hurd](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml)|GNU Hurd -|29|[![OpenEuler](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml)|openEuler > ๐Ÿงช Check our [testing project](https://github.com/acmesh-official/acmetest) diff --git a/acme.sh b/acme.sh index 4d4f6cbe..a5f53469 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=3.1.5 +VER=3.1.4 PROJECT_NAME="acme.sh" @@ -2715,21 +2715,6 @@ _clearcaconf() { _clear_conf "$CA_CONF" "$1" } -#Starts a socat listener in the background, the pid is set to _socat_pid. -#It uses the content, _content_len, _NC and _SOCAT_ERR of _startserver. -#options -_startsocat() { - _socat_opts="$1" - _debug "_NC" "$_NC $_socat_opts" - $_NC $_socat_opts SYSTEM:"sleep 1; \ -echo 'HTTP/1.0 200 OK'; \ -echo 'Content-Length\: $_content_len'; \ -echo ''; \ -printf '%s' '$content';" 2>>"$_SOCAT_ERR" & - _socat_pid="$!" - _debug "_socat_pid" "$_socat_pid" -} - # content localaddress _startserver() { content="$1" @@ -2743,24 +2728,16 @@ _startserver() { _debug Le_Listen_V4 "$Le_Listen_V4" _debug Le_Listen_V6 "$Le_Listen_V6" - _serverproc_v6="" if _exists "socat"; then _NC="socat" - SOCAT_OPTIONS6="" - if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then + if [ "$Le_Listen_V6" ]; then _NC="$_NC -6" SOCAT_OPTIONS=TCP6-LISTEN - elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then + elif [ "$Le_Listen_V4" ]; then _NC="$_NC -4" SOCAT_OPTIONS=TCP4-LISTEN - elif [ "$ncaddr" ]; then - #a single local address belongs to a single family, let socat pick it - SOCAT_OPTIONS=TCP-LISTEN else - #listen on both ipv4 and ipv6, with one socket for each family: - #ipv4-mapped ipv6 addresses are not available everywhere. - SOCAT_OPTIONS=TCP4-LISTEN - SOCAT_OPTIONS6=TCP6-LISTEN + SOCAT_OPTIONS=TCP-LISTEN fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then @@ -2768,10 +2745,6 @@ _startserver() { fi SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork - if [ "$SOCAT_OPTIONS6" ]; then - #ipv6only keeps this socket from colliding with the ipv4 one - SOCAT_OPTIONS6=$SOCAT_OPTIONS6:$Le_HTTPPort,crlf,reuseaddr,fork,ipv6only=1 - fi #Adding bind to local-address if [ "$ncaddr" ]; then @@ -2780,14 +2753,14 @@ _startserver() { _content_len="$(printf "%s" "$content" | wc -c)" _debug _content_len "$_content_len" + _debug "_NC" "$_NC $SOCAT_OPTIONS" export _SOCAT_ERR="$(_mktemp)" - _startsocat "$SOCAT_OPTIONS" - serverproc="$_socat_pid" - if [ "$SOCAT_OPTIONS6" ]; then - #best effort, the host may have no ipv6 support at all - _startsocat "$SOCAT_OPTIONS6" - _serverproc_v6="$_socat_pid" - fi + $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \ +echo 'HTTP/1.0 200 OK'; \ +echo 'Content-Length\: $_content_len'; \ +echo ''; \ +printf '%s' '$content';" 2>"$_SOCAT_ERR" & + serverproc="$!" else _PYTHON="" if _exists "python3"; then @@ -2799,40 +2772,21 @@ _startserver() { fi if [ "$_PYTHON" ]; then _debug "Using python: $_PYTHON" - #a comma separated list of addresses to listen on, one socket for each - _BIND_ADDR="0.0.0.0,::" - if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then + _AF="socket.AF_INET" + _BIND_ADDR="0.0.0.0" + if [ "$Le_Listen_V6" ]; then + _AF="socket.AF_INET6" _BIND_ADDR="::" - elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then - _BIND_ADDR="0.0.0.0" fi if [ "$ncaddr" ]; then _BIND_ADDR="$ncaddr" fi - _debug "_BIND_ADDR" "$_BIND_ADDR" export _SOCAT_ERR="$(_mktemp)" - $_PYTHON -c "import socket,sys,select -res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3] -ads=sys.argv[2].split(',') -ls=[] -for ad in ads: - try: - sk=socket.socket(socket.AF_INET6 if ':' in ad else socket.AF_INET,socket.SOCK_STREAM) - sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - if ':' in ad and len(ads)>1: - sk.setsockopt(socket.IPPROTO_IPV6,socket.IPV6_V6ONLY,1) - sk.bind((ad,int(sys.argv[1]))) - sk.listen(5) - ls.append(sk) - except Exception: - sys.stderr.write(str(sys.exc_info()[1])+'\n') -if not ls: - sys.exit(1) + $_PYTHON -c "import socket,sys;s=socket.socket($_AF,socket.SOCK_STREAM);s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);s.bind((sys.argv[2],int(sys.argv[1])));s.listen(5);res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3]; while True: - for sk in select.select(ls,[],[])[0]: - c,a=sk.accept() - c.sendall(res.encode() if hasattr(res, 'encode') else res) - c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" & + c,a=s.accept() + c.sendall(res.encode() if hasattr(res, 'encode') else res) + c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" & serverproc="$!" _NC="$_PYTHON" else @@ -2855,11 +2809,6 @@ while True: _stopserver() { pid="$1" _debug "pid" "$pid" - if [ "$_serverproc_v6" ]; then - _debug "_serverproc_v6" "$_serverproc_v6" - kill $_serverproc_v6 >/dev/null 2>&1 - _serverproc_v6="" - fi if [ -z "$pid" ]; then rm -f "$_SOCAT_ERR" return @@ -2933,11 +2882,9 @@ _starttlsserver() { _debug Le_Listen_V4 "$Le_Listen_V4" _debug Le_Listen_V6 "$Le_Listen_V6" - #openssl s_server binds a single socket, so both options together can only - #mean: do not force a family, same as when neither of them is given. - if [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then + if [ "$Le_Listen_V4" ]; then __S_OPENSSL="$__S_OPENSSL -4" - elif [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then + elif [ "$Le_Listen_V6" ]; then __S_OPENSSL="$__S_OPENSSL -6" fi @@ -4408,24 +4355,6 @@ deactivateaccount() { fi } -#domain -#Print the Validation Domain Name where the persistent TXT record must be -#published: the "_validation-persist" label prepended to the domain being -#validated (draft-ietf-acme-dns-persist-01 sec 4). -#A wildcard identifier is validated by the record at its base domain, so the -#leading "*." label is dropped: the wildcard scope comes from 'policy=wildcard' -#in the record value, not from a "*" label in the record name (sec 5.1, 10.2). -_dns_persist_txt_name() { - _dpt_domain="$1" - if _startswith "$_dpt_domain" "*."; then - _dpt_domain="$(echo "$_dpt_domain" | sed 's/^\*\.//')" - fi - if [ -z "$_dpt_domain" ]; then - return 1 - fi - echo "_validation-persist.$_dpt_domain" -} - #domain wildcard ca_name days #Print the TXT record(s) the user must add to enable persistent DNS validation #per draft-ietf-acme-dns-persist-01. @@ -4440,20 +4369,6 @@ makednspersistvalue() { return 1 fi - _txt_name="$(_dns_persist_txt_name "$_mdpv_domain")" - if [ -z "$_txt_name" ]; then - _err "Invalid domain: $_mdpv_domain" - return 1 - fi - _debug _txt_name "$_txt_name" - - #A wildcard identifier can only be issued if the record carries - #'policy=wildcard', so don't print a record that is guaranteed to fail. - if _startswith "$_mdpv_domain" "*." && [ "$_mdpv_wildcard" != "1" ]; then - _info "$_mdpv_domain is a wildcard domain, adding 'policy=wildcard' automatically." - _mdpv_wildcard="1" - fi - if [ -n "$_mdpv_days" ]; then case "$_mdpv_days" in '' | *[!0-9]*) @@ -4485,6 +4400,8 @@ makednspersistvalue() { fi _debug "Account URL" "$_accUri" + _txt_name="_validation-persist.$_mdpv_domain" + _txt_suffix="; accounturi=$_accUri" if [ "$_mdpv_wildcard" = "1" ]; then _txt_suffix="$_txt_suffix; policy=wildcard" @@ -5002,18 +4919,11 @@ issue() { if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" mkdir -p "$DOMAIN_PATH" - elif [ -z "$Le_Vlist" ]; then - # Whether the saved order is resumed is decided by Le_Vlist below, so key - # this on Le_Vlist too. With no pending order to resume a new one is - # created, and a stale order link from the previous issuance must not be - # reused. https://github.com/acmesh-official/acme.sh/issues/3635 + elif ! _hasfield "$_web_roots" "$W_DNS"; then Le_OrderFinalize="" Le_LinkOrder="" + Le_LinkCert="" fi - # Per-run state only: it is set after finalize and never read back from the - # saved domain conf. Carrying it over would make a run that gives up while - # the order is still 'processing' download the previous certificate again. - Le_LinkCert="" if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then _err "$_DNS_MANUAL_ERROR" @@ -6039,17 +5949,12 @@ $_authorizations_map" _clearaccountconf "HTTPS_INSECURE" fi - if [ "$Le_Listen_V4" ] || [ "$Le_Listen_V6" ]; then - if [ "$Le_Listen_V4" ]; then - _savedomainconf "Le_Listen_V4" "$Le_Listen_V4" - else - _cleardomainconf Le_Listen_V4 - fi - if [ "$Le_Listen_V6" ]; then - _savedomainconf "Le_Listen_V6" "$Le_Listen_V6" - else - _cleardomainconf Le_Listen_V6 - fi + if [ "$Le_Listen_V4" ]; then + _savedomainconf "Le_Listen_V4" "$Le_Listen_V4" + _cleardomainconf Le_Listen_V6 + elif [ "$Le_Listen_V6" ]; then + _savedomainconf "Le_Listen_V6" "$Le_Listen_V6" + _cleardomainconf Le_Listen_V4 fi if [ "$Le_ForceNewDomainKey" = "1" ]; then @@ -6173,10 +6078,7 @@ $_authorizations_map" #some devices and APIs reject them, so the certs are stored back to back. #https://github.com/acmesh-official/acme.sh/issues/1940 _strip_blank_lines() { - #spell out space and tab: Solaris sed treats [[:space:]] as a literal - #bracket set and silently stops matching the blank lines - _sbl_tab="$(printf '\t')" - sed "/^[ $_sbl_tab]*\$/d" + sed '/^[[:space:]]*$/d' } _split_cert_chain() { @@ -7464,18 +7366,10 @@ deactivate() { done } -#reads the output of "openssl x509 -text" from stdin, prints the hex AKI -#the value is on the line right after the extension header; "grep -A" is not -#portable (Solaris /usr/bin/grep: "illegal option -- A"), so select from the -#header to EOF and keep the second line of that range -_extractAKI() { - sed -n '/X509v3 Authority Key Identifier/,$p' | _head_n 2 | _tail_n 1 | tr -d ': ' | sed "s/keyid//" -} - #cert _getAKI() { _cert="$1" - ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -text -noout | _extractAKI + ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -text -noout | grep -A 1 "X509v3 Authority Key Identifier" | _tail_n 1 | tr -d ': ' | sed "s/keyid//" } #cert @@ -7631,15 +7525,6 @@ _installalias() { _c_home="$1" _initpath - _alias_bin="$LE_WORKING_DIR/$PROJECT_ENTRY" - if [ ! -f "$_alias_bin" ]; then - #ACME_PACKAGED install: no copy in LE_WORKING_DIR, alias the current script - _script="$(_readlink "$_SCRIPT_")" - if [ -f "$_script" ]; then - _alias_bin="$_script" - fi - fi - _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env" if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile" @@ -7657,7 +7542,7 @@ _installalias() { else _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile" fi - _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$_alias_bin$_c_entry\"" + _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" ]; then #the completion file does nothing when sourced by a non-bash shell _setopt "$_envfile" ". \"$LE_WORKING_DIR/$PROJECT_ENTRY.completion\"" @@ -7684,7 +7569,7 @@ _installalias() { else _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile" fi - _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$_alias_bin$_c_entry\"" + _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_csh_profile" "source \"$_cshfile\"" fi @@ -7696,7 +7581,7 @@ _installalias() { if [ "$_c_home" ]; then _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\"" fi - _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$_alias_bin$_c_entry\"" + _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_tcsh_profile" "source \"$_cshfile\"" fi @@ -7770,38 +7655,30 @@ install() { chmod 700 "$LE_CONFIG_HOME" fi - if [ "$ACME_PACKAGED" ]; then - #the script and its hooks are managed by a system package manager, - #do not copy them into LE_WORKING_DIR. https://github.com/acmesh-official/acme.sh/issues/7135 - _info "ACME_PACKAGED is set, skipping the script copy." - else - cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" + cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" - if [ "$?" != "0" ]; then - _err "Installation failed, cannot copy $PROJECT_ENTRY" - return 1 - fi + if [ "$?" != "0" ]; then + _err "Installation failed, cannot copy $PROJECT_ENTRY" + return 1 + fi - _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" + _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" - if [ -f "$PROJECT_ENTRY.completion" ]; then - cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/" - _debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion" - fi + if [ -f "$PROJECT_ENTRY.completion" ]; then + cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/" + _debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion" fi if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then _installalias "$_c_home" fi - if [ -z "$ACME_PACKAGED" ]; then - for subf in $_SUB_FOLDERS; do - if [ -d "$subf" ]; then - mkdir -p "$LE_WORKING_DIR/$subf" - cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/ - fi - done - fi + for subf in $_SUB_FOLDERS; do + if [ -d "$subf" ]; then + mkdir -p "$LE_WORKING_DIR/$subf" + cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/ + fi + done if [ ! -f "$ACCOUNT_CONF_PATH" ]; then _initconf @@ -7829,7 +7706,7 @@ install() { installcronjob "$_c_home" fi - if [ -z "$NO_DETECT_SH" ] && [ -z "$ACME_PACKAGED" ]; then + if [ -z "$NO_DETECT_SH" ]; then #Modify shebang if _exists bash; then _bash_path="$(bash -c "command -v bash 2>/dev/null")" @@ -7854,9 +7731,7 @@ install() { if [ "$_accountemail" ]; then _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail" fi - if [ -z "$ACME_PACKAGED" ]; then - _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" - fi + _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" _info OK } @@ -7870,12 +7745,8 @@ uninstall() { _uninstallalias - if [ -z "$ACME_PACKAGED" ]; then - #don't remove the script when it is managed by a system package manager, - #LE_WORKING_DIR may point to the packaged files - rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" - rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" - fi + rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" + rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself." } @@ -7911,24 +7782,20 @@ cron() { _initpath _info "$(__green "===Starting cron===")" if [ "$AUTO_UPGRADE" = "1" ]; then - if [ "$ACME_PACKAGED" ]; then - _info "ACME_PACKAGED is set, skipping the auto upgrade." - else - export LE_WORKING_DIR - ( - if ! upgrade; then - _err "Cron: Upgrade failed!" - return 1 - fi - ) - . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null - - if [ -t 1 ]; then - __INTERACTIVE="1" + export LE_WORKING_DIR + ( + if ! upgrade; then + _err "Cron: Upgrade failed!" + return 1 fi + ) + . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null - _info "Automatically upgraded to: $VER" + if [ -t 1 ]; then + __INTERACTIVE="1" fi + + _info "Automatically upgraded to: $VER" fi _TREAT_SKIP_AS_SUCCESS="1" renewAll @@ -8170,9 +8037,7 @@ Parameters: --dns-persist-wildcard Used with '--make-dns-persist-value'. Adds 'policy=wildcard' to the generated TXT record so the issuer is also authorized for wildcards - and subdomains (draft-ietf-acme-dns-persist-01). It is implied when - the domain given to -d is a wildcard (e.g. '*.example.com'); the - record itself is always published at the base domain. + and subdomains (draft-ietf-acme-dns-persist-01). --dns-persist-ca-name Used with '--make-dns-persist-value'. Use the given CA identity domain (e.g. 'ssl.com') as the issuer-domain-name in the TXT record. If omitted, the identities are read from the ACME directory's @@ -8230,9 +8095,8 @@ Parameters: --ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension. --always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default. --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted. - --listen-v4 Force standalone/tls server to listen at ipv4 only. - By default the standalone server listens on both ipv4 and ipv6. - --listen-v6 Force standalone/tls server to listen at ipv6 only. + --listen-v4 Force standalone/tls server to listen at ipv4. + --listen-v6 Force standalone/tls server to listen at ipv6. --request-v4 Force client requests to use ipv4 to connect to the CA server. --request-v6 Force client requests to use ipv6 to connect to the CA server. --openssl-bin Specifies a custom openssl bin location. @@ -8261,10 +8125,6 @@ Parameters: } installOnline() { - if [ "$ACME_PACKAGED" ]; then - _err "ACME_PACKAGED is set: acme.sh is managed by the system package manager, please use it to upgrade." - return 1 - fi _info "Installing from online archive." _branch="$BRANCH" @@ -8322,10 +8182,6 @@ _getUpgradeHash() { } upgrade() { - if [ "$ACME_PACKAGED" ]; then - _err "ACME_PACKAGED is set: acme.sh is managed by the system package manager, please use it to upgrade." - exit 1 - fi if ( _initpath [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already up to date!" && exit 0 diff --git a/deploy/byteplus_alb.sh b/deploy/byteplus_alb.sh index 394b431f..8443bb99 100644 --- a/deploy/byteplus_alb.sh +++ b/deploy/byteplus_alb.sh @@ -163,8 +163,8 @@ byteplus_alb_deploy() { # โ”€โ”€ 3. Read cert and key โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # BytePlus requires NO blank lines between PEM blocks in the certificate chain - _public_key=$(_strip_blank_lines <"$_cfullchain" | tr -d '\r') - _private_key=$(_strip_blank_lines <"$_ckey" | tr -d '\r') + _public_key=$(sed '/^[[:space:]]*$/d' "$_cfullchain" | tr -d '\r') + _private_key=$(sed '/^[[:space:]]*$/d' "$_ckey" | tr -d '\r') if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then _err "Failed to read certificate or key file." diff --git a/deploy/cpanel_uapi.sh b/deploy/cpanel_uapi.sh index 02ef6b3e..156044eb 100644 --- a/deploy/cpanel_uapi.sh +++ b/deploy/cpanel_uapi.sh @@ -87,11 +87,7 @@ cpanel_uapi_deploy() { # Auto mode if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then # call API for site config - if [ -n "$_uapi_user" ]; then - _response=$(uapi --user="$_uapi_user" DomainInfo list_domains) - else - _response=$(uapi DomainInfo list_domains) - fi + _response=$(uapi DomainInfo list_domains) # exit if error in response if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then _err "Error in deploying certificate - cannot retrieve sitelist:" diff --git a/deploy/ikuai.sh b/deploy/ikuai.sh deleted file mode 100644 index fa0926dc..00000000 --- a/deploy/ikuai.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/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 -} diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh index f66d27ce..97f9c21c 100644 --- a/deploy/keyhelp.sh +++ b/deploy/keyhelp.sh @@ -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" _H1="Cookie: $_cookie" _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST") - _message=$(echo "$_response" | sed -n '/

/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') + _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') _info "_message" "$_message" if [ -z "$_message" ]; then _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" _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST") - _message=$(echo "$_response" | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') + _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') _info "_message" "$_message" if [ -z "$_message" ]; then _err "Fail to apply certificate." diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 4a8c9dc9..375668ec 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -10,10 +10,6 @@ # Usage (shown values are the examples): # 1. Set optional environment variables # - 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: # acme.sh --deploy --deploy-hook multideploy -d example.com @@ -53,7 +49,7 @@ multideploy_deploy() { _debug _cfullchain "$_cfullchain" _debug _cpfx "$_cpfx" - _getdeployconf MULTIDEPLOY_FILENAME + MULTIDEPLOY_FILENAME="${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}" if [ -z "$MULTIDEPLOY_FILENAME" ]; then MULTIDEPLOY_FILENAME="multideploy.yml" _info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'." @@ -79,8 +75,7 @@ multideploy_deploy() { # 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. # Arguments: -# $@ - Posible deploy file names. A name starting with '/' is treated as an -# absolute path, any other name is relative to the domain directory. +# $@ - Posible deploy file names. # Usage: # _preprocess_deployfile "" "?" _preprocess_deployfile() { @@ -92,21 +87,15 @@ _preprocess_deployfile() { _debug3 "yq is installed." # Check if deploy file exists - found_file="" for file in "$@"; do - if _startswith "$file" "/"; then - _multideploy_path="$file" - else - _multideploy_path="$DOMAIN_PATH/$file" - fi - _debug3 "Checking file" "$_multideploy_path" - if [ -f "$_multideploy_path" ]; then + _debug3 "Checking file" "$DOMAIN_PATH/$file" + if [ -f "$DOMAIN_PATH/$file" ]; then _debug3 "File found" if [ -n "$found_file" ]; then _err "Multiple deploy files found. Please keep only one deploy file." return 1 fi - found_file="$_multideploy_path" + found_file="$file" else _debug3 "File not found" fi @@ -116,12 +105,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." return 1 fi - if ! _check_deployfile "$found_file"; then - _err "Deploy file is not valid: $found_file" + if ! _check_deployfile "$DOMAIN_PATH/$found_file"; then + _err "Deploy file is not valid: $DOMAIN_PATH/$found_file" return 1 fi - echo "$found_file" + echo "$DOMAIN_PATH/$found_file" } # Description: diff --git a/deploy/proxmoxbs.sh b/deploy/proxmoxbs.sh index 30599a44..179b0369 100644 --- a/deploy/proxmoxbs.sh +++ b/deploy/proxmoxbs.sh @@ -116,24 +116,17 @@ HEREDOC export HTTPS_INSECURE=1 export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}" 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)" message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"') - case "$_status_code" in - 2[0-9][0-9]) - if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then - _debug3 response "$response" - _info "Certificate successfully deployed" - return 0 - fi - ;; - esac - _err "Certificate deployment failed (HTTP status $_status_code). $message" - _debug "Response" "$response" - return 1 + _retval=$? + if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then + _debug3 response "$response" + _info "Certificate successfully deployed" + return 0 + else + _err "Certificate deployment failed: $message" + _debug "Response" "$response" + return 1 + fi } diff --git a/deploy/proxmoxve.sh b/deploy/proxmoxve.sh index fd8d69d8..b6298ee7 100644 --- a/deploy/proxmoxve.sh +++ b/deploy/proxmoxve.sh @@ -128,24 +128,17 @@ HEREDOC export HTTPS_INSECURE=1 export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}" 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)" message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"') - case "$_status_code" in - 2[0-9][0-9]) - if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then - _debug3 response "$response" - _info "Certificate successfully deployed" - return 0 - fi - ;; - esac - _err "Certificate deployment failed (HTTP status $_status_code). $message" - _debug "Response" "$response" - return 1 + _retval=$? + if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then + _debug3 response "$response" + _info "Certificate successfully deployed" + return 0 + else + _err "Certificate deployment failed: $message" + _debug "Response" "$response" + return 1 + fi } diff --git a/deploy/shelly.sh b/deploy/shelly.sh deleted file mode 100644 index dbdab346..00000000 --- a/deploy/shelly.sh +++ /dev/null @@ -1,280 +0,0 @@ -#!/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 -# 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 -} diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index 336980a5..d05e503a 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -344,7 +344,6 @@ synology_dsm_deploy() { else _err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more." fi - _logout _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi @@ -355,7 +354,6 @@ synology_dsm_deploy() { if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then _err "Unable to find certificate: $SYNO_CERTIFICATE and \$SYNO_CREATE is not set." - _logout _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi @@ -391,13 +389,13 @@ synology_dsm_deploy() { else _info "Restart HTTP services not necessary." fi - _logout _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" + _logout return 0 else + _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" _err "Unable to update certificate, got error response: $response." _logout - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi } @@ -405,8 +403,6 @@ synology_dsm_deploy() { #################### Private functions below ################################## _logout() { # Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables) - # 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") _debug3 response "$response" } diff --git a/deploy/unifios.sh b/deploy/unifios.sh deleted file mode 100644 index 82b65c69..00000000 --- a/deploy/unifios.sh +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env sh -# Deploy hook for UniFi OS Server (self-hosted). -# -# Supports: -# - UniFi OS Server on macOS -# - UniFi OS Server on Linux -# - UniFi OS Server on Windows should also work (runs under WSL2), but -# has not been tested. -# -# Tested on: Ubuntu 26.04 (remote) and macOS 26.6 (local). -# -# This is a different product from the Cloud Key / UDM hardware and -# self-hosted Unifi Controller covered by the `unifi` deploy hook above -# (that hook already covers Cloud Key running UnifiOS v2.0.0+/Gen2/2+) -- -# this hook targets the separately-installed, self-hosted "UniFi OS Server" -# application instead, which stores certificates in its own Postgres -# database via a REST API rather than a Java keystore, so the `unifi` -# hook's approach does not apply here. -# -# UniFi OS Server exposes a REST API on its management port (default -# 11443) that its own web UI uses for certificate management: -# 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 is -# self-signed (it's a management-only port, not meant for public exposure), -# 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") -# DEPLOY_UNIFIOS_USERNAME - UniFi OS Server admin username (required) -# DEPLOY_UNIFIOS_PASSWORD - UniFi OS Server 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 -} diff --git a/dnsapi/dns_bhosted.sh b/dnsapi/dns_bhosted.sh index 46ddd5dd..1493c60a 100644 --- a/dnsapi/dns_bhosted.sh +++ b/dnsapi/dns_bhosted.sh @@ -323,21 +323,21 @@ _bhosted_extract_id() { fi # JSON: "id":12345 - _id="$(printf "%s" "$_resp" | _egrep_o '"id"[ ]*:[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')" + _id="$(printf "%s" "$_resp" | _egrep_o '"id"[[:space:]]*:[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')" if [ -n "$_id" ]; then printf "%s" "$_id" return 0 fi # key=value: id=12345 - _id="$(printf "%s" "$_resp" | _egrep_o '(^|[^0-9a-zA-Z])id[ ]*=[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')" + _id="$(printf "%s" "$_resp" | _egrep_o '(^|[[:space:][:punct:]])id[[:space:]]*=[[:space:]]*[0-9]+' | _head_n 1 | tr -cd '0-9')" if [ -n "$_id" ]; then printf "%s" "$_id" return 0 fi # "record id 12345" / "recordid 12345" - _id="$(printf "%s" "$_resp" | _egrep_o '(record[ ]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')" + _id="$(printf "%s" "$_resp" | _egrep_o '(record[[:space:]]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')" if [ -n "$_id" ]; then printf "%s" "$_id" return 0 diff --git a/dnsapi/dns_creoline.sh b/dnsapi/dns_creoline.sh index f4d76f8e..9d04af4f 100644 --- a/dnsapi/dns_creoline.sh +++ b/dnsapi/dns_creoline.sh @@ -75,7 +75,7 @@ dns_creoline_rm() { return 1 fi - record_id=$(echo "$response" | _egrep_o "\"id\"[ ]*:[ ]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + record_id=$(echo "$response" | _egrep_o "\"id\"[[:space:]]*:[[:space:]]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then @@ -108,10 +108,10 @@ _get_root() { return 1 fi - _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug _sub_domain "$_sub_domain" - _domain=$(echo "$response" | _egrep_o "\"domain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _domain=$(echo "$response" | _egrep_o "\"domain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug _domain "$_domain" if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then @@ -171,7 +171,7 @@ _creoline_rest() { _err "URI:$uri" return 1 elif _contains "$response" "message"; then - message=$(echo "$response" | _egrep_o "\"message\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") + message=$(echo "$response" | _egrep_o "\"message\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") _err "Error: $message" _err "URI:$uri" return 1 diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 6677b32f..d4b6b6e8 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -285,15 +285,15 @@ _cyon_delete_txt() { list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" - list_txt_response="$(_get "${list_txt_url}")" + list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" _debug list_txt_response "${list_txt_response}" if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi # Find and delete all acme challenge entries for the $fulldomain. - _dns_entries="$(printf "%s\n" "${list_txt_response}" | _egrep_o 'data-hash=\\"[^"]*\\" data-identifier=\\"[^"]*\\"' | sed 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\"/\1 \2/')" + _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" - printf "%s\n" "${_dns_entries}" | while read -r _hash _identifier; do + printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)" diff --git a/dnsapi/dns_czechia.sh b/dnsapi/dns_czechia.sh index 6ad60442..e2ffcf50 100644 --- a/dnsapi/dns_czechia.sh +++ b/dnsapi/dns_czechia.sh @@ -30,9 +30,8 @@ dns_czechia_add() { return 1 fi - _czechia_tab="$(printf '\t')" - _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]*\$//") + _cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//') + _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') if [ -z "$_cz" ] || [ -z "$_tk" ]; then _err "Missing zone or CZ_AuthorizationToken." @@ -109,9 +108,8 @@ dns_czechia_rm() { return 1 fi - _czechia_tab="$(printf '\t')" - _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]*\$//") + _cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//') + _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') if [ -z "$_cz" ] || [ -z "$_tk" ]; then _err "Missing zone or CZ_AuthorizationToken." @@ -182,13 +180,12 @@ _czechia_load_conf() { } _czechia_pick_zone() { - _czechia_pz_tab="$(printf '\t')" _fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//') _best_zone="" _zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g') for _z in $_zones_space; do - _clean_z=$(printf "%s" "$_z" | _lower_case | sed "s/[ $_czechia_pz_tab]//g; s/\.\$//") + _clean_z=$(printf "%s" "$_z" | _lower_case | sed 's/[[:space:]]//g; s/\.$//') [ -z "$_clean_z" ] && continue case "$_fd" in diff --git a/dnsapi/dns_freemyip.sh b/dnsapi/dns_freemyip.sh index 18d8e7f9..d598a657 100644 --- a/dnsapi/dns_freemyip.sh +++ b/dnsapi/dns_freemyip.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip Options: FREEMYIP_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/6247 -Author: Recolic Keghart , @Giova96, ExtremeFiretop +Author: Recolic Keghart , @Giova96 ' FREEMYIP_DNS_API="https://freemyip.com/update?" @@ -68,30 +68,22 @@ dns_freemyip_rm() { return $? } -################ Private functions below ################ +################ Private functions below ################ _get_root() { _fmi_d="$1" - echo "$_fmi_d" | sed 's/.*\.\([^.]*\.[^.]*\.[^.]*\)$/\1/' + echo "$_fmi_d" | rev | cut -d '.' -f 1-3 | rev } # There is random failure while calling freemyip API too fast. This function automatically retry until success. _freemyip_get_until_ok() { _fmi_url="$1" - _fmi_i=1 - while [ "$_fmi_i" -le 8 ]; do - _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 - + for i in $(seq 1 8); do + _debug "HTTP GET freemyip.com API '$_fmi_url', retry $i/8..." + _get "$_fmi_url" | tee /dev/fd/2 | grep OK && return 0 _sleep 1 # DO NOT send the request too fast - _fmi_i=$((_fmi_i + 1)) done - _err "Failed to request freemyip API. Server does not say 'OK'" + _err "Failed to request freemyip API: $_fmi_url . Server does not say 'OK'" return 1 } @@ -101,16 +93,13 @@ _is_root_domain_published() { _webroot="$(_get_root "$_fmi_d")" _info "Verifying '""$_fmi_d""' freemyip webroot (""$_webroot"") is not published yet" - _fmi_i=1 - while [ "$_fmi_i" -le 3 ]; do - _debug "'$_webroot' ns lookup, retry $_fmi_i/3..." - + for i in $(seq 1 3); do + _debug "'$_webroot' ns lookup, retry $i/3..." if [ "$(_ns_lookup "$_fmi_d" TXT)" ]; then _debug "'$_webroot' already has a TXT record published!" return 0 fi _sleep 10 # Give it some time to propagate the TXT record - _fmi_i=$((_fmi_i + 1)) done return 1 } diff --git a/dnsapi/dns_hestiacp.sh b/dnsapi/dns_hestiacp.sh deleted file mode 100644 index 13ee6caf..00000000 --- a/dnsapi/dns_hestiacp.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/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 -' - -######## 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 <]*') - _debug2 domain_list "$_namecheap_domain_list" - i=2 p=1 @@ -123,7 +120,7 @@ _get_root_by_getList() { return 1 fi - if ! _namecheap_is_our_dns "$h"; then + if ! _contains "$response" "$h"; then _debug "$h not found" else _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") @@ -136,29 +133,6 @@ _get_root_by_getList() { return 1 } -#Usage: _namecheap_is_our_dns -#Succeeds only when domains.getList listed exactly 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() { i=100 p=99 diff --git a/dnsapi/dns_netcup.sh b/dnsapi/dns_netcup.sh index 3b291854..8609adf6 100644 --- a/dnsapi/dns_netcup.sh +++ b/dnsapi/dns_netcup.sh @@ -33,11 +33,9 @@ dns_netcup_add() { exit=$(echo "$fulldomain" | tr -dc '.' | wc -c) exit=$(_math "$exit" + 1) i=$exit - _nc_last=$(_nc_lastlevel "$i") - _nc_found="" while - [ "$exit" -ge "$_nc_last" ] + [ "$exit" -gt 0 ] do tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit") if [ "$(_math "$i" - "$exit")" -eq 0 ]; then @@ -53,18 +51,12 @@ dns_netcup_add() { _err "$msg" return 1 else - _nc_found=1 break fi fi fi exit=$(_math "$exit" - 1) done - if [ -z "$_nc_found" ]; then - _err "$msg" - _nc_nozone "$fulldomain" - return 1 - fi logout } @@ -78,11 +70,9 @@ dns_netcup_rm() { exit=$(_math "$exit" + 1) i=$exit rec="" - _nc_last=$(_nc_lastlevel "$i") - _nc_found="" while - [ "$exit" -ge "$_nc_last" ] + [ "$exit" -gt 0 ] do tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit") if [ "$(_math "$i" - "$exit")" -eq 0 ]; then @@ -99,18 +89,12 @@ dns_netcup_rm() { _err "$msg" return 1 else - _nc_found=1 break fi fi fi exit=$(_math "$exit" - 1) done - if [ -z "$_nc_found" ]; then - _err "$msg" - _nc_nozone "$fulldomain" - return 1 - fi ida=0000 idv=0001 @@ -141,27 +125,6 @@ dns_netcup_rm() { 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() { 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) diff --git a/dnsapi/dns_nexdns.sh b/dnsapi/dns_nexdns.sh deleted file mode 100755 index e4447c0e..00000000 --- a/dnsapi/dns_nexdns.sh +++ /dev/null @@ -1,244 +0,0 @@ -#!/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 -' - -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 -} diff --git a/dnsapi/dns_poweradmin.sh b/dnsapi/dns_poweradmin.sh index a4c81835..db31fa4f 100644 --- a/dnsapi/dns_poweradmin.sh +++ b/dnsapi/dns_poweradmin.sh @@ -227,7 +227,7 @@ _poweradmin_rest() { return 1 fi - if printf '%s' "$response" | grep -q '"success"[ ]*:[ ]*false'; then + if printf '%s' "$response" | grep -q '"success"[[:space:]]*:[[:space:]]*false'; then _err "API reported failure on $method $ep" _debug "Response: $response" return 1 diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh index b9abff17..c27fbc5f 100755 --- a/dnsapi/dns_rage4.sh +++ b/dnsapi/dns_rage4.sh @@ -71,7 +71,7 @@ dns_rage4_rm() { _debug "Getting txt records" _rage4_rest "getrecords/?id=${_domain_id}" - _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -n 's/.*"id":\([0-9][0-9]*\),.*/\1/p') + _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p') if [ -z "$_record_id" ]; then _err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'." return 1 diff --git a/dnsapi/dns_rltx.sh b/dnsapi/dns_rltx.sh deleted file mode 100644 index 065ac177..00000000 --- a/dnsapi/dns_rltx.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/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 -} diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh index 8ba9a4fb..565f541b 100644 --- a/dnsapi/dns_selectel.sh +++ b/dnsapi/dns_selectel.sh @@ -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}\"}}}}}" export _H1="Content-Type: application/json" _result=$(_post "$_data_auth" "$auth_uri") - _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | cut -d ':' -f 2- | tr -d ' \t\r') + _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p") _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}" _saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2" diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh index 25130146..782a5d5f 100644 --- a/dnsapi/dns_selfhost.sh +++ b/dnsapi/dns_selfhost.sh @@ -42,10 +42,7 @@ dns_selfhost_add() { # only match full domains (at the beginning of the string or with a leading whitespace), # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com # if the domain is defined multiple times only the last occurance will be matched - # 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") + mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p") _debug2 mapEntry "$mapEntry" if test -z "$mapEntry"; then _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID" @@ -57,7 +54,7 @@ dns_selfhost_add() { rid2=$(echo "$mapEntry" | cut -d: -f3) # read last used rid domain - lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*\).*/\1/p") + lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p") _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry" lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2) diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh index dbc959d6..656a0557 100644 --- a/dnsapi/dns_udr.sh +++ b/dnsapi/dns_udr.sh @@ -145,8 +145,8 @@ _udr_rest() { _debug data "${data}" 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 | tr -d ' \t\r') - _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | tr -d '\r' | sed -e 's/^[ ]*//' -e 's/[ ]*$//') + _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _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 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') _debug response_code "$_code" _debug response_description "$_description" diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh index 0a1cda6b..f59715ac 100644 --- a/dnsapi/dns_world4you.sh +++ b/dnsapi/dns_world4you.sh @@ -61,7 +61,7 @@ dns_world4you_add() { if _contains "$res" "successfully"; then return 0 else - msg=$(_w4y_alert_msg "$res") + msg=$(echo "$res" | grep -A 20 'alert-notification' | grep 'class="weak-title">[^<]' | sed 's/<[^>]*>//g;s/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to add record: Unknown error" echo "$ret" >'error-01.html' @@ -125,7 +125,7 @@ dns_world4you_rm() { if _contains "$res" "successfully"; then return 0 else - msg=$(_w4y_alert_msg "$res") + msg=$(echo "$res" | grep -A 20 'alert-notification' | grep 'class="weak-title">[^<]' | sed 's/<[^>]*>//g;s/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to remove record: Unknown error" echo "$ret" >'error-01.html' @@ -145,17 +145,6 @@ dns_world4you_rm() { ################ Private functions ################ -# Usage: _w4y_alert_msg -# 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 _login() { WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}" diff --git a/dnsapi/dns_yandex360.sh b/dnsapi/dns_yandex360.sh index 98841d6b..18d01361 100644 --- a/dnsapi/dns_yandex360.sh +++ b/dnsapi/dns_yandex360.sh @@ -149,7 +149,7 @@ _check_variables() { org_response="$(echo "$org_response" | _normalizeJson)" YANDEX360_ORG_ID=$( echo "$org_response" | - _egrep_o '"id":[ ]*[0-9]+' | + _egrep_o '"id":[[:space:]]*[0-9]+' | cut -d':' -f2 ) _debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID" @@ -216,7 +216,7 @@ _get_token() { interval=$( echo "$response" | - _egrep_o '"interval":[ ]*[0-9]+' | + _egrep_o '"interval":[[:space:]]*[0-9]+' | cut -d':' -f2 ) _debug 'Polling interval' "$interval" diff --git a/dnsapi/dns_yc.sh b/dnsapi/dns_yc.sh index cacb5977..36c49ce4 100644 --- a/dnsapi/dns_yc.sh +++ b/dnsapi/dns_yc.sh @@ -22,32 +22,21 @@ dns_yc_add() { fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name 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_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 _savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64" else YC_SA_Key_File="$YC_SA_Key_File_Path" - _yc_key_is_temp="" _savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path" fi - YC_Zone_ID="${YC_Zone_ID:-$(_readdomainconf 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_SA_ID="${YC_SA_ID:-$(_readdomainconf 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)}" if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then @@ -76,21 +65,11 @@ dns_yc_add() { return 1 fi 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 - _cleardomainconf YC_Folder_ID _clearaccountconf YC_Folder_ID - _cleardomainconf YC_SA_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_ID + _clearaccountconf YC_SA_Key_ID _clearaccountconf YC_SA_Key_File_PEM_b64 - _cleardomainconf 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." return 1 @@ -131,30 +110,11 @@ dns_yc_rm() { fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name txtvalue=$2 - YC_Zone_ID="${YC_Zone_ID:-$(_readdomainconf 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_SA_ID="${YC_SA_ID:-$(_readdomainconf 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)}" - # 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" if ! _get_root "$fulldomain"; then _err "invalid domain" @@ -164,10 +124,16 @@ dns_yc_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - # upsertRecordSets.deletions removes only the given value from the rrset, - # leaving any other values at the same name (e.g. base + wildcard domain) - # intact -- no need to read the current data set and recompute it. - if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then + _debug "Getting txt records" + if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then + exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$") + _debug exists_txtvalue "$exists_txtvalue" + else + _err "Error: $response" + return 1 + fi + + if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then if _contains "$response" "\"done\": true"; then _info "Delete, OK" return 0 @@ -228,7 +194,7 @@ _get_root() { return 1 fi if _contains "$response" "\"zone\": \"$h\""; then - _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:][^:]*$" | tr -d '"') + _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"') _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") @@ -289,9 +255,7 @@ _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) _debug2 _signature "$_signature" - if [ "$_yc_key_is_temp" ]; then - rm -f "$YC_SA_Key_File" - fi + rm -rf "$YC_SA_Key_File" _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature") _debug2 _jwt "$_jwt" @@ -300,7 +264,7 @@ _yc_login() { _iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")" _debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)" - YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:][^:]*$" | tr -d '"')" + YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')" _debug3 YC_Token return 0 diff --git a/notify/waha.sh b/notify/waha.sh index 573295e9..989f57ac 100755 --- a/notify/waha.sh +++ b/notify/waha.sh @@ -57,8 +57,9 @@ waha_send() { _debug "_data" "$_data" + export _H1="Content-Type: application/json" if [ "$WAHA_API_KEY" ]; then - export _H1="X-Api-Key: $WAHA_API_KEY" + export _H2="X-Api-Key: $WAHA_API_KEY" fi _waha_url="${WAHA_URL}/api/sendText"