mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-08-13 12:33:30 +02:00
Compare commits
No commits in common. "master" and "v3.0.5" have entirely different histories.
286 changed files with 4517 additions and 29291 deletions
190
.github/copilot-instructions.md
vendored
190
.github/copilot-instructions.md
vendored
|
|
@ -1,190 +0,0 @@
|
|||
# GitHub Copilot Shell Scripting (sh) Review Instructions for acme.sh
|
||||
|
||||
## Overall Goal
|
||||
|
||||
Your role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files) for the [acme.sh](https://github.com/acmesh-official/acme.sh) project. Ensure the code exhibits the highest levels of robustness, security, and portability.
|
||||
The review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands.
|
||||
|
||||
## Required Output Format
|
||||
|
||||
Organize the feedback into a single, structured report, using the three-level marking system:
|
||||
|
||||
1. **Critical Issues (Must Fix Before Merge)**
|
||||
2. **Suggestions (Improvements to Consider)**
|
||||
3. **Good Practices (Points to Commend)**
|
||||
|
||||
---
|
||||
|
||||
## Shell Compatibility
|
||||
|
||||
- **POSIX sh only** -- all scripts must target `sh`, not `bash`. No bash-isms allowed.
|
||||
- **Shebang**: always use `#!/usr/bin/env sh` (not `#!/bin/sh`, not `#!/usr/bin/env bash`).
|
||||
- **Use `return`, never `exit`** -- scripts are sourced, not executed as subprocesses. `exit` would kill the parent shell.
|
||||
- **Cross-platform**: code must work on Linux, macOS, FreeBSD, Solaris, and BusyBox environments.
|
||||
|
||||
---
|
||||
|
||||
## Robustness and Error Handling
|
||||
|
||||
- **(Critical)** Enforce the use of the following combination at the start of the script for safety and robustness:
|
||||
- `set -e`: Exit immediately if a command exits with a non-zero status.
|
||||
- `set -u`: Treat unset variables as an error and exit.
|
||||
- `set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails.
|
||||
- **Always check return values** of function calls. If an error occurs, there must be a way to stop execution.
|
||||
- **Return 1** after `_err` messages:
|
||||
```sh
|
||||
if [ -z "$VARIABLE" ]; then
|
||||
_err "VARIABLE is required"
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
- Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks.
|
||||
|
||||
---
|
||||
|
||||
## Security and Quoting
|
||||
|
||||
- **(Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `"$VAR"` and `"$(COMMAND)"`) to prevent **Word Splitting** and **Globbing**.
|
||||
- **(Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details.
|
||||
- Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use.
|
||||
- Avoid `eval` -- warn against and suggest alternatives, as it can lead to arbitrary code execution.
|
||||
|
||||
---
|
||||
|
||||
## Use Built-in Helper Functions
|
||||
|
||||
Never use raw shell commands when acme.sh provides a wrapper function. This is the most critical rule for portability.
|
||||
|
||||
| Instead of | Use |
|
||||
|---|---|
|
||||
| `tr '[:upper:]' '[:lower:]'` | `_lower_case()` |
|
||||
| `tr '[:lower:]' '[:upper:]'` | `_upper_case()` |
|
||||
| `head -n 1` | `_head_n 1` |
|
||||
| `openssl dgst` / `openssl` | `_digest()` / `_hmac()` |
|
||||
| `date` | `_utc_date()` with `sed`/`tr` |
|
||||
| `curl` / `wget` | `_get()` or `_post()` |
|
||||
| `sleep` | `_sleep` |
|
||||
| `base64` / `openssl base64` | `_base64()` |
|
||||
| `$(( ))` arithmetic | `_math()` |
|
||||
| `grep -E` / `grep -Po` | `_egrep_o()` |
|
||||
| `printf` | `echo` |
|
||||
| `idn` command | `_idn()` / `_is_idn()` |
|
||||
| `mktemp` | `_mktemp()` |
|
||||
| `[:space:]` | ` ` |
|
||||
| `[:alnum:]` | `A-Za-z0-9` |
|
||||
| `[:alpha:]` | `A-Za-z` |
|
||||
| `[:digit:]` | `0-9` |
|
||||
| `awk` | `cut` / `sed` / `while read` loops |
|
||||
|
||||
|
||||
|
||||
When fixing a pattern issue, fix **all instances** in the file, not just the one highlighted.
|
||||
|
||||
---
|
||||
|
||||
## Forbidden External Tools
|
||||
|
||||
Do not use these commands -- they are not portable across all target platforms:
|
||||
|
||||
- `jq` (parse JSON with built-in string manipulation)
|
||||
- `grep -A` (removed throughout the project)
|
||||
- `grep -Po` (Perl regex not available everywhere)
|
||||
- `rev`, `xargs`, `iconv`
|
||||
- If you must depend on an external tool, check with `_exists` first:
|
||||
```sh
|
||||
if ! _exists jq; then
|
||||
_err "jq is required"
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
- Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Management
|
||||
|
||||
Use the correct save/read functions depending on hook type:
|
||||
|
||||
- **DNS hooks**: `_readaccountconf_mutable` to read API keys, `_saveaccountconf_mutable` to save them. Do not use `_saveaccountconf` or `_readaccountconf`.
|
||||
- **Deploy hooks**: `_savedeployconf` / `_getdeployconf`
|
||||
- **Notification hooks**: use account conf functions.
|
||||
- Save operations should only happen in the correct lifecycle function (e.g., `_issue()`).
|
||||
- Use environment variables for all configurable values -- do not introduce hardcoded config files.
|
||||
- Do not clear account conf without a clear reason.
|
||||
|
||||
---
|
||||
|
||||
## DNS API Conventions
|
||||
|
||||
- Read the [DNS API Dev Guide](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide) before writing a DNS plugin.
|
||||
- Each file under `dnsapi/` must contain a `{filename}_add` function for adding DNS TXT records.
|
||||
- The `_get_root()` loop counter `i` must start from `1` (not `2`) to support DNS alias mode.
|
||||
- The `dns_*_rm()` function must remove records **by TXT value**, not by replacing/updating. See [#1261](https://github.com/acmesh-official/acme.sh/issues/1261).
|
||||
- Preserve the `dns_*_info` metadata variable block in each DNS script header.
|
||||
|
||||
---
|
||||
|
||||
## Variable Naming
|
||||
|
||||
- Use CamelCase with provider prefix: `KINGHOST_Username` (not `KINGHOST_username`).
|
||||
- Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions.
|
||||
- Avoid confusingly similar names. Prefer one variable with comma-separated values over multiple variables (e.g., `CZ_Zones` with comma support instead of separate `CZ_Zone` and `CZ_Zones`).
|
||||
- Do not define variables with the same name in different scopes.
|
||||
- Variables inside functions should be declared using the `local` keyword to avoid unintentionally modifying global state.
|
||||
|
||||
---
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use `shfmt` for formatting -- CI enforces it.
|
||||
- Reduce indentation where possible.
|
||||
- Single space, not double spaces.
|
||||
- No trailing semicolons after `return` statements.
|
||||
- Add a newline at the end of every file.
|
||||
- Use `$(command)` over backticks `` `command` `` for command substitution.
|
||||
|
||||
---
|
||||
|
||||
## Simplicity
|
||||
|
||||
- Prefer hardcoded sensible defaults over unnecessary configuration variables (e.g., use `3600` for TTL instead of a `DESEC_TTL` variable).
|
||||
- Reject over-engineered solutions. If it can be done in one line, do it in one line.
|
||||
- Follow existing patterns in the codebase -- new hooks should look like existing hooks.
|
||||
- Respect user choices: do not `chmod` files that already exist; the user's permissions take priority.
|
||||
|
||||
---
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
Before a PR can be merged, the following documentation must be provided:
|
||||
|
||||
- **Wiki page**: add or update the relevant page:
|
||||
- DNS APIs: [dnsapi](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) or [dnsapi2](https://github.com/acmesh-official/acme.sh/wiki/dnsapi2)
|
||||
- Deploy hooks: [deployhooks](https://github.com/acmesh-official/acme.sh/wiki/deployhooks)
|
||||
- Notification hooks: [notify](https://github.com/acmesh-official/acme.sh/wiki/notify)
|
||||
- Options: [Options-and-Params](https://github.com/acmesh-official/acme.sh/wiki/Options-and-Params)
|
||||
- **In-code usage**: add usage examples in the help text of `acme.sh` itself.
|
||||
- **README**: add website URLs for new DNS providers.
|
||||
|
||||
---
|
||||
|
||||
## CI and Merge Hygiene
|
||||
|
||||
- All CI checks must pass before merge.
|
||||
- Rebase to the latest `dev` branch frequently -- do not use merge commits.
|
||||
- Enable GitHub Actions on your fork to catch errors early.
|
||||
- Run the [DNS API Test](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test) workflow for DNS plugins.
|
||||
- For Docker changes, ensure the Dockerfile includes any required dependencies.
|
||||
|
||||
---
|
||||
|
||||
## Debug Logging
|
||||
|
||||
- Use `_debug2` (not `_debug3` or other levels) unless there is a specific reason for a different level.
|
||||
|
||||
---
|
||||
|
||||
## Things to Avoid in Reviews
|
||||
|
||||
- Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or `shfmt`.
|
||||
- Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable.
|
||||
50
.github/workflows/Apache.yml
vendored
50
.github/workflows/Apache.yml
vendored
|
|
@ -1,50 +0,0 @@
|
|||
name: Apache
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Apache.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Apache.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Apache:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_APACHE: 1
|
||||
CASE: le_test_apache
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat apache2
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Set up Apache
|
||||
# Apache serves on 5002, which is the HTTP-01 validation port in
|
||||
# Pebble's default config; acme.sh appends the challenge Alias to
|
||||
# the main config itself
|
||||
run: |
|
||||
echo "Listen 5002" | sudo tee /etc/apache2/ports.conf
|
||||
sudo sed -i "s/\*:80/*:5002/" /etc/apache2/sites-available/000-default.conf
|
||||
sudo apache2ctl configtest
|
||||
sudo systemctl restart apache2
|
||||
curl -s -o /dev/null -w "%{http_code}" -H "Host: example.com" http://127.0.0.1:5002/ | grep -E "200|403|404"
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && sudo --preserve-env ./letest.sh
|
||||
650
.github/workflows/DNS.yml
vendored
650
.github/workflows/DNS.yml
vendored
|
|
@ -1,6 +1,5 @@
|
|||
name: DNS
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'dnsapi/*.sh'
|
||||
|
|
@ -26,9 +25,9 @@ jobs:
|
|||
id: step_one
|
||||
run: |
|
||||
if [ "${{secrets.TokenName1}}" ] ; then
|
||||
echo "hasToken=true" >> "$GITHUB_OUTPUT"
|
||||
echo "::set-output name=hasToken::true"
|
||||
else
|
||||
echo "hasToken=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::set-output name=hasToken::false"
|
||||
fi
|
||||
- name: Check the value
|
||||
run: echo ${{ steps.step_one.outputs.hasToken }}
|
||||
|
|
@ -66,7 +65,7 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Set env file
|
||||
|
|
@ -114,29 +113,27 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install tools
|
||||
run: |
|
||||
brew untap aws/tap || true
|
||||
brew install socat
|
||||
run: brew install socat
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
|
|
@ -167,7 +164,7 @@ jobs:
|
|||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install cygwin base packages with chocolatey
|
||||
run: |
|
||||
choco config get cacheLocation
|
||||
|
|
@ -178,33 +175,28 @@ jobs:
|
|||
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
|
||||
shell: cmd
|
||||
- name: Set ENV
|
||||
shell: bash
|
||||
shell: cmd
|
||||
run: |
|
||||
echo 'PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' >> "$GITHUB_ENV"
|
||||
# cygwin git sees the runner workspace as owned by another user and
|
||||
# fails with "dubious ownership" (exit 128) in the checkout post step
|
||||
echo 'GIT_CONFIG_COUNT=1' >> "$GITHUB_ENV"
|
||||
echo 'GIT_CONFIG_KEY_0=safe.directory' >> "$GITHUB_ENV"
|
||||
echo 'GIT_CONFIG_VALUE_0=*' >> "$GITHUB_ENV"
|
||||
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
|
|
@ -212,7 +204,7 @@ jobs:
|
|||
|
||||
|
||||
FreeBSD:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-12
|
||||
needs: Windows
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
|
|
@ -231,105 +223,40 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/freebsd-vm@v1
|
||||
- uses: vmactions/freebsd-vm@v0
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: pkg install -y socat curl
|
||||
usesh: true
|
||||
sync: nfs
|
||||
copyback: false
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
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"
|
||||
|
||||
|
||||
|
||||
GhostBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: FreeBSD
|
||||
# GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run
|
||||
continue-on-error: true
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- 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
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
OpenBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: GhostBSD
|
||||
runs-on: macos-12
|
||||
needs: FreeBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
|
|
@ -347,45 +274,39 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openbsd-vm@v1
|
||||
- uses: vmactions/openbsd-vm@v0
|
||||
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
|
||||
prepare: pkg_add socat curl
|
||||
usesh: true
|
||||
sync: nfs
|
||||
copyback: false
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
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"
|
||||
|
||||
|
||||
|
||||
|
||||
NetBSD:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-12
|
||||
needs: OpenBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
|
|
@ -404,46 +325,40 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/netbsd-vm@v1
|
||||
- uses: vmactions/netbsd-vm@v0
|
||||
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
|
||||
pkg_add curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
copyback: false
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
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"
|
||||
|
||||
|
||||
|
||||
|
||||
DragonFlyBSD:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-12
|
||||
needs: NetBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
|
|
@ -462,108 +377,44 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/dragonflybsd-vm@v1
|
||||
- uses: vmactions/dragonflybsd-vm@v0
|
||||
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
|
||||
pkg install -y curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
copyback: false
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
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"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MidnightBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: DragonFlyBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
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/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
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
Solaris:
|
||||
runs-on: ubuntu-latest
|
||||
needs: MidnightBSD
|
||||
runs-on: macos-12
|
||||
needs: DragonFlyBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
|
|
@ -582,394 +433,33 @@ jobs:
|
|||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/solaris-vm@v1
|
||||
- uses: vmactions/solaris-vm@v0
|
||||
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: |
|
||||
pkgutil -U
|
||||
pkgutil -y -i socat
|
||||
copyback: false
|
||||
prepare: pkgutil -y -i socat
|
||||
run: |
|
||||
pkg set-mediator -v -I default@1.1 openssl
|
||||
export PATH=/usr/gnu/bin:$PATH
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
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"
|
||||
|
||||
|
||||
Omnios:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Solaris
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Omnios doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- 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
|
||||
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"
|
||||
|
||||
|
||||
|
||||
OpenIndiana:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Omnios
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- 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
|
||||
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"
|
||||
|
||||
|
||||
|
||||
Tribblix:
|
||||
runs-on: ubuntu-latest
|
||||
needs: OpenIndiana
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Tribblix doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- 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
|
||||
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"
|
||||
|
||||
|
||||
|
||||
Haiku:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Tribblix
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- 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}}"
|
||||
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"
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
149
.github/workflows/DragonFlyBSD.yml
vendored
149
.github/workflows/DragonFlyBSD.yml
vendored
|
|
@ -1,78 +1,71 @@
|
|||
name: DragonFlyBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/DragonFlyBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/DragonFlyBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
DragonFlyBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: |
|
||||
pkg install -y libnghttp2
|
||||
pkg install -y curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
name: DragonFlyBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/DragonFlyBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/DragonFlyBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
DragonFlyBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: macos-12
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: vmactions/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/dragonflybsd-vm@v0
|
||||
with:
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
|
||||
copyback: "false"
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: |
|
||||
pkg install -y curl socat
|
||||
usesh: true
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
|
||||
|
||||
|
|
|
|||
26
.github/workflows/FreeBSD.yml
vendored
26
.github/workflows/FreeBSD.yml
vendored
|
|
@ -29,19 +29,19 @@ jobs:
|
|||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-12
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
|
|
@ -51,8 +51,8 @@ jobs:
|
|||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
- uses: actions/checkout@v3
|
||||
- uses: vmactions/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
|
|
@ -61,22 +61,16 @@ jobs:
|
|||
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/freebsd-vm@v1
|
||||
- uses: vmactions/freebsd-vm@v0
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg install -y socat curl wget
|
||||
usesh: true
|
||||
sync: nfs
|
||||
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"
|
||||
|
||||
|
||||
|
|
|
|||
83
.github/workflows/GhostBSD.yml
vendored
83
.github/workflows/GhostBSD.yml
vendored
|
|
@ -1,83 +0,0 @@
|
|||
name: GhostBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/GhostBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/GhostBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
GhostBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
# GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run
|
||||
continue-on-error: true
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: pkg install -y socat curl wget
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
85
.github/workflows/Haiku.yml
vendored
85
.github/workflows/Haiku.yml
vendored
|
|
@ -1,85 +0,0 @@
|
|||
name: Haiku
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Haiku.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Haiku.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Haiku:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: |
|
||||
mkdir -p /boot/home/.cache
|
||||
pkgman install -y cronie
|
||||
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"
|
||||
|
||||
76
.github/workflows/Hurd.yml
vendored
76
.github/workflows/Hurd.yml
vendored
|
|
@ -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"
|
||||
13
.github/workflows/Linux.yml
vendored
13
.github/workflows/Linux.yml
vendored
|
|
@ -26,21 +26,14 @@ jobs:
|
|||
Linux:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "gentoo/stage3"]
|
||||
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "centos:7", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 80
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone acmetest
|
||||
run: |
|
||||
cd .. \
|
||||
|
|
|
|||
15
.github/workflows/MacOS.yml
vendored
15
.github/workflows/MacOS.yml
vendored
|
|
@ -29,10 +29,10 @@ jobs:
|
|||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: macos-latest
|
||||
|
|
@ -44,16 +44,9 @@ jobs:
|
|||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install tools
|
||||
run: brew install socat
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 80
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: |
|
||||
cd .. \
|
||||
|
|
|
|||
75
.github/workflows/MidnightBSD.yml
vendored
75
.github/workflows/MidnightBSD.yml
vendored
|
|
@ -1,75 +0,0 @@
|
|||
name: MidnightBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/MidnightBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/MidnightBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
MidnightBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: mport install socat curl wget || true
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
149
.github/workflows/NetBSD.yml
vendored
149
.github/workflows/NetBSD.yml
vendored
|
|
@ -1,77 +1,72 @@
|
|||
name: NetBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/NetBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/NetBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
NetBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: |
|
||||
/usr/sbin/pkg_add curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
name: NetBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/NetBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/NetBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
NetBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: macos-12
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: vmactions/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/netbsd-vm@v0
|
||||
with:
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: |
|
||||
export PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f '1 2' -d.)/All/"
|
||||
pkg_add curl socat
|
||||
usesh: true
|
||||
copyback: false
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
|
||||
|
||||
|
|
|
|||
66
.github/workflows/Nginx.yml
vendored
66
.github/workflows/Nginx.yml
vendored
|
|
@ -1,66 +0,0 @@
|
|||
name: Nginx
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Nginx.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Nginx.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Nginx:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_NGINX: 1
|
||||
CASE: le_test_nginx
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat nginx
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Set up nginx
|
||||
# a backend on 8081 plus a site with an aaPanel/BT style
|
||||
# "location ^~ /" proxy block that shadows plain regex locations
|
||||
# (regression for #6125); the site listens on 5002, which is the
|
||||
# HTTP-01 validation port in Pebble's default config
|
||||
run: |
|
||||
sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
|
||||
server {
|
||||
listen 127.0.0.1:8081;
|
||||
location / {
|
||||
default_type text/plain;
|
||||
return 200 "backend";
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 5002 default_server;
|
||||
server_name example.com;
|
||||
location ^~ / {
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
curl -s -H "Host: example.com" http://127.0.0.1:5002/ | grep backend
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && sudo --preserve-env ./letest.sh
|
||||
81
.github/workflows/Omnios.yml
vendored
81
.github/workflows/Omnios.yml
vendored
|
|
@ -1,81 +0,0 @@
|
|||
name: Omnios
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Omnios.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Omnios.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Omnios:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: pkg install socat wget
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
28
.github/workflows/OpenBSD.yml
vendored
28
.github/workflows/OpenBSD.yml
vendored
|
|
@ -29,19 +29,19 @@ jobs:
|
|||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-12
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
|
|
@ -51,8 +51,8 @@ jobs:
|
|||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
- uses: actions/checkout@v3
|
||||
- uses: vmactions/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
|
|
@ -61,22 +61,16 @@ jobs:
|
|||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openbsd-vm@v1
|
||||
- uses: vmactions/openbsd-vm@v0
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg_add socat curl wget libnghttp2
|
||||
prepare: pkg_add socat curl wget
|
||||
usesh: true
|
||||
sync: nfs
|
||||
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"
|
||||
|
||||
|
||||
|
|
|
|||
70
.github/workflows/OpenEuler.yml
vendored
70
.github/workflows/OpenEuler.yml
vendored
|
|
@ -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"
|
||||
81
.github/workflows/OpenIndiana.yml
vendored
81
.github/workflows/OpenIndiana.yml
vendored
|
|
@ -1,81 +0,0 @@
|
|||
name: OpenIndiana
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenIndiana.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenIndiana.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
OpenIndiana:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: pkg install socat curl
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
8
.github/workflows/PebbleStrict.yml
vendored
8
.github/workflows/PebbleStrict.yml
vendored
|
|
@ -33,11 +33,11 @@ jobs:
|
|||
TEST_CA: "Pebble Intermediate CA"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Clone acmetest
|
||||
|
|
@ -58,14 +58,14 @@ jobs:
|
|||
TEST_IPCERT: 1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat
|
||||
- name: Run Pebble
|
||||
run: |
|
||||
docker run --rm -itd --name=pebble \
|
||||
-e PEBBLE_VA_ALWAYS_VALID=1 \
|
||||
-p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest -config /test/config/pebble-config.json -strict
|
||||
-p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
|
|
|
|||
157
.github/workflows/Solaris.yml
vendored
157
.github/workflows/Solaris.yml
vendored
|
|
@ -1,83 +1,74 @@
|
|||
name: Solaris
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Solaris.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Solaris.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Solaris:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: |
|
||||
pkgutil -U
|
||||
pkgutil -y -i socat curl wget
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
name: Solaris
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Solaris.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Solaris.yml'
|
||||
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Solaris:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: macos-12
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: vmactions/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/solaris-vm@v0
|
||||
with:
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
copyback: "false"
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkgutil -y -i socat curl wget
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
|
||||
|
|
|
|||
80
.github/workflows/Tribblix.yml
vendored
80
.github/workflows/Tribblix.yml
vendored
|
|
@ -1,80 +0,0 @@
|
|||
name: Tribblix
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Tribblix.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Tribblix.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Tribblix:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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/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"
|
||||
prepare: zap install socat curl wget
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
19
.github/workflows/Ubuntu.yml
vendored
19
.github/workflows/Ubuntu.yml
vendored
|
|
@ -29,16 +29,16 @@ jobs:
|
|||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
ACME_USE_WGET: 1
|
||||
- TEST_ACME_Server: "ZeroSSL.com"
|
||||
CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
CA_EMAIL: "githubtest@acme.sh"
|
||||
TEST_PREFERRED_CHAIN: ""
|
||||
- TEST_ACME_Server: "https://localhost:9000/acme/acme/directory"
|
||||
|
|
@ -70,7 +70,7 @@ jobs:
|
|||
TestingDomain: ${{ matrix.TestingDomain }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat wget
|
||||
- name: Start StepCA
|
||||
|
|
@ -80,14 +80,9 @@ jobs:
|
|||
-p 9000:9000 \
|
||||
-e "DOCKER_STEPCA_INIT_NAME=Smallstep" \
|
||||
-e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \
|
||||
-e "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true" \
|
||||
-e "DOCKER_STEPCA_INIT_PASSWORD=test" \
|
||||
--name stepca \
|
||||
smallstep/step-ca:0.23.1
|
||||
|
||||
sleep 5
|
||||
docker exec stepca bash -c "echo test >test" \
|
||||
&& docker exec stepca step ca provisioner add acme --type ACME --admin-subject step --admin-password-file=/home/step/test \
|
||||
smallstep/step-ca \
|
||||
&& sleep 5 && docker exec stepca step ca provisioner add acme --type ACME \
|
||||
&& docker exec stepca kill -1 1 \
|
||||
&& docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt"
|
||||
- name: Clone acmetest
|
||||
|
|
|
|||
15
.github/workflows/Windows.yml
vendored
15
.github/workflows/Windows.yml
vendored
|
|
@ -29,10 +29,10 @@ jobs:
|
|||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
|
||||
# CA: "ZeroSSL RSA Domain Secure Site CA"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: windows-latest
|
||||
|
|
@ -49,7 +49,7 @@ jobs:
|
|||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install cygwin base packages with chocolatey
|
||||
run: |
|
||||
choco config get cacheLocation
|
||||
|
|
@ -67,13 +67,6 @@ jobs:
|
|||
shell: cmd
|
||||
run: |
|
||||
echo "PATH=%PATH%"
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 80
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
shell: cmd
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
|
|
|
|||
114
.github/workflows/blacklist-command.yml
vendored
114
.github/workflows/blacklist-command.yml
vendored
|
|
@ -1,114 +0,0 @@
|
|||
name: Blacklist Command
|
||||
|
||||
# An issue titled "blacklist: <login-or-email>" opened by the maintainer
|
||||
# or a write-access member adds that identity to the Blacklist wiki page
|
||||
# (see wiki-guard.yml) and closes the issue. The wiki-monitor notification
|
||||
# embeds a prefilled link that opens such an issue in one click.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
# Share the wiki-guard concurrency group so we never push to the wiki
|
||||
# at the same time as the guard.
|
||||
concurrency:
|
||||
group: wiki-guard
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
blacklist:
|
||||
# Upstream only: forks have no <fork>.wiki repository to push to.
|
||||
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'blacklist:')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check authorization
|
||||
id: auth
|
||||
run: |
|
||||
assoc="${{ github.event.issue.author_association }}"
|
||||
case "$assoc" in
|
||||
OWNER|MEMBER|COLLABORATOR)
|
||||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
*)
|
||||
echo "issue author is not authorized ($assoc); ignoring"
|
||||
echo "ok=false" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Checkout wiki repository
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
|
||||
- name: Add the identity to the blacklist page
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
id: add
|
||||
env:
|
||||
TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
target="$(printf '%s' "$TITLE" \
|
||||
| sed 's/^blacklist:[[:space:]]*//; s/^@//; s/[[:space:]].*$//' \
|
||||
| tr 'A-Z' 'a-z')"
|
||||
case "$target" in
|
||||
''|*[!a-z0-9._+@-]*)
|
||||
echo "invalid target: '$target'"
|
||||
echo "result=invalid" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
echo "target=$target" >> "$GITHUB_OUTPUT"
|
||||
cd wiki
|
||||
if [ ! -e Blacklist.md ]; then
|
||||
echo "result=nopage" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if grep -Fxiq -- "- $target" Blacklist.md; then
|
||||
echo "result=already" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$(tail -c1 Blacklist.md)" ]; then
|
||||
echo >> Blacklist.md
|
||||
fi
|
||||
printf -- '- %s\n' "$target" >> Blacklist.md
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add Blacklist.md
|
||||
git commit -m "blacklist $target (requested in #${{ github.event.issue.number }})"
|
||||
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
|
||||
echo "result=added" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Reply and close
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
RESULT: ${{ steps.add.outputs.result }}
|
||||
TARGET: ${{ steps.add.outputs.target }}
|
||||
with:
|
||||
script: |
|
||||
const result = process.env.RESULT;
|
||||
const target = process.env.TARGET;
|
||||
const messages = {
|
||||
added: `\`${target}\` has been added to the [Blacklist](https://github.com/${context.repo.owner}/${context.repo.repo}/wiki/Blacklist). The wiki guard will revert their recent wiki changes on its next run.`,
|
||||
already: `\`${target}\` is already on the blacklist.`,
|
||||
invalid: "Could not parse a valid login or email from the issue title.",
|
||||
nopage: "The Blacklist wiki page does not exist."
|
||||
};
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: messages[result] || "No action taken."
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: "closed",
|
||||
state_reason: result === "added" ? "completed" : "not_planned"
|
||||
});
|
||||
41
.github/workflows/dockerhub.yml
vendored
41
.github/workflows/dockerhub.yml
vendored
|
|
@ -15,8 +15,6 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: neilpang/acme.sh
|
||||
|
||||
jobs:
|
||||
CheckToken:
|
||||
|
|
@ -30,9 +28,9 @@ jobs:
|
|||
id: step_one
|
||||
run: |
|
||||
if [ "$DOCKER_PASSWORD" ] ; then
|
||||
echo "hasToken=true" >>$GITHUB_OUTPUT
|
||||
echo "::set-output name=hasToken::true"
|
||||
else
|
||||
echo "hasToken=false" >>$GITHUB_OUTPUT
|
||||
echo "::set-output name=hasToken::false"
|
||||
fi
|
||||
- name: Check the value
|
||||
run: echo ${{ steps.step_one.outputs.hasToken }}
|
||||
|
|
@ -41,31 +39,20 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: CheckToken
|
||||
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${DOCKER_IMAGE}
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: login to docker hub
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
- name: login to ghcr
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
- name: build and push the image
|
||||
run: |
|
||||
DOCKER_IMAGE=neilpang/acme.sh
|
||||
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/}
|
||||
fi
|
||||
|
|
@ -79,22 +66,8 @@ jobs:
|
|||
fi
|
||||
fi
|
||||
|
||||
echo "DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG}" >>"$GITHUB_ENV"
|
||||
|
||||
DOCKER_LABELS=()
|
||||
while read -r label; do
|
||||
DOCKER_LABELS+=(--label "${label}")
|
||||
done <<<"${DOCKER_METADATA_OUTPUT_LABELS}"
|
||||
|
||||
docker buildx build \
|
||||
--tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
|
||||
"${DOCKER_LABELS[@]}" \
|
||||
--output "type=image,push=true" \
|
||||
--build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \
|
||||
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .
|
||||
- name: mirror the image to ghcr (best-effort)
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag ghcr.io/${{ github.repository }}:${DOCKER_IMAGE_TAG} \
|
||||
${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
|
||||
|| echo "::warning::GHCR mirror failed; Docker Hub publish unaffected"
|
||||
|
|
|
|||
120
.github/workflows/issue.yml
vendored
120
.github/workflows/issue.yml
vendored
|
|
@ -2,128 +2,18 @@ name: "Update issues"
|
|||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const item = context.payload.issue || context.payload.pull_request;
|
||||
|
||||
// Close on sight anything opened by a user on the wiki Blacklist
|
||||
// page (maintained by the Wiki Guard workflow).
|
||||
let blacklist = [];
|
||||
try {
|
||||
const res = await fetch(`https://raw.githubusercontent.com/wiki/${context.repo.owner}/${context.repo.repo}/Blacklist.md`);
|
||||
if (res.ok) {
|
||||
blacklist = (await res.text()).split("\n")
|
||||
.filter(l => l.startsWith("- "))
|
||||
.map(l => l.slice(2).trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
core.warning(`Failed to fetch the blacklist: ${e}`);
|
||||
}
|
||||
// A comment on a closed tracking issue reopens it (the standard
|
||||
// closing note promises this). Bots, blacklisted users and the
|
||||
// maintainer's own comments don't reopen.
|
||||
if (context.eventName === "issue_comment") {
|
||||
const issue = context.payload.issue;
|
||||
const commenter = context.payload.comment.user;
|
||||
if (issue.pull_request || issue.state !== "closed") {
|
||||
return;
|
||||
}
|
||||
if (!/^report\s+(bugs?|issues?)\b/i.test(issue.title)) {
|
||||
return;
|
||||
}
|
||||
if (commenter.type === "Bot" ||
|
||||
commenter.login.toLowerCase() === "neilpang" ||
|
||||
blacklist.includes(commenter.login.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: "open"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (blacklist.includes(item.user.login.toLowerCase())) {
|
||||
if (context.payload.pull_request) {
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: item.number,
|
||||
state: "closed"
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: item.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned"
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.payload.pull_request) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issue = context.payload.issue;
|
||||
if (issue.title.startsWith("blacklist:") || issue.title.startsWith("revert:")) {
|
||||
// Handled by the Blacklist / Revert Command workflows.
|
||||
return;
|
||||
}
|
||||
if (/^report\s+(bugs?|issues?)\b/i.test(issue.title)) {
|
||||
// Tracking issue for a third-party dns/deploy/notify api:
|
||||
// no upgrade boilerplate; assign it to the opener, label it,
|
||||
// then close it right away to keep the issue list clean. Any
|
||||
// later comment reopens it (see the issue_comment handler).
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
assignees: [issue.user.login]
|
||||
});
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ["3rd party api"]
|
||||
});
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: "Closing this tracking issue for now to keep the issue list clean. It remains the place to report problems with this provider -- if you hit a bug, comment here and the issue will be reopened."
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: "closed",
|
||||
state_reason: "completed"
|
||||
});
|
||||
return;
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: issue.number,
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "Please upgrade to the latest code and try again first. Maybe it's already fixed. ```acme.sh --upgrade``` If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you. Before posting the log, review it and REDACT any secrets: private keys (`-----BEGIN ... PRIVATE KEY-----` blocks), API tokens and passwords."
|
||||
body: "Please upgrade to the latest code and try again first. Maybe it's already fixed. ```acme.sh --upgrade``` If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you."
|
||||
|
||||
})
|
||||
16
.github/workflows/pr_dns.yml
vendored
16
.github/workflows/pr_dns.yml
vendored
|
|
@ -4,6 +4,8 @@ on:
|
|||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
branches:
|
||||
- 'dev'
|
||||
paths:
|
||||
- 'dnsapi/*.sh'
|
||||
|
||||
|
|
@ -11,9 +13,8 @@ on:
|
|||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'neilpang'
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
|
|
@ -21,14 +22,9 @@ jobs:
|
|||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `**Welcome**
|
||||
READ ME !!!!!
|
||||
Read me !!!!!!
|
||||
First thing: don't send PR to the master branch, please send to the dev branch instead.
|
||||
Please read the [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide).
|
||||
You MUST pass the [DNS-API-Test](../wiki/DNS-API-Test).
|
||||
Then reply on this message, otherwise, your code will not be reviewed or merged.
|
||||
Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2
|
||||
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
|
||||
Please make sure you're read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test).
|
||||
Then reply on this message, otherwise, your code will not be reviewed or merged.
|
||||
We look forward to reviewing your Pull request shortly ✨
|
||||
`
|
||||
})
|
||||
|
||||
|
|
|
|||
7
.github/workflows/pr_notify.yml
vendored
7
.github/workflows/pr_notify.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
name: Check notify api
|
||||
name: Check dns api
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
|
@ -13,9 +13,8 @@ on:
|
|||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'neilpang'
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
|
|
@ -23,7 +22,7 @@ jobs:
|
|||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `**Welcome**
|
||||
Please make sure you've read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify).
|
||||
Please make sure you're read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify).
|
||||
Then reply on this message, otherwise, your code will not be reviewed or merged.
|
||||
We look forward to reviewing your Pull request shortly ✨
|
||||
`
|
||||
|
|
|
|||
110
.github/workflows/revert-command.yml
vendored
110
.github/workflows/revert-command.yml
vendored
|
|
@ -1,110 +0,0 @@
|
|||
name: Revert Command
|
||||
|
||||
# An issue titled "revert: <wiki-commit-sha>" opened by the maintainer or
|
||||
# a write-access member reverts that commit in the wiki repository and
|
||||
# closes the issue. The wiki-monitor notification embeds a prefilled link
|
||||
# that opens such an issue in one click.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
# Share the wiki-guard concurrency group so we never push to the wiki
|
||||
# at the same time as the guard.
|
||||
concurrency:
|
||||
group: wiki-guard
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
revert:
|
||||
# Upstream only: forks have no <fork>.wiki repository to push to.
|
||||
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'revert:')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check authorization
|
||||
id: auth
|
||||
run: |
|
||||
assoc="${{ github.event.issue.author_association }}"
|
||||
case "$assoc" in
|
||||
OWNER|MEMBER|COLLABORATOR)
|
||||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
*)
|
||||
echo "issue author is not authorized ($assoc); ignoring"
|
||||
echo "ok=false" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Checkout wiki repository
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Revert the wiki commit
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
id: revert
|
||||
env:
|
||||
TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
target="$(printf '%s' "$TITLE" \
|
||||
| sed 's/^revert:[[:space:]]*//; s/[[:space:]].*$//' \
|
||||
| tr 'A-Z' 'a-z')"
|
||||
case "$target" in
|
||||
*[!0-9a-f]*|"")
|
||||
echo "invalid commit sha: '$target'"
|
||||
echo "result=invalid" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
echo "target=$target" >> "$GITHUB_OUTPUT"
|
||||
cd wiki
|
||||
if ! git cat-file -e "$target^{commit}" 2>/dev/null; then
|
||||
echo "result=notfound" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
if git revert --no-edit "$target"; then
|
||||
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
|
||||
echo "result=reverted" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
git revert --abort || true
|
||||
echo "result=conflict" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Reply and close
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
RESULT: ${{ steps.revert.outputs.result }}
|
||||
TARGET: ${{ steps.revert.outputs.target }}
|
||||
with:
|
||||
script: |
|
||||
const result = process.env.RESULT;
|
||||
const target = process.env.TARGET;
|
||||
const messages = {
|
||||
reverted: `Wiki commit \`${target}\` has been reverted.`,
|
||||
conflict: `Reverting \`${target}\` conflicts with later edits; please revert manually from the page history.`,
|
||||
notfound: `Commit \`${target}\` was not found in the wiki repository.`,
|
||||
invalid: "Could not parse a commit sha from the issue title."
|
||||
};
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: messages[result] || "No action taken."
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: "closed",
|
||||
state_reason: result === "reverted" ? "completed" : "not_planned"
|
||||
});
|
||||
4
.github/workflows/shellcheck.yml
vendored
4
.github/workflows/shellcheck.yml
vendored
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
ShellCheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Shellcheck
|
||||
run: sudo apt-get install -y shellcheck
|
||||
- name: DoShellcheck
|
||||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
shfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install shfmt
|
||||
run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt
|
||||
- name: shfmt
|
||||
|
|
|
|||
32
.github/workflows/vtag.yml
vendored
32
.github/workflows/vtag.yml
vendored
|
|
@ -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 }}"
|
||||
325
.github/workflows/wiki-guard.yml
vendored
325
.github/workflows/wiki-guard.yml
vendored
|
|
@ -1,325 +0,0 @@
|
|||
name: Wiki Guard
|
||||
|
||||
# Rules enforced here:
|
||||
# - Only the maintainer and write-access members may delete or rename wiki
|
||||
# pages. Anyone else doing so gets blacklisted and the page restored to
|
||||
# its last good revision.
|
||||
# - Only the maintainer and write-access members may edit the Blacklist
|
||||
# wiki page. Anyone else touching it gets blacklisted and the page
|
||||
# reverted.
|
||||
# - Any wiki change made by a blacklisted identity is reverted.
|
||||
# A "good" revision is one authored by the maintainer, by this bot, or by
|
||||
# a non-blacklisted user -- restoring from the deleted commit's parent is
|
||||
# NOT safe, because vandals replace a page before destroying it and the
|
||||
# parent would launder their version into a bot commit.
|
||||
# The gollum event only fires on page create/update, never on deletion,
|
||||
# so violations are caught by polling the wiki git history.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/10 * * * *"
|
||||
gollum:
|
||||
# Piggyback on frequent repo activity, because the cron schedule is
|
||||
# best-effort and often delayed well beyond its interval.
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: wiki-guard
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
guard:
|
||||
# Forks have no <fork>.wiki repository, so the checkout below would
|
||||
# fail there -- run only in the upstream repository.
|
||||
if: github.repository == 'acmesh-official/acme.sh'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout wiki repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Enforce wiki rules
|
||||
id: guard
|
||||
env:
|
||||
# WIKI_GUARD_TOKEN: a PAT with read:org, needed to enumerate
|
||||
# members whose write access comes via the organization -- the
|
||||
# repo-scoped GITHUB_TOKEN only sees direct collaborators.
|
||||
GH_TOKEN: ${{ secrets.WIKI_GUARD_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Logins with write (push) access to the repository, including
|
||||
# organization members -- they may delete/rename pages and edit
|
||||
# the blacklist just like the maintainer. If the API call fails,
|
||||
# the list stays empty and enforcement falls back to
|
||||
# maintainer-only, which is the safe direction.
|
||||
gh api "repos/${GITHUB_REPOSITORY}/collaborators?per_page=100" --paginate \
|
||||
-q '.[] | select(.permissions.push) | .login' 2>/dev/null \
|
||||
| tr 'A-Z' 'a-z' | sort -u > writers.txt || true
|
||||
echo "write-access members loaded: $(wc -l < writers.txt)"
|
||||
cd wiki
|
||||
git config core.quotePath false
|
||||
|
||||
# Any author email under this domain is the maintainer.
|
||||
OWNER_DOMAIN="neilpang.com"
|
||||
# Our own enforcement commits.
|
||||
BOT_EMAIL="41898282+github-actions[bot]@users.noreply.github.com"
|
||||
BL_PAGE="Blacklist.md"
|
||||
# Rolling window; the cron runs every 10 minutes, so 7 days gives
|
||||
# ample overlap without re-judging old changes the maintainer
|
||||
# already accepted.
|
||||
WINDOW="7 days ago"
|
||||
|
||||
: > ../actions.txt
|
||||
: > ../bl_new.txt
|
||||
|
||||
is_owner() {
|
||||
case "$1" in
|
||||
*@"$OWNER_DOMAIN") return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
is_bot() {
|
||||
[ "$1" = "$BOT_EMAIL" ]
|
||||
}
|
||||
|
||||
author_email() {
|
||||
git show -s --format=%ae "$1" | tr 'A-Z' 'a-z'
|
||||
}
|
||||
|
||||
# Identity of a commit author: the GitHub login when the email is a
|
||||
# users.noreply.github.com address, otherwise the email itself.
|
||||
identity_of() {
|
||||
case "$1" in
|
||||
*+*@users.noreply.github.com)
|
||||
printf '%s\n' "$1" | sed 's/^[^+]*+//; s/@users\.noreply\.github\.com$//'
|
||||
;;
|
||||
*@users.noreply.github.com)
|
||||
printf '%s\n' "$1" | sed 's/@users\.noreply\.github\.com$//'
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_blacklisted() {
|
||||
grep -Fxq "$1" ../bl_all.txt
|
||||
}
|
||||
|
||||
# Trusted committers: the maintainer (by email domain), this bot,
|
||||
# and anyone whose GitHub login has write access to the repo.
|
||||
is_trusted() {
|
||||
if is_owner "$1" || is_bot "$1"; then
|
||||
return 0
|
||||
fi
|
||||
grep -Fxq "$(identity_of "$1")" ../writers.txt
|
||||
}
|
||||
|
||||
# Newest commit on file $1 authored by a non-blacklisted user.
|
||||
last_good_for() {
|
||||
for g in $(git log --format=%H --no-renames -- "$1"); do
|
||||
gae="$(author_email "$g")"
|
||||
if is_trusted "$gae"; then
|
||||
printf '%s\n' "$g"
|
||||
return 0
|
||||
fi
|
||||
gid="$(identity_of "$gae")"
|
||||
if ! is_blacklisted "$gid" && ! is_blacklisted "$gae"; then
|
||||
printf '%s\n' "$g"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -e "$BL_PAGE" ]; then
|
||||
page_existed=1
|
||||
else
|
||||
page_existed=""
|
||||
fi
|
||||
|
||||
# ---- 1. Last good version of the blacklist page: the newest
|
||||
# revision authored by the maintainer or by this bot. Everything
|
||||
# else on that page is tampering and is discarded.
|
||||
bl_good_commit=""
|
||||
for c in $(git log --format=%H --no-renames -- "$BL_PAGE"); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
bl_good_commit="$c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -n "$bl_good_commit" ] && git cat-file -e "$bl_good_commit:$BL_PAGE" 2>/dev/null; then
|
||||
git show "$bl_good_commit:$BL_PAGE" > ../bl_page.txt
|
||||
else
|
||||
{
|
||||
echo "# Blacklist"
|
||||
echo ""
|
||||
echo "Users listed below violated the wiki rules (deleted or renamed"
|
||||
echo "pages, or tampered with this page). Their new issues and pull"
|
||||
echo "requests are closed on sight and their wiki edits are reverted"
|
||||
echo "automatically. Only the maintainer and write-access members"
|
||||
echo "may edit this page."
|
||||
echo ""
|
||||
echo "To pardon a user while their violation is still inside the"
|
||||
echo "scan window, replace their entry with: pardon: username"
|
||||
echo ""
|
||||
} > ../bl_page.txt
|
||||
fi
|
||||
sed -n 's/^- *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_good.txt
|
||||
sed -n 's/^[Pp]ardon: *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_pardon.txt
|
||||
|
||||
bl_add() {
|
||||
if grep -Fxq "$1" ../bl_pardon.txt; then
|
||||
return 0
|
||||
fi
|
||||
if ! grep -Fxq "$1" ../bl_good.txt && ! grep -Fxq "$1" ../bl_new.txt; then
|
||||
printf '%s\n' "$1" >> ../bl_new.txt
|
||||
printf '%s\n' "- blacklisted \`$1\`: $2" >> ../actions.txt
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- 2. Blacklist everyone who deleted or renamed a page.
|
||||
# --no-renames makes a rename count as a deletion of the old path.
|
||||
for c in $(git log --since="$WINDOW" --diff-filter=D --no-renames --format=%H); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
an="$(git show -s --format=%an "$c")"
|
||||
bl_add "$(identity_of "$ae")" "deleted or renamed pages in $c ($an <$ae>)"
|
||||
done
|
||||
|
||||
# ---- 3. Blacklist everyone else who touched the blacklist page.
|
||||
# The revert of their tampering falls out of steps 5 and 6.
|
||||
for c in $(git log --since="$WINDOW" --format=%H --no-renames -- "$BL_PAGE"); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
an="$(git show -s --format=%an "$c")"
|
||||
bl_add "$(identity_of "$ae")" "tampered with \`$BL_PAGE\` in $c ($an <$ae>)"
|
||||
done
|
||||
|
||||
sort -u ../bl_new.txt > ../bl_new_u.txt
|
||||
cat ../bl_good.txt ../bl_new_u.txt | sort -u > ../bl_all.txt
|
||||
|
||||
# ---- 4. Restore pages that are currently missing because a
|
||||
# non-maintainer deleted them, using the last good revision.
|
||||
git log --since="$WINDOW" --diff-filter=D --no-renames --name-only --format= \
|
||||
| sort -u \
|
||||
| while IFS= read -r f; do
|
||||
if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ] || [ -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
del="$(git log -1 --diff-filter=D --no-renames --format=%H -- "$f")"
|
||||
if [ -z "$del" ]; then
|
||||
continue
|
||||
fi
|
||||
ae="$(author_email "$del")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
good="$(last_good_for "$f")"
|
||||
if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then
|
||||
git checkout "$good" -- "$f"
|
||||
printf '%s\n' "- restored \`$f\` (deleted in $del) from its last good revision $good" >> ../actions.txt
|
||||
fi
|
||||
done
|
||||
|
||||
# ---- 5. Revert every recent change made by a blacklisted
|
||||
# identity: each touched file goes back to its newest revision
|
||||
# authored by a non-blacklisted user; a file that has no such
|
||||
# revision (they created it) is removed.
|
||||
if [ -s ../bl_all.txt ]; then
|
||||
for c in $(git log --since="$WINDOW" --format=%H --no-renames); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
id="$(identity_of "$ae")"
|
||||
if ! is_blacklisted "$id" && ! is_blacklisted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
git show --name-only --no-renames --format= "$c" \
|
||||
| while IFS= read -r f; do
|
||||
if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ]; then
|
||||
continue
|
||||
fi
|
||||
good="$(last_good_for "$f")"
|
||||
if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then
|
||||
want="$(git rev-parse "$good:$f")"
|
||||
have="$(git hash-object -- "$f" 2>/dev/null || echo missing)"
|
||||
if [ "$want" != "$have" ]; then
|
||||
git checkout "$good" -- "$f"
|
||||
printf '%s\n' "- reverted \`$f\` to its last good revision $good (undoing change by \`$id\` in $c)" >> ../actions.txt
|
||||
fi
|
||||
elif [ -e "$f" ]; then
|
||||
git rm -q -- "$f"
|
||||
printf '%s\n' "- removed \`$f\` created by blacklisted \`$id\` in $c" >> ../actions.txt
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# ---- 6. Regenerate the blacklist page: the last good text plus
|
||||
# any newly blacklisted identities. This both reverts tampering
|
||||
# and records new violators; manual edits by the maintainer are
|
||||
# preserved as the new good text.
|
||||
cp ../bl_page.txt ../bl_page_new.txt
|
||||
if [ -s ../bl_page_new.txt ] && [ -n "$(tail -c1 ../bl_page_new.txt)" ]; then
|
||||
echo >> ../bl_page_new.txt
|
||||
fi
|
||||
while IFS= read -r id; do
|
||||
if [ -n "$id" ] && ! grep -Fxiq -- "- $id" ../bl_page_new.txt; then
|
||||
printf -- '- %s\n' "$id" >> ../bl_page_new.txt
|
||||
fi
|
||||
done < ../bl_new_u.txt
|
||||
if ! cmp -s ../bl_page_new.txt "$BL_PAGE" 2>/dev/null; then
|
||||
cp ../bl_page_new.txt "$BL_PAGE"
|
||||
git add -- "$BL_PAGE"
|
||||
if [ -n "$page_existed" ] || [ -s ../bl_new_u.txt ]; then
|
||||
printf '%s\n' "- updated \`$BL_PAGE\`" >> ../actions.txt
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- 7. Commit, push, notify.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "$BOT_EMAIL"
|
||||
git commit -m "wiki-guard: restore pages and enforce blacklist"
|
||||
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
|
||||
fi
|
||||
if [ -s ../actions.txt ]; then
|
||||
{
|
||||
echo "The wiki guard handled the following rule violations:"
|
||||
echo ""
|
||||
cat ../actions.txt
|
||||
echo ""
|
||||
echo "Blacklist: https://github.com/${GITHUB_REPOSITORY}/wiki/Blacklist"
|
||||
echo "Wiki: https://github.com/${GITHUB_REPOSITORY}/wiki"
|
||||
} > ../guard-msg.txt
|
||||
echo "acted=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No rule violations found."
|
||||
echo "acted=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create issue to notify Neilpang
|
||||
if: steps.guard.outputs.acted == 'true'
|
||||
uses: peter-evans/create-issue-from-file@v6
|
||||
with:
|
||||
title: "Wiki guard: rule violations handled"
|
||||
content-filepath: ./guard-msg.txt
|
||||
assignees: Neilpang
|
||||
82
.github/workflows/wiki-monitor.yml
vendored
82
.github/workflows/wiki-monitor.yml
vendored
|
|
@ -1,82 +0,0 @@
|
|||
name: Notify via Issue on Wiki Edit
|
||||
|
||||
on:
|
||||
gollum:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'neilpang'
|
||||
steps:
|
||||
- name: Checkout wiki repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate wiki change message
|
||||
id: msg
|
||||
run: |
|
||||
actor="${{ github.actor }}"
|
||||
sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH")
|
||||
page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH")
|
||||
page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH")
|
||||
page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH")
|
||||
page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH")
|
||||
page_summary=$(jq -r '.pages[0].summary' "$GITHUB_EVENT_PATH")
|
||||
now="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
cd wiki
|
||||
# Skip notification when the change was authored by the
|
||||
# maintainer himself (any author email under neilpang.com),
|
||||
# e.g. a direct git push to the wiki repository.
|
||||
author_email=$(git show -s --format=%ae "$page_sha" 2>/dev/null | tr 'A-Z' 'a-z')
|
||||
case "$author_email" in
|
||||
*@neilpang.com)
|
||||
echo "Change authored by maintainer ($author_email); skipping notification."
|
||||
echo "notify=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1)
|
||||
if [ -n "$prev_sha" ]; then
|
||||
git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff
|
||||
else
|
||||
echo "(no diff)" > ../wiki.diff
|
||||
fi
|
||||
cd ..
|
||||
{
|
||||
echo "Wiki edited"
|
||||
echo -n "User: "
|
||||
echo "@$actor [$actor]($sender_url)"
|
||||
echo "Time: $now"
|
||||
echo "Page: [$page_name]($page_url) (Action: $page_action)"
|
||||
echo "Comment: $page_summary"
|
||||
echo "[Click here to Revert](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=revert%3A+${page_sha}&body=Revert+wiki+commit+${page_sha}+by+@${actor}.)"
|
||||
echo ""
|
||||
echo "[Click here to Blacklist @$actor](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=blacklist%3A+${actor}&body=Blacklist+@${actor},+requested+from+the+wiki+monitor.)"
|
||||
echo ""
|
||||
echo "----"
|
||||
echo "### diff:"
|
||||
echo '```diff'
|
||||
cat wiki.diff
|
||||
echo '```'
|
||||
} > wiki-change-msg.txt
|
||||
echo "notify=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create issue to notify Neilpang
|
||||
if: steps.msg.outputs.notify == 'true'
|
||||
uses: peter-evans/create-issue-from-file@v6
|
||||
with:
|
||||
title: "Wiki edited"
|
||||
content-filepath: ./wiki-change-msg.txt
|
||||
assignees: Neilpang
|
||||
env:
|
||||
TZ: Asia/Shanghai
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
1. Do NOT send pull request to `master` branch.
|
||||
Please send to `dev` branch instead.
|
||||
Any PR to `master` branch will NOT be merged.
|
||||
|
||||
2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
|
||||
You will NOT get any review without passing this guide. You also need to fix the CI errors.
|
||||
41
Dockerfile
41
Dockerfile
|
|
@ -1,4 +1,4 @@
|
|||
FROM alpine:3.23
|
||||
FROM alpine:3.16.3
|
||||
|
||||
RUN apk --no-cache add -f \
|
||||
openssl \
|
||||
|
|
@ -12,33 +12,20 @@ RUN apk --no-cache add -f \
|
|||
oath-toolkit-oathtool \
|
||||
tar \
|
||||
libidn \
|
||||
jq \
|
||||
yq-go \
|
||||
supercronic
|
||||
jq
|
||||
|
||||
ENV LE_WORKING_DIR=/acmebin
|
||||
|
||||
ENV LE_CONFIG_HOME=/acme.sh
|
||||
|
||||
ENV HOME=/acme.sh
|
||||
ENV LE_CONFIG_HOME /acme.sh
|
||||
|
||||
ARG AUTO_UPGRADE=1
|
||||
|
||||
ENV AUTO_UPGRADE=$AUTO_UPGRADE
|
||||
ENV AUTO_UPGRADE $AUTO_UPGRADE
|
||||
|
||||
#Install
|
||||
COPY ./acme.sh /install_acme.sh/acme.sh
|
||||
COPY ./deploy /install_acme.sh/deploy
|
||||
COPY ./dnsapi /install_acme.sh/dnsapi
|
||||
COPY ./notify /install_acme.sh/notify
|
||||
|
||||
RUN addgroup -g 1000 acme && adduser -h $LE_CONFIG_HOME -s /bin/sh -G acme -D -H -u 1000 acme
|
||||
|
||||
COPY ./ /install_acme.sh/
|
||||
RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/
|
||||
|
||||
RUN ln -s $LE_WORKING_DIR/acme.sh /usr/local/bin/acme.sh
|
||||
|
||||
RUN chown -R acme:acme $LE_CONFIG_HOME
|
||||
RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null##' | crontab -
|
||||
|
||||
RUN for verb in help \
|
||||
version \
|
||||
|
|
@ -72,23 +59,17 @@ RUN for verb in help \
|
|||
set-default-ca \
|
||||
set-default-chain \
|
||||
; do \
|
||||
printf -- "%b" "#!/usr/bin/env sh\n$LE_WORKING_DIR/acme.sh --${verb} --config-home $LE_CONFIG_HOME \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
|
||||
printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
|
||||
; done
|
||||
|
||||
RUN printf "%b" '#!'"/usr/bin/env sh\n \
|
||||
if [ \"\$1\" = \"daemon\" ]; then \n \
|
||||
if [ ! -f \"\$LE_CONFIG_HOME/crontab\" ]; then \n \
|
||||
echo \"\$LE_CONFIG_HOME/crontab not found, generating one\" \n \
|
||||
time=\$(date -u \"+%s\") \n \
|
||||
random_minute=\$((\$time % 60)) \n \
|
||||
random_hour=\$((\$time / 60 % 6)) \n \
|
||||
echo \"\$random_minute \$random_hour,\$((\$random_hour + 6)),\$((\$random_hour + 12)),\$((\$random_hour + 18)) * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \
|
||||
fi \n \
|
||||
echo \"Running Supercronic using crontab at \$LE_CONFIG_HOME/crontab\" \n \
|
||||
exec -- /usr/bin/supercronic \"\$LE_CONFIG_HOME/crontab\" \n \
|
||||
trap \"echo stop && killall crond && exit 0\" SIGTERM SIGINT \n \
|
||||
crond && sleep infinity &\n \
|
||||
wait \n \
|
||||
else \n \
|
||||
exec -- \"\$@\"\n \
|
||||
fi\n" >/entry.sh && chmod +x /entry.sh && chmod -R o+rwx $LE_WORKING_DIR && chmod -R o+rwx $LE_CONFIG_HOME
|
||||
fi" >/entry.sh && chmod +x /entry.sh
|
||||
|
||||
VOLUME /acme.sh
|
||||
|
||||
|
|
|
|||
564
README.md
564
README.md
|
|
@ -1,107 +1,68 @@
|
|||
<p align="center">
|
||||
<a href="https://zerossl.com?utm_source=acme-sh">
|
||||
<picture>
|
||||
<!-- Dark mode -->
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://github.com/user-attachments/assets/1308516b-e0cc-496d-b5df-e3932423ead6" />
|
||||
<!-- Light mode -->
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcset="https://github.com/user-attachments/assets/4ba7a79e-8cc9-4d49-87fc-02d44fb7b043" />
|
||||
<!-- Fallback for environments without media queries -->
|
||||
<img
|
||||
alt="ZeroSSL"
|
||||
src="https://github.com/user-attachments/assets/4ba7a79e-8cc9-4d49-87fc-02d44fb7b043"
|
||||
height="auto" />
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
# An ACME Shell script: acme.sh
|
||||
|
||||
<h1 align="center">🔐 acme.sh</h1>
|
||||
<h3 align="center">An ACME Protocol Client Written Purely in Shell</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg" alt="FreeBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg" alt="OpenBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg" alt="NetBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg" alt="MacOS"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg" alt="Ubuntu"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg" alt="Windows"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg" alt="Solaris"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg" alt="DragonFlyBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml/badge.svg" alt="MidnightBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml/badge.svg" alt="GhostBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg" alt="Omnios"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg" alt="OpenIndiana"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg" alt="Tribblix"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg" alt="Haiku"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml/badge.svg" alt="Hurd"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml/badge.svg" alt="OpenEuler"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg" alt="Shellcheck">
|
||||
<img src="https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg" alt="PebbleStrict">
|
||||
<img src="https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg" alt="DockerHub">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors" alt="Financial Contributors on Open Collective"></a>
|
||||
<a href="https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/acme-sh/Lobby.svg" alt="Join the chat at Gitter"></a>
|
||||
<a href="https://hub.docker.com/r/neilpang/acme.sh" title="Click to view the image on Docker Hub"><img src="https://img.shields.io/docker/stars/neilpang/acme.sh.svg" alt="Docker stars"></a>
|
||||
<a href="https://hub.docker.com/r/neilpang/acme.sh" title="Click to view the image on Docker Hub"><img src="https://img.shields.io/docker/pulls/neilpang/acme.sh.svg" alt="Docker pulls"></a>
|
||||
</p>
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)
|
||||
[](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)
|
||||
|
||||
|
||||
---
|
||||

|
||||

|
||||

|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🐚 An ACME protocol client written **purely in Shell** (Unix shell) language
|
||||
- 📜 Full ACME protocol implementation
|
||||
- 🔑 Support **ECDSA** certificates
|
||||
- 🌐 Support **SAN** and **wildcard** certificates
|
||||
- ⚡ Simple, powerful and very easy to use — only **3 minutes** to learn!
|
||||
- 🔧 Compatible with **Bash**, **dash** and **sh**
|
||||
- 🚫 No dependencies on Python
|
||||
- 🔄 One script to issue, renew and install your certificates automatically
|
||||
- 👤 **DOES NOT** require `root/sudoer` access
|
||||
- 🐳 Docker ready
|
||||
- 🌍 IPv6 ready
|
||||
- 📧 Cron job notifications for renewal or error
|
||||
<a href="https://opencollective.com/acmesh" alt="Financial Contributors on Open Collective"><img src="https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors" /></a>
|
||||
[](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub")
|
||||
[](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub")
|
||||
|
||||
> 💡 It's probably the **easiest & smartest** shell script to automatically issue & renew free certificates.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/acmesh-official/acme.sh/wiki"><strong>📚 Wiki</strong></a> •
|
||||
<a href="https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker"><strong>🐳 Docker Guide</strong></a> •
|
||||
<a href="https://twitter.com/neilpangxa"><strong>🐦 Twitter</strong></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
- An ACME protocol client written purely in Shell (Unix shell) language.
|
||||
- Full ACME protocol implementation.
|
||||
- Support ECDSA certs
|
||||
- Support SAN and wildcard certs
|
||||
- Simple, powerful and very easy to use. You only need 3 minutes to learn it.
|
||||
- Bash, dash and sh compatible.
|
||||
- Purely written in Shell with no dependencies on python.
|
||||
- Just one script to issue, renew and install your certificates automatically.
|
||||
- DOES NOT require `root/sudoer` access.
|
||||
- Docker ready
|
||||
- IPv6 ready
|
||||
- Cron job notifications for renewal or error etc.
|
||||
|
||||
## 🌏 [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
|
||||
It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates.
|
||||
|
||||
---
|
||||
Wiki: https://github.com/acmesh-official/acme.sh/wiki
|
||||
|
||||
## 🏆 Who Uses acme.sh?
|
||||
For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker)
|
||||
|
||||
Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
|
||||
|
||||
|
||||
# [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
|
||||
|
||||
# Who:
|
||||
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)
|
||||
- [ruby-china.org](https://ruby-china.org/topics/31983)
|
||||
- [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management)
|
||||
- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89)
|
||||
- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt)
|
||||
- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)
|
||||
- [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)
|
||||
- [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html)
|
||||
- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
|
||||
- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh)
|
||||
- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
|
||||
- [CentOS Web Panel](https://control-webpanel.com)
|
||||
- [CentOS Web Panel](http://centos-webpanel.com/)
|
||||
- [lnmp.org](https://lnmp.org/)
|
||||
- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Tested OS
|
||||
# Tested OS
|
||||
|
||||
| NO | Status| Platform|
|
||||
|----|-------|---------|
|
||||
|
|
@ -114,83 +75,66 @@
|
|||
|7|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)|OpenBSD
|
||||
|8|[](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)|NetBSD
|
||||
|9|[](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)|DragonFlyBSD
|
||||
|10|[](https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml)|MidnightBSD
|
||||
|11|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios
|
||||
|12|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml)|OpenIndiana
|
||||
|13|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian
|
||||
|14|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
|
||||
|15|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|
||||
|16|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
|
||||
|17|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
|
||||
|18|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|
||||
|19|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|
||||
|20|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
|
||||
|21|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux
|
||||
|22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|
||||
|23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|
||||
|24|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
|
||||
|25|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
|
||||
|26|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix
|
||||
|27|[](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD
|
||||
|28|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml)|GNU Hurd
|
||||
|29|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml)|openEuler
|
||||
|10|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian
|
||||
|11|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|CentOS
|
||||
|12|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
|
||||
|13|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|
||||
|14|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
|
||||
|15|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
|
||||
|16|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|
||||
|17|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|
||||
|18|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
|
||||
|19|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux
|
||||
|10|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|ClearLinux
|
||||
|11|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|
||||
|22|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|
||||
|23|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
|
||||
|
||||
|
||||
> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)
|
||||
>
|
||||
> 🖥️ The testing VMs are supported by [vmactions.org](https://vmactions.org)
|
||||
Check our [testing project](https://github.com/acmesh-official/acmetest):
|
||||
|
||||
---
|
||||
https://github.com/acmesh-official/acmetest
|
||||
|
||||
## 🏛️ Supported CA
|
||||
# Supported CA
|
||||
|
||||
| CA | Status |
|
||||
|---|---|
|
||||
| [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) | ⭐ **Default** |
|
||||
| Letsencrypt.org CA | ✅ Supported |
|
||||
| [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) | ✅ Supported |
|
||||
| [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) | ✅ Supported |
|
||||
| [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) | ✅ Supported |
|
||||
| [Pebble strict Mode](https://github.com/letsencrypt/pebble) | ✅ Supported |
|
||||
| Any [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA | ✅ Supported |
|
||||
- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default)
|
||||
- Letsencrypt.org CA
|
||||
- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA)
|
||||
- [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA)
|
||||
- [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA)
|
||||
- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
|
||||
- Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA
|
||||
|
||||
---
|
||||
# Supported modes
|
||||
|
||||
## ⚙️ Supported Modes
|
||||
- Webroot mode
|
||||
- Standalone mode
|
||||
- Standalone tls-alpn mode
|
||||
- Apache mode
|
||||
- Nginx mode
|
||||
- DNS mode
|
||||
- [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode)
|
||||
- [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode)
|
||||
|
||||
| Mode | Description |
|
||||
|------|-------------|
|
||||
| 📁 Webroot mode | Use existing webroot directory |
|
||||
| 🖥️ Standalone mode | Built-in webserver on port 80 |
|
||||
| 🔐 Standalone tls-alpn mode | Built-in webserver on port 443 |
|
||||
| 🪶 Apache mode | Use Apache for verification |
|
||||
| ⚡ Nginx mode | Use Nginx for verification |
|
||||
| 🌐 DNS mode | Use DNS TXT records |
|
||||
| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |
|
||||
| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |
|
||||
| 📌 [DNS persist mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode) | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |
|
||||
|
||||
---
|
||||
# 1. How to install
|
||||
|
||||
## 📖 Usage Guide
|
||||
### 1. Install online
|
||||
|
||||
### 1️⃣ How to Install
|
||||
|
||||
#### 📥 Install Online
|
||||
|
||||
> Check this project: https://github.com/acmesh-official/get.acme.sh
|
||||
Check this project: https://github.com/acmesh-official/get.acme.sh
|
||||
|
||||
```bash
|
||||
curl https://get.acme.sh | sh -s email=my@example.com
|
||||
```
|
||||
|
||||
**Or:**
|
||||
Or:
|
||||
|
||||
```bash
|
||||
wget -O - https://get.acme.sh | sh -s email=my@example.com
|
||||
```
|
||||
|
||||
#### 📦 Install from Git
|
||||
|
||||
### 2. Or, Install from git
|
||||
|
||||
Clone this project and launch installation:
|
||||
|
||||
|
|
@ -200,11 +144,11 @@ cd ./acme.sh
|
|||
./acme.sh --install -m my@example.com
|
||||
```
|
||||
|
||||
> 💡 You `don't have to be root` then, although `it is recommended`.
|
||||
You `don't have to be root` then, although `it is recommended`.
|
||||
|
||||
📚 **Advanced Installation:** https://github.com/acmesh-official/acme.sh/wiki/How-to-install
|
||||
Advanced Installation: https://github.com/acmesh-official/acme.sh/wiki/How-to-install
|
||||
|
||||
**The installer will perform 3 actions:**
|
||||
The installer will perform 3 actions:
|
||||
|
||||
1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`.
|
||||
All certs will be placed in this folder too.
|
||||
|
|
@ -217,19 +161,17 @@ Cron entry example:
|
|||
0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null
|
||||
```
|
||||
|
||||
> ⚠️ After the installation, you must close the current terminal and reopen it to make the alias take effect.
|
||||
After the installation, you must close the current terminal and reopen it to make the alias take effect.
|
||||
|
||||
✅ **You are ready to issue certs now!**
|
||||
Ok, you are ready to issue certs now.
|
||||
|
||||
**Show help message:**
|
||||
Show help message:
|
||||
|
||||
```sh
|
||||
acme.sh -h
|
||||
root@v1:~# acme.sh -h
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ Issue a Certificate
|
||||
# 2. Just issue a cert
|
||||
|
||||
**Example 1:** Single domain.
|
||||
|
||||
|
|
@ -264,21 +206,17 @@ You must point and bind all the domains to the same webroot dir: `/home/wwwroot/
|
|||
|
||||
The certs will be placed in `~/.acme.sh/example.com/`
|
||||
|
||||
> 🔄 The certs will be renewed automatically every **30** days.
|
||||
The certs will be renewed automatically every **60** days.
|
||||
|
||||
> 🔐 The certs will default to **ECC** certificates.
|
||||
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ Install the Certificate to Apache/Nginx
|
||||
# 3. Install the cert to Apache/Nginx etc.
|
||||
|
||||
After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers.
|
||||
You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future.
|
||||
|
||||
> ⚠️ **IMPORTANT:** You **MUST** use this command to copy the certs to the target files. **DO NOT** use the certs files in `~/.acme.sh/` folder — they are for internal use only, the folder structure may change in the future.
|
||||
|
||||
#### 🪶 Apache Example:
|
||||
**Apache** example:
|
||||
```bash
|
||||
acme.sh --install-cert -d example.com \
|
||||
--cert-file /path/to/certfile/in/apache/cert.pem \
|
||||
|
|
@ -287,7 +225,7 @@ acme.sh --install-cert -d example.com \
|
|||
--reloadcmd "service apache2 force-reload"
|
||||
```
|
||||
|
||||
#### ⚡ Nginx Example:
|
||||
**Nginx** example:
|
||||
```bash
|
||||
acme.sh --install-cert -d example.com \
|
||||
--key-file /path/to/keyfile/in/nginx/key.pem \
|
||||
|
|
@ -301,89 +239,91 @@ The ownership and permission info of existing files are preserved. You can pre-c
|
|||
|
||||
Install/copy the cert/key to the production Apache or Nginx path.
|
||||
|
||||
> 🔄 The cert will be renewed every **30** days by default (configurable). Once renewed, the Apache/Nginx service will be reloaded automatically.
|
||||
The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`.
|
||||
|
||||
> ⚠️ **IMPORTANT:** The `reloadcmd` is very important. The cert can be automatically renewed, but without a correct `reloadcmd`, the cert may not be flushed to your server (like nginx or apache), then your website will not be able to show the renewed cert.
|
||||
|
||||
---
|
||||
**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.**
|
||||
|
||||
### 4️⃣ Use Standalone Server to Issue Certificate
|
||||
# 4. Use Standalone server to issue cert
|
||||
|
||||
> 🔐 Requires root/sudoer or permission to listen on port **80** (TCP)
|
||||
**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))**
|
||||
|
||||
> ⚠️ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
|
||||
Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
|
||||
|
||||
```bash
|
||||
acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
---
|
||||
# 5. Use Standalone ssl server to issue cert
|
||||
|
||||
### 5️⃣ Use Standalone TLS Server to Issue Certificate
|
||||
**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))**
|
||||
|
||||
> 🔐 Requires root/sudoer or permission to listen on port **443** (TCP)
|
||||
|
||||
> ⚠️ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
|
||||
Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
|
||||
|
||||
```bash
|
||||
acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
---
|
||||
|
||||
### 6️⃣ Use Apache Mode
|
||||
# 6. Use Apache mode
|
||||
|
||||
> 🔐 Requires root/sudoer to interact with Apache server
|
||||
**(requires you to be root/sudoer, since it is required to interact with Apache server)**
|
||||
|
||||
If you are running a web server, it is recommended to use the `Webroot mode`.
|
||||
|
||||
Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder.
|
||||
|
||||
Just set string "apache" as the second argument and it will force use of apache plugin automatically.
|
||||
|
||||
```sh
|
||||
acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
> 💡 **Note:** This Apache mode is only to issue the cert, it will **not** change your Apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Apache server, don't worry!
|
||||
**This apache mode is only to issue the cert, it will not change your apache config files.
|
||||
You will need to configure your website config files to use the cert by yourself.
|
||||
We don't want to mess with your apache server, don't worry.**
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
---
|
||||
# 7. Use Nginx mode
|
||||
|
||||
### 7️⃣ Use Nginx Mode
|
||||
|
||||
> 🔐 Requires root/sudoer to interact with Nginx server
|
||||
**(requires you to be root/sudoer, since it is required to interact with Nginx server)**
|
||||
|
||||
If you are running a web server, it is recommended to use the `Webroot mode`.
|
||||
|
||||
Particularly, if you are running an Nginx server, you can use Nginx mode instead. This mode doesn't write any files to your web root folder.
|
||||
Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder.
|
||||
|
||||
It will configure Nginx server automatically to verify the domain and then restore the Nginx config to the original version. So, the config is not changed.
|
||||
Just set string "nginx" as the second argument.
|
||||
|
||||
It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version.
|
||||
|
||||
So, the config is not changed.
|
||||
|
||||
```sh
|
||||
acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
> 💡 **Note:** This Nginx mode is only to issue the cert, it will **not** change your Nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Nginx server, don't worry!
|
||||
**This nginx mode is only to issue the cert, it will not change your nginx config files.
|
||||
You will need to configure your website config files to use the cert by yourself.
|
||||
We don't want to mess with your nginx server, don't worry.**
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
---
|
||||
|
||||
### 8️⃣ Automatic DNS API Integration
|
||||
# 8. Automatic DNS API integration
|
||||
|
||||
If your DNS provider supports API access, we can use that API to automatically issue the certs.
|
||||
|
||||
> ✨ **You don't have to do anything manually!**
|
||||
You don't have to do anything manually!
|
||||
|
||||
📚 **Currently acme.sh supports most DNS providers:** https://github.com/acmesh-official/acme.sh/wiki/dnsapi
|
||||
### Currently acme.sh supports most of the dns providers:
|
||||
|
||||
---
|
||||
https://github.com/acmesh-official/acme.sh/wiki/dnsapi
|
||||
|
||||
### 9️⃣ Use DNS Manual Mode
|
||||
# 9. Use DNS manual mode:
|
||||
|
||||
See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first.
|
||||
|
||||
|
|
@ -413,154 +353,72 @@ Then just rerun with `renew` argument:
|
|||
acme.sh --renew -d example.com
|
||||
```
|
||||
|
||||
✅ **Done!**
|
||||
Ok, it's done.
|
||||
|
||||
> ⚠️ **WARNING:** This is DNS manual mode — it **cannot** be renewed automatically. You will have to add a new TXT record to your domain manually when you renew your cert. **Please use DNS API mode instead.**
|
||||
**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.**
|
||||
|
||||
---
|
||||
**Please use dns api mode instead.**
|
||||
|
||||
### 🔟 Use DNS Persist Mode
|
||||
# 10. Issue ECC certificates
|
||||
|
||||
📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode
|
||||
`Let's Encrypt` can now issue **ECDSA** certificates.
|
||||
|
||||
📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)
|
||||
And we support them too!
|
||||
|
||||
DNS persist mode lets you place a **single, long‑lived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals.
|
||||
Just set the `keylength` parameter with a prefix `ec-`.
|
||||
|
||||
#### 🪄 Step 1: Print the TXT record value
|
||||
For example:
|
||||
|
||||
### Single domain ECC certificate
|
||||
|
||||
```bash
|
||||
acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] [--dns-persist-days 365]
|
||||
acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256
|
||||
```
|
||||
|
||||
Options:
|
||||
### SAN multi domain ECC certificate
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--server <ca>` | Pick the CA (default is your configured default). The account is registered automatically if you have not used this CA before. |
|
||||
| `--dns-persist-wildcard` | Adds `policy=wildcard` to the record so it also authorizes wildcard / subdomain certs. |
|
||||
| `--dns-persist-ca-name <name>` | Use a specific CA identity domain (e.g. `sectigo.com`). If omitted, identities are read from the ACME directory's `caaIdentities` field and one record per identity is printed — you only need to add **any one** of them. |
|
||||
| `--dns-persist-days <N>` | Adds `persistUntil=<unix-timestamp>` to the record, set to N days from now. The CA will refuse new validations against the record after that time. Omit for a record with no expiry. |
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256
|
||||
```
|
||||
|
||||
You should get an output like:
|
||||
Please look at the `keylength` parameter above.
|
||||
|
||||
Valid values are:
|
||||
|
||||
1. **ec-256 (prime256v1, "ECDSA P-256")**
|
||||
2. **ec-384 (secp384r1, "ECDSA P-384")**
|
||||
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
|
||||
|
||||
|
||||
|
||||
# 11. Issue Wildcard certificates
|
||||
|
||||
It's simple, just give a wildcard domain as the `-d` parameter.
|
||||
|
||||
```sh
|
||||
TXT persist domain:_validation-persist.example.com
|
||||
TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
|
||||
```
|
||||
|
||||
#### ✍️ Step 2: Add the TXT record to your DNS
|
||||
|
||||
Add the printed `TXT persist domain` / `TXT persist value` pair as a TXT record at your DNS provider, then wait for it to propagate.
|
||||
|
||||
#### 📜 Step 3: Issue the certificate
|
||||
|
||||
```bash
|
||||
acme.sh --issue -d example.com --dns-persist
|
||||
```
|
||||
|
||||
✅ **Done!** No challenge token is provisioned during issuance — the CA reads the persistent TXT record directly.
|
||||
|
||||
> 🔄 Renewals just work: `acme.sh --renew -d example.com` (or the cron job) reuses the same TXT record automatically — no further DNS edits needed.
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣1️⃣ Issue Certificates of Different Key Types (ECC or RSA)
|
||||
|
||||
Just set the `keylength` to a valid, supported value.
|
||||
|
||||
**Valid values for the `keylength` parameter:**
|
||||
|
||||
| Key Length | Description |
|
||||
|------------|-------------|
|
||||
| `ec-256` | prime256v1, "ECDSA P-256" ⭐ **Default** |
|
||||
| `ec-384` | secp384r1, "ECDSA P-384" |
|
||||
| `ec-521` | secp521r1, "ECDSA P-521" ⚠️ Not supported by Let's Encrypt yet |
|
||||
| `2048` | RSA 2048-bit |
|
||||
| `3072` | RSA 3072-bit |
|
||||
| `4096` | RSA 4096-bit |
|
||||
|
||||
**Examples:**
|
||||
|
||||
#### Single domain with ECDSA P-384 certificate
|
||||
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384
|
||||
```
|
||||
|
||||
#### SAN multi domain with RSA4096 certificate
|
||||
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣2️⃣ Issue Wildcard Certificates
|
||||
|
||||
It's simple! Just give a wildcard domain as the `-d` parameter:
|
||||
|
||||
```sh
|
||||
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
|
||||
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
# 12. How to renew the certs
|
||||
|
||||
### 1️⃣3️⃣ How to Renew Certificates
|
||||
No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days.
|
||||
|
||||
> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days, **or earlier when the CA's ARI says so** (see below).
|
||||
|
||||
However, you can force a renewal:
|
||||
However, you can also force to renew a cert:
|
||||
|
||||
```sh
|
||||
acme.sh --renew -d example.com --force
|
||||
```
|
||||
|
||||
**For ECC cert:**
|
||||
or, for ECC cert:
|
||||
|
||||
```sh
|
||||
acme.sh --renew -d example.com --force --ecc
|
||||
```
|
||||
|
||||
#### 📡 ACME Renewal Information (ARI) — RFC 9773
|
||||
|
||||
📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/ARI
|
||||
|
||||
If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically — **no flag needed, no opt-in**:
|
||||
|
||||
| What | When | Why |
|
||||
|------|------|-----|
|
||||
| 🔍 **Polls `suggestedWindow`** | Every cron run, before deciding to skip | Lets the CA shift the renewal time forward in case of an incident (key compromise, mass revocation, etc.) |
|
||||
| 🎯 **Picks a random renewal time** inside the window | Right after a successful issuance/renewal | Disperses renewals across the network so all clients don't hit the CA at the same instant |
|
||||
| 🔗 **Sends `replaces=<certID>`** in `newOrder` | On renewal | Lets the CA correlate the new order with the certificate it supersedes (RFC 9773 §5) |
|
||||
| ↩️ **Retries without `replaces`** | If the CA rejects with `alreadyReplaced` or an ARI validation error | Robust against edge cases (e.g. switching CAs, retired issuers) |
|
||||
|
||||
**Renewal trigger logic:** the cert is renewed if **any one** of the following becomes true:
|
||||
|
||||
1. `--force` is given
|
||||
2. The CA's **ARI `suggestedWindow` has started**
|
||||
3. The cached `Le_NextRenewTime` has passed (default fallback for CAs without ARI)
|
||||
|
||||
You can see the resulting next renewal time (already ARI-picked when applicable) in:
|
||||
|
||||
```sh
|
||||
acme.sh --info -d example.com
|
||||
# Look for: Le_NextRenewTimeStr=...
|
||||
```
|
||||
|
||||
For the live ARI window the CA is currently advertising, run with `--debug 2`:
|
||||
|
||||
```sh
|
||||
acme.sh --renew -d example.com --debug 2 2>&1 | grep -i 'ARI suggestedWindow'
|
||||
```
|
||||
|
||||
> 💡 If your CA does not advertise `renewalInfo`, `acme.sh` falls back to the classic 30-day rule — no behavior change.
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣4️⃣ How to Stop Certificate Renewal
|
||||
# 13. How to stop cert renewal
|
||||
|
||||
To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
|
||||
|
||||
|
|
@ -570,80 +428,73 @@ acme.sh --remove -d example.com [--ecc]
|
|||
|
||||
The cert/key file is not removed from the disk.
|
||||
|
||||
> 💡 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) manually.
|
||||
You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself.
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣5️⃣ How to Upgrade acme.sh
|
||||
# 14. How to upgrade `acme.sh`
|
||||
|
||||
> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code.
|
||||
acme.sh is in constant development, so it's strongly recommended to use the latest code.
|
||||
|
||||
**Update to latest:**
|
||||
You can update acme.sh to the latest code:
|
||||
|
||||
```sh
|
||||
acme.sh --upgrade
|
||||
```
|
||||
|
||||
**Enable auto upgrade:**
|
||||
You can also enable auto upgrade:
|
||||
|
||||
```sh
|
||||
acme.sh --upgrade --auto-upgrade
|
||||
```
|
||||
|
||||
**Disable auto upgrade:**
|
||||
Then **acme.sh** will be kept up to date automatically.
|
||||
|
||||
Disable auto upgrade:
|
||||
|
||||
```sh
|
||||
acme.sh --upgrade --auto-upgrade 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣6️⃣ Issue a Certificate from an Existing CSR
|
||||
# 15. Issue a cert from an existing CSR
|
||||
|
||||
📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
|
||||
https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣7️⃣ Send Notifications in Cronjob
|
||||
# 16. Send notifications in cronjob
|
||||
|
||||
📚 https://github.com/acmesh-official/acme.sh/wiki/notify
|
||||
https://github.com/acmesh-official/acme.sh/wiki/notify
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣8️⃣ Under the Hood
|
||||
# 17. Under the Hood
|
||||
|
||||
> 🔧 Speak ACME language using shell, directly to "Let's Encrypt".
|
||||
Speak ACME language using shell, directly to "Let's Encrypt".
|
||||
|
||||
---
|
||||
TODO:
|
||||
|
||||
### 1️⃣9️⃣ Acknowledgments
|
||||
|
||||
| Project | Link |
|
||||
|---------|------|
|
||||
| 🙏 Acme-tiny | https://github.com/diafygi/acme-tiny |
|
||||
| 📜 ACME protocol | https://github.com/ietf-wg-acme/acme |
|
||||
# 18. Acknowledgments
|
||||
|
||||
---
|
||||
1. Acme-tiny: https://github.com/diafygi/acme-tiny
|
||||
2. ACME protocol: https://github.com/ietf-wg-acme/acme
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### 💻 Code Contributors
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
<a href="https://github.com/acmesh-official/acme.sh/graphs/contributors"><img src="https://opencollective.com/acmesh/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
If you want to become a contributor make sure to read [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
### 💰 Financial Contributors
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)]
|
||||
|
||||
#### 👤 Individuals
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/individuals.svg?width=890"></a>
|
||||
|
||||
#### 🏢 Organizations
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)]
|
||||
|
||||
|
|
@ -658,43 +509,24 @@ Support this project with your organization. Your logo will show up here with a
|
|||
<a href="https://opencollective.com/acmesh/organization/8/website"><img src="https://opencollective.com/acmesh/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/avatar.svg"></a>
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣0️⃣ License & Others
|
||||
#### Sponsors
|
||||
|
||||
📄 **License:** GPLv3
|
||||
[](https://www.quantumca.com.cn/?__utm_source=acmesh-donation)
|
||||
|
||||
⭐ Please **Star** and **Fork** this project!
|
||||
|
||||
🐛 [Issues](https://github.com/acmesh-official/acme.sh/issues) and 🔀 [Pull Requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
|
||||
# 19. License & Others
|
||||
|
||||
---
|
||||
License is GPLv3
|
||||
|
||||
### 2️⃣1️⃣ Donate
|
||||
Please Star and Fork me.
|
||||
|
||||
> 💝 Your donation makes **acme.sh** better!
|
||||
[Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
|
||||
|
||||
| Method | Link |
|
||||
|--------|------|
|
||||
| PayPal / Alipay(支付宝) / Wechat(微信) | [https://donate.acme.sh/](https://donate.acme.sh/) |
|
||||
|
||||
📜 [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)
|
||||
# 20. Donate
|
||||
Your donation makes **acme.sh** better:
|
||||
|
||||
---
|
||||
1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/)
|
||||
|
||||
### 2️⃣2️⃣ About This Repository
|
||||
|
||||
> [!NOTE]
|
||||
> This repository is officially maintained by <strong>ZeroSSL</strong> as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community!
|
||||
> For more information about our services, including free and paid SSL/TLS certificates, visit https://zerossl.com.
|
||||
>
|
||||
> All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL.
|
||||
<p align="center">
|
||||
<a href="https://zerossl.com">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://zerossl.com/assets/images/zerossl_logo_white.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://zerossl.com/assets/images/zerossl_logo.svg">
|
||||
<img src="https://zerossl.com/assets/images/zerossl_logo.svg" alt="ZeroSSL" width="256">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
[Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)
|
||||
|
|
|
|||
|
|
@ -1,341 +0,0 @@
|
|||
# Bash completion for acme.sh: https://github.com/acmesh-official/acme.sh
|
||||
#
|
||||
# "acme.sh --install" copies this file to the acme.sh home dir and wires
|
||||
# it into acme.sh.env, so the completion is loaded automatically in new
|
||||
# bash sessions after installation.
|
||||
#
|
||||
# To use it without installing acme.sh, source it from ~/.bashrc, or copy
|
||||
# it to /usr/share/bash-completion/completions/acme.sh
|
||||
#
|
||||
# Zsh users can load it with:
|
||||
# autoload -U +X bashcompinit && bashcompinit
|
||||
# . /path/to/acme.sh.completion
|
||||
|
||||
# This file may also be sourced by non-bash shells via acme.sh.env,
|
||||
# so silently do nothing if the "complete" builtin is not available.
|
||||
if ! command -v complete >/dev/null 2>&1; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
# Add each word of $1 that starts with $cur to COMPREPLY.
|
||||
# The words are read line by line, so that candidates like a wildcard
|
||||
# domain "*.example.com" are never glob-expanded against the cwd.
|
||||
_acme_sh_add_matches() {
|
||||
local _word
|
||||
while read -r _word; do
|
||||
[ -n "$_word" ] || continue
|
||||
case "$_word" in
|
||||
"$cur"*) COMPREPLY=("${COMPREPLY[@]}" "$_word") ;;
|
||||
esac
|
||||
done <<EOF
|
||||
$(printf '%s\n' "$1" | tr ' ' '\n')
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
_acme_sh_files() {
|
||||
local _file
|
||||
while IFS= read -r _file; do
|
||||
[ -n "$_file" ] || continue
|
||||
COMPREPLY=("${COMPREPLY[@]}" "$_file")
|
||||
done <<EOF
|
||||
$(compgen -f -- "$cur")
|
||||
EOF
|
||||
if command -v compopt >/dev/null 2>&1; then
|
||||
compopt -o filenames 2>/dev/null
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
_acme_sh_dirs() {
|
||||
local _dir
|
||||
while IFS= read -r _dir; do
|
||||
[ -n "$_dir" ] || continue
|
||||
COMPREPLY=("${COMPREPLY[@]}" "$_dir")
|
||||
done <<EOF
|
||||
$(compgen -d -- "$cur")
|
||||
EOF
|
||||
if command -v compopt >/dev/null 2>&1; then
|
||||
compopt -o filenames 2>/dev/null
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Complete the domains that already have a cert: every directory in the
|
||||
# config home that contains a "<domain>.conf" file ("_ecc" suffix stripped).
|
||||
_acme_sh_domains() {
|
||||
local _dir _name _domains=""
|
||||
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
|
||||
for _dir in "$_acme_conf_home"/*/; do
|
||||
[ -d "$_dir" ] || continue
|
||||
_name="${_dir%/}"
|
||||
_name="${_name##*/}"
|
||||
_name="${_name%_ecc}"
|
||||
if [ -f "${_dir}${_name}.conf" ]; then
|
||||
case " $_domains " in
|
||||
*" $_name "*) ;;
|
||||
*) _domains="$_domains $_name" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
_acme_sh_add_matches "$_domains"
|
||||
}
|
||||
|
||||
# Complete hook names from a subfolder of the acme.sh home dir.
|
||||
# $1: subfolder (dnsapi/deploy/notify), $2: file name prefix or empty.
|
||||
_acme_sh_hooks() {
|
||||
local _file _hooks=""
|
||||
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
|
||||
for _file in "$_acme_home/$1/$2"*.sh; do
|
||||
[ -f "$_file" ] || continue
|
||||
_file="${_file##*/}"
|
||||
_hooks="$_hooks ${_file%.sh}"
|
||||
done
|
||||
_acme_sh_add_matches "$_hooks"
|
||||
}
|
||||
|
||||
_acme_sh_completion() {
|
||||
local cur prev _acme_home _acme_conf_home
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev=""
|
||||
if [ "$COMP_CWORD" -gt 0 ]; then
|
||||
prev="${COMP_WORDS[COMP_CWORD - 1]}"
|
||||
fi
|
||||
_acme_home="${LE_WORKING_DIR:-$HOME/.acme.sh}"
|
||||
_acme_conf_home="${LE_CONFIG_HOME:-$_acme_home}"
|
||||
|
||||
# The first argument is the command.
|
||||
if [ "$COMP_CWORD" -eq 1 ]; then
|
||||
_acme_sh_add_matches "
|
||||
--help
|
||||
--version
|
||||
--install
|
||||
--install-online
|
||||
--uninstall
|
||||
--upgrade
|
||||
--issue
|
||||
--deploy
|
||||
--sign-csr
|
||||
--show-csr
|
||||
--install-cert
|
||||
--renew
|
||||
--renew-all
|
||||
--revoke
|
||||
--remove
|
||||
--list
|
||||
--list-profiles
|
||||
--info
|
||||
--to-pkcs12
|
||||
--to-pkcs8
|
||||
--create-account-key
|
||||
--create-domain-key
|
||||
--create-csr
|
||||
--deactivate
|
||||
--update-account
|
||||
--register-account
|
||||
--deactivate-account
|
||||
--make-dns-persist-value
|
||||
--install-cronjob
|
||||
--uninstall-cronjob
|
||||
--cron
|
||||
--set-notify
|
||||
--set-default-ca
|
||||
--set-default-chain
|
||||
"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Complete the value of the previous option.
|
||||
case "$prev" in
|
||||
-d | --domain | --challenge-alias | --domain-alias)
|
||||
_acme_sh_domains
|
||||
return 0
|
||||
;;
|
||||
--dns)
|
||||
# The dns hook argument is optional, keep completing options if the
|
||||
# current word already looks like one.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_hooks "dnsapi" "dns_"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--deploy-hook)
|
||||
_acme_sh_hooks "deploy" ""
|
||||
return 0
|
||||
;;
|
||||
--notify-hook)
|
||||
_acme_sh_hooks "notify" ""
|
||||
return 0
|
||||
;;
|
||||
--server)
|
||||
_acme_sh_add_matches "letsencrypt letsencrypt_test zerossl sslcom google google_test actalis"
|
||||
return 0
|
||||
;;
|
||||
-k | --keylength | -ak | --accountkeylength)
|
||||
_acme_sh_add_matches "2048 3072 4096 8192 ec-256 ec-384 ec-521"
|
||||
return 0
|
||||
;;
|
||||
--debug)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_add_matches "0 1 2 3"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--log)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--nginx)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--auto-upgrade | --always-force-new-domain-key)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_add_matches "0 1"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--log-level)
|
||||
_acme_sh_add_matches "1 2"
|
||||
return 0
|
||||
;;
|
||||
--syslog)
|
||||
_acme_sh_add_matches "0 3 6 7"
|
||||
return 0
|
||||
;;
|
||||
--notify-level)
|
||||
_acme_sh_add_matches "0 1 2 3"
|
||||
return 0
|
||||
;;
|
||||
--notify-mode)
|
||||
_acme_sh_add_matches "0 1"
|
||||
return 0
|
||||
;;
|
||||
--revoke-reason)
|
||||
_acme_sh_add_matches "0 1 2 3 4 5 6 7 8 9 10"
|
||||
return 0
|
||||
;;
|
||||
--cert-file | --key-file | --ca-file | --fullchain-file | --csr | --accountconf | --accountkey | --ca-bundle | --openssl-bin)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
-w | --webroot | --home | --cert-home | --config-home | --ca-path)
|
||||
_acme_sh_dirs
|
||||
return 0
|
||||
;;
|
||||
-m | --email | --password | --useragent | --days | --valid-from | --valid-to | --httpport | --tlsport | --local-address | --dnssleep | --pre-hook | --post-hook | --renew-hook | --reloadcmd | --extended-key-usage | -b | --branch | --notify-source | --eab-kid | --eab-hmac-key | --preferred-chain | --cert-profile | --certificate-profile | --dns-persist-ca-name | --dns-persist-days)
|
||||
# These options take a free-form value, offer nothing.
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Complete the parameters.
|
||||
_acme_sh_add_matches "
|
||||
--accountconf
|
||||
--accountkey
|
||||
--accountkeylength
|
||||
--alpn
|
||||
--always-force-new-domain-key
|
||||
--apache
|
||||
--auto-upgrade
|
||||
--branch
|
||||
--ca-bundle
|
||||
--ca-file
|
||||
--ca-path
|
||||
--cert-file
|
||||
--cert-home
|
||||
--cert-profile
|
||||
--challenge-alias
|
||||
--config-home
|
||||
--csr
|
||||
--days
|
||||
--debug
|
||||
--deploy-hook
|
||||
--dns
|
||||
--dns-persist
|
||||
--dns-persist-ca-name
|
||||
--dns-persist-days
|
||||
--dns-persist-wildcard
|
||||
--dnssleep
|
||||
--domain
|
||||
--domain-alias
|
||||
--eab-hmac-key
|
||||
--eab-kid
|
||||
--ecc
|
||||
--email
|
||||
--extended-key-usage
|
||||
--force
|
||||
--force-color
|
||||
--fullchain-file
|
||||
--home
|
||||
--httpport
|
||||
--insecure
|
||||
--key-file
|
||||
--keylength
|
||||
--listen-v4
|
||||
--listen-v6
|
||||
--listraw
|
||||
--local-address
|
||||
--log
|
||||
--log-level
|
||||
--nginx
|
||||
--no-color
|
||||
--no-cron
|
||||
--no-profile
|
||||
--notify-hook
|
||||
--notify-level
|
||||
--notify-mode
|
||||
--notify-source
|
||||
--ocsp-must-staple
|
||||
--openssl-bin
|
||||
--output-insecure
|
||||
--password
|
||||
--post-hook
|
||||
--pre-hook
|
||||
--preferred-chain
|
||||
--reloadcmd
|
||||
--renew-hook
|
||||
--revoke-reason
|
||||
--server
|
||||
--staging
|
||||
--standalone
|
||||
--stateless
|
||||
--stop-renew-on-error
|
||||
--syslog
|
||||
--tlsport
|
||||
--treat-skip-as-success
|
||||
--use-wget
|
||||
--useragent
|
||||
--valid-from
|
||||
--valid-to
|
||||
--webroot
|
||||
--yes-I-know-dns-manual-mode-enough-go-ahead-please
|
||||
"
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _acme_sh_completion acme.sh
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Script to create certificate to Alibaba Cloud CDN
|
||||
#
|
||||
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
|
||||
#
|
||||
# This deployment required following variables
|
||||
# export Ali_Key="ALIACCESSKEY"
|
||||
# export Ali_Secret="ALISECRETKEY"
|
||||
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
|
||||
#
|
||||
# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
|
||||
# export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com"
|
||||
# If you have multiple CDN domains using the same certificate, just
|
||||
# export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
|
||||
#
|
||||
# For DCDN, see ali_dcdn deploy hook
|
||||
|
||||
Ali_CDN_API="https://cdn.aliyuncs.com/"
|
||||
|
||||
ali_cdn_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"
|
||||
|
||||
# Load dnsapi/dns_ali.sh to reduce the duplicated codes
|
||||
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
|
||||
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
|
||||
# shellcheck source=/dev/null
|
||||
if ! . "$dnsapi_ali"; then
|
||||
_err "Error loading file $dnsapi_ali. Please check your API file and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_prepare_ali_credentials || return 1
|
||||
|
||||
_getdeployconf DEPLOY_ALI_CDN_DOMAIN
|
||||
if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then
|
||||
_savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN"
|
||||
else
|
||||
DEPLOY_ALI_CDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode upper-hex <"$_cfullchain")
|
||||
_key=$(_url_encode upper-hex <"$_ckey")
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
||||
## update domain ssl config
|
||||
for domain in $DEPLOY_ALI_CDN_DOMAIN; do
|
||||
_set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
|
||||
if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then
|
||||
_info "Domain $domain certificate has been deployed successfully"
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# domain pub pri
|
||||
_set_cdn_domain_ssl_certificate_query() {
|
||||
endpoint=$Ali_CDN_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=SetCdnDomainSSLCertificate'
|
||||
query=$query'&CertType=upload'
|
||||
query=$query'&DomainName='$1
|
||||
query=$query'&Format=json'
|
||||
query=$query'&SSLPri='$3
|
||||
query=$query'&SSLProtocol=on'
|
||||
query=$query'&SSLPub='$2
|
||||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Version=2018-05-10'
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Script to create certificate to Alibaba Cloud DCDN
|
||||
#
|
||||
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
|
||||
#
|
||||
# This deployment required following variables
|
||||
# export Ali_Key="ALIACCESSKEY"
|
||||
# export Ali_Secret="ALISECRETKEY"
|
||||
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
|
||||
#
|
||||
# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
|
||||
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com"
|
||||
# If you have multiple CDN domains using the same certificate, just
|
||||
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com"
|
||||
#
|
||||
# For regular CDN, see ali_cdn deploy hook
|
||||
|
||||
Ali_DCDN_API="https://dcdn.aliyuncs.com/"
|
||||
|
||||
ali_dcdn_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"
|
||||
|
||||
# Load dnsapi/dns_ali.sh to reduce the duplicated codes
|
||||
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
|
||||
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
|
||||
# shellcheck source=/dev/null
|
||||
if ! . "$dnsapi_ali"; then
|
||||
_err "Error loading file $dnsapi_ali. Please check your API file and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_prepare_ali_credentials || return 1
|
||||
|
||||
_getdeployconf DEPLOY_ALI_DCDN_DOMAIN
|
||||
if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then
|
||||
_savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN"
|
||||
else
|
||||
DEPLOY_ALI_DCDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode upper-hex <"$_cfullchain")
|
||||
_key=$(_url_encode upper-hex <"$_ckey")
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
||||
## update domain ssl config
|
||||
for domain in $DEPLOY_ALI_DCDN_DOMAIN; do
|
||||
_set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
|
||||
if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then
|
||||
_info "Domain $domain certificate has been deployed successfully"
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# domain pub pri
|
||||
_set_dcdn_domain_ssl_certificate_query() {
|
||||
endpoint=$Ali_DCDN_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=SetDcdnDomainSSLCertificate'
|
||||
query=$query'&CertType=upload'
|
||||
query=$query'&DomainName='$1
|
||||
query=$query'&Format=json'
|
||||
query=$query'&SSLPri='$3
|
||||
query=$query'&SSLProtocol=on'
|
||||
query=$query'&SSLPub='$2
|
||||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Version=2018-01-15'
|
||||
}
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Deploy hook: Baidu Cloud CDN
|
||||
#
|
||||
# Code generated by GitHub Copilot with Claude Sonnet 4.6 and OpenAI Codex with GPT-5.6 Sol
|
||||
#
|
||||
# API Doc: https://cloud.baidu.com/doc/CDN/s/Zkna2r57w
|
||||
#
|
||||
# Uses the same credential variables as dnsapi/dns_baidu.sh:
|
||||
# export Baidu_AK="your-access-key-id"
|
||||
# export Baidu_SK="your-secret-access-key"
|
||||
#
|
||||
# To deploy to a CDN domain different from the certificate CN
|
||||
# (e.g. wildcard or multi-domain certs):
|
||||
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn.example.com"
|
||||
#
|
||||
# Multiple CDN domains sharing the same certificate:
|
||||
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
|
||||
|
||||
BAIDU_CDN_HOST="cdn.baidubce.com"
|
||||
_BAIDU_CDN_BCE_AUTH_RESULT=""
|
||||
|
||||
baidu_cdn_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"
|
||||
|
||||
if ! _baidu_cdn_load_credentials; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_BAIDU_CDN_DOMAIN
|
||||
if [ "$DEPLOY_BAIDU_CDN_DOMAIN" ]; then
|
||||
_savedeployconf DEPLOY_BAIDU_CDN_DOMAIN "$DEPLOY_BAIDU_CDN_DOMAIN"
|
||||
else
|
||||
DEPLOY_BAIDU_CDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
# Build JSON "domains" array from space-separated domain list
|
||||
_domains_json=""
|
||||
for _d in $DEPLOY_BAIDU_CDN_DOMAIN; do
|
||||
_d_e="$(_baidu_cdn_json_escape "$_d")"
|
||||
if [ -z "$_domains_json" ]; then
|
||||
_domains_json="\"${_d_e}\""
|
||||
else
|
||||
_domains_json="${_domains_json},\"${_d_e}\""
|
||||
fi
|
||||
done
|
||||
|
||||
# Build a valid cert name: must start with a letter, allow [A-Za-z0-9-/.], max 65 chars
|
||||
_cert_name="$(printf "%s" "$_cdomain" | sed 's/\*\./wildcard./g;s/[^A-Za-z0-9./]/-/g' | cut -c 1-65)"
|
||||
case "$_cert_name" in
|
||||
[A-Za-z]*) ;;
|
||||
*) _cert_name="c${_cert_name}" ;;
|
||||
esac
|
||||
|
||||
# PEM content is already Base64 inside the -----BEGIN/END----- wrappers.
|
||||
# The API expects the raw PEM as a JSON string, so newlines must be escaped as \n.
|
||||
_cert_pem="$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')"
|
||||
_key_pem="$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')"
|
||||
|
||||
_debug2 _cert_name "$_cert_name"
|
||||
_debug2 _domains_json "[$_domains_json]"
|
||||
|
||||
# Build JSON payload
|
||||
_payload="{\"domains\":[${_domains_json}],\"certificate\":{\"certName\":\"${_cert_name}\",\"certServerData\":\"${_cert_pem}\",\"certPrivateData\":\"${_key_pem}\"}}"
|
||||
|
||||
# Generate BCE v1 authorization header (query string included in canonical request)
|
||||
_cdn_path="/v2/domain/certificate"
|
||||
_cdn_query="action=put"
|
||||
_ts="$(_utc_date | sed 's/ /T/')Z"
|
||||
_content_type="application/json; charset=utf-8"
|
||||
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
|
||||
|
||||
if ! _baidu_cdn_bce_auth "POST" "$_cdn_path" "$_cdn_query" "$BAIDU_CDN_HOST" "$_ts" "3600" "$_content_type" "$_payload_hash"; then
|
||||
_err "Failed to sign request"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_H1="Authorization: $_BAIDU_CDN_BCE_AUTH_RESULT"
|
||||
_H2="x-bce-date: $_ts"
|
||||
_H3="x-bce-content-sha256: $_payload_hash"
|
||||
_H4="Host: $BAIDU_CDN_HOST"
|
||||
_H5=""
|
||||
|
||||
_url="https://${BAIDU_CDN_HOST}${_cdn_path}?${_cdn_query}"
|
||||
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Failed to call Baidu Cloud CDN API"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 response "$response"
|
||||
|
||||
if _contains "$response" "\"certId\""; then
|
||||
_info "Certificate deployed to Baidu Cloud CDN for: $DEPLOY_BAIDU_CDN_DOMAIN"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_err "Failed to deploy certificate to Baidu Cloud CDN: $response"
|
||||
return 1
|
||||
}
|
||||
|
||||
# BCE v1 signing with canonical query string support.
|
||||
# The CDN endpoint uses ?action=put so it must be included in the canonical request.
|
||||
_baidu_cdn_bce_auth() {
|
||||
_method="$1"
|
||||
_uri="$2"
|
||||
_query="$3"
|
||||
_host="$4"
|
||||
_ts="$5"
|
||||
_expire="$6"
|
||||
_ct="$7"
|
||||
_payload_hash="$8"
|
||||
|
||||
_BAIDU_CDN_BCE_AUTH_RESULT=""
|
||||
|
||||
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
|
||||
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
|
||||
_canonical_uri="$(_baidu_cdn_bce_encode_path "$_uri")"
|
||||
|
||||
_host_e="$(printf "%s" "$_host" | _url_encode upper-hex)"
|
||||
_date_e="$(printf "%s" "$_ts" | _url_encode upper-hex)"
|
||||
_ct_e="$(printf "%s" "$_ct" | _url_encode upper-hex)"
|
||||
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
|
||||
|
||||
_canonical_headers="content-type:${_ct_e}
|
||||
host:${_host_e}
|
||||
x-bce-content-sha256:${_hash_e}
|
||||
x-bce-date:${_date_e}"
|
||||
|
||||
_canonical_request="${_method}
|
||||
${_canonical_uri}
|
||||
${_query}
|
||||
${_canonical_headers}"
|
||||
|
||||
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
|
||||
_signing_key="$(_baidu_cdn_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
|
||||
_signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
|
||||
_signature="$(_baidu_cdn_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
|
||||
|
||||
_BAIDU_CDN_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
|
||||
}
|
||||
|
||||
_baidu_cdn_load_credentials() {
|
||||
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
|
||||
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
|
||||
|
||||
Baidu_AK="$(_baidu_cdn_trim_ws "$Baidu_AK")"
|
||||
Baidu_SK="$(_baidu_cdn_trim_ws "$Baidu_SK")"
|
||||
|
||||
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
|
||||
_err "Baidu_AK and Baidu_SK are required"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
|
||||
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_cdn_bce_encode_path() {
|
||||
_p="$1"
|
||||
_out=""
|
||||
if [ "${_p#"/"}" != "$_p" ]; then
|
||||
_out="/"
|
||||
fi
|
||||
|
||||
_rest="${_p#/}"
|
||||
while [ -n "$_rest" ]; do
|
||||
_seg="${_rest%%/*}"
|
||||
if [ "$_seg" ]; then
|
||||
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
|
||||
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
||||
else
|
||||
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
||||
fi
|
||||
fi
|
||||
if [ "${_rest#*/}" = "$_rest" ]; then
|
||||
break
|
||||
fi
|
||||
_rest="${_rest#*/}"
|
||||
done
|
||||
|
||||
if [ -z "$_out" ]; then
|
||||
_out="/"
|
||||
fi
|
||||
printf "%s" "$_out"
|
||||
}
|
||||
|
||||
_baidu_cdn_trim_ws() {
|
||||
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
|
||||
}
|
||||
|
||||
_baidu_cdn_json_escape() {
|
||||
_s="$1"
|
||||
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
|
||||
printf "%s" "$_s" |
|
||||
sed 's/\\/\\\\/g; s/ /\\t/g' |
|
||||
_baidu_cdn_json_encode
|
||||
}
|
||||
|
||||
_baidu_cdn_json_encode() {
|
||||
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
|
||||
printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
|
||||
}
|
||||
|
||||
_baidu_cdn_hmac_sha256_hexkey() {
|
||||
_key_hex="$1"
|
||||
_msg="$2"
|
||||
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
|
||||
}
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
#
|
||||
# acme.sh deploy hook: BytePlus Application Load Balancer (ALB)
|
||||
# https://github.com/acmesh-official/acme.sh/wiki/deployhooks
|
||||
#
|
||||
# Deploys SSL/TLS certificates issued by acme.sh to BytePlus ALB.
|
||||
# Supports automatic renewal with zero-downtime certificate rotation
|
||||
# for certificates that have already been uploaded and have a saved
|
||||
# BytePlus CertificateId.
|
||||
#
|
||||
# ┌─────────────────────────────────────────────────────────────────────┐
|
||||
# │ FIRST TIME (new domain) │
|
||||
# │ 1. acme.sh --issue -d example.com -w /var/www/html/ │
|
||||
# │ 2. Upload/import the certificate to BytePlus ALB manually │
|
||||
# │ 3. Save/configure the existing CertificateId for this hook │
|
||||
# │ 4. Manually assign cert to ALB Listener (one-time only) │
|
||||
# │ │
|
||||
# │ RENEWAL (fully automatic after CertificateId is configured) │
|
||||
# │ acme.sh cron triggers renew → deploy hook runs automatically │
|
||||
# │ → ReplaceCertificate (UpdateMode=new) — single API call │
|
||||
# │ → All attached listeners updated, old cert auto-deleted │
|
||||
# └─────────────────────────────────────────────────────────────────────┘
|
||||
#
|
||||
# Required environment variables:
|
||||
# export BYTEPLUS_ACCESS_KEY="AKAPxxxxxxxxxx"
|
||||
# export BYTEPLUS_SECRET_KEY="your-secret-key"
|
||||
#
|
||||
# Optional environment variables:
|
||||
# export BYTEPLUS_REGION="ap-southeast-3" # default: ap-southeast-3
|
||||
# export BYTEPLUS_HOST="alb.ap-southeast-3.byteplusapi.com" # custom API host
|
||||
# export BYTEPLUS_PROJECT_NAME="live" # default: "default" project
|
||||
# export BYTEPLUS_CERT_NAME="" # default: acme-{domain}-{YYYYMMDD-HHMM}
|
||||
# export BYTEPLUS_CERT_DESCRIPTION="" # default: empty
|
||||
# export BYTEPLUS_DELETE_OLD_CERT="true" # default: true — auto-delete after replace
|
||||
#
|
||||
# API notes:
|
||||
# - All BytePlus ALB APIs use GET with query string parameters
|
||||
# - Request signing: HMAC-SHA256 with signed headers host;x-date
|
||||
# - PublicKey/PrivateKey are URL-encoded (RFC 3986) in query string
|
||||
# - ReplaceCertificate with UpdateMode=new uploads + replaces in 1 call
|
||||
#
|
||||
# Dependencies: curl, openssl, awk (standard on most Linux)
|
||||
#
|
||||
# Docs:
|
||||
# Signing — https://docs.byteplus.com/en/docs/byteplus-platform/reference-how-to-calculate-a-signature
|
||||
# ALB API — https://docs.byteplus.com/en/docs/byteplus-alb
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Constants
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# SHA-256 hash of empty string (used for GET requests with no body)
|
||||
_BYTEPLUS_EMPTY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Main deploy function — called by acme.sh
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
byteplus_alb_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"
|
||||
|
||||
# ── 1. Load & validate credentials ──────────────────────────────────────────
|
||||
|
||||
# Preserve environment values before _getdeployconf (which may reset them)
|
||||
_env_project_name="${BYTEPLUS_PROJECT_NAME:-}"
|
||||
_env_delete_old="${BYTEPLUS_DELETE_OLD_CERT:-}"
|
||||
|
||||
_getdeployconf BYTEPLUS_ACCESS_KEY
|
||||
_getdeployconf BYTEPLUS_SECRET_KEY
|
||||
_getdeployconf BYTEPLUS_REGION
|
||||
_getdeployconf BYTEPLUS_HOST
|
||||
_getdeployconf BYTEPLUS_PROJECT_NAME
|
||||
_getdeployconf BYTEPLUS_DELETE_OLD_CERT
|
||||
_getdeployconf BYTEPLUS_CERT_NAME
|
||||
|
||||
# Restore from environment if _getdeployconf cleared them
|
||||
if [ -z "$BYTEPLUS_PROJECT_NAME" ] && [ -n "$_env_project_name" ]; then
|
||||
_debug "Restoring BYTEPLUS_PROJECT_NAME from environment"
|
||||
BYTEPLUS_PROJECT_NAME="$_env_project_name"
|
||||
fi
|
||||
if [ -z "$BYTEPLUS_DELETE_OLD_CERT" ] && [ -n "$_env_delete_old" ]; then
|
||||
BYTEPLUS_DELETE_OLD_CERT="$_env_delete_old"
|
||||
fi
|
||||
|
||||
# Validate required credentials
|
||||
if [ -z "$BYTEPLUS_ACCESS_KEY" ]; then
|
||||
_err "BYTEPLUS_ACCESS_KEY is not set."
|
||||
_err "Please run: export BYTEPLUS_ACCESS_KEY=\"your-access-key\""
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$BYTEPLUS_SECRET_KEY" ]; then
|
||||
_err "BYTEPLUS_SECRET_KEY is not set."
|
||||
_err "Please run: export BYTEPLUS_SECRET_KEY=\"your-secret-key\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save credentials for future runs
|
||||
_savedeployconf BYTEPLUS_ACCESS_KEY "$BYTEPLUS_ACCESS_KEY"
|
||||
_savedeployconf BYTEPLUS_SECRET_KEY "$BYTEPLUS_SECRET_KEY"
|
||||
|
||||
# Region (default: ap-southeast-3)
|
||||
BYTEPLUS_REGION="${BYTEPLUS_REGION:-ap-southeast-3}"
|
||||
_savedeployconf BYTEPLUS_REGION "$BYTEPLUS_REGION"
|
||||
|
||||
# Project name
|
||||
if [ -n "$BYTEPLUS_PROJECT_NAME" ]; then
|
||||
_savedeployconf BYTEPLUS_PROJECT_NAME "$BYTEPLUS_PROJECT_NAME"
|
||||
_info "Using project: $BYTEPLUS_PROJECT_NAME"
|
||||
else
|
||||
_info "WARNING: BYTEPLUS_PROJECT_NAME is not set. Cert will go to 'default' project."
|
||||
fi
|
||||
|
||||
# Delete old cert toggle (default: true)
|
||||
BYTEPLUS_DELETE_OLD_CERT="${BYTEPLUS_DELETE_OLD_CERT:-true}"
|
||||
_savedeployconf BYTEPLUS_DELETE_OLD_CERT "$BYTEPLUS_DELETE_OLD_CERT"
|
||||
|
||||
# API host — custom override or auto-build from region
|
||||
if [ -n "$BYTEPLUS_HOST" ]; then
|
||||
_BYTEPLUS_HOST="$BYTEPLUS_HOST"
|
||||
_savedeployconf BYTEPLUS_HOST "$BYTEPLUS_HOST"
|
||||
else
|
||||
_BYTEPLUS_HOST="alb.${BYTEPLUS_REGION}.byteplusapi.com"
|
||||
fi
|
||||
_info "Using API host: $_BYTEPLUS_HOST"
|
||||
_BYTEPLUS_SERVICE="alb"
|
||||
|
||||
# ── 2. Build certificate name ────────────────────────────────────────────────
|
||||
|
||||
_date_tag=$(date -u +%Y%m%d-%H%M)
|
||||
# Replace wildcard * and dots for a valid cert name
|
||||
_safe_domain=$(echo "$_cdomain" | sed 's/\*\.//g' | sed 's/\./-/g')
|
||||
# Safe identifier version for deployconf keys: map all non [A-Za-z0-9_] to _
|
||||
_conf_key=$(echo "$_cdomain" | sed 's/^\*\.//' | sed 's/[^A-Za-z0-9_]/_/g')
|
||||
|
||||
if [ -z "$BYTEPLUS_CERT_NAME" ]; then
|
||||
BYTEPLUS_CERT_NAME="acme-${_safe_domain}-${_date_tag}"
|
||||
fi
|
||||
|
||||
# Enforce BytePlus naming rules: start with letter, max 128 chars
|
||||
BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | sed 's/[^A-Za-z0-9._-]/-/g')
|
||||
case "$BYTEPLUS_CERT_NAME" in
|
||||
[A-Za-z]*) ;;
|
||||
|
||||
*)
|
||||
BYTEPLUS_CERT_NAME="a$BYTEPLUS_CERT_NAME"
|
||||
;;
|
||||
esac
|
||||
BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | cut -c1-128)
|
||||
|
||||
_info "Certificate name: $BYTEPLUS_CERT_NAME"
|
||||
|
||||
# ── 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')
|
||||
|
||||
if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then
|
||||
_err "Failed to read certificate or key file."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ── 4. Deploy: first-time upload or renewal replace ─────────────────────────
|
||||
|
||||
_getdeployconf "BYTEPLUS_CERT_ID_${_conf_key}"
|
||||
_old_cert_id=$(eval echo "\$BYTEPLUS_CERT_ID_${_conf_key}")
|
||||
|
||||
if [ -z "$_old_cert_id" ]; then
|
||||
_byteplus_first_time_deploy
|
||||
else
|
||||
_byteplus_renewal_deploy
|
||||
fi
|
||||
|
||||
# Check if deploy step set _new_cert_id
|
||||
if [ -z "$_new_cert_id" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ── 5. Save new CertificateId for next renewal ───────────────────────────────
|
||||
|
||||
_savedeployconf "BYTEPLUS_CERT_ID_${_conf_key}" "$_new_cert_id"
|
||||
_info "Saved CertificateId '$_new_cert_id' for domain '$_cdomain'."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Deploy: First time — UploadCertificate
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_first_time_deploy() {
|
||||
_info "No previous CertificateId found."
|
||||
_err "Refusing to upload certificate material because this hook passes PublicKey/PrivateKey as request parameters."
|
||||
_err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings."
|
||||
_err "Please upload the certificate to BytePlus manually for the initial deployment, set BYTEPLUS_CERT_ID_${_conf_key} to that CertificateId, and rerun."
|
||||
_err "This hook stores CertificateId values per domain using deployconf, so the variable name must include the current domain-specific suffix."
|
||||
_err "This hook must be updated to send PublicKey and PrivateKey in a POST body before automatic first-time upload can be enabled safely."
|
||||
return 1
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Deploy: Renewal — ReplaceCertificate (UpdateMode=new)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_renewal_deploy() {
|
||||
_info "Replacing old certificate '$_old_cert_id' (UpdateMode=new)..."
|
||||
_err "Refusing to replace certificate material because this hook passes PublicKey/PrivateKey as request parameters."
|
||||
_err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings."
|
||||
_err "Please replace the certificate in BytePlus manually for renewal until this hook is updated to send PublicKey and PrivateKey in a POST body safely."
|
||||
return 1
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Delete old certificate (with retry)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_delete_old_cert() {
|
||||
_del_cert_id="$1"
|
||||
|
||||
_info "Waiting 5s for cert status to settle..."
|
||||
_sleep 5
|
||||
|
||||
_info "Deleting old certificate '$_del_cert_id'..."
|
||||
_del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}")
|
||||
|
||||
if echo "$_del_response" | grep -q '"Error"'; then
|
||||
_info "Delete failed, retrying in 10s..."
|
||||
_sleep 10
|
||||
_del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}")
|
||||
|
||||
if echo "$_del_response" | grep -q '"Error"'; then
|
||||
_info "Warning: Could not delete old certificate '$_del_cert_id'."
|
||||
_info "Error: $(_byteplus_extract_error "$_del_response")"
|
||||
_info "Please remove it manually from BytePlus Console."
|
||||
else
|
||||
_info "Old certificate '$_del_cert_id' deleted (retry succeeded)."
|
||||
fi
|
||||
else
|
||||
_info "Old certificate '$_del_cert_id' deleted."
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# JSON response helpers
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Extract CertificateId from API response JSON
|
||||
_byteplus_extract_cert_id() {
|
||||
echo "$1" | _egrep_o '"CertificateId"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"'
|
||||
}
|
||||
|
||||
# Extract error message from API response JSON
|
||||
_byteplus_extract_error() {
|
||||
_code=$(echo "$1" | _egrep_o '"Code"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"')
|
||||
_msg=$(echo "$1" | _egrep_o '"Message"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"')
|
||||
if [ -n "$_code" ]; then
|
||||
printf '%s — %s' "$_code" "$_msg"
|
||||
else
|
||||
printf '%s' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# BytePlus ALB API caller
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Usage: _byteplus_alb_api ACTION [param1=val1] [param2=val2] ...
|
||||
# All parameters sent via GET query string. Signing: HMAC-SHA256, host;x-date.
|
||||
_byteplus_alb_api() {
|
||||
_action="$1"
|
||||
shift
|
||||
|
||||
# Build query string — all params go in URL
|
||||
_query_params="Action=${_action}&Version=2020-04-01"
|
||||
|
||||
for _param in "$@"; do
|
||||
_pname="${_param%%=*}"
|
||||
_pval="${_param#*=}"
|
||||
_query_params="${_query_params}&${_pname}=$(_byteplus_urlencode "$_pval")"
|
||||
done
|
||||
|
||||
# Timestamps
|
||||
_x_date=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
_date_only=$(date -u +%Y%m%d)
|
||||
|
||||
# Sort query params for canonical request
|
||||
_sorted_query=$(echo "$_query_params" | tr '&' '\n' | LC_ALL=C sort | tr '\n' '&' | sed 's/&$//')
|
||||
|
||||
# Canonical headers — only host and x-date
|
||||
_canonical_headers="host:${_BYTEPLUS_HOST}
|
||||
x-date:${_x_date}
|
||||
"
|
||||
_signed_headers="host;x-date"
|
||||
|
||||
# Canonical request
|
||||
_canonical_request="GET
|
||||
/
|
||||
${_sorted_query}
|
||||
${_canonical_headers}
|
||||
${_signed_headers}
|
||||
${_BYTEPLUS_EMPTY_HASH}"
|
||||
|
||||
# Do not log _canonical_request because the query string may contain
|
||||
# URL-encoded certificate or private key material.
|
||||
|
||||
# Hash of canonical request
|
||||
# _digest is provided by acme.sh and works across OpenSSL versions.
|
||||
_cr_hash=$(printf '%s' "$_canonical_request" | _digest sha256 hex)
|
||||
|
||||
# Credential scope
|
||||
_credential_scope="${_date_only}/${BYTEPLUS_REGION}/${_BYTEPLUS_SERVICE}/request"
|
||||
|
||||
# String to sign
|
||||
_string_to_sign="HMAC-SHA256
|
||||
${_x_date}
|
||||
${_credential_scope}
|
||||
${_cr_hash}"
|
||||
|
||||
_debug2 _string_to_sign "$_string_to_sign"
|
||||
|
||||
# Signing key derivation (HMAC chain)
|
||||
# _hmac <algo> <hex-key> reads data from stdin and returns a hex digest.
|
||||
# acme.sh's _hmac abstracts away OpenSSL version differences, so this works
|
||||
# on both modern (-mac HMAC -macopt hexkey:) and older (-hmac) OpenSSL builds.
|
||||
#
|
||||
# The first step seeds the chain from the raw secret key, so we convert it
|
||||
# to hex first with _hex_dump (also an acme.sh built-in).
|
||||
_secret_hex=$(printf '%s' "$BYTEPLUS_SECRET_KEY" | _hex_dump | tr -d ' \n')
|
||||
_k_date=$(printf '%s' "$_date_only" | _hmac sha256 "$_secret_hex" hex)
|
||||
_k_region=$(printf '%s' "$BYTEPLUS_REGION" | _hmac sha256 "$_k_date" hex)
|
||||
_k_service=$(printf '%s' "$_BYTEPLUS_SERVICE" | _hmac sha256 "$_k_region" hex)
|
||||
_k_signing=$(printf '%s' "request" | _hmac sha256 "$_k_service" hex)
|
||||
|
||||
# Final signature
|
||||
_signature=$(printf '%s' "$_string_to_sign" | _hmac sha256 "$_k_signing" hex)
|
||||
|
||||
# Authorization header
|
||||
_auth="HMAC-SHA256 Credential=${BYTEPLUS_ACCESS_KEY}/${_credential_scope}, SignedHeaders=${_signed_headers}, Signature=${_signature}"
|
||||
|
||||
_secure_debug2 _auth "$_auth"
|
||||
|
||||
# Send request parameters in the POST body instead of the URL query string.
|
||||
# This avoids exposing sensitive or large values in debug-logged URLs and
|
||||
# reduces the risk of exceeding URL length limits.
|
||||
_url="https://${_BYTEPLUS_HOST}/"
|
||||
_body="$_sorted_query"
|
||||
|
||||
_saved_H1="${_H1:-}"
|
||||
_saved_H2="${_H2:-}"
|
||||
_saved_H3="${_H3:-}"
|
||||
_saved_H4="${_H4:-}"
|
||||
_saved_H5="${_H5:-}"
|
||||
|
||||
_H1="Authorization: ${_auth}"
|
||||
_H2="X-Date: ${_x_date}"
|
||||
_H3="Host: ${_BYTEPLUS_HOST}"
|
||||
_H4="Content-Type: application/x-www-form-urlencoded"
|
||||
_H5=""
|
||||
|
||||
_response="$(_post "$_body" "$_url" "" "POST")"
|
||||
_request_ret="$?"
|
||||
|
||||
_H1="$_saved_H1"
|
||||
_H2="$_saved_H2"
|
||||
_H3="$_saved_H3"
|
||||
_H4="$_saved_H4"
|
||||
_H5="$_saved_H5"
|
||||
|
||||
if [ "$_request_ret" != "0" ]; then
|
||||
_err "byteplus_alb_api request failed for [$_action]"
|
||||
return 1
|
||||
fi
|
||||
_debug2 "_byteplus_alb_api response [$_action]" "$_response"
|
||||
printf '%s' "$_response"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# URL encode (RFC 3986)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_urlencode() {
|
||||
printf '%s' "$1" | _url_encode
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to CacheFly
|
||||
# https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post
|
||||
|
||||
# This deployment required following variables
|
||||
# export CACHEFLY_TOKEN="Your CacheFly API Token"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
CACHEFLY_API_BASE="https://api.cachefly.com/api/2.5"
|
||||
|
||||
cachefly_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"
|
||||
|
||||
if [ -z "$CACHEFLY_TOKEN" ]; then
|
||||
_err "CACHEFLY_TOKEN is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf CACHEFLY_TOKEN "$CACHEFLY_TOKEN"
|
||||
fi
|
||||
|
||||
_info "Deploying certificate to CacheFly..."
|
||||
|
||||
## upload certificate
|
||||
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
_request_body="{\"certificate\":\"$string_fullchain\",\"certificateKey\":\"$string_key\"}"
|
||||
_debug _request_body "$_request_body"
|
||||
_debug CACHEFLY_TOKEN "$CACHEFLY_TOKEN"
|
||||
export _H1="Authorization: Bearer $CACHEFLY_TOKEN"
|
||||
_response=$(_post "$_request_body" "$CACHEFLY_API_BASE/certificates" "" "POST" "application/json")
|
||||
|
||||
if _contains "$_response" "message"; then
|
||||
_err "Error in deploying $_cdomain certificate to CacheFly."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
_debug response "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to CacheFly."
|
||||
return 0
|
||||
}
|
||||
|
|
@ -52,15 +52,7 @@ cpanel_uapi_deploy() {
|
|||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode <"$_ccert")
|
||||
# with --signcsr the private key was never handed to acme.sh, so the key
|
||||
# file does not exist; skip it instead of spilling a shell redirection
|
||||
# error on every renewal (cPanel keeps using the already-installed key)
|
||||
if [ -f "$_ckey" ]; then
|
||||
_key=$(_url_encode <"$_ckey")
|
||||
else
|
||||
_debug "Key file $_ckey does not exist (csr mode), not sending a key."
|
||||
_key=""
|
||||
fi
|
||||
_key=$(_url_encode <"$_ckey")
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
|
@ -87,11 +79,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:"
|
||||
|
|
@ -206,8 +194,7 @@ __cpanel_parse_response() {
|
|||
printf("%s%s=%s\n", prefix, $2, $3);
|
||||
}
|
||||
}' |
|
||||
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' |
|
||||
sed -e 's/^"//' -e 's/"$//' # YAML double-quotes values starting with '*' (wildcard subdomains)
|
||||
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p'
|
||||
}
|
||||
|
||||
# Load parameter by prefix+name - fallback to default if not set, and save to config
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to DirectAdmin
|
||||
# https://docs.directadmin.com/directadmin/customizing-workflow/api-all-about.html#creating-a-login-key
|
||||
# https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs
|
||||
|
||||
# This deployment required following variables
|
||||
# export DirectAdmin_SCHEME="https" # Optional, https or http, defaults to https
|
||||
# export DirectAdmin_ENDPOINT="example.com:2222"
|
||||
# export DirectAdmin_USERNAME="Your DirectAdmin Username"
|
||||
# export DirectAdmin_KEY="Your DirectAdmin Login Key or Password"
|
||||
# export DirectAdmin_MAIN_DOMAIN="Your DirectAdmin Main Domain, NOT Subdomain"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
directadmin_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"
|
||||
|
||||
if [ -z "$DirectAdmin_ENDPOINT" ]; then
|
||||
_err "DirectAdmin_ENDPOINT is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT"
|
||||
fi
|
||||
if [ -z "$DirectAdmin_USERNAME" ]; then
|
||||
_err "DirectAdmin_USERNAME is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_USERNAME "$DirectAdmin_USERNAME"
|
||||
fi
|
||||
if [ -z "$DirectAdmin_KEY" ]; then
|
||||
_err "DirectAdmin_KEY is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_KEY "$DirectAdmin_KEY"
|
||||
fi
|
||||
if [ -z "$DirectAdmin_MAIN_DOMAIN" ]; then
|
||||
_err "DirectAdmin_MAIN_DOMAIN is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN"
|
||||
fi
|
||||
|
||||
# Optional SCHEME
|
||||
_getdeployconf DirectAdmin_SCHEME
|
||||
# set default values for DirectAdmin_SCHEME
|
||||
[ -n "${DirectAdmin_SCHEME}" ] || DirectAdmin_SCHEME="https"
|
||||
|
||||
_info "Deploying certificate to DirectAdmin..."
|
||||
|
||||
# upload certificate
|
||||
string_cfullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
_request_body="{\"domain\":\"$DirectAdmin_MAIN_DOMAIN\",\"action\":\"save\",\"type\":\"paste\",\"certificate\":\"$string_key\n$string_cfullchain\n\"}"
|
||||
_debug _request_body "$_request_body"
|
||||
_debug DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT"
|
||||
_debug DirectAdmin_USERNAME "$DirectAdmin_USERNAME"
|
||||
_debug DirectAdmin_KEY "$DirectAdmin_KEY"
|
||||
_debug DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN"
|
||||
_response=$(_post "$_request_body" "$DirectAdmin_SCHEME://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json")
|
||||
|
||||
if _contains "$_response" "error=1"; then
|
||||
_err "Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@
|
|||
#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
|
||||
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640"
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000"
|
||||
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
|
||||
|
|
@ -20,7 +18,6 @@ docker_deploy() {
|
|||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cpfx="$6"
|
||||
_debug _cdomain "$_cdomain"
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
|
||||
|
|
@ -73,18 +70,6 @@ docker_deploy() {
|
|||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
|
||||
|
|
@ -103,12 +88,6 @@ docker_deploy() {
|
|||
_savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
|
||||
|
|
@ -126,20 +105,6 @@ docker_deploy() {
|
|||
if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
|
||||
_info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
_err "Can not change owner of key file in container"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
|
||||
_info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
_err "Can not change mode of key file in container"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
|
||||
|
|
@ -160,12 +125,6 @@ docker_deploy() {
|
|||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then
|
||||
if ! _docker_cp "$_cid" "$_cpfx" "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
|
||||
_info "Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
|
||||
if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then
|
||||
|
|
@ -217,22 +176,10 @@ _docker_exec() {
|
|||
_debug2 cjson "$cjson"
|
||||
execid="$(echo "$cjson" | cut -d '"' -f 4)"
|
||||
_debug execid "$execid"
|
||||
#Detach:true is required for podman's docker-compatible API: with
|
||||
#Detach:false it streams the command output on the connection, so the
|
||||
#non-empty response was misread as an error (issue #4977). The real
|
||||
#result is checked via the exec inspect ExitCode below instead.
|
||||
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")"
|
||||
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")"
|
||||
_debug2 ejson "$ejson"
|
||||
_et=0
|
||||
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
|
||||
while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do
|
||||
sleep 1
|
||||
_et="$(_math "$_et" + 1)"
|
||||
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
|
||||
done
|
||||
_debug2 ijson "$ijson"
|
||||
if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then
|
||||
_err "docker exec error: $ijson"
|
||||
if [ "$ejson" ]; then
|
||||
_err "$ejson"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
|
|
@ -326,27 +273,16 @@ _check_curl_version() {
|
|||
_minor="$(_getfield "$_cversion" 2 '.')"
|
||||
_debug2 "_minor" "$_minor"
|
||||
|
||||
if [ "$_major" -ge "8" ]; then
|
||||
#ok
|
||||
return 0
|
||||
fi
|
||||
if [ "$_major" = "7" ]; then
|
||||
if [ "$_minor" -lt "40" ]; then
|
||||
_err "curl v$_cversion doesn't support unit socket"
|
||||
_err "Please upgrade to curl 7.40 or later."
|
||||
return 1
|
||||
fi
|
||||
if [ "$_minor" -lt "50" ]; then
|
||||
_debug "Use short host name"
|
||||
export _CURL_NO_HOST=1
|
||||
else
|
||||
export _CURL_NO_HOST=
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
if [ "$_major$_minor" -lt "740" ]; then
|
||||
_err "curl v$_cversion doesn't support unit socket"
|
||||
_err "Please upgrade to curl 7.40 or later."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_major$_minor" -lt "750" ]; then
|
||||
_debug "Use short host name"
|
||||
export _CURL_NO_HOST=1
|
||||
else
|
||||
export _CURL_NO_HOST=
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to edgio using its API
|
||||
# https://docs.edg.io/guides/v7/develop/rest_api/authentication
|
||||
# https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
||||
|
||||
# This deployment required following variables
|
||||
# export EDGIO_CLIENT_ID="Your Edgio Client ID"
|
||||
# export EDGIO_CLIENT_SECRET="Your Edgio Client Secret"
|
||||
# export EDGIO_ENVIRONMENT_ID="Your Edgio Environment ID"
|
||||
|
||||
# If have more than one Environment ID
|
||||
# export EDGIO_ENVIRONMENT_ID="ENVIRONMENT_ID_1 ENVIRONMENT_ID_2"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
edgio_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"
|
||||
|
||||
if [ -z "$EDGIO_CLIENT_ID" ]; then
|
||||
_err "EDGIO_CLIENT_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf EDGIO_CLIENT_ID "$EDGIO_CLIENT_ID"
|
||||
fi
|
||||
|
||||
if [ -z "$EDGIO_CLIENT_SECRET" ]; then
|
||||
_err "EDGIO_CLIENT_SECRET is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf EDGIO_CLIENT_SECRET "$EDGIO_CLIENT_SECRET"
|
||||
fi
|
||||
|
||||
if [ -z "$EDGIO_ENVIRONMENT_ID" ]; then
|
||||
_err "EDGIO_ENVIRONMENT_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf EDGIO_ENVIRONMENT_ID "$EDGIO_ENVIRONMENT_ID"
|
||||
fi
|
||||
|
||||
_info "Getting access token"
|
||||
_data="client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config"
|
||||
_debug Get_access_token_data "$_data"
|
||||
_response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded")
|
||||
_debug Get_access_token_response "$_response"
|
||||
_access_token=$(echo "$_response" | _json_decode | _egrep_o '"access_token":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||
_debug _access_token "$_access_token"
|
||||
if [ -z "$_access_token" ]; then
|
||||
_err "Error in getting access token"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Uploading certificate"
|
||||
string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n')
|
||||
string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
for ENVIRONMENT_ID in $EDGIO_ENVIRONMENT_ID; do
|
||||
_data="{\"environment_id\":\"$ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}"
|
||||
_debug Upload_certificate_data "$_data"
|
||||
_H1="Authorization: Bearer $_access_token"
|
||||
_response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json")
|
||||
if _contains "$_response" "message"; then
|
||||
_err "Error in deploying $_cdomain certificate to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
_debug Upload_certificate_response "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID."
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
@ -109,5 +109,6 @@ exim4_deploy() {
|
|||
fi
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# Script to deploy a certificate to FortiGate via API and set it as the current web GUI certificate.
|
||||
#
|
||||
# FortiGate's native ACME integration does not support wildcard certificates or domain validation,
|
||||
# and is not supported if you have a custom management web port (eg. DNAT web traffic).
|
||||
#
|
||||
# REQUIRED:
|
||||
# export FGT_HOST="fortigate_hostname-or-ip"
|
||||
# export FGT_TOKEN="fortigate_api_token"
|
||||
#
|
||||
# OPTIONAL:
|
||||
# export FGT_PORT="10443" # Custom HTTPS port (defaults to 443 if not set)
|
||||
#
|
||||
# Run `acme.sh --deploy -d example.com --deploy-hook fortigate --insecure` to use this script.
|
||||
# `--insecure` is required on first run if not already using a valid SSL certificate on firewall.
|
||||
|
||||
# Function to parse a FortiGate API response
|
||||
_fortigate_parse_response() {
|
||||
_fortigate_response="$1"
|
||||
_fortigate_func="$2"
|
||||
_fortigate_status=$(echo "$_fortigate_response" | _egrep_o '"status":[ ]*"[^"]*"' | cut -d '"' -f 4)
|
||||
|
||||
if [ "$_fortigate_status" != "success" ]; then
|
||||
_err "[$_fortigate_func] Operation failed. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "[$_fortigate_func] Operation successful."
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to deploy a base64-encoded certificate to the firewall
|
||||
_fortigate_deployer() {
|
||||
_fortigate_cert_base64=$(_base64 <"$_fortigate_cfullchain" | tr -d '\n')
|
||||
_fortigate_key_base64=$(_base64 <"$_fortigate_ckey" | tr -d '\n')
|
||||
_fortigate_payload=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"type": "regular",
|
||||
"scope": "global",
|
||||
"certname": "$_fortigate_cert_name",
|
||||
"key_file_content": "$_fortigate_key_base64",
|
||||
"file_content": "$_fortigate_cert_base64"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/local/import"
|
||||
_debug "Uploading certificate via URL: $_fortigate_url"
|
||||
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
|
||||
_debug "FortiGate API Response: $_fortigate_response"
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Deploying certificate" || return 1
|
||||
}
|
||||
|
||||
# Function to upload a CA certificate to the firewall
|
||||
# FortiGate does not automatically extract the CA from the full chain.
|
||||
_fortigate_upload_ca_cert() {
|
||||
_fortigate_ca_base64=$(_base64 <"$_fortigate_cca" | tr -d '\n')
|
||||
_fortigate_payload=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"import_method": "file",
|
||||
"scope": "global",
|
||||
"file_content": "$_fortigate_ca_base64"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/ca/import"
|
||||
_debug "Uploading CA certificate via URL: $_fortigate_url"
|
||||
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
|
||||
_debug "FortiGate API CA Response: $_fortigate_response"
|
||||
|
||||
# FortiGate error -328 means that the CA certificate already exists.
|
||||
if echo "$_fortigate_response" | grep -q '"error":[ ]*-328'; then
|
||||
_debug "CA certificate already exists. Skipping CA upload."
|
||||
return 0
|
||||
fi
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Deploying CA certificate" || return 1
|
||||
}
|
||||
|
||||
# Function to activate the new certificate
|
||||
_fortigate_set_active_web_cert() {
|
||||
_fortigate_payload=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"admin-server-cert": "$_fortigate_cert_name"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/system/global"
|
||||
_debug "Setting GUI certificate..."
|
||||
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "PUT" "application/json")
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Assigning active certificate" || return 1
|
||||
}
|
||||
|
||||
# Function to clean up the previously deployed certificate
|
||||
_fortigate_cleanup_previous_certificate() {
|
||||
_getdeployconf FGT_LAST_CERT
|
||||
|
||||
if [ -n "$FGT_LAST_CERT" ] && [ "$FGT_LAST_CERT" != "$_fortigate_cert_name" ]; then
|
||||
_debug "Found previously deployed certificate: $FGT_LAST_CERT. Deleting it."
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/vpn.certificate/local/${FGT_LAST_CERT}"
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "" "$_fortigate_url" "" "DELETE" "application/json")
|
||||
_debug "Delete certificate API response: $_fortigate_response"
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Delete previous certificate" || return 1
|
||||
else
|
||||
_debug "No previous certificate found."
|
||||
fi
|
||||
}
|
||||
|
||||
# Main deploy-hook function
|
||||
fortigate_deploy() {
|
||||
# Include date and time to ensure unique names.
|
||||
_fortigate_cert_name="$(echo "$1" | sed 's/*/WILDCARD_/g')_$(date -u +"%Y-%m-%d_%H-%M-%S")"
|
||||
_fortigate_ckey="$2"
|
||||
_fortigate_cca="$4"
|
||||
_fortigate_cfullchain="$5"
|
||||
|
||||
if [ ! -f "$_fortigate_ckey" ] || [ ! -f "$_fortigate_cfullchain" ]; then
|
||||
_err "Valid key and/or certificate not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save required environment variables if set; otherwise load saved values.
|
||||
for _fortigate_var in FGT_HOST FGT_TOKEN FGT_PORT; do
|
||||
if [ -n "$(eval echo "\$$_fortigate_var")" ]; then
|
||||
_debug "Detected ENV variable $_fortigate_var. Saving to file."
|
||||
_savedeployconf "$_fortigate_var" "$(eval echo "\$$_fortigate_var")" 1
|
||||
else
|
||||
_debug "Attempting to load variable $_fortigate_var from file."
|
||||
_getdeployconf "$_fortigate_var"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$FGT_HOST" ] || [ -z "$FGT_TOKEN" ]; then
|
||||
_err "FGT_HOST and FGT_TOKEN must be set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
FGT_PORT="${FGT_PORT:-443}"
|
||||
_debug "Using FortiGate port: $FGT_PORT"
|
||||
|
||||
# Upload the new certificate.
|
||||
_fortigate_deployer || return 1
|
||||
|
||||
# Upload the CA certificate.
|
||||
if [ -n "$_fortigate_cca" ] && [ -f "$_fortigate_cca" ]; then
|
||||
_fortigate_upload_ca_cert || return 1
|
||||
else
|
||||
_debug "No CA certificate provided."
|
||||
fi
|
||||
|
||||
# Activate the new certificate.
|
||||
_fortigate_set_active_web_cert || return 1
|
||||
|
||||
# Delete the previously deployed certificate only after successful activation.
|
||||
_fortigate_cleanup_previous_certificate || return 1
|
||||
|
||||
# Save the new certificate name for cleanup during the next deployment.
|
||||
_savedeployconf "FGT_LAST_CERT" "$_fortigate_cert_name" 1
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Here is the script to deploy the cert to G-Core CDN service (https://gcore.com/) using the G-Core Labs API (https://apidocs.gcore.com/cdn).
|
||||
# Here is the script to deploy the cert to G-Core CDN service (https://gcorelabs.com/ru/) using the G-Core Labs API (https://docs.gcorelabs.com/cdn/).
|
||||
# Returns 0 when success.
|
||||
#
|
||||
# Written by temoffey <temofffey@gmail.com>
|
||||
# Public domain, 2019
|
||||
# Update by DreamOfIce <admin@dreamofice.cn> in 2023
|
||||
|
||||
#export DEPLOY_GCORE_CDN_USERNAME=myusername
|
||||
#export DEPLOY_GCORE_CDN_PASSWORD=mypassword
|
||||
|
|
@ -57,7 +56,7 @@ gcore_cdn_deploy() {
|
|||
_request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
|
||||
_debug _request "$_request"
|
||||
export _H1="Content-Type:application/json"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/iam/auth/jwt/login")
|
||||
_response=$(_post "$_request" "https://api.gcdn.co/auth/jwt/login")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
|
||||
_debug _regex "$_regex"
|
||||
|
|
@ -70,8 +69,8 @@ gcore_cdn_deploy() {
|
|||
fi
|
||||
|
||||
_info "Find CDN resource with cname $_cdomain"
|
||||
export _H2="Authorization:Bearer $_token"
|
||||
_response=$(_get "https://api.gcore.com/cdn/resources")
|
||||
export _H2="Authorization:Token $_token"
|
||||
_response=$(_get "https://api.gcdn.co/resources")
|
||||
_debug _response "$_response"
|
||||
_regex="\"primary_resource\":null},"
|
||||
_debug _regex "$_regex"
|
||||
|
|
@ -103,7 +102,7 @@ gcore_cdn_deploy() {
|
|||
_date=$(date "+%d.%m.%Y %H:%M:%S")
|
||||
_request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}"
|
||||
_debug _request "$_request"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/cdn/sslData")
|
||||
_response=$(_post "$_request" "https://api.gcdn.co/sslData")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"id\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
|
|
@ -118,7 +117,7 @@ gcore_cdn_deploy() {
|
|||
_info "Update CDN resource"
|
||||
_request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}"
|
||||
_debug _request "$_request"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/cdn/resources/$_resourceId" '' "PUT")
|
||||
_response=$(_post "$_request" "https://api.gcdn.co/resources/$_resourceId" '' "PUT")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"sslData\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
|
|
@ -134,7 +133,7 @@ gcore_cdn_deploy() {
|
|||
_info "Not found old SSL certificate"
|
||||
else
|
||||
_info "Delete old SSL certificate"
|
||||
_response=$(_post '' "https://api.gcore.com/cdn/sslData/$_sslDataOld" '' "DELETE")
|
||||
_response=$(_post '' "https://api.gcdn.co/sslData/$_sslDataOld" '' "DELETE")
|
||||
_debug _response "$_response"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -36,20 +36,6 @@
|
|||
# Note: This functionality requires HAProxy was compiled against
|
||||
# a version of OpenSSL that supports this.
|
||||
#
|
||||
# export DEPLOY_HAPROXY_HOT_UPDATE="yes"
|
||||
# export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock"
|
||||
#
|
||||
# OPTIONAL: Deploy the certificate over the HAProxy stats socket without
|
||||
# needing to reload HAProxy. Default is "no".
|
||||
#
|
||||
# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
|
||||
# address format. The certificate can be deployed to a comma separated ',' list
|
||||
# of hosts ("TCP4:10.0.0.1:1999,TCP4:10.0.0.2:1999")
|
||||
#
|
||||
# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock"
|
||||
#
|
||||
# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead
|
||||
# of a stats socket, use this variable.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
|
|
@ -60,7 +46,6 @@ haproxy_deploy() {
|
|||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cmdpfx=""
|
||||
|
||||
# Some defaults
|
||||
DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
|
||||
|
|
@ -68,8 +53,6 @@ haproxy_deploy() {
|
|||
DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
|
||||
DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
|
||||
DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
|
||||
DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no"
|
||||
DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock"
|
||||
|
||||
_debug _cdomain "${_cdomain}"
|
||||
_debug _ckey "${_ckey}"
|
||||
|
|
@ -103,11 +86,6 @@ haproxy_deploy() {
|
|||
_savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
|
||||
elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
|
||||
Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
|
||||
# We better not have '*' as the first character
|
||||
if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then
|
||||
# removes the first characters and add a _ instead
|
||||
Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
|
||||
|
|
@ -140,36 +118,6 @@ haproxy_deploy() {
|
|||
Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
|
||||
fi
|
||||
|
||||
# HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_HOT_UPDATE
|
||||
_debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}"
|
||||
if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then
|
||||
Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}"
|
||||
_savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}"
|
||||
elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then
|
||||
Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
|
||||
fi
|
||||
|
||||
# STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_STATS_SOCKET
|
||||
_debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}"
|
||||
if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then
|
||||
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}"
|
||||
_savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
|
||||
elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then
|
||||
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
|
||||
fi
|
||||
|
||||
# MASTER_CLI is optional. No defaults are used. When the master CLI is used,
|
||||
# all commands are sent with a prefix.
|
||||
_getdeployconf DEPLOY_HAPROXY_MASTER_CLI
|
||||
_debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}"
|
||||
if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then
|
||||
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}"
|
||||
_savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
|
||||
_cmdpfx="@1 " # command prefix used for master CLI only.
|
||||
fi
|
||||
|
||||
# Set the suffix depending if we are creating a bundle or not
|
||||
if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
|
||||
_info "Bundle creation requested"
|
||||
|
|
@ -199,7 +147,7 @@ haproxy_deploy() {
|
|||
# Create a temporary PEM file
|
||||
_temppem="$(_mktemp)"
|
||||
_debug _temppem "${_temppem}"
|
||||
cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}"
|
||||
cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}"
|
||||
_ret="$?"
|
||||
|
||||
# Check that we could create the temporary file
|
||||
|
|
@ -272,18 +220,12 @@ haproxy_deploy() {
|
|||
_cafile_argument=""
|
||||
fi
|
||||
_debug _cafile_argument "${_cafile_argument}"
|
||||
# OpenSSL 1.1+ expects -header Host=value (one argument), while
|
||||
# LibreSSL keeps the old two-argument form -header Host value at any
|
||||
# version (3.x/4.x), so it must be detected by name, not by number.
|
||||
_openssl_name=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f1)
|
||||
# if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
|
||||
_openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
|
||||
_debug _openssl_name "${_openssl_name}"
|
||||
_debug _openssl_version "${_openssl_version}"
|
||||
_openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
|
||||
_openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
|
||||
if [ "${_openssl_name}" = "LibreSSL" ]; then
|
||||
_header_sep=" "
|
||||
elif [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
|
||||
if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
|
||||
_header_sep="="
|
||||
else
|
||||
_header_sep=" "
|
||||
|
|
@ -323,91 +265,15 @@ haproxy_deploy() {
|
|||
fi
|
||||
fi
|
||||
|
||||
if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then
|
||||
# set the socket name for messages
|
||||
if [ -n "${_cmdpfx}" ]; then
|
||||
_socketname="master CLI"
|
||||
else
|
||||
_socketname="stats socket"
|
||||
fi
|
||||
|
||||
# Update certificate over HAProxy stats socket or master CLI.
|
||||
if _exists socat; then
|
||||
IFS=','
|
||||
for _statssock in ${Le_Deploy_haproxy_stats_socket}; do
|
||||
# look for the certificate on the stats socket, to choose between updating or creating one
|
||||
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
|
||||
_debug _socat_cert_cmd "${_socat_cert_cmd}"
|
||||
eval "${_socat_cert_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_newcert="1"
|
||||
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
|
||||
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
|
||||
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
|
||||
eval "${_socat_crtlist_show_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
|
||||
return "${_ret}"
|
||||
fi
|
||||
# create a new certificate
|
||||
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
|
||||
_debug _socat_new_cmd "${_socat_new_cmd}"
|
||||
eval "${_socat_new_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't create '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
else
|
||||
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
fi
|
||||
# printf %b, not "echo -e": dash's echo has no -e and sends a literal "-e " to the socket.
|
||||
# "Transaction updated" is replied instead of "created" when an uncommitted transaction exists.
|
||||
_socat_cert_set_cmd="printf '%b\n' '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -qE 'Transaction (created|updated)'"
|
||||
_secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
|
||||
eval "${_socat_cert_set_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
|
||||
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
|
||||
eval "${_socat_cert_commit_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't commit '${_pem}' in haproxy"
|
||||
return ${_ret}
|
||||
fi
|
||||
if [ "${_newcert}" = "1" ]; then
|
||||
# if this is a new certificate, it needs to be inserted into the crt-list`
|
||||
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
|
||||
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
|
||||
eval "${_socat_cert_add_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
_err "'socat' is not available, couldn't update over ${_socketname}"
|
||||
fi
|
||||
# Reload HAProxy
|
||||
_debug _reload "${_reload}"
|
||||
eval "${_reload}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} during reload"
|
||||
return ${_ret}
|
||||
else
|
||||
# Reload HAProxy
|
||||
_debug _reload "${_reload}"
|
||||
eval "${_reload}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} during reload"
|
||||
return ${_ret}
|
||||
else
|
||||
_info "Reload successful"
|
||||
fi
|
||||
_info "Reload successful"
|
||||
fi
|
||||
|
||||
return 0
|
||||
|
|
|
|||
114
deploy/ikuai.sh
114
deploy/ikuai.sh
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to a Kemp Loadmaster.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
#DEPLOY_KEMP_TOKEN="token"
|
||||
#DEPLOY_KEMP_URL="https://kemplm.example.com"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
kemplm_deploy() {
|
||||
_domain="$1"
|
||||
_key_file="$2"
|
||||
_cert_file="$3"
|
||||
_ca_file="$4"
|
||||
_fullchain_file="$5"
|
||||
|
||||
_debug _domain "$_domain"
|
||||
_debug _key_file "$_key_file"
|
||||
_debug _cert_file "$_cert_file"
|
||||
_debug _ca_file "$_ca_file"
|
||||
_debug _fullchain_file "$_fullchain_file"
|
||||
|
||||
if ! _exists jq; then
|
||||
_err "jq not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename
|
||||
_kemp_domain=$(echo "${_domain}" | sed 's/\*\.//')
|
||||
_debug _kemp_domain "$_kemp_domain"
|
||||
|
||||
# Read config from saved values or env
|
||||
_getdeployconf DEPLOY_KEMP_TOKEN
|
||||
_getdeployconf DEPLOY_KEMP_URL
|
||||
|
||||
_debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
|
||||
_secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
|
||||
|
||||
if [ -z "$DEPLOY_KEMP_TOKEN" ]; then
|
||||
_err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$DEPLOY_KEMP_URL" ]; then
|
||||
_err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save current values
|
||||
_savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
|
||||
_savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
|
||||
|
||||
# Check if certificate is already installed
|
||||
_info "Check if certificate is already present"
|
||||
_list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}"
|
||||
_debug3 _list_request "${_list_request}"
|
||||
_kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "^${_kemp_domain}$")
|
||||
_debug2 _kemp_cert_count "${_kemp_cert_count}"
|
||||
|
||||
_kemp_replace_cert=1
|
||||
if [ "${_kemp_cert_count}" -eq 0 ]; then
|
||||
_kemp_replace_cert=0
|
||||
_info "Certificate does not exist on Kemp Loadmaster"
|
||||
else
|
||||
_info "Certificate already exists on Kemp Loadmaster"
|
||||
fi
|
||||
_debug _kemp_replace_cert "${_kemp_replace_cert}"
|
||||
|
||||
# Upload new certificate to Kemp Loadmaster
|
||||
_kemp_upload_cert=$(_mktemp)
|
||||
cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}"
|
||||
|
||||
_info "Uploading certificate to Kemp Loadmaster"
|
||||
_add_data=$(cat "${_kemp_upload_cert}")
|
||||
_add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}"
|
||||
_debug3 _add_request "${_add_request}"
|
||||
_kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2")
|
||||
_retval=$?
|
||||
_debug2 _kemp_post_result "${_kemp_post_result}"
|
||||
if [ "${_retval}" -eq 0 ]; then
|
||||
_kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status')
|
||||
_kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message')
|
||||
if [ "${_kemp_post_status}" = "ok" ]; then
|
||||
_info "Upload successful"
|
||||
else
|
||||
_err "Upload failed: ${_kemp_post_message}"
|
||||
_retval=1
|
||||
fi
|
||||
else
|
||||
_err "Upload failed"
|
||||
_retval=1
|
||||
fi
|
||||
|
||||
rm "${_kemp_upload_cert}"
|
||||
|
||||
return $_retval
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to KeyHelp
|
||||
# This deployment required following variables
|
||||
# export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com"
|
||||
# export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username"
|
||||
# export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password"
|
||||
# export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID"
|
||||
|
||||
# Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID.
|
||||
# https://DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=xxx
|
||||
|
||||
# If have more than one domain name
|
||||
# export DEPLOY_KEYHELP_DOMAIN_ID="111 222 333"
|
||||
|
||||
keyhelp_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"
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_BASEURL" ]; then
|
||||
_err "DEPLOY_KEYHELP_BASEURL is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_BASEURL "$DEPLOY_KEYHELP_BASEURL"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_USERNAME" ]; then
|
||||
_err "DEPLOY_KEYHELP_USERNAME is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_USERNAME "$DEPLOY_KEYHELP_USERNAME"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_PASSWORD" ]; then
|
||||
_err "DEPLOY_KEYHELP_PASSWORD is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_PASSWORD "$DEPLOY_KEYHELP_PASSWORD"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_DOMAIN_ID" ]; then
|
||||
_err "DEPLOY_KEYHELP_DOMAIN_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_DOMAIN_ID "$DEPLOY_KEYHELP_DOMAIN_ID"
|
||||
fi
|
||||
|
||||
# Optional DEPLOY_KEYHELP_ENFORCE_HTTPS
|
||||
_getdeployconf DEPLOY_KEYHELP_ENFORCE_HTTPS
|
||||
# set default values for DEPLOY_KEYHELP_ENFORCE_HTTPS
|
||||
[ -n "${DEPLOY_KEYHELP_ENFORCE_HTTPS}" ] || DEPLOY_KEYHELP_ENFORCE_HTTPS="1"
|
||||
|
||||
_info "Logging in to keyhelp panel"
|
||||
username_encoded="$(printf "%s" "${DEPLOY_KEYHELP_USERNAME}" | _url_encode)"
|
||||
password_encoded="$(printf "%s" "${DEPLOY_KEYHELP_PASSWORD}" | _url_encode)"
|
||||
_H1="Content-Type: application/x-www-form-urlencoded"
|
||||
_response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?submit=1&username=$username_encoded&password=$password_encoded" "TRUE")
|
||||
_cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
|
||||
|
||||
# If cookies is not empty then logon successful
|
||||
if [ -z "$_cookie" ]; then
|
||||
_err "Fail to get cookie."
|
||||
return 1
|
||||
fi
|
||||
_debug "cookie" "$_cookie"
|
||||
|
||||
_info "Uploading certificate"
|
||||
_date=$(date +"%Y%m%d")
|
||||
encoded_key="$(_url_encode <"$_ckey")"
|
||||
encoded_ccert="$(_url_encode <"$_ccert")"
|
||||
encoded_cca="$(_url_encode <"$_cca")"
|
||||
certificate_name="$_cdomain-$_date"
|
||||
|
||||
_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 class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
||||
_info "_message" "$_message"
|
||||
if [ -z "$_message" ]; then
|
||||
_err "Fail to upload certificate."
|
||||
return 1
|
||||
fi
|
||||
|
||||
for DOMAIN_ID in $DEPLOY_KEYHELP_DOMAIN_ID; do
|
||||
_info "Apply certificate to domain id $DOMAIN_ID"
|
||||
_response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID")
|
||||
cert_value=$(echo "$_response" | grep "$certificate_name" | sed -n 's/.*value="\([^"]*\).*/\1/p')
|
||||
target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p')
|
||||
if [ "$target_type" = "directory" ]; then
|
||||
path=$(echo "$_response" | awk '/name="path"/{getline; print}' | sed -n 's/.*value="\([^"]*\).*/\1/p')
|
||||
fi
|
||||
echo "$_response" | grep "is_prefer_https" | grep "checked" >/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
is_prefer_https=1
|
||||
else
|
||||
is_prefer_https=0
|
||||
fi
|
||||
echo "$_response" | grep "hsts_enabled" | grep "checked" >/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
hsts_enabled=1
|
||||
else
|
||||
hsts_enabled=0
|
||||
fi
|
||||
_debug "cert_value" "$cert_value"
|
||||
if [ -z "$cert_value" ]; then
|
||||
_err "Fail to get certificate id."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_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 class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
||||
_info "_message" "$_message"
|
||||
if [ -z "$_message" ]; then
|
||||
_err "Fail to apply certificate."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
_info "Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID."
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
keyhelp_api_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
|
||||
# Read config from saved values or env
|
||||
_getdeployconf DEPLOY_KEYHELP_HOST
|
||||
_getdeployconf DEPLOY_KEYHELP_API_KEY
|
||||
|
||||
_debug DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST"
|
||||
_secure_debug DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY"
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_HOST" ]; then
|
||||
_err "KeyHelp host not found, please define DEPLOY_KEYHELP_HOST."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$DEPLOY_KEYHELP_API_KEY" ]; then
|
||||
_err "KeyHelp api key not found, please define DEPLOY_KEYHELP_API_KEY."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save current values
|
||||
_savedeployconf DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST"
|
||||
_savedeployconf DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY"
|
||||
|
||||
_request_key="$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')"
|
||||
_request_cert="$(tr '\n' ':' <"$_ccert" | sed 's/:/\\n/g')"
|
||||
_request_ca="$(tr '\n' ':' <"$_cca" | sed 's/:/\\n/g')"
|
||||
|
||||
_request_body="{
|
||||
\"name\": \"$_cdomain\",
|
||||
\"components\": {
|
||||
\"private_key\": \"$_request_key\",
|
||||
\"certificate\": \"$_request_cert\",
|
||||
\"ca_certificate\": \"$_request_ca\"
|
||||
}
|
||||
}"
|
||||
|
||||
_hosts="$(echo "$DEPLOY_KEYHELP_HOST" | tr "," " ")"
|
||||
_keys="$(echo "$DEPLOY_KEYHELP_API_KEY" | tr "," " ")"
|
||||
_i=1
|
||||
|
||||
for _host in $_hosts; do
|
||||
_key="$(_getfield "$_keys" "$_i" " ")"
|
||||
_i="$(_math "$_i" + 1)"
|
||||
|
||||
export _H1="X-API-Key: $_key"
|
||||
|
||||
_put_url="$_host/api/v2/certificates/name/$_cdomain"
|
||||
if _post "$_request_body" "$_put_url" "" "PUT" "application/json" >/dev/null; then
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||
else
|
||||
_err "Cannot make PUT request to $_put_url"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_code" = "404" ]; then
|
||||
_info "$_cdomain not found, creating new entry at $_host"
|
||||
|
||||
_post_url="$_host/api/v2/certificates"
|
||||
if _post "$_request_body" "$_post_url" "" "POST" "application/json" >/dev/null; then
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||
else
|
||||
_err "Cannot make POST request to $_post_url"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if _startswith "$_code" "2"; then
|
||||
_info "$_cdomain set at $_host"
|
||||
else
|
||||
_err "HTTP status code is $_code"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy-hook to very simply copy files to set directories and then
|
||||
# execute whatever reloadcmd the admin needs afterwards. This can be
|
||||
# useful for configurations where the "multideploy" hook (in development)
|
||||
# is used or when an admin wants ACME.SH to renew certs but needs to
|
||||
# manually configure deployment via an external script
|
||||
# (e.g. The deploy-freenas script for TrueNAS Core/Scale
|
||||
# https://github.com/danb35/deploy-freenas/ )
|
||||
#
|
||||
# If the same file is configured for the certificate key
|
||||
# and the certificate and/or full chain, a combined PEM file will
|
||||
# be output instead.
|
||||
#
|
||||
# Environment variables to be utilized are as follows:
|
||||
#
|
||||
# DEPLOY_LOCALCOPY_CERTKEY - /path/to/target/cert.key
|
||||
# DEPLOY_LOCALCOPY_CERTIFICATE - /path/to/target/cert.cer
|
||||
# DEPLOY_LOCALCOPY_FULLCHAIN - /path/to/target/fullchain.cer
|
||||
# DEPLOY_LOCALCOPY_CA - /path/to/target/ca.cer
|
||||
# DEPLOY_LOCALCOPY_PFX - /path/to/target/cert.pfx
|
||||
# DEPLOY_LOCALCOPY_RELOADCMD - "echo 'this is my cmd'"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
localcopy_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cpfx="$6"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
_debug _cpfx "$_cpfx"
|
||||
|
||||
_getdeployconf DEPLOY_LOCALCOPY_CERTIFICATE
|
||||
_getdeployconf DEPLOY_LOCALCOPY_CERTKEY
|
||||
_getdeployconf DEPLOY_LOCALCOPY_FULLCHAIN
|
||||
_getdeployconf DEPLOY_LOCALCOPY_CA
|
||||
_getdeployconf DEPLOY_LOCALCOPY_RELOADCMD
|
||||
_getdeployconf DEPLOY_LOCALCOPY_PFX
|
||||
_combined_target=""
|
||||
_combined_srccert=""
|
||||
|
||||
# Create PEM file
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" ] &&
|
||||
{ [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ] ||
|
||||
[ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; }; then
|
||||
|
||||
_combined_target="$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then
|
||||
_combined_srccert="$_ccert"
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE"
|
||||
DEPLOY_LOCALCOPY_CERTIFICATE=""
|
||||
fi
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then
|
||||
_combined_srccert="$_cfullchain"
|
||||
_savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN"
|
||||
DEPLOY_LOCALCOPY_FULLCHAIN=""
|
||||
fi
|
||||
DEPLOY_LOCALCOPY_CERTKEY=""
|
||||
_info "Creating combined PEM"
|
||||
_debug "Creating combined PEM at $_combined_target"
|
||||
if ! [ -f "$_combined_target" ]; then
|
||||
touch "$_combined_target" || return 1
|
||||
chmod 600 "$_combined_target"
|
||||
fi
|
||||
if ! cat "$_combined_srccert" "$_ckey" >"$_combined_target"; then
|
||||
_err "Failed to create PEM file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then
|
||||
_info "Copying certificate"
|
||||
_debug "Copying $_ccert to $DEPLOY_LOCALCOPY_CERTIFICATE"
|
||||
if ! cat "$_ccert" >"$DEPLOY_LOCALCOPY_CERTIFICATE"; then
|
||||
_err "Failed to copy certificate, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" ]; then
|
||||
_info "Copying certificate key"
|
||||
_debug "Copying $_ckey to $DEPLOY_LOCALCOPY_CERTKEY"
|
||||
if ! [ -f "$DEPLOY_LOCALCOPY_CERTKEY" ]; then
|
||||
touch "$DEPLOY_LOCALCOPY_CERTKEY" || return 1
|
||||
chmod 600 "$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
fi
|
||||
if ! cat "$_ckey" >"$DEPLOY_LOCALCOPY_CERTKEY"; then
|
||||
_err "Failed to copy certificate key, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then
|
||||
_info "Copying fullchain"
|
||||
_debug "Copying $_cfullchain to $DEPLOY_LOCALCOPY_FULLCHAIN"
|
||||
if ! cat "$_cfullchain" >"$DEPLOY_LOCALCOPY_FULLCHAIN"; then
|
||||
_err "Failed to copy fullchain, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_CA" ]; then
|
||||
_info "Copying CA"
|
||||
_debug "Copying $_cca to $DEPLOY_LOCALCOPY_CA"
|
||||
if ! cat "$_cca" >"$DEPLOY_LOCALCOPY_CA"; then
|
||||
_err "Failed to copy CA, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CA "$DEPLOY_LOCALCOPY_CA"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_PFX" ]; then
|
||||
_info "Copying PFX"
|
||||
_debug "Copying $_cpfx to $DEPLOY_LOCALCOPY_PFX"
|
||||
if ! [ -f "$DEPLOY_LOCALCOPY_PFX" ]; then
|
||||
touch "$DEPLOY_LOCALCOPY_PFX" || return 1
|
||||
chmod 600 "$DEPLOY_LOCALCOPY_PFX"
|
||||
fi
|
||||
if ! cat "$_cpfx" >"$DEPLOY_LOCALCOPY_PFX"; then
|
||||
_err "Failed to copy PFX, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_PFX "$DEPLOY_LOCALCOPY_PFX"
|
||||
fi
|
||||
|
||||
_reload=$DEPLOY_LOCALCOPY_RELOADCMD
|
||||
_debug "Running reloadcmd $_reload"
|
||||
|
||||
if [ -z "$_reload" ]; then
|
||||
_info "Reloadcmd not provided, skipping."
|
||||
else
|
||||
_info "Reloading"
|
||||
if eval "$_reload"; then
|
||||
_info "Reload successful."
|
||||
_savedeployconf DEPLOY_LOCALCOPY_RELOADCMD "$DEPLOY_LOCALCOPY_RELOADCMD" "base64"
|
||||
else
|
||||
_err "Reload failed."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "$(__green "'localcopy' deploy success")"
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
################################################################################
|
||||
# ACME.sh 3rd party deploy plugin for multiple (same) services
|
||||
################################################################################
|
||||
# Authors: tomo2403 (creator), https://github.com/tomo2403
|
||||
# Updated: 2025-03-01
|
||||
# Issues: https://github.com/acmesh-official/acme.sh/issues and mention @tomo2403
|
||||
################################################################################
|
||||
# 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
|
||||
################################################################################
|
||||
# Dependencies:
|
||||
# - yq
|
||||
################################################################################
|
||||
# Return value:
|
||||
# 0 means success, otherwise error.
|
||||
################################################################################
|
||||
|
||||
MULTIDEPLOY_VERSION="1.0"
|
||||
|
||||
# Description: This function handles the deployment of certificates to multiple services.
|
||||
# It processes the provided certificate files and deploys them according to the
|
||||
# configuration specified in the multideploy file.
|
||||
#
|
||||
# Parameters:
|
||||
# _cdomain - The domain name for which the certificate is issued.
|
||||
# _ckey - The private key file for the certificate.
|
||||
# _ccert - The certificate file.
|
||||
# _cca - The CA (Certificate Authority) file.
|
||||
# _cfullchain - The full chain certificate file.
|
||||
# _cpfx - The PFX (Personal Information Exchange) file.
|
||||
multideploy_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cpfx="$6"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
_debug _cpfx "$_cpfx"
|
||||
|
||||
_getdeployconf MULTIDEPLOY_FILENAME
|
||||
if [ -z "$MULTIDEPLOY_FILENAME" ]; then
|
||||
MULTIDEPLOY_FILENAME="multideploy.yml"
|
||||
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
|
||||
else
|
||||
_savedeployconf "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
|
||||
_debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
|
||||
fi
|
||||
|
||||
if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then
|
||||
_err "Failed to preprocess deploy file."
|
||||
return 1
|
||||
fi
|
||||
_debug3 "File" "$file"
|
||||
|
||||
# Deploy to services
|
||||
_deploy_services "$file"
|
||||
_exitCode="$?"
|
||||
|
||||
return "$_exitCode"
|
||||
}
|
||||
|
||||
# Description:
|
||||
# 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.
|
||||
# Usage:
|
||||
# _preprocess_deployfile "<deploy_file1>" "<deploy_file2>?"
|
||||
_preprocess_deployfile() {
|
||||
# Check if yq is installed
|
||||
if ! command -v yq >/dev/null 2>&1; then
|
||||
_err "yq is not installed! Please install yq and try again."
|
||||
return 1
|
||||
fi
|
||||
_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 "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"
|
||||
else
|
||||
_debug3 "File not found"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$found_file" ]; then
|
||||
_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"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$found_file"
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function checks the deploy file for version compatibility and the existence of the specified configuration and services.
|
||||
# Arguments:
|
||||
# $1 - The path to the deploy configuration file.
|
||||
# $2 - The name of the deploy configuration to use.
|
||||
# Usage:
|
||||
# _check_deployfile "<deploy_file_path>"
|
||||
_check_deployfile() {
|
||||
_deploy_file="$1"
|
||||
_debug2 "check: Deploy file" "$_deploy_file"
|
||||
|
||||
# Check version
|
||||
_deploy_file_version=$(yq -r '.version' "$_deploy_file")
|
||||
if [ "$MULTIDEPLOY_VERSION" != "$_deploy_file_version" ]; then
|
||||
_err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version."
|
||||
return 1
|
||||
fi
|
||||
_debug2 "check: Deploy file version is compatible: $_deploy_file_version"
|
||||
|
||||
# Extract all services from config
|
||||
_services=$(yq -r '.services[].name' "$_deploy_file")
|
||||
|
||||
if [ -z "$_services" ]; then
|
||||
_err "Config does not have any services to deploy to."
|
||||
return 1
|
||||
fi
|
||||
_debug2 "check: Config has services."
|
||||
echo "$_services" | while read -r _service; do
|
||||
_debug3 " - $_service"
|
||||
done
|
||||
|
||||
# Check if extracted services exist in services list
|
||||
echo "$_services" | while read -r _service; do
|
||||
_debug2 "check: Checking service: $_service"
|
||||
# Check if service exists
|
||||
_service_config=$(yq -r ".services[] | select(.name == \"$_service\")" "$_deploy_file")
|
||||
if [ -z "$_service_config" ] || [ "$_service_config" = "null" ]; then
|
||||
_err "Service '$_service' not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_service_hook=$(echo "$_service_config" | yq -r ".hook" -)
|
||||
if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then
|
||||
_err "Service '$_service' does not have a hook."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_service_environment=$(echo "$_service_config" | yq -r ".environment" -)
|
||||
if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then
|
||||
_err "Service '$_service' does not have an environment."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Description: This function takes a list of environment variables in YAML format,
|
||||
# parses them, and exports each key-value pair as environment variables.
|
||||
# Arguments:
|
||||
# $1 - A string containing the list of environment variables in YAML format.
|
||||
# Usage:
|
||||
# _export_envs "$env_list"
|
||||
_export_envs() {
|
||||
_env_list="$1"
|
||||
|
||||
_secure_debug3 "Exporting envs" "$_env_list"
|
||||
|
||||
echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do
|
||||
# Using eval to expand nested variables in the configuration file
|
||||
_value=$(eval 'echo "'"$_value"'"')
|
||||
_savedeployconf "$_key" "$_value"
|
||||
_secure_debug3 "Saved $_key" "$_value"
|
||||
done
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function takes a YAML formatted string of environment variables, parses it,
|
||||
# and clears each environment variable. It logs the process of clearing each variable.
|
||||
#
|
||||
# Note: Environment variables for a hook may be optional and differ between
|
||||
# services using the same hook.
|
||||
# If one service sets optional environment variables and another does not, the
|
||||
# variables may persist and affect subsequent deployments.
|
||||
# Clearing these variables after each service ensures that only the
|
||||
# environment variables explicitly specified for each service in the deploy
|
||||
# file are used.
|
||||
# Arguments:
|
||||
# $1 - A YAML formatted string containing environment variable key-value pairs.
|
||||
# Usage:
|
||||
# _clear_envs "<yaml_string>"
|
||||
_clear_envs() {
|
||||
_env_list="$1"
|
||||
|
||||
_secure_debug3 "Clearing envs" "$_env_list"
|
||||
env_pairs=$(echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value')
|
||||
|
||||
echo "$env_pairs" | while IFS='=' read -r _key _value; do
|
||||
_debug3 "Deleting key" "$_key"
|
||||
_cleardeployconf "$_key"
|
||||
unset -v "$_key"
|
||||
done
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function deploys services listed in the deploy configuration file.
|
||||
# Arguments:
|
||||
# $1 - The path to the deploy configuration file.
|
||||
# $2 - The list of services to deploy.
|
||||
# Usage:
|
||||
# _deploy_services "<deploy_file_path>" "<services_list>"
|
||||
_deploy_services() {
|
||||
_deploy_file="$1"
|
||||
_debug3 "Deploy file" "$_deploy_file"
|
||||
|
||||
_tempfile=$(mktemp)
|
||||
trap 'rm -f $_tempfile' EXIT
|
||||
|
||||
yq -r '.services[].name' "$_deploy_file" >"$_tempfile"
|
||||
_debug3 "Services" "$(cat "$_tempfile")"
|
||||
|
||||
_failedServices=""
|
||||
_failedCount=0
|
||||
while read -r _service <&3; do
|
||||
_debug2 "Service" "$_service"
|
||||
_hook=$(yq -r ".services[] | select(.name == \"$_service\").hook" "$_deploy_file")
|
||||
_envs=$(yq -r ".services[] | select(.name == \"$_service\").environment" "$_deploy_file")
|
||||
|
||||
_export_envs "$_envs"
|
||||
if ! _deploy_service "$_service" "$_hook"; then
|
||||
_failedServices="$_service, $_failedServices"
|
||||
_failedCount=$((_failedCount + 1))
|
||||
fi
|
||||
_clear_envs "$_envs"
|
||||
done 3<"$_tempfile"
|
||||
|
||||
_debug3 "Failed services" "$_failedServices"
|
||||
_debug2 "Failed count" "$_failedCount"
|
||||
if [ -n "$_failedServices" ]; then
|
||||
_info "$(__red "Deployment failed") for services: $_failedServices"
|
||||
else
|
||||
_debug "All services deployed successfully."
|
||||
fi
|
||||
|
||||
return "$_failedCount"
|
||||
}
|
||||
|
||||
# Description: Deploys a service using the specified hook.
|
||||
# Arguments:
|
||||
# $1 - The name of the service to deploy.
|
||||
# $2 - The hook to use for deployment.
|
||||
# Usage:
|
||||
# _deploy_service <service_name> <hook>
|
||||
_deploy_service() {
|
||||
_name="$1"
|
||||
_hook="$2"
|
||||
|
||||
_debug2 "SERVICE" "$_name"
|
||||
_debug2 "HOOK" "$_hook"
|
||||
|
||||
_info "$(__green "Deploying") to '$_name' using '$_hook'"
|
||||
_deploy "$_cdomain" "$_hook"
|
||||
}
|
||||
|
|
@ -54,8 +54,6 @@ mydevil_deploy() {
|
|||
# Usage: ip=$(mydevil_get_ip domain.com)
|
||||
# echo $ip
|
||||
mydevil_get_ip() {
|
||||
# tr squeezes runs of blanks into one tab so plain cut works everywhere;
|
||||
# cut -w is BSD-only and unknown to GNU coreutils
|
||||
devil dns list "$1" | tr -s ' \t' '\t' | cut -s -f 3,7 | grep "^A$(printf '\t')" | cut -s -f 2 || return 1
|
||||
devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to Netlify
|
||||
# https://docs.netlify.com/api/get-started/#authentication
|
||||
# https://open-api.netlify.com/#tag/sniCertificate
|
||||
|
||||
# This deployment required following variables
|
||||
# export Netlify_ACCESS_TOKEN="Your Netlify Access Token"
|
||||
# export Netlify_SITE_ID="Your Netlify Site ID"
|
||||
|
||||
# If have more than one SITE ID
|
||||
# export Netlify_SITE_ID="SITE_ID_1 SITE_ID_2"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
netlify_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"
|
||||
|
||||
if [ -z "$Netlify_ACCESS_TOKEN" ]; then
|
||||
_err "Netlify_ACCESS_TOKEN is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN"
|
||||
fi
|
||||
if [ -z "$Netlify_SITE_ID" ]; then
|
||||
_err "Netlify_SITE_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf Netlify_SITE_ID "$Netlify_SITE_ID"
|
||||
fi
|
||||
|
||||
_info "Deploying certificate to Netlify..."
|
||||
|
||||
## upload certificate
|
||||
string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n')
|
||||
string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
for SITE_ID in $Netlify_SITE_ID; do
|
||||
_request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}"
|
||||
_debug _request_body "$_request_body"
|
||||
_debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN"
|
||||
export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN"
|
||||
_response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$SITE_ID/ssl" "" "POST" "application/json")
|
||||
|
||||
if _contains "$_response" "\"error\""; then
|
||||
_err "Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
_debug response "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to Netlify SITE_ID $SITE_ID."
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
296
deploy/panos.sh
296
deploy/panos.sh
|
|
@ -7,27 +7,13 @@
|
|||
#
|
||||
# Firewall admin with superuser and IP address is required.
|
||||
#
|
||||
# REQUIRED:
|
||||
# export PANOS_HOST=""
|
||||
# export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role
|
||||
# export PANOS_PASS=""
|
||||
#
|
||||
# OPTIONAL
|
||||
# export PANOS_TEMPLATE="" # Template Name of panorama managed devices
|
||||
# export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically
|
||||
# export PANOS_VSYS="Shared" # name of the vsys to import the certificate
|
||||
# export PANOS_CERTNAME="" # use a custom certificate name to work around Panorama's 31-character limit
|
||||
#
|
||||
# The script will automatically generate a new API key if
|
||||
# no key is found, or if a saved key has expired or is invalid.
|
||||
# export PANOS_USER="" # required
|
||||
# export PANOS_PASS="" # required
|
||||
# export PANOS_HOST="" # required
|
||||
|
||||
_COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds
|
||||
_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes)
|
||||
|
||||
# This function is to parse the XML response from the firewall
|
||||
# This function is to parse the XML
|
||||
parse_response() {
|
||||
type=$2
|
||||
_debug "API Response: $1"
|
||||
if [ "$type" = 'keygen' ]; then
|
||||
status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g')
|
||||
if [ "$status" = "success" ]; then
|
||||
|
|
@ -37,52 +23,25 @@ parse_response() {
|
|||
message="PAN-OS Key could not be set."
|
||||
fi
|
||||
else
|
||||
if [ "$type" = 'commit' ]; then
|
||||
job_id=$(echo "$1" | sed 's/^.*\(<job>\)\(.*\)<\/job>.*/\2/g')
|
||||
_commit_job_id=$job_id
|
||||
elif [ "$type" = 'job_status' ]; then
|
||||
job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*<result>\([^<]*\)<\/result>.*/\1/g')
|
||||
_commit_job_status=$job_status
|
||||
fi
|
||||
status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g')
|
||||
message=$(echo "$1" | tr -d '\n' | sed 's/.*\(<result>\|<msg>\|<line>\)\([^<]*\).*/\2/g')
|
||||
_debug "Firewall message: $message"
|
||||
if [ "$type" = 'keytest' ] && [ "$status" != "success" ]; then
|
||||
_debug "**** API Key has EXPIRED or is INVALID ****"
|
||||
unset _panos_key
|
||||
fi
|
||||
status=$(echo "$1" | sed 's/^.*"\([a-z]*\)".*/\1/g')
|
||||
message=$(echo "$1" | sed 's/^.*<result>\(.*\)<\/result.*/\1/g')
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#This function is used to deploy to the firewall
|
||||
deployer() {
|
||||
content=""
|
||||
type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push
|
||||
type=$1 # Types are keygen, cert, key, commit
|
||||
_debug "**** Deploying $type *****"
|
||||
panos_url="https://$_panos_host/api/"
|
||||
export _H1="Content-Type: application/x-www-form-urlencoded"
|
||||
|
||||
#Test API Key by performing a lookup
|
||||
if [ "$type" = 'keytest' ]; then
|
||||
_debug "**** Testing saved API Key ****"
|
||||
# Get Version Info to test key
|
||||
content="type=version&key=$_panos_key"
|
||||
## Exclude all scopes for the empty commit
|
||||
#_exclude_scope="<device-and-network>excluded</device-and-network><shared-object>excluded</shared-object>"
|
||||
#content="type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>"
|
||||
fi
|
||||
|
||||
# Generate API Key
|
||||
if [ "$type" = 'keygen' ]; then
|
||||
_debug "**** Generating new API Key ****"
|
||||
_H1="Content-Type: application/x-www-form-urlencoded"
|
||||
content="type=keygen&user=$_panos_user&password=$_panos_pass"
|
||||
# content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
|
||||
fi
|
||||
|
||||
# Deploy Cert or Key
|
||||
if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then
|
||||
_debug "**** Deploying $type ****"
|
||||
#Generate DELIM
|
||||
#Generate DEIM
|
||||
delim="-----MultipartDelimiter$(date "+%s%N")"
|
||||
nl="\015\012"
|
||||
#Set Header
|
||||
|
|
@ -90,31 +49,19 @@ deployer() {
|
|||
if [ "$type" = 'cert' ]; then
|
||||
panos_url="${panos_url}?type=import"
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
|
||||
if [ "$_panos_template" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
|
||||
fi
|
||||
if [ "$_panos_vsys" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
|
||||
fi
|
||||
fi
|
||||
if [ "$type" = 'key' ]; then
|
||||
panos_url="${panos_url}?type=import"
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_certname.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
|
||||
if [ "$_panos_template" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
|
||||
fi
|
||||
if [ "$_panos_vsys" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
|
||||
fi
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
|
||||
fi
|
||||
#Close multipart
|
||||
content="$content${nl}--$delim--${nl}${nl}"
|
||||
|
|
@ -122,43 +69,20 @@ deployer() {
|
|||
content=$(printf %b "$content")
|
||||
fi
|
||||
|
||||
# Commit changes
|
||||
if [ "$type" = 'commit' ]; then
|
||||
_debug "**** Committing changes ****"
|
||||
#Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution!
|
||||
if [ "$FORCE" ]; then
|
||||
_debug "Force switch detected. Committing ALL changes to the firewall."
|
||||
cmd=$(printf "%s" "<commit><force><partial><admin><member>$_panos_user</member></admin></partial></force></commit>" | _url_encode)
|
||||
else
|
||||
cmd=$(printf "%s" "<commit><partial><admin><member>$_panos_user</member></admin></partial></commit>" | _url_encode)
|
||||
fi
|
||||
content="type=commit&action=partial&key=$_panos_key&cmd=$cmd"
|
||||
export _H1="Content-Type: application/x-www-form-urlencoded"
|
||||
cmd=$(printf "%s" "<commit><partial><$_panos_user></$_panos_user></partial></commit>" | _url_encode)
|
||||
content="type=commit&key=$_panos_key&cmd=$cmd"
|
||||
fi
|
||||
|
||||
# Query job status
|
||||
if [ "$type" = 'job_status' ]; then
|
||||
echo "**** Querying job $_commit_job_id status ****"
|
||||
cmd=$(printf "%s" "<show><jobs><id>$_commit_job_id</id></jobs></show>" | _url_encode)
|
||||
content="type=op&key=$_panos_key&cmd=$cmd"
|
||||
fi
|
||||
|
||||
# Push changes
|
||||
if [ "$type" = 'push' ]; then
|
||||
echo "**** Pushing changes ****"
|
||||
cmd=$(printf "%s" "<commit-all><template-stack><name>$_panos_template_stack</name><admin><member>$_panos_user</member></admin></template-stack></commit-all>" | _url_encode)
|
||||
content="type=commit&action=all&key=$_panos_key&cmd=$cmd"
|
||||
fi
|
||||
|
||||
response=$(_post "$content" "$panos_url" "" "POST")
|
||||
parse_response "$response" "$type"
|
||||
# Saving response to variables
|
||||
response_status=$status
|
||||
#DEBUG
|
||||
_debug response_status "$response_status"
|
||||
if [ "$response_status" = "success" ]; then
|
||||
_debug "Successfully deployed $type"
|
||||
return 0
|
||||
elif [ "$_commit_job_status" ]; then
|
||||
_debug "Commit Job Status = $_commit_job_status"
|
||||
else
|
||||
_err "Deploy of type $type failed. Try deploying with --debug to troubleshoot."
|
||||
_debug "$message"
|
||||
|
|
@ -168,162 +92,48 @@ deployer() {
|
|||
|
||||
# This is the main function that will call the other functions to deploy everything.
|
||||
panos_deploy() {
|
||||
_cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_cfullchain="$5"
|
||||
|
||||
# VALID FILE CHECK
|
||||
if [ ! -f "$_ckey" ] || [ ! -f "$_cfullchain" ]; then
|
||||
_err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# PANOS_HOST
|
||||
if [ "$PANOS_HOST" ]; then
|
||||
_debug "Detected ENV variable PANOS_HOST. Saving to file."
|
||||
_savedeployconf PANOS_HOST "$PANOS_HOST" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_HOST from file."
|
||||
_getdeployconf PANOS_HOST
|
||||
fi
|
||||
|
||||
# PANOS USER
|
||||
if [ "$PANOS_USER" ]; then
|
||||
_debug "Detected ENV variable PANOS_USER. Saving to file."
|
||||
_savedeployconf PANOS_USER "$PANOS_USER" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_USER from file."
|
||||
# PANOS ENV VAR check
|
||||
if [ -z "$PANOS_USER" ] || [ -z "$PANOS_PASS" ] || [ -z "$PANOS_HOST" ]; then
|
||||
_debug "No ENV variables found lets check for saved variables"
|
||||
_getdeployconf PANOS_USER
|
||||
fi
|
||||
|
||||
# PANOS_PASS
|
||||
if [ "$PANOS_PASS" ]; then
|
||||
_debug "Detected ENV variable PANOS_PASS. Saving to file."
|
||||
_savedeployconf PANOS_PASS "$PANOS_PASS" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_PASS from file."
|
||||
_getdeployconf PANOS_PASS
|
||||
fi
|
||||
|
||||
# PANOS_KEY
|
||||
if [ "$PANOS_KEY" ]; then
|
||||
_debug "Detected ENV variable PANOS_KEY. Saving to file."
|
||||
_savedeployconf PANOS_KEY "$PANOS_KEY" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_KEY from file."
|
||||
_getdeployconf PANOS_KEY
|
||||
fi
|
||||
|
||||
# PANOS_TEMPLATE
|
||||
if [ "$PANOS_TEMPLATE" ]; then
|
||||
_debug "Detected ENV variable PANOS_TEMPLATE. Saving to file."
|
||||
_savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_TEMPLATE from file."
|
||||
_getdeployconf PANOS_TEMPLATE
|
||||
fi
|
||||
|
||||
# PANOS_TEMPLATE_STACK
|
||||
if [ "$PANOS_TEMPLATE_STACK" ]; then
|
||||
_debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file."
|
||||
_savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_TEMPLATE_STACK from file."
|
||||
_getdeployconf PANOS_TEMPLATE_STACK
|
||||
fi
|
||||
|
||||
# PANOS_TEMPLATE_STACK
|
||||
if [ "$PANOS_VSYS" ]; then
|
||||
_debug "Detected ENV variable PANOS_VSYS. Saving to file."
|
||||
_savedeployconf PANOS_VSYS "$PANOS_VSYS" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_VSYS from file."
|
||||
_getdeployconf PANOS_VSYS
|
||||
fi
|
||||
|
||||
# PANOS_CERTNAME
|
||||
if [ "$PANOS_CERTNAME" ]; then
|
||||
_debug "Detected ENV variable PANOS_CERTNAME. Saving to file."
|
||||
_savedeployconf PANOS_CERTNAME "$PANOS_CERTNAME" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_CERTNAME from file."
|
||||
_getdeployconf PANOS_CERTNAME
|
||||
fi
|
||||
|
||||
#Store variables
|
||||
_panos_host=$PANOS_HOST
|
||||
_panos_user=$PANOS_USER
|
||||
_panos_pass=$PANOS_PASS
|
||||
_panos_key=$PANOS_KEY
|
||||
_panos_template=$PANOS_TEMPLATE
|
||||
_panos_template_stack=$PANOS_TEMPLATE_STACK
|
||||
_panos_vsys=$PANOS_VSYS
|
||||
_panos_certname=$PANOS_CERTNAME
|
||||
|
||||
#Test API Key if found. If the key is invalid, the variable _panos_key will be unset.
|
||||
if [ "$_panos_host" ] && [ "$_panos_key" ]; then
|
||||
_debug "**** Testing API KEY ****"
|
||||
deployer keytest
|
||||
fi
|
||||
|
||||
# Check for valid variables
|
||||
if [ -z "$_panos_host" ]; then
|
||||
_err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs."
|
||||
return 1
|
||||
else
|
||||
# Use certificate name based on the first domain on the certificate if no custom certificate name is set
|
||||
if [ -z "$_panos_certname" ]; then
|
||||
_panos_certname="$_cdomain"
|
||||
_savedeployconf PANOS_CERTNAME "$_panos_certname" 1
|
||||
fi
|
||||
|
||||
# Generate a new API key if no valid API key is found
|
||||
if [ -z "$_panos_key" ]; then
|
||||
if [ -z "$_panos_user" ]; then
|
||||
_err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs."
|
||||
return 1
|
||||
elif [ -z "$_panos_pass" ]; then
|
||||
_err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs."
|
||||
return 1
|
||||
fi
|
||||
_debug "**** Generating new PANOS API KEY ****"
|
||||
deployer keygen
|
||||
_savedeployconf PANOS_KEY "$_panos_key" 1
|
||||
fi
|
||||
|
||||
# Confirm that a valid key was generated
|
||||
if [ -z "$_panos_key" ]; then
|
||||
_err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
|
||||
_getdeployconf PANOS_HOST
|
||||
_panos_user=$PANOS_USER
|
||||
_panos_pass=$PANOS_PASS
|
||||
_panos_host=$PANOS_HOST
|
||||
if [ -z "$_panos_user" ] && [ -z "$_panos_pass" ] && [ -z "$_panos_host" ]; then
|
||||
_err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs."
|
||||
return 1
|
||||
else
|
||||
# A commit of a failed import would leave a mismatched cert/key pair
|
||||
# on the firewall and can lock the admin out of the management
|
||||
# interface, see https://github.com/acmesh-official/acme.sh/issues/4716
|
||||
if ! deployer cert; then
|
||||
_err "Cert import failed. Aborting without committing."
|
||||
return 1
|
||||
fi
|
||||
if ! deployer key; then
|
||||
_err "Key import failed. Aborting without committing. Warning: the firewall now has an uncommitted mismatched cert/key pair in its candidate config."
|
||||
return 1
|
||||
fi
|
||||
if ! deployer commit; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$_panos_template_stack" ]; then
|
||||
# try to get job status for 20 times in 30 sec interval
|
||||
i=0
|
||||
while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do
|
||||
deployer job_status
|
||||
if [ "$_commit_job_status" = "OK" ]; then
|
||||
echo "Commit finished!"
|
||||
break
|
||||
fi
|
||||
sleep $_COMMIT_WAIT_INTERVAL
|
||||
i=$((i + 1))
|
||||
done
|
||||
deployer push
|
||||
fi
|
||||
_debug "Using saved env variables."
|
||||
fi
|
||||
else
|
||||
_debug "Detected ENV variables to be saved to the deploy conf."
|
||||
# Encrypt and save user
|
||||
_savedeployconf PANOS_USER "$PANOS_USER" 1
|
||||
_savedeployconf PANOS_PASS "$PANOS_PASS" 1
|
||||
_savedeployconf PANOS_HOST "$PANOS_HOST" 1
|
||||
_panos_user="$PANOS_USER"
|
||||
_panos_pass="$PANOS_PASS"
|
||||
_panos_host="$PANOS_HOST"
|
||||
fi
|
||||
_debug "Let's use username and pass to generate token."
|
||||
if [ -z "$_panos_user" ] || [ -z "$_panos_pass" ] || [ -z "$_panos_host" ]; then
|
||||
_err "Please pass username and password and host as env variables PANOS_USER, PANOS_PASS and PANOS_HOST"
|
||||
return 1
|
||||
else
|
||||
_debug "Getting PANOS KEY"
|
||||
deployer keygen
|
||||
if [ -z "$_panos_key" ]; then
|
||||
_err "Missing apikey."
|
||||
return 1
|
||||
else
|
||||
deployer cert
|
||||
deployer key
|
||||
deployer commit
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy certificates to a proxmox backup server using the API.
|
||||
#
|
||||
# Environment variables that can be set are:
|
||||
# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to
|
||||
# _cdomain.
|
||||
# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on.
|
||||
# Defaults to 8007.
|
||||
# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root.
|
||||
# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates
|
||||
# with. Defaults to pam.
|
||||
# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the
|
||||
# user account. Defaults to acme.
|
||||
# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required.
|
||||
|
||||
proxmoxbs_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug2 _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# "Sane" defaults.
|
||||
_getdeployconf DEPLOY_PROXMOXBS_SERVER
|
||||
if [ -z "$DEPLOY_PROXMOXBS_SERVER" ]; then
|
||||
_target_hostname="$_cdomain"
|
||||
else
|
||||
_target_hostname="$DEPLOY_PROXMOXBS_SERVER"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_SERVER "$DEPLOY_PROXMOXBS_SERVER"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_SERVER "$_target_hostname"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT
|
||||
if [ -z "$DEPLOY_PROXMOXBS_SERVER_PORT" ]; then
|
||||
_target_port="8007"
|
||||
else
|
||||
_target_port="$DEPLOY_PROXMOXBS_SERVER_PORT"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT "$DEPLOY_PROXMOXBS_SERVER_PORT"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_SERVER_PORT "$_target_port"
|
||||
|
||||
# Complete URL.
|
||||
_target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom"
|
||||
_debug TARGET_URL "$_target_url"
|
||||
|
||||
# More "sane" defaults.
|
||||
_getdeployconf DEPLOY_PROXMOXBS_USER
|
||||
if [ -z "$DEPLOY_PROXMOXBS_USER" ]; then
|
||||
_proxmoxbs_user="root"
|
||||
else
|
||||
_proxmoxbs_user="$DEPLOY_PROXMOXBS_USER"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_USER "$DEPLOY_PROXMOXBS_USER"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_USER "$_proxmoxbs_user"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXBS_USER_REALM
|
||||
if [ -z "$DEPLOY_PROXMOXBS_USER_REALM" ]; then
|
||||
_proxmoxbs_user_realm="pam"
|
||||
else
|
||||
_proxmoxbs_user_realm="$DEPLOY_PROXMOXBS_USER_REALM"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_USER_REALM "$DEPLOY_PROXMOXBS_USER_REALM"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_USER_REALM "$_proxmoxbs_user_realm"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME
|
||||
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" ]; then
|
||||
_proxmoxbs_api_token_name="acme"
|
||||
else
|
||||
_proxmoxbs_api_token_name="$DEPLOY_PROXMOXBS_API_TOKEN_NAME"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME "$DEPLOY_PROXMOXBS_API_TOKEN_NAME"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME "$_proxmoxbs_api_token_name"
|
||||
|
||||
# This is required.
|
||||
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY
|
||||
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" ]; then
|
||||
_err "API key not provided."
|
||||
return 1
|
||||
else
|
||||
_proxmoxbs_api_token_key="$DEPLOY_PROXMOXBS_API_TOKEN_KEY"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY "$DEPLOY_PROXMOXBS_API_TOKEN_KEY"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY "$_proxmoxbs_api_token_key"
|
||||
|
||||
# PBS API Token header value. Used in "Authorization: PBSAPIToken".
|
||||
_proxmoxbs_header_api_token="${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}"
|
||||
_debug2 "Auth Header" "$_proxmoxbs_header_api_token"
|
||||
|
||||
# Ugly. I hate putting heredocs inside functions because heredocs don't
|
||||
# account for whitespace correctly but it _does_ work and is several times
|
||||
# cleaner than anything else I had here.
|
||||
#
|
||||
# This dumps the json payload to a variable that should be passable to the
|
||||
# _psot function.
|
||||
_json_payload=$(
|
||||
cat <<HEREDOC
|
||||
{
|
||||
"certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')",
|
||||
"key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')",
|
||||
"node":"localhost",
|
||||
"restart":true,
|
||||
"force":true
|
||||
}
|
||||
HEREDOC
|
||||
)
|
||||
_debug2 Payload "$_json_payload"
|
||||
|
||||
_info "Push certificates to server"
|
||||
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
|
||||
|
||||
}
|
||||
|
|
@ -99,11 +99,11 @@ proxmoxve_deploy() {
|
|||
_proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY "$_proxmoxve_api_token_key"
|
||||
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY _proxmoxve_api_token_key
|
||||
|
||||
# PVE API Token header value. Used in "Authorization: PVEAPIToken".
|
||||
_proxmoxve_header_api_token="${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}"
|
||||
_debug2 "Auth Header" "$_proxmoxve_header_api_token"
|
||||
_debug2 "Auth Header" _proxmoxve_header_api_token
|
||||
|
||||
# Ugly. I hate putting heredocs inside functions because heredocs don't
|
||||
# account for whitespace correctly but it _does_ work and is several times
|
||||
|
|
@ -124,28 +124,9 @@ HEREDOC
|
|||
)
|
||||
_debug2 Payload "$_json_payload"
|
||||
|
||||
_info "Push certificates to server"
|
||||
export HTTPS_INSECURE=1
|
||||
# Push certificates to server.
|
||||
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
|
||||
_post "$_json_payload" "$_target_url" "" POST "application/json"
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@
|
|||
# export QINIU_CDN_DOMAIN="cdn.example.com"
|
||||
# If you have more than one domain, just
|
||||
# export QINIU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
|
||||
# Optional: force HTTPS redirect (default: false)
|
||||
# export QINIU_FORCE_HTTPS="true"
|
||||
|
||||
QINIU_API_BASE="https://api.qiniu.com"
|
||||
|
||||
|
|
@ -46,12 +44,6 @@ qiniu_deploy() {
|
|||
QINIU_CDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
if [ -z "$QINIU_FORCE_HTTPS" ]; then
|
||||
QINIU_FORCE_HTTPS="false"
|
||||
else
|
||||
_savedomainconf QINIU_FORCE_HTTPS "$QINIU_FORCE_HTTPS"
|
||||
fi
|
||||
|
||||
## upload certificate
|
||||
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
|
@ -77,7 +69,7 @@ qiniu_deploy() {
|
|||
_debug certId "$_certId"
|
||||
|
||||
## update domain ssl config
|
||||
update_body="{\"certid\":$_certId,\"forceHttps\":$QINIU_FORCE_HTTPS}"
|
||||
update_body="{\"certid\":$_certId,\"forceHttps\":false}"
|
||||
for domain in $QINIU_CDN_DOMAIN; do
|
||||
update_path="/domain/$domain/httpsconf"
|
||||
update_access_token="$(_make_access_token "$update_path")"
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ routeros_deploy() {
|
|||
_savedeployconf ROUTER_OS_PORT "$ROUTER_OS_PORT"
|
||||
_savedeployconf ROUTER_OS_SSH_CMD "$ROUTER_OS_SSH_CMD"
|
||||
_savedeployconf ROUTER_OS_SCP_CMD "$ROUTER_OS_SCP_CMD"
|
||||
_savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES" "base64"
|
||||
_savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES"
|
||||
|
||||
# push key to routeros
|
||||
if ! _scp_certificate "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"; then
|
||||
|
|
@ -137,19 +137,17 @@ routeros_deploy() {
|
|||
return $_err_code
|
||||
fi
|
||||
|
||||
DEPLOY_SCRIPT_CMD=":do {/system script remove \"LECertDeploy-$_cdomain\" } on-error={ }; \
|
||||
/system script add name=\"LECertDeploy-$_cdomain\" owner=$ROUTER_OS_USERNAME \
|
||||
DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=$ROUTER_OS_USERNAME \
|
||||
comment=\"generated by routeros deploy script in acme.sh\" \
|
||||
source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
|
||||
\n/certificate remove [ find name=$_cdomain.cer_1 ];\
|
||||
\n/certificate remove [ find name=$_cdomain.cer_2 ];\
|
||||
\n/certificate remove [ find name=$_cdomain.cer_3 ];\
|
||||
\ndelay 1;\
|
||||
\n/certificate import file-name=\\\"$_cdomain.cer\\\" passphrase=\\\"\\\";\
|
||||
\n/certificate import file-name=\\\"$_cdomain.key\\\" passphrase=\\\"\\\";\
|
||||
\n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\";\
|
||||
\n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\";\
|
||||
\ndelay 1;\
|
||||
\n:do {/file remove $_cdomain.cer; } on-error={ }\
|
||||
\n:do {/file remove $_cdomain.key; } on-error={ }\
|
||||
\n/file remove $_cdomain.cer;\
|
||||
\n/file remove $_cdomain.key;\
|
||||
\ndelay 2;\
|
||||
\n/ip service set www-ssl certificate=$_cdomain.cer_0;\
|
||||
\n$ROUTER_OS_ADDITIONAL_SERVICES;\
|
||||
|
|
@ -160,11 +158,11 @@ source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
|
|||
return $_err_code
|
||||
fi
|
||||
|
||||
if ! _ssh_remote_cmd "/system script run \"LECertDeploy-$_cdomain\""; then
|
||||
if ! _ssh_remote_cmd "/system script run \"LE Cert Deploy - $_cdomain\""; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
if ! _ssh_remote_cmd "/system script remove \"LECertDeploy-$_cdomain\""; then
|
||||
if ! _ssh_remote_cmd "/system script remove \"LE Cert Deploy - $_cdomain\""; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
|
|
|
|||
200
deploy/ruckus.sh
200
deploy/ruckus.sh
|
|
@ -1,200 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to Ruckus ZoneDirector / Unleashed.
|
||||
#
|
||||
# Public domain, 2024, Tony Rielly <https://github.com/ms264556>
|
||||
#
|
||||
# ```sh
|
||||
# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus
|
||||
# ```
|
||||
#
|
||||
# Then you need to set the environment variables for the
|
||||
# deploy script to work.
|
||||
#
|
||||
# ```sh
|
||||
# export RUCKUS_HOST=myruckus.example.com
|
||||
# export RUCKUS_USER=myruckususername
|
||||
# export RUCKUS_PASS=myruckuspassword
|
||||
#
|
||||
# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus
|
||||
# ```
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
ruckus_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_err_code=0
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf RUCKUS_HOST
|
||||
_getdeployconf RUCKUS_USER
|
||||
_getdeployconf RUCKUS_PASS
|
||||
|
||||
if [ -z "$RUCKUS_HOST" ]; then
|
||||
_debug "Using _cdomain as RUCKUS_HOST, please set if not correct."
|
||||
RUCKUS_HOST="$_cdomain"
|
||||
fi
|
||||
|
||||
if [ -z "$RUCKUS_USER" ]; then
|
||||
_err "Need to set the env variable RUCKUS_USER"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$RUCKUS_PASS" ]; then
|
||||
_err "Need to set the env variable RUCKUS_PASS"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf RUCKUS_HOST "$RUCKUS_HOST"
|
||||
_savedeployconf RUCKUS_USER "$RUCKUS_USER"
|
||||
_savedeployconf RUCKUS_PASS "$RUCKUS_PASS"
|
||||
|
||||
_debug RUCKUS_HOST "$RUCKUS_HOST"
|
||||
_debug RUCKUS_USER "$RUCKUS_USER"
|
||||
_secure_debug RUCKUS_PASS "$RUCKUS_PASS"
|
||||
|
||||
export ACME_HTTP_NO_REDIRECTS=1
|
||||
|
||||
_info "Discovering the login URL"
|
||||
_get "https://$RUCKUS_HOST" >/dev/null
|
||||
_login_url="$(_response_header 'Location')"
|
||||
if [ -n "$_login_url" ]; then
|
||||
_login_path=$(echo "$_login_url" | sed 's|https\?://[^/]\+||')
|
||||
if [ -z "$_login_path" ]; then
|
||||
# redirect was to a different host
|
||||
_err "Connection failed: redirected to a different host. Configure Unleashed with a Preferred Master or Management Interface."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${_login_url}" ]; then
|
||||
_err "Connection failed: couldn't find login page."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_base_url=$(dirname "$_login_url")
|
||||
_login_page=$(basename "$_login_url")
|
||||
|
||||
if [ "$_login_page" = "index.html" ]; then
|
||||
_err "Connection temporarily unavailable: Unleashed Rebuilding."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_login_page" = "wizard.jsp" ]; then
|
||||
_err "Connection failed: Setup Wizard not complete."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Login"
|
||||
_username_encoded="$(printf "%s" "$RUCKUS_USER" | _url_encode)"
|
||||
_password_encoded="$(printf "%s" "$RUCKUS_PASS" | _url_encode)"
|
||||
_login_query="$(printf "%s" "username=${_username_encoded}&password=${_password_encoded}&ok=Log+In")"
|
||||
_post "$_login_query" "$_login_url" >/dev/null
|
||||
|
||||
_login_code="$(_response_code)"
|
||||
if [ "$_login_code" = "200" ]; then
|
||||
_err "Login failed: incorrect credentials."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Collect Session Cookie"
|
||||
_H1="Cookie: $(_response_cookie)"
|
||||
export _H1
|
||||
_info "Collect CSRF Token"
|
||||
_H2="X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')"
|
||||
export _H2
|
||||
|
||||
if _isRSA "$_ckey" >/dev/null 2>&1; then
|
||||
_debug "Using RSA certificate."
|
||||
else
|
||||
_info "Verifying ECC certificate support."
|
||||
|
||||
_ul_version="$(_get_unleashed_version)"
|
||||
if [ -z "$_ul_version" ]; then
|
||||
_err "Your controller doesn't support ECC certificates. Please deploy an RSA certificate."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_ul_version_major="$(echo "$_ul_version" | cut -d . -f 1)"
|
||||
_ul_version_minor="$(echo "$_ul_version" | cut -d . -f 2)"
|
||||
if [ "$_ul_version_major" -lt "200" ]; then
|
||||
_err "ZoneDirector doesn't support ECC certificates. Please deploy an RSA certificate."
|
||||
return 1
|
||||
elif [ "$_ul_version_minor" -lt "13" ]; then
|
||||
_err "Unleashed $_ul_version_major.$_ul_version_minor doesn't support ECC certificates. Please deploy an RSA certificate or upgrade to Unleashed 200.13+."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "ECC certificates OK for Unleashed $_ul_version_major.$_ul_version_minor."
|
||||
fi
|
||||
|
||||
_info "Uploading certificate"
|
||||
_post_upload "uploadcert" "$_cfullchain"
|
||||
|
||||
_info "Uploading private key"
|
||||
_post_upload "uploadprivatekey" "$_ckey"
|
||||
|
||||
_info "Replacing certificate"
|
||||
_replace_cert_ajax='<ajax-request action="docmd" comp="system" updater="rid.0.5" xcmd="replace-cert" checkAbility="6" timeout="-1"><xcmd cmd="replace-cert" cn="'$RUCKUS_HOST'"/></ajax-request>'
|
||||
_post "$_replace_cert_ajax" "$_base_url/_cmdstat.jsp" >/dev/null
|
||||
|
||||
_info "Rebooting"
|
||||
_cert_reboot_ajax='<ajax-request action="docmd" comp="worker" updater="rid.0.5" xcmd="cert-reboot" checkAbility="6"><xcmd cmd="cert-reboot" action="undefined"/></ajax-request>'
|
||||
_post "$_cert_reboot_ajax" "$_base_url/_cmdstat.jsp" >/dev/null
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_response_code() {
|
||||
_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n" | _egrep_o "^[0-9]*"
|
||||
}
|
||||
|
||||
_response_header() {
|
||||
grep <"$HTTP_HEADER" -i "^$1:" | cut -d ':' -f 2- | tr -d "\r\n\t "
|
||||
}
|
||||
|
||||
_response_cookie() {
|
||||
_response_header 'Set-Cookie' | sed 's/;.*//'
|
||||
}
|
||||
|
||||
_get_unleashed_version() {
|
||||
_post '<ajax-request action="getstat" comp="system"><sysinfo/></ajax-request>' "$_base_url/_cmdstat.jsp" | _egrep_o "version-num=\"[^\"]*\"" | cut -d '"' -f 2
|
||||
}
|
||||
|
||||
_post_upload() {
|
||||
_post_action="$1"
|
||||
_post_file="$2"
|
||||
|
||||
_post_boundary="----FormBoundary$(date "+%s%N")"
|
||||
|
||||
_post_data="$({
|
||||
printf -- "--%s\r\n" "$_post_boundary"
|
||||
printf -- "Content-Disposition: form-data; name=\"u\"; filename=\"%s\"\r\n" "$_post_action"
|
||||
printf -- "Content-Type: application/octet-stream\r\n\r\n"
|
||||
printf -- "%s\r\n" "$(cat "$_post_file")"
|
||||
|
||||
printf -- "--%s\r\n" "$_post_boundary"
|
||||
printf -- "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
|
||||
printf -- "%s\r\n" "$_post_action"
|
||||
|
||||
printf -- "--%s\r\n" "$_post_boundary"
|
||||
printf -- "Content-Disposition: form-data; name=\"callback\"\r\n\r\n"
|
||||
printf -- "%s\r\n" "uploader_$_post_action"
|
||||
|
||||
printf -- "--%s--\r\n\r\n" "$_post_boundary"
|
||||
})"
|
||||
|
||||
_post "$_post_data" "$_base_url/_upload.jsp?request_type=xhr" "" "" "multipart/form-data; boundary=$_post_boundary" >/dev/null
|
||||
}
|
||||
280
deploy/shelly.sh
280
deploy/shelly.sh
|
|
@ -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 <method> <params_json>
|
||||
# Returns 0 on success, 1 on error.
|
||||
_shelly_rpc() {
|
||||
_shelly_method="$1"
|
||||
_shelly_params="$2"
|
||||
|
||||
_shelly_body='{"id":1,"method":"'"$_shelly_method"'","params":'"$_shelly_params"'}'
|
||||
|
||||
_debug "RPC method: $_shelly_method"
|
||||
_debug2 "RPC body: $_shelly_body"
|
||||
|
||||
# shellcheck disable=SC2090
|
||||
if [ -n "$_shelly_auth_header" ]; then
|
||||
export _H1="Authorization: $_shelly_auth_header"
|
||||
else
|
||||
export _H1=""
|
||||
fi
|
||||
|
||||
_post "$_shelly_body" "http://${SHELLY_HOST}/rpc" "" "" "application/json"
|
||||
_shelly_ret=$?
|
||||
|
||||
if [ "$_shelly_ret" != "0" ]; then
|
||||
_err "HTTP request failed for $_shelly_method (curl/wget error $_shelly_ret)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Empty response means something went wrong (auth required but not provided, etc.)
|
||||
if [ -z "$response" ]; then
|
||||
_err "Empty response from Shelly device. If authentication is enabled on the device, set SHELLY_PASSWORD."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate response looks like a Shelly JSON-RPC response.
|
||||
# Catches non-JSON responses such as HTTP 429 "Too Many Requests" which
|
||||
# would otherwise pass the empty and "error" checks below.
|
||||
if ! _startswith "$response" '{' || ! _contains "$response" '"id"'; then
|
||||
_err "Invalid response from Shelly device: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for JSON-RPC error in response
|
||||
if _contains "$response" '"error"'; then
|
||||
_err "RPC error from Shelly: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "RPC response: $response"
|
||||
|
||||
# Increment nonce counter and rebuild auth header for next request
|
||||
if [ -n "$_shelly_auth_header" ]; then
|
||||
_shelly_nc=$((_shelly_nc + 1))
|
||||
_shelly_build_auth_header
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Upload the certificate to the device.
|
||||
# Note: We do NOT clear the existing certificate first, because the Shelly
|
||||
# auto-removes all three files (cert, key, CA) when any one is cleared.
|
||||
# Uploading overwrites in place — no clearing needed.
|
||||
_shelly_upload_cert() {
|
||||
_shelly_cert_data="$(_json_encode <"$_cfullchain")"
|
||||
|
||||
_debug "Uploading certificate"
|
||||
if ! _shelly_rpc "Shelly.PutHTTPServerCert" '{"data":"'"$_shelly_cert_data"'"}'; then
|
||||
_err "Failed to upload certificate to device"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Upload the private key to the device.
|
||||
# Note: Do not clear first — see _shelly_upload_cert for rationale.
|
||||
_shelly_upload_key() {
|
||||
_shelly_key_data="$(_json_encode <"$_ckey")"
|
||||
|
||||
_debug "Uploading key"
|
||||
if ! _shelly_rpc "Shelly.PutHTTPServerKey" '{"data":"'"$_shelly_key_data"'"}'; then
|
||||
_err "Failed to upload key to device"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
448
deploy/ssh.sh
448
deploy/ssh.sh
|
|
@ -14,7 +14,7 @@
|
|||
# The following examples are for QNAP NAS running QTS 4.2
|
||||
# export DEPLOY_SSH_CMD="" # defaults to "ssh -T"
|
||||
# export DEPLOY_SSH_USER="admin" # required
|
||||
# export DEPLOY_SSH_SERVER="host1 host2:8022 192.168.0.1:9022" # defaults to domain name, support multiple servers with optional port
|
||||
# export DEPLOY_SSH_SERVER="qnap" # defaults to domain name
|
||||
# export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem"
|
||||
# export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem"
|
||||
# export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem"
|
||||
|
|
@ -23,10 +23,7 @@
|
|||
# export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value
|
||||
# export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy
|
||||
# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
|
||||
# export DEPLOY_SSH_USE_SCP="" yes or no, default to no
|
||||
# export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q"
|
||||
# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c
|
||||
# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes
|
||||
#
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
|
|
@ -45,160 +42,74 @@ ssh_deploy() {
|
|||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# USER is required to login by SSH to remote host.
|
||||
_migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER
|
||||
_getdeployconf DEPLOY_SSH_USER
|
||||
_debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
|
||||
if [ -z "$DEPLOY_SSH_USER" ]; then
|
||||
_err "DEPLOY_SSH_USER not defined."
|
||||
return 1
|
||||
if [ -z "$Le_Deploy_ssh_user" ]; then
|
||||
_err "DEPLOY_SSH_USER not defined."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
Le_Deploy_ssh_user="$DEPLOY_SSH_USER"
|
||||
_savedomainconf Le_Deploy_ssh_user "$Le_Deploy_ssh_user"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
|
||||
|
||||
# SERVER is optional. If not provided then use _cdomain
|
||||
_migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER
|
||||
_getdeployconf DEPLOY_SSH_SERVER
|
||||
_debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
|
||||
if [ -z "$DEPLOY_SSH_SERVER" ]; then
|
||||
DEPLOY_SSH_SERVER="$_cdomain"
|
||||
if [ -n "$DEPLOY_SSH_SERVER" ]; then
|
||||
Le_Deploy_ssh_server="$DEPLOY_SSH_SERVER"
|
||||
_savedomainconf Le_Deploy_ssh_server "$Le_Deploy_ssh_server"
|
||||
elif [ -z "$Le_Deploy_ssh_server" ]; then
|
||||
Le_Deploy_ssh_server="$_cdomain"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
|
||||
|
||||
# CMD is optional. If not provided then use ssh
|
||||
_migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD
|
||||
_getdeployconf DEPLOY_SSH_CMD
|
||||
_debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
|
||||
if [ -z "$DEPLOY_SSH_CMD" ]; then
|
||||
DEPLOY_SSH_CMD="ssh -T"
|
||||
if [ -n "$DEPLOY_SSH_CMD" ]; then
|
||||
Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD"
|
||||
_savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd"
|
||||
elif [ -z "$Le_Deploy_ssh_cmd" ]; then
|
||||
Le_Deploy_ssh_cmd="ssh -T"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
|
||||
|
||||
# REMOTE_SHELL is optional. If not provided then use sh
|
||||
_migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_SHELL
|
||||
_debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
|
||||
if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then
|
||||
DEPLOY_SSH_REMOTE_SHELL="sh -c"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
|
||||
|
||||
# REMOTE_CMD_QUOTE is optional. If not provided then yes
|
||||
_migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE
|
||||
_debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
|
||||
if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then
|
||||
DEPLOY_SSH_REMOTE_CMD_QUOTE="yes"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
|
||||
|
||||
# BACKUP is optional. If not provided then default to previously saved value or yes.
|
||||
_migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP
|
||||
_getdeployconf DEPLOY_SSH_BACKUP
|
||||
_debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
|
||||
if [ -z "$DEPLOY_SSH_BACKUP" ]; then
|
||||
DEPLOY_SSH_BACKUP="yes"
|
||||
if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then
|
||||
Le_Deploy_ssh_backup="no"
|
||||
elif [ -z "$Le_Deploy_ssh_backup" ] || [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
Le_Deploy_ssh_backup="yes"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
|
||||
_savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup"
|
||||
|
||||
# BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
|
||||
_migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH
|
||||
_getdeployconf DEPLOY_SSH_BACKUP_PATH
|
||||
_debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
|
||||
if [ -z "$DEPLOY_SSH_BACKUP_PATH" ]; then
|
||||
DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy"
|
||||
if [ -n "$DEPLOY_SSH_BACKUP_PATH" ]; then
|
||||
Le_Deploy_ssh_backup_path="$DEPLOY_SSH_BACKUP_PATH"
|
||||
elif [ -z "$Le_Deploy_ssh_backup_path" ]; then
|
||||
Le_Deploy_ssh_backup_path=".acme_ssh_deploy"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
|
||||
_savedomainconf Le_Deploy_ssh_backup_path "$Le_Deploy_ssh_backup_path"
|
||||
|
||||
# MULTI_CALL is optional. If not provided then default to previously saved
|
||||
# value (which may be undefined... equivalent to "no").
|
||||
_migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL
|
||||
_getdeployconf DEPLOY_SSH_MULTI_CALL
|
||||
_debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
|
||||
if [ -z "$DEPLOY_SSH_MULTI_CALL" ]; then
|
||||
DEPLOY_SSH_MULTI_CALL="no"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
|
||||
|
||||
# KEYFILE is optional.
|
||||
# If provided then private key will be copied to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE
|
||||
_getdeployconf DEPLOY_SSH_KEYFILE
|
||||
_debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
|
||||
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
_savedeployconf DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
Le_Deploy_ssh_multi_call="yes"
|
||||
_savedomainconf Le_Deploy_ssh_multi_call "$Le_Deploy_ssh_multi_call"
|
||||
elif [ "$DEPLOY_SSH_MULTI_CALL" = "no" ]; then
|
||||
Le_Deploy_ssh_multi_call=""
|
||||
_cleardomainconf Le_Deploy_ssh_multi_call
|
||||
fi
|
||||
|
||||
# CERTFILE is optional.
|
||||
# If provided then certificate will be copied or appended to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE
|
||||
_getdeployconf DEPLOY_SSH_CERTFILE
|
||||
_debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
|
||||
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
_savedeployconf DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
|
||||
fi
|
||||
|
||||
# CAFILE is optional.
|
||||
# If provided then CA intermediate certificate will be copied or appended to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE
|
||||
_getdeployconf DEPLOY_SSH_CAFILE
|
||||
_debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
|
||||
if [ -n "$DEPLOY_SSH_CAFILE" ]; then
|
||||
_savedeployconf DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
|
||||
fi
|
||||
|
||||
# FULLCHAIN is optional.
|
||||
# If provided then fullchain certificate will be copied or appended to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN
|
||||
_getdeployconf DEPLOY_SSH_FULLCHAIN
|
||||
_debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
|
||||
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
|
||||
_savedeployconf DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
|
||||
fi
|
||||
|
||||
# REMOTE_CMD is optional.
|
||||
# If provided then this command will be executed on remote host.
|
||||
_migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_CMD
|
||||
_debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
|
||||
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
|
||||
_savedeployconf DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
|
||||
fi
|
||||
|
||||
# USE_SCP is optional. If not provided then default to previously saved
|
||||
# value (which may be undefined... equivalent to "no").
|
||||
_getdeployconf DEPLOY_SSH_USE_SCP
|
||||
_debug2 DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
|
||||
if [ -z "$DEPLOY_SSH_USE_SCP" ]; then
|
||||
DEPLOY_SSH_USE_SCP="no"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
|
||||
|
||||
# SCP_CMD is optional. If not provided then use scp
|
||||
_getdeployconf DEPLOY_SSH_SCP_CMD
|
||||
_debug2 DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
|
||||
if [ -z "$DEPLOY_SSH_SCP_CMD" ]; then
|
||||
DEPLOY_SSH_SCP_CMD="scp -q"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
|
||||
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
DEPLOY_SSH_MULTI_CALL="yes"
|
||||
_info "Using scp as alternate method for copying files. Multicall Mode is implicit"
|
||||
elif [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
_info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
|
||||
else
|
||||
_info "Required commands batched and sent in single call to remote host"
|
||||
fi
|
||||
|
||||
_returnCode=0
|
||||
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
|
||||
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
|
||||
if ! _ssh_deploy; then
|
||||
# in case of an error, remember it, but keep going for the remaining servers
|
||||
_returnCode=1
|
||||
fi
|
||||
_deploy_ssh_servers=$Le_Deploy_ssh_server
|
||||
for Le_Deploy_ssh_server in $_deploy_ssh_servers; do
|
||||
_ssh_deploy
|
||||
done
|
||||
|
||||
return $_returnCode
|
||||
}
|
||||
|
||||
_ssh_deploy() {
|
||||
|
|
@ -206,25 +117,16 @@ _ssh_deploy() {
|
|||
_cmdstr=""
|
||||
_backupprefix=""
|
||||
_backupdir=""
|
||||
_local_cert_file=""
|
||||
_local_ca_file=""
|
||||
_local_full_file=""
|
||||
|
||||
case $DEPLOY_SSH_SERVER in
|
||||
*:*)
|
||||
_host=${DEPLOY_SSH_SERVER%:*}
|
||||
_port=${DEPLOY_SSH_SERVER##*:}
|
||||
;;
|
||||
*)
|
||||
_host=$DEPLOY_SSH_SERVER
|
||||
_port=
|
||||
;;
|
||||
esac
|
||||
_info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server"
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
_info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
|
||||
else
|
||||
_info "Required commands batched and sent in single call to remote host"
|
||||
fi
|
||||
|
||||
_info "Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port"
|
||||
|
||||
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
_backupprefix="$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup"
|
||||
if [ "$Le_Deploy_ssh_backup" = "yes" ]; then
|
||||
_backupprefix="$Le_Deploy_ssh_backup_path/$_cdomain-backup"
|
||||
_backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
|
||||
# run cleanup on the backup directory, erase all older
|
||||
# than 180 days (15552000 seconds).
|
||||
|
|
@ -236,7 +138,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
|
|||
_cmdstr="mkdir -p $_backupdir; $_cmdstr"
|
||||
_info "Backup of old certificate files will be placed in remote directory $_backupdir"
|
||||
_info "Backup directories erased after 180 days."
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
|
@ -244,186 +146,129 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
|
|||
fi
|
||||
fi
|
||||
|
||||
# KEYFILE is optional.
|
||||
# If provided then private key will be copied to provided filename.
|
||||
_getdeployconf DEPLOY_SSH_KEYFILE
|
||||
_debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
|
||||
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
Le_Deploy_ssh_keyfile="$DEPLOY_SSH_KEYFILE"
|
||||
_savedomainconf Le_Deploy_ssh_keyfile "$Le_Deploy_ssh_keyfile"
|
||||
fi
|
||||
if [ -n "$Le_Deploy_ssh_keyfile" ]; then
|
||||
if [ "$Le_Deploy_ssh_backup" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_keyfile $_backupdir >/dev/null;"
|
||||
fi
|
||||
|
||||
# copy new key into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
if ! _scp_remote_cmd "$_ckey" "$DEPLOY_SSH_KEYFILE"; then
|
||||
# copy new certificate into file.
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $Le_Deploy_ssh_keyfile;"
|
||||
_info "will copy private key to remote file $Le_Deploy_ssh_keyfile"
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# If file doesn't exist, create it and change its permissions.
|
||||
_cmdstr="$_cmdstr test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE;"
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;"
|
||||
_info "will copy private key to remote file $DEPLOY_SSH_KEYFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# CERTFILE is optional.
|
||||
# If provided then certificate will be copied or appended to provided filename.
|
||||
_getdeployconf DEPLOY_SSH_CERTFILE
|
||||
_debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
|
||||
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
|
||||
_savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
|
||||
fi
|
||||
if [ -n "$Le_Deploy_ssh_certfile" ]; then
|
||||
_pipe=">"
|
||||
if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
if [ "$Le_Deploy_ssh_certfile" = "$Le_Deploy_ssh_keyfile" ]; then
|
||||
# if filename is same as previous file then append.
|
||||
_pipe=">>"
|
||||
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_certfile $_backupdir >/dev/null;"
|
||||
fi
|
||||
|
||||
# copy new certificate into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
_local_cert_file=$(_mktemp)
|
||||
if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
cat "$_ckey" >>"$_local_cert_file"
|
||||
fi
|
||||
cat "$_ccert" >>"$_local_cert_file"
|
||||
if ! _scp_remote_cmd "$_local_cert_file" "$DEPLOY_SSH_CERTFILE"; then
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $Le_Deploy_ssh_certfile;"
|
||||
_info "will copy certificate to remote file $Le_Deploy_ssh_certfile"
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;"
|
||||
_info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# CAFILE is optional.
|
||||
# If provided then CA intermediate certificate will be copied or appended to provided filename.
|
||||
_getdeployconf DEPLOY_SSH_CAFILE
|
||||
_debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
|
||||
if [ -n "$DEPLOY_SSH_CAFILE" ]; then
|
||||
Le_Deploy_ssh_cafile="$DEPLOY_SSH_CAFILE"
|
||||
_savedomainconf Le_Deploy_ssh_cafile "$Le_Deploy_ssh_cafile"
|
||||
fi
|
||||
if [ -n "$Le_Deploy_ssh_cafile" ]; then
|
||||
_pipe=">"
|
||||
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ] ||
|
||||
[ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
if [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_keyfile" ] ||
|
||||
[ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_certfile" ]; then
|
||||
# if filename is same as previous file then append.
|
||||
_pipe=">>"
|
||||
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_cafile $_backupdir >/dev/null;"
|
||||
fi
|
||||
|
||||
# copy new certificate into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
_local_ca_file=$(_mktemp)
|
||||
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
cat "$_ckey" >>"$_local_ca_file"
|
||||
fi
|
||||
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
cat "$_ccert" >>"$_local_ca_file"
|
||||
fi
|
||||
cat "$_cca" >>"$_local_ca_file"
|
||||
if ! _scp_remote_cmd "$_local_ca_file" "$DEPLOY_SSH_CAFILE"; then
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $Le_Deploy_ssh_cafile;"
|
||||
_info "will copy CA file to remote file $Le_Deploy_ssh_cafile"
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;"
|
||||
_info "will copy CA file to remote file $DEPLOY_SSH_CAFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# FULLCHAIN is optional.
|
||||
# If provided then fullchain certificate will be copied or appended to provided filename.
|
||||
_getdeployconf DEPLOY_SSH_FULLCHAIN
|
||||
_debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
|
||||
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
|
||||
Le_Deploy_ssh_fullchain="$DEPLOY_SSH_FULLCHAIN"
|
||||
_savedomainconf Le_Deploy_ssh_fullchain "$Le_Deploy_ssh_fullchain"
|
||||
fi
|
||||
if [ -n "$Le_Deploy_ssh_fullchain" ]; then
|
||||
_pipe=">"
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ] ||
|
||||
[ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ] ||
|
||||
[ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
|
||||
if [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_keyfile" ] ||
|
||||
[ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_certfile" ] ||
|
||||
[ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_cafile" ]; then
|
||||
# if filename is same as previous file then append.
|
||||
_pipe=">>"
|
||||
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_fullchain $_backupdir >/dev/null;"
|
||||
fi
|
||||
|
||||
# copy new certificate into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
_local_full_file=$(_mktemp)
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
cat "$_ckey" >>"$_local_full_file"
|
||||
fi
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
cat "$_ccert" >>"$_local_full_file"
|
||||
fi
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
|
||||
cat "$_cca" >>"$_local_full_file"
|
||||
fi
|
||||
cat "$_cfullchain" >>"$_local_full_file"
|
||||
if ! _scp_remote_cmd "$_local_full_file" "$DEPLOY_SSH_FULLCHAIN"; then
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $Le_Deploy_ssh_fullchain;"
|
||||
_info "will copy fullchain to remote file $Le_Deploy_ssh_fullchain"
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;"
|
||||
_info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# cleanup local files if any
|
||||
if [ -f "$_local_cert_file" ]; then
|
||||
rm -f "$_local_cert_file"
|
||||
fi
|
||||
if [ -f "$_local_ca_file" ]; then
|
||||
rm -f "$_local_ca_file"
|
||||
fi
|
||||
if [ -f "$_local_full_file" ]; then
|
||||
rm -f "$_local_full_file"
|
||||
fi
|
||||
|
||||
# REMOTE_CMD is optional.
|
||||
# If provided then this command will be executed on remote host.
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_CMD
|
||||
_debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
|
||||
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
|
||||
_cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;"
|
||||
_info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
Le_Deploy_ssh_remote_cmd="$DEPLOY_SSH_REMOTE_CMD"
|
||||
_savedomainconf Le_Deploy_ssh_remote_cmd "$Le_Deploy_ssh_remote_cmd"
|
||||
fi
|
||||
if [ -n "$Le_Deploy_ssh_remote_cmd" ]; then
|
||||
_cmdstr="$_cmdstr $Le_Deploy_ssh_remote_cmd;"
|
||||
_info "Will execute remote command $Le_Deploy_ssh_remote_cmd"
|
||||
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
|
@ -437,29 +282,17 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
|
|||
return $_err_code
|
||||
fi
|
||||
fi
|
||||
# cleanup in case all is ok
|
||||
return 0
|
||||
}
|
||||
|
||||
#cmd
|
||||
_ssh_remote_cmd() {
|
||||
_cmd="$1"
|
||||
|
||||
_ssh_cmd="$DEPLOY_SSH_CMD"
|
||||
if [ -n "$_port" ]; then
|
||||
_ssh_cmd="$_ssh_cmd -p $_port"
|
||||
fi
|
||||
|
||||
_secure_debug "Remote commands to execute: $_cmd"
|
||||
_info "Submitting sequence of commands to remote server by $_ssh_cmd"
|
||||
|
||||
if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then
|
||||
# quotations in bash cmd below intended. Squash travis spellcheck error
|
||||
# shellcheck disable=SC2029
|
||||
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'"
|
||||
else
|
||||
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd"
|
||||
fi
|
||||
_info "Submitting sequence of commands to remote server by ssh"
|
||||
# quotations in bash cmd below intended. Squash travis spellcheck error
|
||||
# shellcheck disable=SC2029
|
||||
$Le_Deploy_ssh_cmd "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmd'"
|
||||
_err_code="$?"
|
||||
|
||||
if [ "$_err_code" != "0" ]; then
|
||||
|
|
@ -468,26 +301,3 @@ _ssh_remote_cmd() {
|
|||
|
||||
return $_err_code
|
||||
}
|
||||
|
||||
# cmd scp
|
||||
_scp_remote_cmd() {
|
||||
_src=$1
|
||||
_dest=$2
|
||||
|
||||
_scp_cmd="$DEPLOY_SSH_SCP_CMD"
|
||||
if [ -n "$_port" ]; then
|
||||
_scp_cmd="$_scp_cmd -P $_port"
|
||||
fi
|
||||
|
||||
_secure_debug "Remote copy source $_src to destination $_dest"
|
||||
_info "Submitting secure copy by $_scp_cmd"
|
||||
|
||||
$_scp_cmd "$_src" "$DEPLOY_SSH_USER"@"$_host":"$_dest"
|
||||
_err_code="$?"
|
||||
|
||||
if [ "$_err_code" != "0" ]; then
|
||||
_err "Error code $_err_code returned from scp"
|
||||
fi
|
||||
|
||||
return $_err_code
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,89 +10,46 @@
|
|||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
strongswan_deploy() {
|
||||
_cdomain="${1}"
|
||||
_ckey="${2}"
|
||||
_ccert="${3}"
|
||||
_cca="${4}"
|
||||
_cfullchain="${5}"
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_info "Using strongswan"
|
||||
if _exists ipsec; then
|
||||
_ipsec=ipsec
|
||||
elif _exists strongswan; then
|
||||
_ipsec=strongswan
|
||||
fi
|
||||
if _exists swanctl; then
|
||||
_swanctl=swanctl
|
||||
fi
|
||||
# For legacy stroke mode
|
||||
if [ -n "${_ipsec}" ]; then
|
||||
_info "${_ipsec} command detected"
|
||||
_confdir=$(${_ipsec} --confdir)
|
||||
if [ -z "${_confdir}" ]; then
|
||||
_err "no strongswan --confdir is detected"
|
||||
return 1
|
||||
fi
|
||||
_info _confdir "${_confdir}"
|
||||
__deploy_cert "stroke" "${_confdir}" "$@"
|
||||
${_ipsec} reload
|
||||
fi
|
||||
# For modern vici mode
|
||||
if [ -n "${_swanctl}" ]; then
|
||||
_info "${_swanctl} command detected"
|
||||
for _dir in /usr/local/etc/swanctl /etc/swanctl /etc/strongswan/swanctl; do
|
||||
if [ -d ${_dir} ]; then
|
||||
_confdir=${_dir}
|
||||
_info _confdir "${_confdir}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "${_confdir}" ]; then
|
||||
_err "no swanctl config dir is found"
|
||||
return 1
|
||||
fi
|
||||
__deploy_cert "vici" "${_confdir}" "$@"
|
||||
${_swanctl} --load-creds
|
||||
fi
|
||||
if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then
|
||||
_err "no strongswan or ipsec command is detected"
|
||||
_err "no swanctl is detected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
__deploy_cert() {
|
||||
_swan_mode="${1}"
|
||||
_confdir="${2}"
|
||||
_cdomain="${3}"
|
||||
_ckey="${4}"
|
||||
_ccert="${5}"
|
||||
_cca="${6}"
|
||||
_cfullchain="${7}"
|
||||
_debug _cdomain "${_cdomain}"
|
||||
_debug _ckey "${_ckey}"
|
||||
_debug _ccert "${_ccert}"
|
||||
_debug _cca "${_cca}"
|
||||
_debug _cfullchain "${_cfullchain}"
|
||||
_debug _swan_mode "${_swan_mode}"
|
||||
_debug _confdir "${_confdir}"
|
||||
if [ "${_swan_mode}" = "vici" ]; then
|
||||
_dir_private="private"
|
||||
_dir_cert="x509"
|
||||
_dir_ca="x509ca"
|
||||
elif [ "${_swan_mode}" = "stroke" ]; then
|
||||
_dir_private="ipsec.d/private"
|
||||
_dir_cert="ipsec.d/certs"
|
||||
_dir_ca="ipsec.d/cacerts"
|
||||
if [ -x /usr/sbin/ipsec ]; then
|
||||
_ipsec=/usr/sbin/ipsec
|
||||
elif [ -x /usr/sbin/strongswan ]; then
|
||||
_ipsec=/usr/sbin/strongswan
|
||||
elif [ -x /usr/local/sbin/ipsec ]; then
|
||||
_ipsec=/usr/local/sbin/ipsec
|
||||
else
|
||||
_err "unknown StrongSwan mode ${_swan_mode}"
|
||||
_err "no strongswan or ipsec command is detected"
|
||||
return 1
|
||||
fi
|
||||
cat "${_ckey}" >"${_confdir}/${_dir_private}/$(basename "${_ckey}")"
|
||||
cat "${_ccert}" >"${_confdir}/${_dir_cert}/$(basename "${_ccert}")"
|
||||
cat "${_cca}" >"${_confdir}/${_dir_ca}/$(basename "${_cca}")"
|
||||
if [ "${_swan_mode}" = "stroke" ]; then
|
||||
cat "${_cfullchain}" >"${_confdir}/${_dir_ca}/$(basename "${_cfullchain}")"
|
||||
|
||||
_info _ipsec "$_ipsec"
|
||||
|
||||
_confdir=$($_ipsec --confdir)
|
||||
if [ $? -ne 0 ] || [ -z "$_confdir" ]; then
|
||||
_err "no strongswan --confdir is detected"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info _confdir "$_confdir"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
cat "$_ckey" >"${_confdir}/ipsec.d/private/$(basename "$_ckey")"
|
||||
cat "$_ccert" >"${_confdir}/ipsec.d/certs/$(basename "$_ccert")"
|
||||
cat "$_cca" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cca")"
|
||||
cat "$_cfullchain" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cfullchain")"
|
||||
|
||||
$_ipsec reload
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,34 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
################################################################################
|
||||
# ACME.sh 3rd party deploy plugin for Synology DSM
|
||||
################################################################################
|
||||
# Authors: Brian Hartvigsen (creator), https://github.com/tresni
|
||||
# Martin Arndt (contributor), https://troublezone.net/
|
||||
# Updated: 2023-07-03
|
||||
# Issues: https://github.com/acmesh-official/acme.sh/issues/2727
|
||||
################################################################################
|
||||
# Usage (shown values are the examples):
|
||||
# 1. Set required environment variables:
|
||||
# - use automatically created temp admin user to authenticate
|
||||
# export SYNO_USE_TEMP_ADMIN=1
|
||||
# - or provide your own admin user credential to authenticate
|
||||
# 1. export SYNO_USERNAME="adminUser"
|
||||
# 2. export SYNO_PASSWORD="adminPassword"
|
||||
# 2. Set optional environment variables
|
||||
# - common optional variables
|
||||
# - export SYNO_SCHEME="http" - defaults to "http"
|
||||
# - export SYNO_HOSTNAME="localhost" - defaults to "localhost"
|
||||
# - export SYNO_PORT="5000" - defaults to "5000"
|
||||
# - export SYNO_CREATE=1 - to allow creating the cert if it doesn't exist
|
||||
# - export SYNO_CERTIFICATE="" - to replace a specific cert by its
|
||||
# description
|
||||
# - temp admin optional variables
|
||||
# - export SYNO_LOCAL_HOSTNAME=1 - if set to 1, force to treat hostname is
|
||||
# targeting current local machine (since
|
||||
# this method only locally supported)
|
||||
# - exsiting admin 2FA-OTP optional variables
|
||||
# - export SYNO_OTP_CODE="XXXXXX" - if set, script won't require to
|
||||
# interactive input the OTP code
|
||||
# - export SYNO_DEVICE_NAME="CertRenewal" - if set, script won't require to
|
||||
# interactive input the device name
|
||||
# - export SYNO_DEVICE_ID="" - (deprecated, auth with OTP code instead)
|
||||
# required for omitting 2FA-OTP
|
||||
# 3. Run command:
|
||||
# acme.sh --deploy --deploy-hook synology_dsm -d example.com
|
||||
################################################################################
|
||||
# Here is a script to deploy cert to Synology DSM
|
||||
#
|
||||
# It requires following environment variables:
|
||||
#
|
||||
# SYNO_Username - Synology Username to login (must be an administrator)
|
||||
# SYNO_Password - Synology Password to login
|
||||
# SYNO_Certificate - Certificate description to target for replacement
|
||||
#
|
||||
# The following environmental variables may be set if you don't like their
|
||||
# default values:
|
||||
#
|
||||
# SYNO_Scheme - defaults to http
|
||||
# SYNO_Hostname - defaults to localhost
|
||||
# SYNO_Port - defaults to 5000
|
||||
# SYNO_DID - device ID to skip OTP - defaults to empty
|
||||
# SYNO_TOTP_SECRET - TOTP secret to generate OTP - defaults to empty
|
||||
#
|
||||
# Dependencies:
|
||||
# - curl
|
||||
# - synouser & synogroup & synosetkeyvalue (Required for SYNO_USE_TEMP_ADMIN=1)
|
||||
################################################################################
|
||||
# Return value:
|
||||
# 0 means success, otherwise error.
|
||||
################################################################################
|
||||
# -------------
|
||||
# - jq and curl
|
||||
# - oathtool (When using 2 Factor Authentication and SYNO_TOTP_SECRET is set)
|
||||
#
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
########## Public functions ####################################################
|
||||
#domain keyfile certfile cafile fullchain
|
||||
synology_dsm_deploy() {
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
|
|
@ -55,390 +36,148 @@ synology_dsm_deploy() {
|
|||
|
||||
_debug _cdomain "$_cdomain"
|
||||
|
||||
# Get username and password, but don't save until we authenticated successfully
|
||||
_migratedeployconf SYNO_Username SYNO_USERNAME
|
||||
_migratedeployconf SYNO_Password SYNO_PASSWORD
|
||||
_migratedeployconf SYNO_Device_ID SYNO_DEVICE_ID
|
||||
_migratedeployconf SYNO_Device_Name SYNO_DEVICE_NAME
|
||||
_getdeployconf SYNO_USERNAME
|
||||
_getdeployconf SYNO_PASSWORD
|
||||
_getdeployconf SYNO_DEVICE_ID
|
||||
_getdeployconf SYNO_DEVICE_NAME
|
||||
|
||||
# Prepare to use temp admin if SYNO_USE_TEMP_ADMIN is set
|
||||
_getdeployconf SYNO_USE_TEMP_ADMIN
|
||||
_check2cleardeployconfexp SYNO_USE_TEMP_ADMIN
|
||||
_debug2 SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
|
||||
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then
|
||||
_err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
|
||||
_err "Notice: temp admin user authorization method only supports local deployment on DSM."
|
||||
return 1
|
||||
fi
|
||||
if synouser --help 2>&1 | grep -q 'Permission denied'; then
|
||||
_err "For creating temp admin user, the deploy script must be run as root."
|
||||
return 1
|
||||
fi
|
||||
|
||||
[ -n "$SYNO_USERNAME" ] || _savedeployconf SYNO_USERNAME ""
|
||||
[ -n "$SYNO_PASSWORD" ] || _savedeployconf SYNO_PASSWORD ""
|
||||
|
||||
_debug "Setting temp admin user credential..."
|
||||
SYNO_USERNAME=sc-acmesh-tmp
|
||||
SYNO_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
|
||||
# Set 2FA-OTP settings to empty consider they won't be needed.
|
||||
SYNO_DEVICE_ID=
|
||||
SYNO_DEVICE_NAME=
|
||||
SYNO_OTP_CODE=
|
||||
else
|
||||
_debug2 SYNO_USERNAME "$SYNO_USERNAME"
|
||||
_secure_debug2 SYNO_PASSWORD "$SYNO_PASSWORD"
|
||||
_debug2 SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
|
||||
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
fi
|
||||
|
||||
if [ -z "$SYNO_USERNAME" ] || [ -z "$SYNO_PASSWORD" ]; then
|
||||
_err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_USERNAME and SYNO_PASSWORD."
|
||||
# Get Username and Password, but don't save until we successfully authenticate
|
||||
_getdeployconf SYNO_Username
|
||||
_getdeployconf SYNO_Password
|
||||
_getdeployconf SYNO_Create
|
||||
_getdeployconf SYNO_DID
|
||||
_getdeployconf SYNO_TOTP_SECRET
|
||||
if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then
|
||||
_err "SYNO_Username & SYNO_Password must be set"
|
||||
return 1
|
||||
fi
|
||||
_debug2 SYNO_Username "$SYNO_Username"
|
||||
_secure_debug2 SYNO_Password "$SYNO_Password"
|
||||
|
||||
# Optional scheme, hostname and port for Synology DSM
|
||||
_migratedeployconf SYNO_Scheme SYNO_SCHEME
|
||||
_migratedeployconf SYNO_Hostname SYNO_HOSTNAME
|
||||
_migratedeployconf SYNO_Port SYNO_PORT
|
||||
_getdeployconf SYNO_SCHEME
|
||||
_getdeployconf SYNO_HOSTNAME
|
||||
_getdeployconf SYNO_PORT
|
||||
# Optional scheme, hostname, and port for Synology DSM
|
||||
_getdeployconf SYNO_Scheme
|
||||
_getdeployconf SYNO_Hostname
|
||||
_getdeployconf SYNO_Port
|
||||
|
||||
# Default values for scheme, hostname and port
|
||||
# Defaulting to localhost and http, because it's localhost…
|
||||
[ -n "$SYNO_SCHEME" ] || SYNO_SCHEME=http
|
||||
[ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME=localhost
|
||||
[ -n "$SYNO_PORT" ] || SYNO_PORT=5000
|
||||
_savedeployconf SYNO_SCHEME "$SYNO_SCHEME"
|
||||
_savedeployconf SYNO_HOSTNAME "$SYNO_HOSTNAME"
|
||||
_savedeployconf SYNO_PORT "$SYNO_PORT"
|
||||
_debug2 SYNO_SCHEME "$SYNO_SCHEME"
|
||||
_debug2 SYNO_HOSTNAME "$SYNO_HOSTNAME"
|
||||
_debug2 SYNO_PORT "$SYNO_PORT"
|
||||
# default vaules for scheme, hostname, and port
|
||||
# defaulting to localhost and http because it's localhost...
|
||||
[ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http"
|
||||
[ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost"
|
||||
[ -n "${SYNO_Port}" ] || SYNO_Port="5000"
|
||||
|
||||
# Get the certificate description, but don't save it until we verify it's real
|
||||
_migratedeployconf SYNO_Certificate SYNO_CERTIFICATE "base64"
|
||||
_getdeployconf SYNO_CERTIFICATE
|
||||
_check2cleardeployconfexp SYNO_CERTIFICATE
|
||||
_debug SYNO_CERTIFICATE "${SYNO_CERTIFICATE:-}"
|
||||
_savedeployconf SYNO_Scheme "$SYNO_Scheme"
|
||||
_savedeployconf SYNO_Hostname "$SYNO_Hostname"
|
||||
_savedeployconf SYNO_Port "$SYNO_Port"
|
||||
|
||||
_debug2 SYNO_Scheme "$SYNO_Scheme"
|
||||
_debug2 SYNO_Hostname "$SYNO_Hostname"
|
||||
_debug2 SYNO_Port "$SYNO_Port"
|
||||
|
||||
# Get the certificate description, but don't save it until we verfiy it's real
|
||||
_getdeployconf SYNO_Certificate
|
||||
_debug SYNO_Certificate "${SYNO_Certificate:-}"
|
||||
|
||||
# shellcheck disable=SC1003 # We are not trying to escape a single quote
|
||||
if printf "%s" "$SYNO_CERTIFICATE" | grep '\\'; then
|
||||
if printf "%s" "$SYNO_Certificate" | grep '\\'; then
|
||||
_err "Do not use a backslash (\) in your certificate description"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Getting API version..."
|
||||
_base_url="$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT"
|
||||
_base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port"
|
||||
_debug _base_url "$_base_url"
|
||||
|
||||
_debug "Getting API version"
|
||||
response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth")
|
||||
api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p')
|
||||
api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p')
|
||||
_debug3 response "$response"
|
||||
_debug3 api_path "$api_path"
|
||||
_debug3 api_version "$api_version"
|
||||
|
||||
# Login, get the session ID and SynoToken from JSON
|
||||
_info "Logging into $SYNO_HOSTNAME:$SYNO_PORT..."
|
||||
encoded_username="$(printf "%s" "$SYNO_USERNAME" | _url_encode)"
|
||||
encoded_password="$(printf "%s" "$SYNO_PASSWORD" | _url_encode)"
|
||||
|
||||
# ## START ## - DEPRECATED, for backward compatibility
|
||||
_getdeployconf SYNO_TOTP_SECRET
|
||||
# Login, get the token from JSON and session id from cookie
|
||||
_info "Logging into $SYNO_Hostname:$SYNO_Port"
|
||||
encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)"
|
||||
encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)"
|
||||
|
||||
otp_code=""
|
||||
if [ -n "$SYNO_TOTP_SECRET" ]; then
|
||||
_info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!"
|
||||
_info " See synology_dsm.sh script or ACME.sh Wiki page for details:"
|
||||
_info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide"
|
||||
if ! _exists oathtool; then
|
||||
if _exists oathtool; then
|
||||
otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)"
|
||||
else
|
||||
_err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET"
|
||||
return 1
|
||||
fi
|
||||
DEPRECATED_otp_code="$(oathtool --base32 --totp "$SYNO_TOTP_SECRET" 2>/dev/null)"
|
||||
|
||||
if [ -z "$SYNO_DEVICE_ID" ]; then
|
||||
_getdeployconf SYNO_DID
|
||||
[ -n "$SYNO_DID" ] || SYNO_DEVICE_ID="$SYNO_DID"
|
||||
fi
|
||||
if [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
_H1="Cookie: did=$SYNO_DEVICE_ID"
|
||||
export _H1
|
||||
_debug3 H1 "${_H1}"
|
||||
fi
|
||||
|
||||
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DEVICE_ID" "$_base_url/webapi/$api_path?enable_syno_token=yes")
|
||||
_debug3 response "$response"
|
||||
# ## END ## - DEPRECATED, for backward compatibility
|
||||
# If SYNO_DEVICE_ID or SYNO_OTP_CODE is set, we treat current account enabled 2FA-OTP.
|
||||
# Notice that if SYNO_USE_TEMP_ADMIN=1, both variables will be unset
|
||||
else
|
||||
if [ -n "$SYNO_DEVICE_ID" ] || [ -n "$SYNO_OTP_CODE" ]; then
|
||||
response='{"error":{"code":403}}'
|
||||
# Assume the current account disabled 2FA-OTP, try to log in right away.
|
||||
else
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_getdeployconf SYNO_LOCAL_HOSTNAME
|
||||
_debug SYNO_LOCAL_HOSTNAME "${SYNO_LOCAL_HOSTNAME:-}"
|
||||
if [ "$SYNO_HOSTNAME" != "localhost" ] && [ "$SYNO_HOSTNAME" != "127.0.0.1" ]; then
|
||||
if [ "$SYNO_LOCAL_HOSTNAME" != "1" ]; then
|
||||
_err "SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
_debug "Creating temp admin user in Synology DSM..."
|
||||
if synogroup --help | grep -q '\-\-memberadd '; then
|
||||
_temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD"
|
||||
synogroup --memberadd administrators "$SYNO_USERNAME" >/dev/null
|
||||
elif synogroup --help | grep -q '\-\-member '; then
|
||||
# For supporting DSM 6.x which only has `--member` parameter.
|
||||
cur_admins=$(synogroup --get administrators | awk -F '[][]' '/Group Members/,0{if(NF>1)printf "%s ", $2}')
|
||||
if [ -n "$cur_admins" ]; then
|
||||
_temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD"
|
||||
_secure_debug3 admin_users "$cur_admins$SYNO_USERNAME"
|
||||
# shellcheck disable=SC2086
|
||||
synogroup --member administrators $cur_admins $SYNO_USERNAME >/dev/null
|
||||
else
|
||||
_err "The tool synogroup may be broken, please set SYNO_USERNAME and SYNO_PASSWORD instead."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_err "Unsupported synogroup tool detected, please set SYNO_USERNAME and SYNO_PASSWORD instead."
|
||||
return 1
|
||||
fi
|
||||
# havig a workaround to temporary disable enforce 2FA-OTP, will restore
|
||||
# it soon (after a single request), though if any accident occurs like
|
||||
# unexpected interruption, this setting can be easily reverted manually.
|
||||
otp_enforce_option=$(synogetkeyvalue /etc/synoinfo.conf otp_enforce_option)
|
||||
if [ -n "$otp_enforce_option" ] && [ "${otp_enforce_option:-"none"}" != "none" ]; then
|
||||
synosetkeyvalue /etc/synoinfo.conf otp_enforce_option none
|
||||
_info "Enforcing 2FA-OTP has been disabled to complete temp admin authentication."
|
||||
_info "Notice: it will be restored soon, if not, you can restore it manually via Control Panel."
|
||||
_info "previous_otp_enforce_option" "$otp_enforce_option"
|
||||
else
|
||||
otp_enforce_option=""
|
||||
fi
|
||||
fi
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes")
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ] && [ -n "$otp_enforce_option" ]; then
|
||||
synosetkeyvalue /etc/synoinfo.conf otp_enforce_option "$otp_enforce_option"
|
||||
_info "Restored previous enforce 2FA-OTP option."
|
||||
fi
|
||||
_debug3 response "$response"
|
||||
fi
|
||||
fi
|
||||
|
||||
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
|
||||
_debug2 error_code "$error_code"
|
||||
# Account has 2FA-OTP enabled, since error 403 reported.
|
||||
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf
|
||||
if [ "$error_code" = "403" ]; then
|
||||
if [ -z "$SYNO_DEVICE_NAME" ]; then
|
||||
printf "Enter device name or leave empty for default (CertRenewal): "
|
||||
read -r SYNO_DEVICE_NAME
|
||||
[ -n "$SYNO_DEVICE_NAME" ] || SYNO_DEVICE_NAME="CertRenewal"
|
||||
fi
|
||||
|
||||
if [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
# Omit OTP code with SYNO_DEVICE_ID.
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_DEVICE_NAME&device_id=$SYNO_DEVICE_ID")
|
||||
_secure_debug3 response "$response"
|
||||
else
|
||||
# Require the OTP code if still unset.
|
||||
if [ -z "$SYNO_OTP_CODE" ]; then
|
||||
printf "Enter OTP code for user '%s': " "$SYNO_USERNAME"
|
||||
read -r SYNO_OTP_CODE
|
||||
fi
|
||||
_secure_debug SYNO_OTP_CODE "${SYNO_OTP_CODE:-}"
|
||||
|
||||
if [ -z "$SYNO_OTP_CODE" ]; then
|
||||
response='{"error":{"code":404}}'
|
||||
else
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_DEVICE_NAME&otp_code=$SYNO_OTP_CODE")
|
||||
_secure_debug3 response "$response"
|
||||
|
||||
id_property='device_id'
|
||||
[ "${api_version}" -gt '6' ] || id_property='did'
|
||||
SYNO_DEVICE_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p')
|
||||
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
fi
|
||||
fi
|
||||
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
|
||||
_debug2 error_code "$error_code"
|
||||
if [ -n "$SYNO_DID" ]; then
|
||||
_H1="Cookie: did=$SYNO_DID"
|
||||
export _H1
|
||||
_debug3 H1 "${_H1}"
|
||||
fi
|
||||
|
||||
if [ -n "$error_code" ]; then
|
||||
if [ "$error_code" = "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
_cleardeployconf SYNO_DEVICE_ID
|
||||
_err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window."
|
||||
elif [ "$error_code" = "404" ]; then
|
||||
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
|
||||
elif [ "$error_code" = "406" ]; then
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_err "Failed with unexcepted error, please report this by providing full log with '--debug 3'."
|
||||
else
|
||||
_err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue."
|
||||
fi
|
||||
elif [ "$error_code" = "400" ]; then
|
||||
_err "Failed to authenticate, no such account or incorrect password."
|
||||
elif [ "$error_code" = "401" ]; then
|
||||
_err "Failed to authenticate with a non-existent account."
|
||||
elif [ "$error_code" = "408" ] || [ "$error_code" = "409" ] || [ "$error_code" = "410" ]; then
|
||||
_err "Failed to authenticate, the account password has expired or must be changed."
|
||||
else
|
||||
_err "Failed to authenticate with error: $error_code."
|
||||
fi
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
|
||||
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes")
|
||||
token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p')
|
||||
_debug "Session ID" "$sid"
|
||||
_debug SynoToken "$token"
|
||||
if [ -z "$sid" ] || [ -z "$token" ]; then
|
||||
# Still can't get necessary info even got no errors, may Synology have API updated?
|
||||
_err "Unable to authenticate to $_base_url, you may report this by providing full log with '--debug 3'."
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
_debug3 response "$response"
|
||||
_debug token "$token"
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
_err "Unable to authenticate to $SYNO_Hostname:$SYNO_Port using $SYNO_Scheme."
|
||||
_err "Check your username and password."
|
||||
_err "If two-factor authentication is enabled for the user, set SYNO_TOTP_SECRET."
|
||||
return 1
|
||||
fi
|
||||
sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
|
||||
|
||||
_H1="X-SYNO-TOKEN: $token"
|
||||
export _H1
|
||||
_debug2 H1 "${_H1}"
|
||||
|
||||
# Now that we know the username and password are good, save them if not in temp admin mode.
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_cleardeployconf SYNO_USERNAME
|
||||
_cleardeployconf SYNO_PASSWORD
|
||||
_cleardeployconf SYNO_DEVICE_ID
|
||||
_cleardeployconf SYNO_DEVICE_NAME
|
||||
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
|
||||
_savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_LOCAL_HOSTNAME"
|
||||
else
|
||||
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME" "base64"
|
||||
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" "base64"
|
||||
_savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
_savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
|
||||
fi
|
||||
# Now that we know the username and password are good, save them
|
||||
_savedeployconf SYNO_Username "$SYNO_Username"
|
||||
_savedeployconf SYNO_Password "$SYNO_Password"
|
||||
_savedeployconf SYNO_DID "$SYNO_DID"
|
||||
_savedeployconf SYNO_TOTP_SECRET "$SYNO_TOTP_SECRET"
|
||||
|
||||
_info "Getting certificates in Synology DSM..."
|
||||
_info "Getting certificates in Synology DSM"
|
||||
response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi")
|
||||
_debug3 response "$response"
|
||||
escaped_certificate="$(printf "%s" "$SYNO_CERTIFICATE" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')"
|
||||
escaped_certificate="$(printf "%s" "$SYNO_Certificate" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')"
|
||||
_debug escaped_certificate "$escaped_certificate"
|
||||
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p")
|
||||
_debug2 id "$id"
|
||||
|
||||
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
|
||||
_debug2 error_code "$error_code"
|
||||
if [ -n "$error_code" ]; then
|
||||
if [ "$error_code" -eq 105 ]; then
|
||||
_err "Current user is not administrator and does not have sufficient permission for deploying."
|
||||
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"
|
||||
if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then
|
||||
_err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_migratedeployconf SYNO_Create SYNO_CREATE
|
||||
_getdeployconf SYNO_CREATE
|
||||
_debug2 SYNO_CREATE "$SYNO_CREATE"
|
||||
# we've verified this certificate description is a thing, so save it
|
||||
_savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64"
|
||||
|
||||
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
|
||||
|
||||
# We've verified this certificate description is a thing, so save it
|
||||
_savedeployconf SYNO_CERTIFICATE "$SYNO_CERTIFICATE" "base64"
|
||||
|
||||
_info "Generating form POST request..."
|
||||
_info "Generate form POST request"
|
||||
nl="\0015\0012"
|
||||
delim="--------------------------$(_utc_date | tr -d -- '-: ')"
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_CERTIFICATE}"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}"
|
||||
if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
|
||||
_debug2 default "This is the default certificate"
|
||||
_debug2 default "this is the default certificate"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true"
|
||||
else
|
||||
_debug2 default "This is NOT the default certificate"
|
||||
_debug2 default "this is NOT the default certificate"
|
||||
fi
|
||||
content="$content${nl}--$delim--${nl}"
|
||||
content="$(printf "%b_" "$content")"
|
||||
content="${content%_}" # protect trailing \n
|
||||
|
||||
_info "Upload certificate to the Synology DSM."
|
||||
_info "Upload certificate to the Synology DSM"
|
||||
response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}")
|
||||
_debug3 response "$response"
|
||||
|
||||
if ! echo "$response" | grep '"error":' >/dev/null; then
|
||||
if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
|
||||
_info "Restart HTTP services succeeded."
|
||||
_info "http services were restarted"
|
||||
else
|
||||
_info "Restart HTTP services not necessary."
|
||||
_info "http services were NOT restarted"
|
||||
fi
|
||||
_logout
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 0
|
||||
else
|
||||
_err "Unable to update certificate, got error response: $response."
|
||||
_logout
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
_err "Unable to update certificate, error code $response"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
_logout() {
|
||||
# Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables)
|
||||
# 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"
|
||||
}
|
||||
|
||||
_temp_admin_create() {
|
||||
_username="$1"
|
||||
_password="$2"
|
||||
synouser --del "$_username" >/dev/null 2>/dev/null
|
||||
synouser --add "$_username" "$_password" "" 0 "" 0 >/dev/null
|
||||
}
|
||||
|
||||
_temp_admin_cleanup() {
|
||||
_flag=$1
|
||||
_username=$2
|
||||
|
||||
if [ -n "${_flag}" ]; then
|
||||
_debug "Cleanuping temp admin info..."
|
||||
synouser --del "$_username" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# key
|
||||
_check2cleardeployconfexp() {
|
||||
_key="$1"
|
||||
_clear_key="CLEAR_$_key"
|
||||
# Clear saved settings if explicitly requested
|
||||
if [ -n "$(eval echo \$"$_clear_key")" ]; then
|
||||
_debug2 "$_key: value cleared from config, exported value will be ignored."
|
||||
_cleardeployconf "$_key"
|
||||
eval "$_key"=
|
||||
export "$_key"=
|
||||
eval SAVED_"$_key"=
|
||||
export SAVED_"$_key"=
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#
|
||||
# Following environment variables must be set:
|
||||
#
|
||||
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI>"
|
||||
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI"
|
||||
#
|
||||
# The following environmental variables may be set if you don't like their
|
||||
# default values:
|
||||
|
|
@ -64,20 +64,6 @@ truenas_deploy() {
|
|||
_response=$(_get "$_api_url/system/state")
|
||||
_info "TrueNAS system state: $_response."
|
||||
|
||||
_info "Getting TrueNAS version"
|
||||
_response=$(_get "$_api_url/system/version")
|
||||
|
||||
if echo "$_response" | grep -q "SCALE"; then
|
||||
_truenas_os=$(echo "$_response" | cut -d '-' -f 2)
|
||||
_truenas_version=$(echo "$_response" | cut -d '-' -f 3 | tr -d '"' | cut -d '.' -f 1,2)
|
||||
else
|
||||
_truenas_os="unknown"
|
||||
_truenas_version="unknown"
|
||||
fi
|
||||
|
||||
_info "Detected TrueNAS system os: $_truenas_os"
|
||||
_info "Detected TrueNAS system version: $_truenas_version"
|
||||
|
||||
if [ -z "$_response" ]; then
|
||||
_err "Unable to authenticate to $_api_url."
|
||||
_err 'Check your connection settings are correct, e.g.'
|
||||
|
|
@ -129,106 +115,27 @@ truenas_deploy() {
|
|||
|
||||
_debug3 _activate_result "$_activate_result"
|
||||
|
||||
_truenas_version_23_10="23.10"
|
||||
_truenas_version_24_10="24.10"
|
||||
_info "Checking if WebDAV certificate is the same as the TrueNAS web UI"
|
||||
_webdav_list=$(_get "$_api_url/webdav")
|
||||
_webdav_cert_id=$(echo "$_webdav_list" | grep '"certssl":' | tr -d -- '"certsl: ,')
|
||||
|
||||
_check_version=$(printf "%s\n%s" "$_truenas_version_23_10" "$_truenas_version" | sort -V | head -n 1)
|
||||
if [ "$_truenas_os" != "SCALE" ] || [ "$_check_version" != "$_truenas_version_23_10" ]; then
|
||||
_info "Checking if WebDAV certificate is the same as the TrueNAS web UI"
|
||||
_webdav_list=$(_get "$_api_url/webdav")
|
||||
_webdav_cert_id=$(echo "$_webdav_list" | grep '"certssl":' | tr -d -- '"certsl: ,')
|
||||
|
||||
if [ "$_webdav_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the WebDAV certificate"
|
||||
_debug _webdav_cert_id "$_webdav_cert_id"
|
||||
_webdav_data="{\"certssl\": \"${_cert_id}\"}"
|
||||
_activate_webdav_cert="$(_post "$_webdav_data" "$_api_url/webdav" "" "PUT" "application/json")"
|
||||
_webdav_new_cert_id=$(echo "$_activate_webdav_cert" | _json_decode | grep '"certssl":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_webdav_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "WebDAV certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set WebDAV certificate"
|
||||
_debug3 _activate_webdav_cert "$_activate_webdav_cert"
|
||||
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
|
||||
return 1
|
||||
fi
|
||||
if [ "$_webdav_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the WebDAV certificate"
|
||||
_debug _webdav_cert_id "$_webdav_cert_id"
|
||||
_webdav_data="{\"certssl\": \"${_cert_id}\"}"
|
||||
_activate_webdav_cert="$(_post "$_webdav_data" "$_api_url/webdav" "" "PUT" "application/json")"
|
||||
_webdav_new_cert_id=$(echo "$_activate_webdav_cert" | _json_decode | grep '"certssl":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_webdav_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "WebDAV certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set WebDAV certificate"
|
||||
_debug3 _activate_webdav_cert "$_activate_webdav_cert"
|
||||
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
|
||||
else
|
||||
_info "WebDAV certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
|
||||
_info "Checking if S3 certificate is the same as the TrueNAS web UI"
|
||||
_s3_list=$(_get "$_api_url/s3")
|
||||
_s3_cert_id=$(echo "$_s3_list" | grep '"certificate":' | tr -d -- '"certifa:_ ,')
|
||||
|
||||
if [ "$_s3_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the S3 certificate"
|
||||
_debug _s3_cert_id "$_s3_cert_id"
|
||||
_s3_data="{\"certificate\": \"${_cert_id}\"}"
|
||||
_activate_s3_cert="$(_post "$_s3_data" "$_api_url/s3" "" "PUT" "application/json")"
|
||||
_s3_new_cert_id=$(echo "$_activate_s3_cert" | _json_decode | grep '"certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_s3_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "S3 certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set S3 certificate"
|
||||
_debug3 _activate_s3_cert "$_activate_s3_cert"
|
||||
_debug3 _s3_new_cert_id "$_s3_new_cert_id"
|
||||
return 1
|
||||
fi
|
||||
_debug3 _activate_s3_cert "$_activate_s3_cert"
|
||||
else
|
||||
_info "S3 certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$_truenas_os" = "SCALE" ]; then
|
||||
_check_version=$(printf "%s\n%s" "$_truenas_version_24_10" "$_truenas_version" | sort -V | head -n 1)
|
||||
if [ "$_check_version" != "$_truenas_version_24_10" ]; then
|
||||
_info "Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
|
||||
if _exists jq; then
|
||||
_info "Query all chart release"
|
||||
_release_list=$(_get "$_api_url/chart/release")
|
||||
_related_name_list=$(printf "%s" "$_release_list" | jq -r "[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique")
|
||||
_release_length=$(printf "%s" "$_related_name_list" | jq -r "length")
|
||||
_info "Found $_release_length related chart release in list: $_related_name_list"
|
||||
for i in $(seq 0 $((_release_length - 1))); do
|
||||
_release_name=$(echo "$_related_name_list" | jq -r ".[$i]")
|
||||
_info "Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name"
|
||||
#Read the chart release configuration
|
||||
_chart_config=$(printf "%s" "$_release_list" | jq -r ".[] | select(.name==\"$_release_name\")")
|
||||
#Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress
|
||||
_updated_chart_config=$(printf "%s" "$_chart_config" | jq "(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert ) |= $_cert_id | .config.ingress ")
|
||||
_update_chart_result="$(_post "{\"values\" : { \"ingress\" : $_updated_chart_config } }" "$_api_url/chart/release/id/$_release_name" "" "PUT" "application/json")"
|
||||
_debug3 _update_chart_result "$_update_chart_result"
|
||||
done
|
||||
else
|
||||
_info "Tool 'jq' does not exists, skip chart release checking"
|
||||
fi
|
||||
else
|
||||
_info "Checking if any app is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
|
||||
if _exists jq; then
|
||||
_info "Query all apps"
|
||||
_app_list=$(_get "$_api_url/app")
|
||||
_app_id_list=$(printf "%s" "$_app_list" | jq -r '.[].name')
|
||||
_app_length=$(echo "$_app_id_list" | wc -l)
|
||||
_info "Found $_app_length apps"
|
||||
_info "Checking for each app if an update is needed"
|
||||
for i in $(seq 1 "$_app_length"); do
|
||||
_app_id=$(echo "$_app_id_list" | sed -n "${i}p")
|
||||
_app_config="$(_post "\"$_app_id\"" "$_api_url/app/config" "" "POST" "application/json")"
|
||||
# Check if the app use the same certificate TrueNAS web UI
|
||||
_app_active_cert_config=$(echo "$_app_config" | tr -d '\000-\037' | _json_decode | jq -r ".ix_certificates[\"$_active_cert_id\"]")
|
||||
if [ "$_app_active_cert_config" != "null" ]; then
|
||||
_info "Updating certificate from $_active_cert_id to $_cert_id for app: $_app_id"
|
||||
#Replace the old certificate id with the new one in path
|
||||
_update_app_result="$(_post "{\"values\" : { \"network\": { \"certificate_id\": $_cert_id } } }" "$_api_url/app/id/$_app_id" "" "PUT" "application/json")"
|
||||
_debug3 _update_app_result "$_update_app_result"
|
||||
fi
|
||||
done
|
||||
else
|
||||
_info "Tool 'jq' does not exists, skip app checking"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
|
||||
else
|
||||
_info "WebDAV certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
|
||||
_info "Checking if FTP certificate is the same as the TrueNAS web UI"
|
||||
|
|
@ -254,6 +161,29 @@ truenas_deploy() {
|
|||
_info "FTP certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
|
||||
_info "Checking if S3 certificate is the same as the TrueNAS web UI"
|
||||
_s3_list=$(_get "$_api_url/s3")
|
||||
_s3_cert_id=$(echo "$_s3_list" | grep '"certificate":' | tr -d -- '"certifa:_ ,')
|
||||
|
||||
if [ "$_s3_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the S3 certificate"
|
||||
_debug _s3_cert_id "$_s3_cert_id"
|
||||
_s3_data="{\"certificate\": \"${_cert_id}\"}"
|
||||
_activate_s3_cert="$(_post "$_s3_data" "$_api_url/s3" "" "PUT" "application/json")"
|
||||
_s3_new_cert_id=$(echo "$_activate_s3_cert" | _json_decode | grep '"certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_s3_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "S3 certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set S3 certificate"
|
||||
_debug3 _activate_s3_cert "$_activate_s3_cert"
|
||||
_debug3 _s3_new_cert_id "$_s3_new_cert_id"
|
||||
return 1
|
||||
fi
|
||||
_debug3 _activate_s3_cert "$_activate_s3_cert"
|
||||
else
|
||||
_info "S3 certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
|
||||
_info "Deleting old certificate"
|
||||
_delete_result="$(_post "" "$_api_url/certificate/id/$_active_cert_id" "" "DELETE" "application/json")"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,363 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# TrueNAS deploy script for SCALE/CORE using websocket
|
||||
# It is recommend to use a wildcard certificate
|
||||
#
|
||||
# Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html
|
||||
#
|
||||
# Tested with TrueNAS Scale - Electric Eel 24.10
|
||||
# Changes certificate in the following services:
|
||||
# - Web UI
|
||||
# - FTP
|
||||
# - iX Apps
|
||||
#
|
||||
# The following environment variables must be set:
|
||||
# ------------------------------------------------
|
||||
#
|
||||
# # API KEY
|
||||
# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys
|
||||
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI>"
|
||||
# Optional:
|
||||
# export DEPLOY_TRUENAS_HOSTNAME="<TRUENAS_HOSTNAME_OR_IP>"
|
||||
# export DEPLOY_TRUENAS_PROTOCOL="wss" # ws or wss
|
||||
# export DEPLOY_TRUENAS_PORT="443" # optional, e.g. 80, 443, 8443
|
||||
|
||||
#
|
||||
|
||||
### Private functions
|
||||
|
||||
# Call websocket method
|
||||
# Usage:
|
||||
# _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'")
|
||||
# _info "$_ws_response"
|
||||
#
|
||||
# Output:
|
||||
# {"z": 9}
|
||||
#
|
||||
# Arguments:
|
||||
# $@ - midclt arguments for call
|
||||
#
|
||||
# Returns:
|
||||
# JSON/JOBID
|
||||
_ws_call() {
|
||||
_debug "_ws_call arg1" "$1"
|
||||
_debug "_ws_call arg2" "$2"
|
||||
_debug "_ws_call arg3" "$3"
|
||||
if [ $# -eq 3 ]; then
|
||||
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3")
|
||||
fi
|
||||
if [ $# -eq 2 ]; then
|
||||
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2")
|
||||
fi
|
||||
if [ $# -eq 1 ]; then
|
||||
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1")
|
||||
fi
|
||||
_debug "_ws_response" "$_ws_response"
|
||||
printf "%s" "$_ws_response"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Upload certificate with webclient api
|
||||
_ws_upload_cert() {
|
||||
|
||||
/usr/bin/env python - <<EOF
|
||||
import sys
|
||||
|
||||
from truenas_api_client import Client
|
||||
with Client(uri="$_ws_uri") as c:
|
||||
|
||||
### Login with API key
|
||||
print("I:Trying to upload new certificate...")
|
||||
ret = c.call("auth.login_with_api_key", "${DEPLOY_TRUENAS_APIKEY}")
|
||||
if ret:
|
||||
### upload certificate
|
||||
with open('$1', 'r') as file:
|
||||
fullchain = file.read()
|
||||
with open('$2', 'r') as file:
|
||||
privatekey = file.read()
|
||||
ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey}, job=True)
|
||||
print("R:" + str(ret["id"]))
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("R:0")
|
||||
print("E:_ws_upload_cert error!")
|
||||
sys.exit(7)
|
||||
EOF
|
||||
|
||||
return $?
|
||||
|
||||
}
|
||||
|
||||
# Check argument is a number
|
||||
# Usage:
|
||||
#
|
||||
# Output:
|
||||
# n/a
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Anything
|
||||
#
|
||||
# Returns:
|
||||
# 0: true
|
||||
# 1: false
|
||||
_ws_check_jobid() {
|
||||
case "$1" in
|
||||
[0-9]*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Wait for job to finish and return result as JSON
|
||||
# Usage:
|
||||
# _ws_result=$(_ws_get_job_result "$_ws_jobid")
|
||||
# _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"')
|
||||
#
|
||||
# Output:
|
||||
# JSON result of the job
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - JobID
|
||||
#
|
||||
# Returns:
|
||||
# n/a
|
||||
_ws_get_job_result() {
|
||||
while true; do
|
||||
_sleep 2
|
||||
_ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]")
|
||||
if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then
|
||||
_ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')"
|
||||
_debug "_ws_result" "$_ws_result"
|
||||
printf "%s" "$_ws_result"
|
||||
_ws_error="$(printf "%s" "$_ws_response" | jq '.[]."error"')"
|
||||
if [ "$_ws_error" != "null" ]; then
|
||||
_err "Job $1 failed:"
|
||||
_err "$_ws_error"
|
||||
return 7
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
########################
|
||||
### Public functions ###
|
||||
########################
|
||||
|
||||
# truenas_ws_deploy
|
||||
#
|
||||
# Deploy new certificate to TrueNAS services
|
||||
#
|
||||
# Arguments
|
||||
# 1: Domain
|
||||
# 2: Key-File
|
||||
# 3: Certificate-File
|
||||
# 4: CA-File
|
||||
# 5: FullChain-File
|
||||
# Returns:
|
||||
# 0: Success
|
||||
# 1: Missing API Key
|
||||
# 2: TrueNAS not ready
|
||||
# 3: Not a JobID
|
||||
# 4: FTP cert error
|
||||
# 5: WebUI cert error
|
||||
# 6: Job error
|
||||
# 7: WS call error
|
||||
#
|
||||
truenas_ws_deploy() {
|
||||
_domain="$1"
|
||||
_file_key="$2"
|
||||
_file_cert="$3"
|
||||
_file_ca="$4"
|
||||
_file_fullchain="$5"
|
||||
_debug _domain "$_domain"
|
||||
_debug _file_key "$_file_key"
|
||||
_debug _file_cert "$_file_cert"
|
||||
_debug _file_ca "$_file_ca"
|
||||
_debug _file_fullchain "$_file_fullchain"
|
||||
|
||||
########## Environment check
|
||||
|
||||
_info "Checking environment variables..."
|
||||
_getdeployconf DEPLOY_TRUENAS_APIKEY
|
||||
_getdeployconf DEPLOY_TRUENAS_HOSTNAME
|
||||
_getdeployconf DEPLOY_TRUENAS_PROTOCOL
|
||||
_getdeployconf DEPLOY_TRUENAS_PORT
|
||||
|
||||
# Check API Key
|
||||
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then
|
||||
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable."
|
||||
return 1
|
||||
fi
|
||||
# Check Hostname, default to localhost if not set
|
||||
if [ -z "$DEPLOY_TRUENAS_HOSTNAME" ]; then
|
||||
_info "TrueNAS hostname not set. Using 'localhost'."
|
||||
DEPLOY_TRUENAS_HOSTNAME="localhost"
|
||||
fi
|
||||
# Check protocol, default to ws if not set
|
||||
if [ -z "$DEPLOY_TRUENAS_PROTOCOL" ]; then
|
||||
_info "TrueNAS protocol not set. Using 'ws'."
|
||||
DEPLOY_TRUENAS_PROTOCOL="ws"
|
||||
fi
|
||||
|
||||
# Check port, optional
|
||||
if [ -n "$DEPLOY_TRUENAS_PORT" ]; then
|
||||
case "$DEPLOY_TRUENAS_PORT" in
|
||||
'' | *[!0-9]*)
|
||||
_err "Invalid TrueNAS port '$DEPLOY_TRUENAS_PORT'. DEPLOY_TRUENAS_PORT must be numeric."
|
||||
return 8
|
||||
;;
|
||||
esac
|
||||
|
||||
_ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME:$DEPLOY_TRUENAS_PORT/websocket"
|
||||
else
|
||||
_ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket"
|
||||
fi
|
||||
|
||||
_debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
|
||||
_debug2 DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL"
|
||||
_debug _ws_uri "$_ws_uri"
|
||||
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
|
||||
_info "Environment variables: OK"
|
||||
|
||||
########## Health check
|
||||
|
||||
_info "Checking TrueNAS health..."
|
||||
_ws_response=$(_ws_call "system.ready" | tr '[:lower:]' '[:upper:]')
|
||||
_ws_ret=$?
|
||||
if [ $_ws_ret -gt 0 ]; then
|
||||
_err "Error calling system.ready:"
|
||||
_err "$_ws_response"
|
||||
return $_ws_ret
|
||||
fi
|
||||
|
||||
if [ "$_ws_response" != "TRUE" ]; then
|
||||
_err "TrueNAS is not ready."
|
||||
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME, DEPLOY_TRUENAS_PROTOCOL and DEPLOY_TRUENAS_PORT."
|
||||
_err "Verify API key."
|
||||
return 2
|
||||
fi
|
||||
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
|
||||
_savedeployconf DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
|
||||
_savedeployconf DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL"
|
||||
_savedeployconf DEPLOY_TRUENAS_PORT "$DEPLOY_TRUENAS_PORT"
|
||||
_info "TrueNAS health: OK"
|
||||
|
||||
########## System info
|
||||
|
||||
_info "Gather system info..."
|
||||
_ws_response=$(_ws_call "system.info")
|
||||
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"')
|
||||
_info "TrueNAS version: $_truenas_version"
|
||||
|
||||
########## Gather current certificate
|
||||
|
||||
_info "Gather current WebUI certificate..."
|
||||
_ws_response="$(_ws_call "system.general.config")"
|
||||
_ui_certificate_id=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"')
|
||||
_ui_certificate_name=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."name"')
|
||||
_info "Current WebUI certificate ID: $_ui_certificate_id"
|
||||
_info "Current WebUI certificate name: $_ui_certificate_name"
|
||||
|
||||
########## Upload new certificate
|
||||
|
||||
_info "Upload new certificate..."
|
||||
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')"
|
||||
_info "New WebUI certificate name: $_certname"
|
||||
_debug _certname "$_certname"
|
||||
_ws_out=$(_ws_upload_cert "$_file_fullchain" "$_file_key" "$_certname")
|
||||
|
||||
echo "$_ws_out" | while IFS= read -r LINE; do
|
||||
case "$LINE" in
|
||||
I:*)
|
||||
_info "${LINE#I:}"
|
||||
;;
|
||||
D:*)
|
||||
_debug "${LINE#D:}"
|
||||
;;
|
||||
E*)
|
||||
_err "${LINE#E:}"
|
||||
;;
|
||||
*) ;;
|
||||
|
||||
esac
|
||||
done
|
||||
|
||||
_new_certid=$(echo "$_ws_out" | grep 'R:' | cut -d ':' -f 2)
|
||||
|
||||
_info "New certificate ID: $_new_certid"
|
||||
|
||||
########## FTP
|
||||
|
||||
_info "Replace FTP certificate..."
|
||||
_ws_response=$(_ws_call "ftp.update" "{\"ssltls_certificate\": $_new_certid}")
|
||||
_ftp_certid=$(printf "%s" "$_ws_response" | jq -r '."ssltls_certificate"')
|
||||
if [ "$_ftp_certid" != "$_new_certid" ]; then
|
||||
_err "Cannot set FTP certificate."
|
||||
_debug "_ws_response" "$_ws_response"
|
||||
return 4
|
||||
fi
|
||||
|
||||
########## ix Apps (SCALE only)
|
||||
|
||||
_info "Replace app certificates..."
|
||||
_ws_response=$(_ws_call "app.query")
|
||||
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do
|
||||
_info "Checking app $_app_name..."
|
||||
_ws_response=$(_ws_call "app.config" "$_app_name")
|
||||
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then
|
||||
_info "App has certificate option, setup new certificate..."
|
||||
_info "App will be redeployed after updating the certificate."
|
||||
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}")
|
||||
_debug "_ws_jobid" "$_ws_jobid"
|
||||
if ! _ws_check_jobid "$_ws_jobid"; then
|
||||
_err "No JobID returned from websocket method."
|
||||
return 3
|
||||
fi
|
||||
_ws_result=$(_ws_get_job_result "$_ws_jobid")
|
||||
_ws_ret=$?
|
||||
if [ $_ws_ret -gt 0 ]; then
|
||||
return $_ws_ret
|
||||
fi
|
||||
_debug "_ws_result" "$_ws_result"
|
||||
_info "App certificate replaced."
|
||||
else
|
||||
_info "App has no certificate option, skipping..."
|
||||
fi
|
||||
done
|
||||
|
||||
########## WebUI
|
||||
|
||||
_info "Replace WebUI certificate..."
|
||||
_ws_response=$(_ws_call "system.general.update" "{\"ui_certificate\": $_new_certid}")
|
||||
_changed_certid=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"')
|
||||
if [ "$_changed_certid" != "$_new_certid" ]; then
|
||||
_err "WebUI certificate change error.."
|
||||
return 5
|
||||
else
|
||||
_info "WebUI certificate replaced."
|
||||
fi
|
||||
_info "Restarting WebUI..."
|
||||
_ws_response=$(_ws_call "system.general.ui_restart")
|
||||
_info "Waiting for UI restart..."
|
||||
_sleep 15
|
||||
|
||||
########## Certificates
|
||||
|
||||
_info "Deleting old certificate..."
|
||||
_ws_jobid=$(_ws_call "certificate.delete" "$_ui_certificate_id")
|
||||
if ! _ws_check_jobid "$_ws_jobid"; then
|
||||
_err "No JobID returned from websocket method."
|
||||
return 3
|
||||
fi
|
||||
_ws_result=$(_ws_get_job_result "$_ws_jobid")
|
||||
_ws_ret=$?
|
||||
if [ $_ws_ret -gt 0 ]; then
|
||||
return $_ws_ret
|
||||
fi
|
||||
|
||||
_info "Have a nice day...bye!"
|
||||
|
||||
}
|
||||
147
deploy/unifi.sh
147
deploy/unifi.sh
|
|
@ -5,15 +5,6 @@
|
|||
# - self-hosted Unifi Controller
|
||||
# - Unifi Cloud Key (Gen1/2/2+)
|
||||
# - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only)
|
||||
# - Unifi Dream Machine
|
||||
# This has not been tested on other "all-in-one" devices such as
|
||||
# UDM Pro or Unifi Express.
|
||||
#
|
||||
# OS Version v2.0.0+
|
||||
# Network Application version 7.0.0+
|
||||
# OS version ~3.1 removed java and keytool from the UnifiOS.
|
||||
# Using PKCS12 format keystore appears to work fine.
|
||||
#
|
||||
# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
|
@ -30,9 +21,7 @@
|
|||
# Keystore password (built into Unifi Controller, not a user-set password):
|
||||
#DEPLOY_UNIFI_KEYPASS="aircontrolenterprise"
|
||||
# Command to restart Unifi Controller:
|
||||
# DEPLOY_UNIFI_RELOAD="systemctl restart unifi"
|
||||
# System Properties file location for controller
|
||||
#DEPLOY_UNIFI_SYSTEM_PROPERTIES="/usr/lib/unifi/data/system.properties"
|
||||
#DEPLOY_UNIFI_RELOAD="service unifi restart"
|
||||
#
|
||||
# Settings for Unifi Cloud Key Gen1 (nginx admin pages):
|
||||
# Directory where cloudkey.crt and cloudkey.key live:
|
||||
|
|
@ -45,7 +34,7 @@
|
|||
# Directory where unifi-core.crt and unifi-core.key live:
|
||||
#DEPLOY_UNIFI_CORE_CONFIG="/data/unifi-core/config/"
|
||||
# Command to restart unifi-core:
|
||||
# DEPLOY_UNIFI_OS_RELOAD="systemctl restart unifi-core"
|
||||
#DEPLOY_UNIFI_RELOAD="systemctl restart unifi-core"
|
||||
#
|
||||
# At least one of DEPLOY_UNIFI_KEYSTORE, DEPLOY_UNIFI_CLOUDKEY_CERTDIR,
|
||||
# or DEPLOY_UNIFI_CORE_CONFIG must exist to receive the deployed certs.
|
||||
|
|
@ -71,16 +60,12 @@ unifi_deploy() {
|
|||
_getdeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR
|
||||
_getdeployconf DEPLOY_UNIFI_CORE_CONFIG
|
||||
_getdeployconf DEPLOY_UNIFI_RELOAD
|
||||
_getdeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES
|
||||
_getdeployconf DEPLOY_UNIFI_OS_RELOAD
|
||||
|
||||
_debug2 DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
|
||||
_debug2 DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
|
||||
_debug2 DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
|
||||
_debug2 DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
|
||||
_debug2 DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
|
||||
_debug2 DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD"
|
||||
_debug2 DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES"
|
||||
|
||||
# Space-separated list of environments detected and installed:
|
||||
_services_updated=""
|
||||
|
|
@ -89,16 +74,14 @@ unifi_deploy() {
|
|||
_reload_cmd=""
|
||||
|
||||
# Unifi Controller environment (self hosted or any Cloud Key) --
|
||||
# auto-detect by file /usr/lib/unifi/data/keystore
|
||||
# auto-detect by file /usr/lib/unifi/data/keystore:
|
||||
_unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}"
|
||||
if [ -f "$_unifi_keystore" ]; then
|
||||
_info "Installing certificate for Unifi Controller (Java keystore)"
|
||||
_debug _unifi_keystore "$_unifi_keystore"
|
||||
if ! _exists keytool; then
|
||||
_do_keytool=0
|
||||
_info "Installing certificate for Unifi Controller (PKCS12 keystore)."
|
||||
else
|
||||
_do_keytool=1
|
||||
_info "Installing certificate for Unifi Controller (Java keystore)"
|
||||
_err "keytool not found"
|
||||
return 1
|
||||
fi
|
||||
if [ ! -w "$_unifi_keystore" ]; then
|
||||
_err "The file $_unifi_keystore is not writable, please change the permission."
|
||||
|
|
@ -109,7 +92,6 @@ unifi_deploy() {
|
|||
|
||||
_debug "Generate import pkcs12"
|
||||
_import_pkcs12="$(_mktemp)"
|
||||
_debug "_toPkcs $_import_pkcs12 $_ckey $_ccert $_cca $_unifi_keypass unifi root"
|
||||
_toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
|
||||
# shellcheck disable=SC2181
|
||||
if [ "$?" != "0" ]; then
|
||||
|
|
@ -117,79 +99,22 @@ unifi_deploy() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
# Save the existing keystore in case something goes wrong.
|
||||
mv -f "${_unifi_keystore}" "${_unifi_keystore}"_original
|
||||
_info "Previous keystore saved to ${_unifi_keystore}_original."
|
||||
|
||||
if [ "$_do_keytool" -eq 1 ]; then
|
||||
_debug "Import into keystore: $_unifi_keystore"
|
||||
if keytool -importkeystore \
|
||||
-deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
|
||||
-srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
|
||||
-alias unifi -noprompt; then
|
||||
_debug "Import keystore success!"
|
||||
else
|
||||
_err "Error importing into Unifi Java keystore."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
_info "Restoring original keystore."
|
||||
mv -f "${_unifi_keystore}"_original "${_unifi_keystore}"
|
||||
rm "$_import_pkcs12"
|
||||
return 1
|
||||
fi
|
||||
_debug "Import into keystore: $_unifi_keystore"
|
||||
if keytool -importkeystore \
|
||||
-deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
|
||||
-srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
|
||||
-alias unifi -noprompt; then
|
||||
_debug "Import keystore success!"
|
||||
rm "$_import_pkcs12"
|
||||
else
|
||||
_debug "Copying new keystore to $_unifi_keystore"
|
||||
cp -f "$_import_pkcs12" "$_unifi_keystore"
|
||||
_err "Error importing into Unifi Java keystore."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
rm "$_import_pkcs12"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# correct file ownership according to the directory, the keystore is placed in
|
||||
_unifi_keystore_dir=$(dirname "${_unifi_keystore}")
|
||||
# shellcheck disable=SC2012
|
||||
_unifi_keystore_dir_owner=$(ls -ld "${_unifi_keystore_dir}" | awk '{print $3}')
|
||||
# shellcheck disable=SC2012
|
||||
_unifi_keystore_owner=$(ls -l "${_unifi_keystore}" | awk '{print $3}')
|
||||
if ! [ "${_unifi_keystore_owner}" = "${_unifi_keystore_dir_owner}" ]; then
|
||||
_debug "Changing keystore owner to ${_unifi_keystore_dir_owner}"
|
||||
chown "$_unifi_keystore_dir_owner" "${_unifi_keystore}" >/dev/null 2>&1 # fail quietly if we're not running as root
|
||||
fi
|
||||
|
||||
# Update unifi service for certificate cipher compatibility
|
||||
_unifi_system_properties="${DEPLOY_UNIFI_SYSTEM_PROPERTIES:-/usr/lib/unifi/data/system.properties}"
|
||||
if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \
|
||||
-in "$_import_pkcs12" \
|
||||
-password pass:aircontrolenterprise \
|
||||
-nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \
|
||||
-noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then
|
||||
if [ -f "$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" ]; then
|
||||
_unifi_system_properties="$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties"
|
||||
else
|
||||
_unifi_system_properties="/usr/lib/unifi/data/system.properties"
|
||||
fi
|
||||
if [ -f "${_unifi_system_properties}" ]; then
|
||||
cp -f "${_unifi_system_properties}" "${_unifi_system_properties}"_original
|
||||
_info "Updating system configuration for cipher compatibility."
|
||||
_info "Saved original system config to ${_unifi_system_properties}_original"
|
||||
sed -i '/unifi\.https\.ciphers/d' "${_unifi_system_properties}"
|
||||
echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>"${_unifi_system_properties}"
|
||||
sed -i '/unifi\.https\.sslEnabledProtocols/d' "${_unifi_system_properties}"
|
||||
echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>"${_unifi_system_properties}"
|
||||
_info "System configuration updated."
|
||||
fi
|
||||
fi
|
||||
|
||||
rm "$_import_pkcs12"
|
||||
|
||||
# Restarting unifi-core will bring up unifi, doing it out of order results in
|
||||
# a certificate error, and breaks wifiman.
|
||||
# Restart if we aren't doing Unifi OS (e.g. unifi-core service), otherwise stop for later restart.
|
||||
_unifi_reload="${DEPLOY_UNIFI_RELOAD:-systemctl restart unifi}"
|
||||
if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload"
|
||||
else
|
||||
_info "Stopping Unifi Controller for later restart."
|
||||
_unifi_stop=$(echo "${_unifi_reload}" | sed -e 's/restart/stop/')
|
||||
$_unifi_stop
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload"
|
||||
_info "Unifi Controller stopped."
|
||||
if systemctl -q is-active unifi; then
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }service unifi restart"
|
||||
fi
|
||||
_services_updated="${_services_updated} unifi"
|
||||
_info "Install Unifi Controller certificate success!"
|
||||
|
|
@ -209,24 +134,13 @@ unifi_deploy() {
|
|||
return 1
|
||||
fi
|
||||
# Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks.
|
||||
# It appears that unifi won't start if this is a symlink, so we'll copy it instead.
|
||||
|
||||
# if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then
|
||||
# _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'"
|
||||
# return 1
|
||||
# fi
|
||||
|
||||
_info "Updating ${_cloudkey_certdir}/unifi.keystore.jks"
|
||||
if [ -e "${_cloudkey_certdir}/unifi.keystore.jks" ]; then
|
||||
if [ -L "${_cloudkey_certdir}/unifi.keystore.jks" ]; then
|
||||
rm -f "${_cloudkey_certdir}/unifi.keystore.jks"
|
||||
else
|
||||
mv "${_cloudkey_certdir}/unifi.keystore.jks" "${_cloudkey_certdir}/unifi.keystore.jks_original"
|
||||
fi
|
||||
# Normally /usr/lib/unifi/data/keystore is a symlink there (so the keystore was
|
||||
# updated above), but if not, we don't know how to handle this installation:
|
||||
if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then
|
||||
_err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "${_unifi_keystore}" "${_cloudkey_certdir}/unifi.keystore.jks"
|
||||
|
||||
cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt"
|
||||
cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key"
|
||||
(cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks)
|
||||
|
|
@ -251,17 +165,12 @@ unifi_deploy() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
# Save the existing certs in case something goes wrong.
|
||||
cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt
|
||||
cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key
|
||||
_info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt.key."
|
||||
|
||||
cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt"
|
||||
cat "$_ckey" >"${_unifi_core_config}/unifi-core.key"
|
||||
|
||||
_unifi_os_reload="${DEPLOY_UNIFI_OS_RELOAD:-systemctl restart unifi-core}"
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_os_reload"
|
||||
|
||||
if systemctl -q is-active unifi-core; then
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi-core"
|
||||
fi
|
||||
_info "Install UnifiOS certificate success!"
|
||||
_services_updated="${_services_updated} unifi-core"
|
||||
elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then
|
||||
|
|
@ -300,8 +209,6 @@ unifi_deploy() {
|
|||
_savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
|
||||
_savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
|
||||
_savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
|
||||
_savedeployconf DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD"
|
||||
_savedeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
159
deploy/vault.sh
159
deploy/vault.sh
|
|
@ -7,16 +7,13 @@
|
|||
#
|
||||
# VAULT_PREFIX - this contains the prefix path in vault
|
||||
# VAULT_ADDR - vault requires this to find your vault server
|
||||
# VAULT_SAVE_TOKEN - set to anything if you want to save the token
|
||||
# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying
|
||||
# VAULT_KV_V2 - set to anything if you are using v2 of the kv engine
|
||||
#
|
||||
# additionally, you need to ensure that VAULT_TOKEN is avialable
|
||||
# to access the vault server
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
vault_deploy() {
|
||||
|
|
@ -48,154 +45,34 @@ vault_deploy() {
|
|||
fi
|
||||
_savedeployconf VAULT_ADDR "$VAULT_ADDR"
|
||||
|
||||
_getdeployconf VAULT_SAVE_TOKEN
|
||||
_savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_RENEW_TOKEN
|
||||
_savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_KV_V2
|
||||
_savedeployconf VAULT_KV_V2 "$VAULT_KV_V2"
|
||||
|
||||
_getdeployconf VAULT_TOKEN
|
||||
if [ -z "$VAULT_TOKEN" ]; then
|
||||
_err "VAULT_TOKEN needs to be defined"
|
||||
return 1
|
||||
fi
|
||||
if [ -n "$VAULT_SAVE_TOKEN" ]; then
|
||||
_savedeployconf VAULT_TOKEN "$VAULT_TOKEN"
|
||||
fi
|
||||
|
||||
_migratedeployconf FABIO VAULT_FABIO_MODE
|
||||
|
||||
# JSON does not allow multiline strings.
|
||||
# So replacing new-lines with "\n" here
|
||||
_ckey=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$2")
|
||||
_ccert=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$3")
|
||||
_cca=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$4")
|
||||
_cfullchain=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$5")
|
||||
|
||||
export _H1="X-Vault-Token: $VAULT_TOKEN"
|
||||
|
||||
if [ -n "$VAULT_RENEW_TOKEN" ]; then
|
||||
URL="$VAULT_ADDR/v1/auth/token/renew-self"
|
||||
_info "Renew the Vault token to default TTL"
|
||||
_response=$(_post "" "$URL")
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Failed to renew the Vault token"
|
||||
return 1
|
||||
fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Failed to renew the Vault token: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
_ckey=$(sed -z 's/\n/\\n/g' <"$2")
|
||||
_ccert=$(sed -z 's/\n/\\n/g' <"$3")
|
||||
_cca=$(sed -z 's/\n/\\n/g' <"$4")
|
||||
_cfullchain=$(sed -z 's/\n/\\n/g' <"$5")
|
||||
|
||||
URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain"
|
||||
export _H1="X-Vault-Token: $VAULT_TOKEN"
|
||||
|
||||
if [ -n "$VAULT_FABIO_MODE" ]; then
|
||||
_info "Writing certificate and key to $URL in Fabio mode"
|
||||
if [ -n "$FABIO" ]; then
|
||||
if [ -n "$VAULT_KV_V2" ]; then
|
||||
_response=$(_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error: $_response"
|
||||
return 1
|
||||
fi
|
||||
_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL"
|
||||
else
|
||||
_response=$(_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error: $_response"
|
||||
return 1
|
||||
fi
|
||||
_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL"
|
||||
fi
|
||||
else
|
||||
if [ -n "$VAULT_KV_V2" ]; then
|
||||
_info "Writing certificate to $URL/cert.pem"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing key to $URL/cert.key"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.key: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing CA certificate to $URL/ca.pem"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing ca.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing full-chain certificate to $URL/fullchain.pem"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing fullchain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem"
|
||||
_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key"
|
||||
_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem"
|
||||
_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem"
|
||||
else
|
||||
_info "Writing certificate to $URL/cert.pem"
|
||||
_response=$(_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing key to $URL/cert.key"
|
||||
_response=$(_post "{\"value\": \"$_ckey\"}" "$URL/cert.key")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.key: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing CA certificate to $URL/ca.pem"
|
||||
_response=$(_post "{\"value\": \"$_cca\"}" "$URL/ca.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing ca.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing full-chain certificate to $URL/fullchain.pem"
|
||||
_response=$(_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing fullchain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions
|
||||
if _contains "$(_get "$URL/chain.pem")" "-----BEGIN CERTIFICATE-----"; then
|
||||
_err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning"
|
||||
_info "Updating CA certificate to $URL/chain.pem for backward compatibility"
|
||||
if [ -n "$VAULT_KV_V2" ]; then
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing chain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_response=$(_post "{\"value\": \"$_cca\"}" "$URL/chain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing chain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem"
|
||||
_post "{\"value\": \"$_ckey\"}" "$URL/cert.key"
|
||||
_post "{\"value\": \"$_cca\"}" "$URL/chain.pem"
|
||||
_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem"
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@
|
|||
#
|
||||
# VAULT_PREFIX - this contains the prefix path in vault
|
||||
# VAULT_ADDR - vault requires this to find your vault server
|
||||
# VAULT_SAVE_TOKEN - set to anything if you want to save the token
|
||||
# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying
|
||||
#
|
||||
# additionally, you need to ensure that VAULT_TOKEN is avialable or
|
||||
# `vault auth` has applied the appropriate authorization for the vault binary
|
||||
|
|
@ -35,36 +33,15 @@ vault_cli_deploy() {
|
|||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# validate required env vars
|
||||
_getdeployconf VAULT_PREFIX
|
||||
if [ -z "$VAULT_PREFIX" ]; then
|
||||
_err "VAULT_PREFIX needs to be defined (contains prefix path in vault)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf VAULT_PREFIX "$VAULT_PREFIX"
|
||||
|
||||
_getdeployconf VAULT_ADDR
|
||||
if [ -z "$VAULT_ADDR" ]; then
|
||||
_err "VAULT_ADDR needs to be defined (contains vault connection address)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf VAULT_ADDR "$VAULT_ADDR"
|
||||
|
||||
_getdeployconf VAULT_SAVE_TOKEN
|
||||
_savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_RENEW_TOKEN
|
||||
_savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_TOKEN
|
||||
if [ -z "$VAULT_TOKEN" ]; then
|
||||
_err "VAULT_TOKEN needs to be defined"
|
||||
return 1
|
||||
fi
|
||||
if [ -n "$VAULT_SAVE_TOKEN" ]; then
|
||||
_savedeployconf VAULT_TOKEN "$VAULT_TOKEN"
|
||||
fi
|
||||
|
||||
_migratedeployconf FABIO VAULT_FABIO_MODE
|
||||
|
||||
VAULT_CMD=$(command -v vault)
|
||||
if [ ! $? ]; then
|
||||
|
|
@ -72,33 +49,13 @@ vault_cli_deploy() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if [ -n "$VAULT_RENEW_TOKEN" ]; then
|
||||
_info "Renew the Vault token to default TTL"
|
||||
if ! $VAULT_CMD token renew; then
|
||||
_err "Failed to renew the Vault token"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$VAULT_FABIO_MODE" ]; then
|
||||
_info "Writing certificate and key to ${VAULT_PREFIX}/${_cdomain} in Fabio mode"
|
||||
if [ -n "$FABIO" ]; then
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
|
||||
else
|
||||
_info "Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
|
||||
_info "Writing key to ${VAULT_PREFIX}/${_cdomain}/cert.key"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
|
||||
_info "Writing CA certificate to ${VAULT_PREFIX}/${_cdomain}/ca.pem"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/ca.pem" value=@"$_cca" || return 1
|
||||
_info "Writing full-chain certificate to ${VAULT_PREFIX}/${_cdomain}/fullchain.pem"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
|
||||
|
||||
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions
|
||||
if $VAULT_CMD kv get "${VAULT_PREFIX}/${_cdomain}/chain.pem" >/dev/null; then
|
||||
_err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning"
|
||||
_info "Updating CA certificate to ${VAULT_PREFIX}/${_cdomain}/chain.pem for backward compatibility"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,5 +106,5 @@ vsftpd_deploy() {
|
|||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,158 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# install a certificate on a Windows host over OpenSSH and bind it to the Remote
|
||||
# Desktop listener (RDP-Tcp).
|
||||
#
|
||||
# One ssh invocation does the whole job:
|
||||
# * the PFX is built locally, base64'd, and embedded as a string literal
|
||||
# inside a generated PowerShell script;
|
||||
# * the script is piped to `powershell.exe -Command -` over ssh. No scp,
|
||||
# no temp files on the Windows host.
|
||||
#
|
||||
# First run:
|
||||
# export DEPLOY_WIN_RDP_HOST=winserver.example.com
|
||||
# acme.sh --deploy -d winserver.example.com --deploy-hook windows_rdp
|
||||
#
|
||||
# Available variables:
|
||||
# DEPLOY_WIN_RDP_HOST required SSH host
|
||||
# DEPLOY_WIN_RDP_USER optional SSH user, must be a local administrator (can also by set via ssh_config)
|
||||
# DEPLOY_WIN_RDP_PORT optional SSH port, default 22
|
||||
# DEPLOY_WIN_RDP_SSH_OPTS optional extra ssh options, e.g.
|
||||
# "-i /root/.ssh/win_id_ed25519 -o StrictHostKeyChecking=yes"
|
||||
# DEPLOY_WIN_RDP_LISTENER optional RDP listener name, default RDP-Tcp
|
||||
# DEPLOY_WIN_RDP_RESTART optional "1" to restart TermService after install.
|
||||
# Active RDP sessions will drop!
|
||||
|
||||
windows_rdp_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"
|
||||
|
||||
if ! _exists "ssh"; then
|
||||
_err "ssh is required but was not found in PATH."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ---- configuration ------------------------------------------------------
|
||||
_getdeployconf DEPLOY_WIN_RDP_HOST
|
||||
_getdeployconf DEPLOY_WIN_RDP_USER
|
||||
_getdeployconf DEPLOY_WIN_RDP_PORT
|
||||
_getdeployconf DEPLOY_WIN_RDP_SSH_OPTS
|
||||
_getdeployconf DEPLOY_WIN_RDP_LISTENER
|
||||
_getdeployconf DEPLOY_WIN_RDP_RESTART
|
||||
|
||||
if [ -z "$DEPLOY_WIN_RDP_HOST" ]; then
|
||||
_err "DEPLOY_WIN_RDP_HOST must be set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_WIN_RDP_HOST "$DEPLOY_WIN_RDP_HOST"
|
||||
[ -n "$DEPLOY_WIN_RDP_USER" ] && _savedeployconf DEPLOY_WIN_RDP_USER "$DEPLOY_WIN_RDP_USER"
|
||||
[ -n "$DEPLOY_WIN_RDP_PORT" ] && _savedeployconf DEPLOY_WIN_RDP_PORT "$DEPLOY_WIN_RDP_PORT"
|
||||
[ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ] && _savedeployconf DEPLOY_WIN_RDP_SSH_OPTS "$DEPLOY_WIN_RDP_SSH_OPTS"
|
||||
[ -n "$DEPLOY_WIN_RDP_LISTENER" ] && _savedeployconf DEPLOY_WIN_RDP_LISTENER "$DEPLOY_WIN_RDP_LISTENER"
|
||||
[ -n "$DEPLOY_WIN_RDP_RESTART" ] && _savedeployconf DEPLOY_WIN_RDP_RESTART "$DEPLOY_WIN_RDP_RESTART"
|
||||
|
||||
_port="${DEPLOY_WIN_RDP_PORT:-22}"
|
||||
_listener="${DEPLOY_WIN_RDP_LISTENER:-RDP-Tcp}"
|
||||
if [ -n "$DEPLOY_WIN_RDP_USER" ]; then
|
||||
_target="$DEPLOY_WIN_RDP_USER@$DEPLOY_WIN_RDP_HOST"
|
||||
else
|
||||
_target="$DEPLOY_WIN_RDP_HOST"
|
||||
fi
|
||||
_pfx_pass="acme"
|
||||
|
||||
# ---- build thumbprint + PFX locally ------------------------------------
|
||||
_thumb="$(_fingerprint "$_ccert" 'sha1')"
|
||||
if [ -z "$_thumb" ]; then
|
||||
_err "Failed to compute certificate thumbprint."
|
||||
return 1
|
||||
fi
|
||||
_debug "Thumbprint: $_thumb"
|
||||
|
||||
_debug "Building PFX at $_pfx_file"
|
||||
_pfx_file="$(_mktemp)"
|
||||
if ! _toPkcs "$_pfx_file" "$_ckey" "$_ccert" "$_cca" "$_pfx_pass"; then
|
||||
_err "Failed to build PFX archive."
|
||||
rm -f "$_pfx_file"
|
||||
return 1
|
||||
fi
|
||||
_pfx_b64=$(_base64 "multiline" <"$_pfx_file")
|
||||
rm -f "$_pfx_file"
|
||||
|
||||
# ---- build installer script --------------------------------------------
|
||||
if [ "$DEPLOY_WIN_RDP_RESTART" = "1" ]; then
|
||||
_restart_ps='Restart-Service -Name TermService -Force'
|
||||
else
|
||||
_restart_ps='# New RdP connections will pick up the new cert automatically.'
|
||||
fi
|
||||
|
||||
# Escape every literal `$` with `\$` so the shell does not expand it.
|
||||
# Values substituted from shell: $_pfx_b64, $_pfx_pass, $_thumb, $_listener.
|
||||
_ps1=$(
|
||||
cat <<PSEOF
|
||||
|
||||
\$ErrorActionPreference = 'Stop'
|
||||
|
||||
\$pfxBytes = [Convert]::FromBase64String('${_pfx_b64}')
|
||||
|
||||
# Note: It is quite important to use a X509Certificate2Collection here in any case, since we otherwise
|
||||
# could run into quite a lot of trouble when importing the certificate including its entire chain
|
||||
# and its private key. Windows might behave arbitrarily and not consistently import the certificate
|
||||
# at all - unless "Exportable" is included in the storage flags. However, then the certificate seems
|
||||
# unaccessible to TermService for some weird reasons despite all permissions being set (at least on my
|
||||
# Win 11 lab machine). This might be some security setting that prevents TermService from working with
|
||||
# exportable keys? I don't know - importing the entire collection including chain or not always fixes
|
||||
# the issues.
|
||||
#
|
||||
# Note2: If you should have kicked yourself out for some reason, then deleting the certificate will make
|
||||
# TermService restore the original, self-signed certificate after at least after the second login attempt.
|
||||
# Deleting the certificate can be easily accomplished via the Powershell, since SSH access will still be
|
||||
# present in any case - the following command should get you out of trouble:
|
||||
# \$cert = Get-ChildItem -Path 'Cert:\LocalMachine\My\\${_thumb}' | Select-Object -First 1 | Remove-Item
|
||||
|
||||
\$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]'MachineKeySet,PersistKeySet'
|
||||
\$certs = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
|
||||
\$certs.Import(\$pfxBytes, '${_pfx_pass}', \$flags)
|
||||
|
||||
\$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My', 'LocalMachine')
|
||||
\$store.Open('ReadWrite')
|
||||
\$store.AddRange(\$certs)
|
||||
\$store.Close()
|
||||
Write-Host "Installed certs into LocalMachine\\My"
|
||||
|
||||
\$ts = Get-CimInstance -Namespace root/cimv2/terminalservices -ClassName Win32_TSGeneralSetting -Filter "TerminalName='${_listener}'"
|
||||
if (-not \$ts) { throw "Listener '${_listener}' not found." }
|
||||
Set-CimInstance -InputObject \$ts -Property @{SSLCertificateSHA1Hash="${_thumb}"}
|
||||
Write-Host "Listener ${_listener} now uses ${_thumb}"
|
||||
|
||||
${_restart_ps}
|
||||
PSEOF
|
||||
)
|
||||
_debug "Powershell script:${_ps1}"
|
||||
|
||||
# ---- run over a single ssh connection ----------------------------------
|
||||
_ssh_opts="-o BatchMode=yes -p $_port"
|
||||
if [ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ]; then
|
||||
_ssh_opts="$_ssh_opts $DEPLOY_WIN_RDP_SSH_OPTS"
|
||||
fi
|
||||
|
||||
_info "Deploying to $DEPLOY_WIN_RDP_HOST ..."
|
||||
# shellcheck disable=SC2086
|
||||
if ! printf '%s\n' "$_ps1" | ssh $_ssh_opts "$_target" \
|
||||
'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command -'; then
|
||||
_err "Remote install failed. Re-run acme.sh with --debug to see the PowerShell output."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Certificate for $_cdomain deployed and bound to $_listener on $DEPLOY_WIN_RDP_HOST."
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,500 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy certificates to Zyxel GS1900 series switches
|
||||
#
|
||||
# This script uses the https web administration interface in order
|
||||
# to upload updated certificates to Zyxel GS1900 series switches.
|
||||
# Only a few models have been tested but untested switches from the
|
||||
# same model line may work as well. If you test and confirm a switch
|
||||
# as working please submit a pull request updating this compatibility
|
||||
# list!
|
||||
#
|
||||
# Known Issues:
|
||||
# 1. This is a consumer grade switch and is a bit underpowered
|
||||
# the longer the RSA key size the slower your switch web UI
|
||||
# will be. RSA 2048 will work, RSA 4096 will work but you may
|
||||
# experience performance problems.
|
||||
# 2. You must use RSA certificates. The switch will reject EC-256
|
||||
# and EC-384 certificates in firmware 2.80
|
||||
# See: https://community.zyxel.com/en/discussion/21506/bug-cannot-import-ssl-cert-on-gs1900-8-and-gs1900-24e-firmware-v2-80/
|
||||
#
|
||||
# Current GS1900 Switch Compatibility:
|
||||
# GS1900-8 - Working as of firmware V2.80
|
||||
# GS1900-8HP - Untested
|
||||
# GS1900-10HP - Untested
|
||||
# GS1900-16 - Untested
|
||||
# GS1900-24 - Untested
|
||||
# GS1900-24E - Working as of firmware V2.80
|
||||
# GS1900-24EP - Untested
|
||||
# GS1900-24HP - Untested
|
||||
# GS1900-48 - Untested
|
||||
# GS1900-48HP - Untested
|
||||
#
|
||||
# Prerequisite Setup Steps:
|
||||
# 1. Install at least firmware V2.80 on your switch
|
||||
# 2. Enable HTTPS web management on your switch
|
||||
#
|
||||
# Usage:
|
||||
# 1. Ensure the switch has firmware V2.80 or later.
|
||||
# 2. Ensure the switch has HTTPS management enabled.
|
||||
# 3. Set the appropriate environment variables for your environment.
|
||||
#
|
||||
# DEPLOY_ZYXEL_SWITCH - The switch hostname. (Default: _cdomain)
|
||||
# DEPLOY_ZYXEL_SWITCH_USER - The webadmin user. (Default: admin)
|
||||
# DEPLOY_ZYXEL_SWITCH_PASSWORD - The webadmin password for the switch.
|
||||
# DEPLOY_ZYXEL_SWITCH_REBOOT - If "1" reboot after update. (Default: "0")
|
||||
#
|
||||
# 4. Run the deployment plugin:
|
||||
# acme.sh --deploy --deploy-hook zyxel_gs1900 -d example.com
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
zyxel_gs1900_deploy() {
|
||||
_zyxel_gs1900_minimum_firmware_version="v2.80"
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug2 _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH_USER
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH_REBOOT
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH="$_cdomain"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH_USER" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH_USER="admin"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH_PASSWORD" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH_PASSWORD="1234"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH_REBOOT" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH_REBOOT="0"
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
|
||||
|
||||
_debug DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
|
||||
_debug DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
|
||||
_secure_debug DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
|
||||
_debug DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
|
||||
|
||||
_zyxel_switch_base_uri="https://${DEPLOY_ZYXEL_SWITCH}"
|
||||
|
||||
_info "Beginning to deploy to a Zyxel GS1900 series switch at ${_zyxel_switch_base_uri}."
|
||||
_zyxel_gs1900_deployment_precheck || return $?
|
||||
|
||||
_zyxel_gs1900_should_update
|
||||
if [ "$?" != "0" ]; then
|
||||
_info "The switch already has our certificate installed. No update required."
|
||||
return 0
|
||||
else
|
||||
_info "The switch does not yet have our certificate installed."
|
||||
fi
|
||||
|
||||
_info "Logging into the switch web interface."
|
||||
_zyxel_gs1900_login || return $?
|
||||
|
||||
_info "Validating the switch is compatible with this deployment process."
|
||||
_zyxel_gs1900_validate_device_compatibility || return $?
|
||||
|
||||
_info "Uploading the certificate."
|
||||
_zyxel_gs1900_upload_certificate || return $?
|
||||
|
||||
if [ "$DEPLOY_ZYXEL_SWITCH_REBOOT" = "1" ]; then
|
||||
_info "Rebooting the switch."
|
||||
_zyxel_gs1900_trigger_reboot || return $?
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_zyxel_gs1900_deployment_precheck() {
|
||||
# Initialize the keylength if it isn't already
|
||||
if [ -z "$Le_Keylength" ]; then
|
||||
Le_Keylength=""
|
||||
fi
|
||||
|
||||
if _isEccKey "$Le_Keylength"; then
|
||||
_info "Warning: Zyxel GS1900 switches are not currently known to work with ECC keys!"
|
||||
_info "You can continue, but your switch may reject your key."
|
||||
elif [ -n "$Le_Keylength" ] && [ "$Le_Keylength" -gt "2048" ]; then
|
||||
_info "Warning: Your RSA key length is greater than 2048!"
|
||||
_info "You can continue, but you may experience performance issues in the web administration interface."
|
||||
fi
|
||||
|
||||
# Check the server for some common failure modes prior to authentication and certificate upload in order to avoid
|
||||
# sending a certificate when we may not want to.
|
||||
test_login_response=$(_post "username=test&password=test&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" 2>&1)
|
||||
test_login_page_exitcode="$?"
|
||||
_debug3 "Test Login Response: ${test_login_response}"
|
||||
if [ "$test_login_page_exitcode" -ne "0" ]; then
|
||||
if { [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "60" ]; } || { [ "${ACME_USE_WGET:-0}" = "1" ] && [ "$test_login_page_exitcode" = "5" ]; }; then
|
||||
_err "The SSL certificate at $_zyxel_switch_base_uri could not be validated."
|
||||
_err "Please double check your hostname, port, and that you are actually connecting to your switch."
|
||||
_err "If the problem persists then please ensure that the certificate is not self-signed, has not"
|
||||
_err "expired, and matches the switch hostname. If you expect validation to fail then you can disable"
|
||||
_err "certificate validation by running with --insecure."
|
||||
return 1
|
||||
elif [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "56" ]; then
|
||||
_debug3 "Intentionally ignore curl exit code 56 in our precheck"
|
||||
else
|
||||
_err "Failed to submit the initial login attempt to $_zyxel_switch_base_uri."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
_zyxel_gs1900_login() {
|
||||
# Login to the switch and set the appropriate auth cookie in _H1
|
||||
username_encoded=$(printf "%s" "$DEPLOY_ZYXEL_SWITCH_USER" | _url_encode)
|
||||
password_encoded=$(_zyxel_gs1900_password_obfuscate "$DEPLOY_ZYXEL_SWITCH_PASSWORD" | _url_encode)
|
||||
|
||||
login_response=$(_post "username=${username_encoded}&password=${password_encoded}&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
|
||||
auth_response=$(_post "authId=${login_response}&login_chk=true" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
|
||||
if [ "$auth_response" != "OK" ]; then
|
||||
_err "Login failed due to invalid credentials."
|
||||
_err "Please double check the configured username and password and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sessionid=$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';')
|
||||
_secure_debug2 "sessionid" "$sessionid"
|
||||
|
||||
export _H1="Cookie: $sessionid"
|
||||
_secure_debug2 "_H1" "$_H1"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_zyxel_gs1900_validate_device_compatibility() {
|
||||
# Check the switches model and firmware version and throw errors
|
||||
# if this script isn't compatible.
|
||||
device_info_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12" | tr -d '\n')
|
||||
|
||||
model_name=$(_zyxel_gs1900_get_model "$device_info_html")
|
||||
_debug2 "model_name" "$model_name"
|
||||
if [ -z "$model_name" ]; then
|
||||
_err "Could not find the switch model name."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
return $?
|
||||
fi
|
||||
|
||||
if ! expr "$model_name" : "GS1900-" >/dev/null; then
|
||||
_err "Switch is an unsupported model: $model_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
firmware_version=$(_zyxel_gs1900_get_firmware_version "$device_info_html")
|
||||
_debug2 "firmware_version" "$firmware_version"
|
||||
if [ -z "$firmware_version" ]; then
|
||||
_err "Could not find the switch firmware version."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
return $?
|
||||
fi
|
||||
|
||||
_debug2 "_zyxel_gs1900_minimum_firmware_version" "$_zyxel_gs1900_minimum_firmware_version"
|
||||
minimum_major_version=$(_zyxel_gs1900_parse_major_version "$_zyxel_gs1900_minimum_firmware_version")
|
||||
_debug2 "minimum_major_version" "$minimum_major_version"
|
||||
minimum_minor_version=$(_zyxel_gs1900_parse_minor_version "$_zyxel_gs1900_minimum_firmware_version")
|
||||
_debug2 "minimum_minor_version" "$minimum_minor_version"
|
||||
|
||||
_debug2 "firmware_version" "$firmware_version"
|
||||
firmware_major_version=$(_zyxel_gs1900_parse_major_version "$firmware_version")
|
||||
_debug2 "firmware_major_version" "$firmware_major_version"
|
||||
firmware_minor_version=$(_zyxel_gs1900_parse_minor_version "$firmware_version")
|
||||
_debug2 "firmware_minor_version" "$firmware_minor_version"
|
||||
|
||||
_ret=0
|
||||
if [ "$firmware_major_version" -lt "$minimum_major_version" ]; then
|
||||
_ret=1
|
||||
elif [ "$firmware_major_version" -eq "$minimum_major_version" ] && [ "$firmware_minor_version" -lt "$minimum_minor_version" ]; then
|
||||
_ret=1
|
||||
fi
|
||||
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version."
|
||||
fi
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
_zyxel_gs1900_should_update() {
|
||||
# Get the remote certificate serial number
|
||||
_remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect "${DEPLOY_ZYXEL_SWITCH}:443" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
|
||||
_debug3 "_remote_cert" "$_remote_cert"
|
||||
|
||||
_remote_cert_serial=$(printf "%s" "${_remote_cert}" | ${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial)
|
||||
_debug2 "_remote_cert_serial" "$_remote_cert_serial"
|
||||
|
||||
# Get our certificate serial number
|
||||
_our_cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial <"${_ccert}")
|
||||
_debug2 "_our_cert_serial" "$_our_cert_serial"
|
||||
|
||||
[ "${_remote_cert_serial}" != "${_our_cert_serial}" ]
|
||||
}
|
||||
|
||||
_zyxel_gs1900_upload_certificate() {
|
||||
# Generate a PKCS12 certificate with a temporary password since the web interface
|
||||
# requires a password be present. Then upload that certificate.
|
||||
temp_cert_password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 64)
|
||||
_secure_debug2 "temp_cert_password" "$temp_cert_password"
|
||||
|
||||
temp_pkcs12="$(_mktemp)"
|
||||
_debug2 "temp_pkcs12" "$temp_pkcs12"
|
||||
_toPkcs "$temp_pkcs12" "$_ckey" "$_ccert" "$_cca" "$temp_cert_password"
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Failed to generate a pkcs12 certificate."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
|
||||
# ensure the temporary certificate file is cleaned up
|
||||
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
|
||||
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Load the upload page
|
||||
upload_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5914" | tr -d '\n')
|
||||
|
||||
# Get the first instance of XSSID from the upload page
|
||||
form_xss_value=$(printf "%s" "$upload_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g' | head -n 1)
|
||||
_secure_debug2 "form_xss_value" "$form_xss_value"
|
||||
|
||||
_info "Generating the certificate upload request"
|
||||
upload_post_request="$(_mktemp)"
|
||||
upload_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
{
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"XSSID\"\r\n\r\n%s\r\n" "${form_xss_value}"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"http_file\"; filename=\"temp_pkcs12.pfx\"\r\n"
|
||||
printf "Content-Type: application/pkcs12\r\n\r\n"
|
||||
cat "${temp_pkcs12}"
|
||||
printf "\r\n"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"pwd\"\r\n\r\n%s\r\n" "${temp_cert_password}"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"cmd\"\r\n\r\n%s\r\n" "31"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"sysSubmit\"\r\n\r\n%s\r\n" "Import"
|
||||
printf -- "--%s--\r\n" "${upload_post_boundary}"
|
||||
} >"${upload_post_request}"
|
||||
|
||||
_info "Upload certificate to the switch"
|
||||
|
||||
# Unfortunately we cannot rely upon the switch response across switch models
|
||||
# to return a consistent body return - so we cannot inspect the result of this
|
||||
# upload to determine success.
|
||||
upload_response=$(_zyxel_upload_pkcs12 "${upload_post_request}" "${upload_post_boundary}" 2>&1)
|
||||
_debug3 "Upload response: ${upload_response}"
|
||||
rm "${upload_post_request}"
|
||||
|
||||
# Pause for a few seconds to give the switch a chance to process the certificate
|
||||
# For some reason I've found this to be necessary on my GS1900-24E
|
||||
_debug2 "Waiting 4 seconds for the switch to process the newly uploaded certificate."
|
||||
sleep "4"
|
||||
|
||||
# Check to see whether or not our update was successful
|
||||
_ret=0
|
||||
_zyxel_gs1900_should_update
|
||||
if [ "$?" != "0" ]; then
|
||||
_info "The certificate was updated successfully"
|
||||
else
|
||||
_ret=1
|
||||
_err "The certificate upload does not appear to have worked."
|
||||
_err "The remote certificate does not match the certificate we tried to upload."
|
||||
_err "Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug."
|
||||
fi
|
||||
|
||||
# ensure the temporary files are cleaned up
|
||||
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
|
||||
|
||||
return $_ret
|
||||
}
|
||||
|
||||
# make the certificate upload request using either
|
||||
# --data binary with @ for file access in CURL
|
||||
# or using --post-file for wget to ensure we upload
|
||||
# the pkcs12 without getting tripped up on null bytes
|
||||
#
|
||||
# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker]
|
||||
_zyxel_upload_pkcs12() {
|
||||
bodyfilename="$1"
|
||||
multipartformmarker="$2"
|
||||
_post_url="${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi"
|
||||
httpmethod="POST"
|
||||
_postContentType="multipart/form-data; boundary=${multipartformmarker}"
|
||||
|
||||
if [ -z "$httpmethod" ]; then
|
||||
httpmethod="POST"
|
||||
fi
|
||||
_debug $httpmethod
|
||||
_debug "_post_url" "$_post_url"
|
||||
_debug2 "bodyfilename" "$bodyfilename"
|
||||
_debug2 "_postContentType" "$_postContentType"
|
||||
|
||||
_inithttp
|
||||
|
||||
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
|
||||
_CURL="$_ACME_CURL"
|
||||
if [ "$HTTPS_INSECURE" ]; then
|
||||
_CURL="$_CURL --insecure "
|
||||
fi
|
||||
if [ "$httpmethod" = "HEAD" ]; then
|
||||
_CURL="$_CURL -I "
|
||||
fi
|
||||
_debug "_CURL" "$_CURL"
|
||||
|
||||
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data-binary "@${bodyfilename}" "$_post_url")"
|
||||
|
||||
_ret="$?"
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
|
||||
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
||||
_err "Here is the curl dump log:"
|
||||
_err "$(cat "$_CURL_DUMP")"
|
||||
fi
|
||||
fi
|
||||
elif [ "$_ACME_WGET" ]; then
|
||||
_WGET="$_ACME_WGET"
|
||||
if [ "$HTTPS_INSECURE" ]; then
|
||||
_WGET="$_WGET --no-check-certificate "
|
||||
fi
|
||||
_debug "_WGET" "$_WGET"
|
||||
|
||||
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-file="${bodyfilename}" "$_post_url" 2>"$HTTP_HEADER")"
|
||||
|
||||
_ret="$?"
|
||||
if [ "$_ret" = "8" ]; then
|
||||
_ret=0
|
||||
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
|
||||
fi
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
|
||||
fi
|
||||
if _contains "$_WGET" " -d "; then
|
||||
# Demultiplex wget debug output
|
||||
cat "$HTTP_HEADER" >&2
|
||||
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
|
||||
fi
|
||||
# remove leading whitespaces from header to match curl format
|
||||
_sed_i 's/^ //g' "$HTTP_HEADER"
|
||||
else
|
||||
_ret="$?"
|
||||
_err "Neither curl nor wget have been found, cannot make $httpmethod request."
|
||||
fi
|
||||
_debug "_ret" "$_ret"
|
||||
printf "%s" "$response"
|
||||
return $_ret
|
||||
}
|
||||
|
||||
_zyxel_gs1900_trigger_reboot() {
|
||||
# Trigger a reboot via the management reboot page in the web ui
|
||||
reboot_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888" | tr -d '\n')
|
||||
reboot_xss_value=$(printf "%s" "$reboot_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g')
|
||||
_secure_debug2 "reboot_xss_value" "$reboot_xss_value"
|
||||
|
||||
reboot_response_html=$(_post "XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi" '' "POST" "application/x-www-form-urlencoded")
|
||||
reboot_message=$(printf "%s" "$reboot_response_html" | tr -d '\t\r\n\v\f' | _egrep_o "Rebooting now...")
|
||||
|
||||
if [ -z "$reboot_message" ]; then
|
||||
_err "Failed to trigger switch reboot!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# password
|
||||
_zyxel_gs1900_password_obfuscate() {
|
||||
# Return the password obfuscated via the same method used by the
|
||||
# switch's web UI login process
|
||||
echo "$1" | awk '{
|
||||
encoded = "";
|
||||
password = $1;
|
||||
allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
len = length($1);
|
||||
pwi = length($1);
|
||||
|
||||
for (i=1; i <= (321 - pwi); i++)
|
||||
{
|
||||
if (0 == i % 5 && pwi > 0)
|
||||
{
|
||||
encoded = (encoded)(substr(password, pwi--, 1));
|
||||
}
|
||||
else if (i == 123)
|
||||
{
|
||||
if (len < 10)
|
||||
{
|
||||
encoded = (encoded)(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded = (encoded)(int(len / 10));
|
||||
}
|
||||
}
|
||||
else if (i == 289)
|
||||
{
|
||||
encoded = (encoded)(len % 10)
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1))
|
||||
}
|
||||
}
|
||||
printf("%s", encoded);
|
||||
}'
|
||||
}
|
||||
|
||||
# html label
|
||||
_zyxel_html_table_lookup() {
|
||||
# Look up a value in the html representing the status page of the switch
|
||||
# when provided with the html of the page and the label (i.e. "Model Name:")
|
||||
html="$1"
|
||||
label=$(printf "%s" "$2" | tr -d ' ')
|
||||
lookup_result=$(printf "%s" "$html" | tr -d "\t\r\n\v\f" | sed 's/<tr>/\n<tr>/g' | sed 's/<td[^>]*>/<td>/g' | tr -d ' ' | grep -i "$label" | sed "s/<tr><td>$label<\/td><td>\([^<]\{1,\}\)<\/td><\/tr>/\1/i")
|
||||
printf "%s" "$lookup_result"
|
||||
return 0
|
||||
}
|
||||
|
||||
# html
|
||||
_zyxel_gs1900_get_model() {
|
||||
html="$1"
|
||||
model_name=$(_zyxel_html_table_lookup "$html" "Model Name:")
|
||||
printf "%s" "$model_name"
|
||||
}
|
||||
|
||||
# html
|
||||
_zyxel_gs1900_get_firmware_version() {
|
||||
html="$1"
|
||||
firmware_version=$(_zyxel_html_table_lookup "$html" "Firmware Version:" | _egrep_o "V[^.]+.[^(]+")
|
||||
printf "%s" "$firmware_version"
|
||||
}
|
||||
|
||||
# version_number
|
||||
_zyxel_gs1900_parse_major_version() {
|
||||
printf "%s" "$1" | sed 's/^V\([0-9]\{1,\}\).\{1,\}$/\1/gi'
|
||||
}
|
||||
|
||||
# version_number
|
||||
_zyxel_gs1900_parse_minor_version() {
|
||||
printf "%s" "$1" | sed 's/^.\{1,\}\.\([0-9]\{1,\}\)$/\1/gi'
|
||||
}
|
||||
|
|
@ -1,43 +1,46 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_1984hosting_info='1984.hosting
|
||||
Domains: 1984.is
|
||||
Site: 1984.hosting
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_1984hosting
|
||||
Options:
|
||||
One984HOSTING_Username Username
|
||||
One984HOSTING_Password Password
|
||||
One984HOSTING_TOTP_Secret Base32 TOTP shared secret. Required only if the account has 2FA enabled. Requires oathtool. Used to mint the OTP code automatically at login so cron renewals keep working.
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/2851
|
||||
Author: Adrian Fedoreanu
|
||||
'
|
||||
#This file name is "dns_1984hosting.sh"
|
||||
#So, here must be a method dns_1984hosting_add()
|
||||
#Which will be called by acme.sh to add the txt record to your api system.
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
#Author: Adrian Fedoreanu
|
||||
#Report Bugs here: https://github.com/acmesh-official/acme.sh
|
||||
# or here... https://github.com/acmesh-official/acme.sh/issues/2851
|
||||
#
|
||||
######## Public functions #####################
|
||||
|
||||
# Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
# Add a text record.
|
||||
# Export 1984HOSTING username and password in following variables
|
||||
#
|
||||
# One984HOSTING_Username=username
|
||||
# One984HOSTING_Password=password
|
||||
#
|
||||
# sessionid cookie is saved in ~/.acme.sh/account.conf
|
||||
# username/password need to be set only when changed.
|
||||
|
||||
#Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_1984hosting_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
_info "Add TXT record using 1984Hosting."
|
||||
_info "Add TXT record using 1984Hosting"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
if ! _1984hosting_login; then
|
||||
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file."
|
||||
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "First detect the root zone."
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Invalid domain '$fulldomain'."
|
||||
_err "invalid domain" "$fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
_debug "Add TXT record $fulldomain with value '$txtvalue'."
|
||||
_debug "Add TXT record $fulldomain with value '$txtvalue'"
|
||||
value="$(printf '%s' "$txtvalue" | _url_encode)"
|
||||
url="https://1984.hosting/domains/entry/"
|
||||
|
||||
|
|
@ -50,131 +53,102 @@ dns_1984hosting_add() {
|
|||
_debug2 postdata "$postdata"
|
||||
|
||||
_authpost "$postdata" "$url"
|
||||
if _contains "$_response" '"haserrors": true'; then
|
||||
_err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post."
|
||||
response="$(echo "$_response" | _normalizeJson)"
|
||||
_debug2 response "$response"
|
||||
|
||||
if _contains "$response" '"haserrors": true'; then
|
||||
_err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post"
|
||||
return 1
|
||||
elif _contains "$_response" "html>"; then
|
||||
_err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file."
|
||||
elif _contains "$response" "html>"; then
|
||||
_err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file"
|
||||
return 1
|
||||
elif _contains "$_response" '"auth": false'; then
|
||||
_err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie."
|
||||
elif _contains "$response" '"auth": false'; then
|
||||
_err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Added acme challenge TXT record for $fulldomain at 1984Hosting."
|
||||
_info "Added acme challenge TXT record for $fulldomain at 1984Hosting"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Usage: fulldomain txtvalue
|
||||
# Remove the txt record after validation.
|
||||
#Usage: fulldomain txtvalue
|
||||
#Remove the txt record after validation.
|
||||
dns_1984hosting_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
_info "Delete TXT record using 1984Hosting."
|
||||
_info "Delete TXT record using 1984Hosting"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
if ! _1984hosting_login; then
|
||||
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file."
|
||||
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "First detect the root zone."
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Invalid domain '$fulldomain'."
|
||||
_err "invalid domain" "$fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
_debug "Delete $fulldomain TXT record."
|
||||
_debug "Delete $fulldomain TXT record"
|
||||
|
||||
url="https://1984.hosting/domains"
|
||||
if ! _get_zone_id "$url" "$_domain"; then
|
||||
_err "Invalid zone '$_domain'."
|
||||
_err "invalid zone" "$_domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_htmlget "$url/$_zone_id" "$txtvalue"
|
||||
_debug2 _response "$_response"
|
||||
entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')"
|
||||
_debug2 entry_id "$entry_id"
|
||||
if [ -z "$entry_id" ]; then
|
||||
_err "Error getting TXT entry_id for $1."
|
||||
_err "Error getting TXT entry_id for $1"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_authpost "entry=$entry_id" "$url/delentry/"
|
||||
if ! _contains "$_response" '"ok": true'; then
|
||||
_err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post."
|
||||
response="$(echo "$_response" | _normalizeJson)"
|
||||
_debug2 response "$response"
|
||||
|
||||
if ! _contains "$response" '"ok": true'; then
|
||||
_err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting."
|
||||
_info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting"
|
||||
return 0
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
# usage: _1984hosting_login username password
|
||||
# returns 0 success
|
||||
_1984hosting_login() {
|
||||
if ! _check_credentials; then return 1; fi
|
||||
|
||||
if _check_cookies; then
|
||||
_debug "Already logged in."
|
||||
_debug "Already logged in"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_debug "Login to 1984Hosting as user $One984HOSTING_Username."
|
||||
_debug "Login to 1984Hosting as user $One984HOSTING_Username"
|
||||
username=$(printf '%s' "$One984HOSTING_Username" | _url_encode)
|
||||
password=$(printf '%s' "$One984HOSTING_Password" | _url_encode)
|
||||
url="https://1984.hosting/accounts/checkuserauth/"
|
||||
|
||||
# When 2FA is enabled, mint a fresh TOTP code from the stored shared secret.
|
||||
# Empty otpkey is accepted by the server when 2FA is off.
|
||||
otpkey=""
|
||||
if [ -n "$One984HOSTING_TOTP_Secret" ]; then
|
||||
if ! _exists oathtool; then
|
||||
_err "oathtool is required to use One984HOSTING_TOTP_Secret for 2FA. Please install it."
|
||||
return 1
|
||||
fi
|
||||
otpcode="$(oathtool --base32 --totp "$One984HOSTING_TOTP_Secret" 2>/dev/null)"
|
||||
if [ -z "$otpcode" ]; then
|
||||
_err "Failed to generate TOTP code from One984HOSTING_TOTP_Secret."
|
||||
return 1
|
||||
fi
|
||||
otpkey="$(printf '%s' "$otpcode" | _url_encode)"
|
||||
fi
|
||||
|
||||
# Fetch the login page to obtain CSRF and session cookies.
|
||||
# Note: _get sets the global 'url', so assign the auth URL afterwards.
|
||||
_get "https://1984.hosting/accounts/login/" >/dev/null
|
||||
csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')"
|
||||
sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')"
|
||||
|
||||
if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then
|
||||
_err "One or more cookies are empty: '$csrftoken', '$sessionid'."
|
||||
return 1
|
||||
fi
|
||||
|
||||
export _H1="Cookie: $csrftoken; $sessionid"
|
||||
export _H2="Referer: https://1984.hosting/accounts/login/"
|
||||
csrf_header=$(echo "$csrftoken" | sed 's/csrftoken=//' | _head_n 1)
|
||||
export _H3="X-CSRFToken: $csrf_header"
|
||||
|
||||
url="https://1984.hosting/api/auth/"
|
||||
response="$(_post "username=$username&password=$password&otpkey=$otpkey" "$url")"
|
||||
response="$(_post "username=$username&password=$password&otpkey=" $url)"
|
||||
response="$(echo "$response" | _normalizeJson)"
|
||||
_debug2 response "$response"
|
||||
|
||||
if _contains "$response" '"loggedin": true'; then
|
||||
One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')"
|
||||
One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')"
|
||||
One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
|
||||
One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
|
||||
export One984HOSTING_SESSIONID_COOKIE
|
||||
export One984HOSTING_CSRFTOKEN_COOKIE
|
||||
_saveaccountconf_mutable One984HOSTING_Username "$One984HOSTING_Username"
|
||||
_saveaccountconf_mutable One984HOSTING_Password "$One984HOSTING_Password"
|
||||
if [ -n "$One984HOSTING_TOTP_Secret" ]; then
|
||||
_saveaccountconf_mutable One984HOSTING_TOTP_Secret "$One984HOSTING_TOTP_Secret"
|
||||
else
|
||||
_clearaccountconf_mutable One984HOSTING_TOTP_Secret
|
||||
fi
|
||||
_saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
|
||||
_saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
|
||||
return 0
|
||||
|
|
@ -183,14 +157,9 @@ _1984hosting_login() {
|
|||
}
|
||||
|
||||
_check_credentials() {
|
||||
One984HOSTING_Username="${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}"
|
||||
One984HOSTING_Password="${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}"
|
||||
One984HOSTING_TOTP_Secret="${One984HOSTING_TOTP_Secret:-$(_readaccountconf_mutable One984HOSTING_TOTP_Secret)}"
|
||||
if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then
|
||||
One984HOSTING_Username=""
|
||||
One984HOSTING_Password=""
|
||||
_clearaccountconf_mutable One984HOSTING_Username
|
||||
_clearaccountconf_mutable One984HOSTING_Password
|
||||
_err "You haven't specified 1984Hosting username or password yet."
|
||||
_err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again."
|
||||
return 1
|
||||
|
|
@ -202,43 +171,42 @@ _check_cookies() {
|
|||
One984HOSTING_SESSIONID_COOKIE="${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}"
|
||||
One984HOSTING_CSRFTOKEN_COOKIE="${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}"
|
||||
if [ -z "$One984HOSTING_SESSIONID_COOKIE" ] || [ -z "$One984HOSTING_CSRFTOKEN_COOKIE" ]; then
|
||||
_debug "No cached cookie(s) found."
|
||||
_debug "No cached cookie(s) found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_authget "https://1984.hosting/api/auth/"
|
||||
if _contains "$_response" '"ok": true'; then
|
||||
_debug "Cached cookies still valid."
|
||||
_authget "https://1984.hosting/accounts/loginstatus/"
|
||||
if _contains "$response" '"ok": true'; then
|
||||
_debug "Cached cookies still valid"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_debug "Cached cookies no longer valid. Clearing cookies."
|
||||
_debug "Cached cookies no longer valid"
|
||||
One984HOSTING_SESSIONID_COOKIE=""
|
||||
One984HOSTING_CSRFTOKEN_COOKIE=""
|
||||
_clearaccountconf_mutable One984HOSTING_SESSIONID_COOKIE
|
||||
_clearaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE
|
||||
_saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
|
||||
_saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
|
||||
return 1
|
||||
}
|
||||
|
||||
# _acme-challenge.www.domain.com
|
||||
# Returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
#_acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
_get_root() {
|
||||
domain="$1"
|
||||
i=1
|
||||
p=1
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
|
||||
# not valid
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
return 1
|
||||
fi
|
||||
|
||||
_authget "https://1984.hosting/domains/zonestatus/$h/?cached=no"
|
||||
if _contains "$_response" '"ok": true'; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_authget "https://1984.hosting/domains/soacheck/?zone=$h&nameserver=ns0.1984.is."
|
||||
if _contains "$_response" "serial" && ! _contains "$_response" "null"; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain="$h"
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -248,52 +216,46 @@ _get_root() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Usage: _get_zone_id url domain.com
|
||||
# Returns zone id for domain.com
|
||||
# Memoized per-domain so add/rm don't re-fetch the same zone list within a run.
|
||||
# Keyed on domain (not url) since the url is always the domains listing.
|
||||
#usage: _get_zone_id url domain.com
|
||||
#returns zone id for domain.com
|
||||
_get_zone_id() {
|
||||
url=$1
|
||||
domain=$2
|
||||
if [ "$_zone_id_for" = "$domain" ] && [ -n "$_zone_id" ]; then
|
||||
_debug2 _zone_id "$_zone_id (cached)"
|
||||
return 0
|
||||
fi
|
||||
_htmlget "$url" "$domain"
|
||||
_debug2 _response "$_response"
|
||||
_zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)"
|
||||
_debug2 _zone_id "$_zone_id"
|
||||
if [ -z "$_zone_id" ]; then
|
||||
_err "Error getting _zone_id for $2."
|
||||
_err "Error getting _zone_id for $2"
|
||||
return 1
|
||||
fi
|
||||
_zone_id_for="$domain"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Add extra headers to request
|
||||
# add extra headers to request
|
||||
_authget() {
|
||||
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
|
||||
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
|
||||
_response=$(_get "$1" | _normalizeJson)
|
||||
_debug2 _response "$_response"
|
||||
}
|
||||
|
||||
# Truncate huge HTML response
|
||||
# truncate huge HTML response
|
||||
# echo: Argument list too long
|
||||
_htmlget() {
|
||||
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
|
||||
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
|
||||
_response=$(_get "$1" | grep "$2")
|
||||
if _contains "$_response" "@$2"; then
|
||||
_response=$(echo "$_response" | grep -v "[@]" | _head_n 1)
|
||||
fi
|
||||
_debug2 _response "$_response"
|
||||
}
|
||||
|
||||
# Add extra headers to request
|
||||
# add extra headers to request
|
||||
_authpost() {
|
||||
_get_zone_id "https://1984.hosting/domains" "$_domain"
|
||||
csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | sed 's/csrftoken=//' | _head_n 1)"
|
||||
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
|
||||
url="https://1984.hosting/domains"
|
||||
_get_zone_id "$url" "$_domain"
|
||||
csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")"
|
||||
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
|
||||
export _H2="Referer: https://1984.hosting/domains/$_zone_id"
|
||||
export _H3="X-CSRFToken: $csrf_header"
|
||||
_response="$(_post "$1" "$2" | _normalizeJson)"
|
||||
_debug2 _response "$_response"
|
||||
_response=$(_post "$1" "$2")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_acmedns_info='acme-dns Server API
|
||||
The acme-dns is a limited DNS server with RESTful API to handle ACME DNS challenges.
|
||||
Site: github.com/joohoi/acme-dns
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_acmedns
|
||||
Options:
|
||||
ACMEDNS_USERNAME Username. Optional.
|
||||
ACMEDNS_PASSWORD Password. Optional.
|
||||
ACMEDNS_SUBDOMAIN Subdomain. Optional.
|
||||
ACMEDNS_BASE_URL API endpoint. Default: "https://auth.acme-dns.io".
|
||||
Issues: github.com/dampfklon/acme.sh
|
||||
Author: Wolfgang Ebner, Sven Neubuaer
|
||||
'
|
||||
|
||||
#
|
||||
#Author: Wolfgang Ebner
|
||||
#Author: Sven Neubuaer
|
||||
#Report Bugs here: https://github.com/dampfklon/acme.sh
|
||||
#
|
||||
# Usage:
|
||||
# export ACMEDNS_BASE_URL="https://auth.acme-dns.io"
|
||||
#
|
||||
# You can optionally define an already existing account:
|
||||
#
|
||||
# export ACMEDNS_USERNAME="<username>"
|
||||
# export ACMEDNS_PASSWORD="<password>"
|
||||
# export ACMEDNS_SUBDOMAIN="<subdomain>"
|
||||
#
|
||||
######## Public functions #####################
|
||||
|
||||
#Usage: dns_acmedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
|
|
@ -37,16 +37,6 @@ dns_acmedns_add() {
|
|||
ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}"
|
||||
ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}"
|
||||
|
||||
#for compatibility: old versions stored ACMEDNS_UPDATE_URL in the account
|
||||
#conf (issue 3899). Do not clear it here: it must stay available for the
|
||||
#other domains that have not migrated to their domain conf yet.
|
||||
if [ -z "$ACMEDNS_BASE_URL" ]; then
|
||||
_acmedns_update_url="$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)"
|
||||
if [ "$_acmedns_update_url" ]; then
|
||||
ACMEDNS_BASE_URL="$(echo "$_acmedns_update_url" | sed 's#/update$##')"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$ACMEDNS_BASE_URL" = "" ]; then
|
||||
ACMEDNS_BASE_URL="https://auth.acme-dns.io"
|
||||
fi
|
||||
|
|
@ -81,7 +71,7 @@ dns_acmedns_add() {
|
|||
data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
|
||||
|
||||
_debug data "$data"
|
||||
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST" "application/json")"
|
||||
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
|
||||
_debug response "$response"
|
||||
|
||||
if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
|
||||
|
|
|
|||
18
dnsapi/dns_acmeproxy.sh
Executable file → Normal file
18
dnsapi/dns_acmeproxy.sh
Executable file → Normal file
|
|
@ -1,17 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_acmeproxy_info='AcmeProxy Server API
|
||||
AcmeProxy can be used to as a single host in your network to request certificates through a DNS API.
|
||||
Clients can connect with the one AcmeProxy host so you do not need to store DNS API credentials on every single host.
|
||||
Site: github.com/mdbraber/acmeproxy
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_acmeproxy
|
||||
Options:
|
||||
ACMEPROXY_ENDPOINT API Endpoint
|
||||
ACMEPROXY_USERNAME Username
|
||||
ACMEPROXY_PASSWORD Password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/2251
|
||||
Author: Maarten den Braber
|
||||
'
|
||||
|
||||
## Acmeproxy DNS provider to be used with acmeproxy (https://github.com/mdbraber/acmeproxy)
|
||||
## API integration by Maarten den Braber
|
||||
##
|
||||
## Report any bugs via https://github.com/mdbraber/acme.sh
|
||||
|
||||
dns_acmeproxy_add() {
|
||||
fulldomain="${1}"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_active24_info='Active24.cz
|
||||
Site: Active24.cz
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24
|
||||
Options:
|
||||
Active24_ApiKey API Key. Called "Identifier" in the Active24 Admin
|
||||
Active24_ApiSecret API Secret. Called "Secret key" in the Active24 Admin
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/2059
|
||||
'
|
||||
|
||||
Active24_Api="https://rest.active24.cz"
|
||||
# export Active24_ApiKey=ak48l3h7-ak5d-qn4t-p8gc-b6fs8c3l
|
||||
# export Active24_ApiSecret=ajvkeo3y82ndsu2smvxy3o36496dcascksldncsq
|
||||
#ACTIVE24_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||
|
||||
ACTIVE24_Api="https://api.active24.com"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
# Used to add txt record
|
||||
|
|
@ -22,8 +15,8 @@ dns_active24_add() {
|
|||
_active24_init
|
||||
|
||||
_info "Adding txt record"
|
||||
if _active24_rest POST "/v2/service/$_service_id/dns/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":300}"; then
|
||||
if _contains "$response" "error"; then
|
||||
if _active24_rest POST "dns/$_domain/txt/v1" "{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}"; then
|
||||
if _contains "$response" "errors"; then
|
||||
_err "Add txt record error."
|
||||
return 1
|
||||
else
|
||||
|
|
@ -31,7 +24,6 @@ dns_active24_add() {
|
|||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
_err "Add txt record error."
|
||||
return 1
|
||||
}
|
||||
|
|
@ -45,25 +37,19 @@ dns_active24_rm() {
|
|||
_active24_init
|
||||
|
||||
_debug "Getting txt records"
|
||||
# The API needs to send data in body in order the filter to work
|
||||
# TODO: web can also add content $txtvalue to filter and then get the id from response
|
||||
_active24_rest GET "/v2/service/$_service_id/dns/record" "{\"page\":1,\"descending\":true,\"sortBy\":\"name\",\"rowsPerPage\":100,\"totalRecords\":0,\"filters\":{\"type\":[\"TXT\"],\"name\":\"${_sub_domain}\"}}"
|
||||
#_active24_rest GET "/v2/service/$_service_id/dns/record?rowsPerPage=100"
|
||||
_active24_rest GET "dns/$_domain/records/v1"
|
||||
|
||||
if _contains "$response" "error"; then
|
||||
if _contains "$response" "errors"; then
|
||||
_err "Error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Note: it might never be more than one record actually, NEEDS more INVESTIGATION
|
||||
record_ids=$(printf "%s" "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2)
|
||||
_debug2 record_ids "$record_ids"
|
||||
hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-)
|
||||
|
||||
for redord_id in $record_ids; do
|
||||
_debug "Removing record_id" "$redord_id"
|
||||
_debug "txtvalue" "$txtvalue"
|
||||
if _active24_rest DELETE "/v2/service/$_service_id/dns/record/$redord_id" ""; then
|
||||
if _contains "$response" "error"; then
|
||||
for hash_id in $hash_ids; do
|
||||
_debug "Removing hash_id" "$hash_id"
|
||||
if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then
|
||||
if _contains "$response" "errors"; then
|
||||
_err "Unable to remove txt record."
|
||||
return 1
|
||||
else
|
||||
|
|
@ -77,17 +63,23 @@ dns_active24_rm() {
|
|||
return 1
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
#_acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
# _domain_id=sdjkglgdfewsdfg
|
||||
_get_root() {
|
||||
domain=$1
|
||||
i=1
|
||||
p=1
|
||||
|
||||
if ! _active24_rest GET "/v1/user/self/service"; then
|
||||
if ! _active24_rest GET "dns/domains/v1"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
i=2
|
||||
p=1
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug "h" "$h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
|
|
@ -95,7 +87,7 @@ _get_root() {
|
|||
fi
|
||||
|
||||
if _contains "$response" "\"$h\"" >/dev/null; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -105,98 +97,21 @@ _get_root() {
|
|||
return 1
|
||||
}
|
||||
|
||||
_active24_init() {
|
||||
Active24_ApiKey="${Active24_ApiKey:-$(_readaccountconf_mutable Active24_ApiKey)}"
|
||||
Active24_ApiSecret="${Active24_ApiSecret:-$(_readaccountconf_mutable Active24_ApiSecret)}"
|
||||
#Active24_ServiceId="${Active24_ServiceId:-$(_readaccountconf_mutable Active24_ServiceId)}"
|
||||
|
||||
if [ -z "$Active24_ApiKey" ] || [ -z "$Active24_ApiSecret" ]; then
|
||||
Active24_ApiKey=""
|
||||
Active24_ApiSecret=""
|
||||
_err "You don't specify Active24 api key and ApiSecret yet."
|
||||
_err "Please create your key and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
#save the credentials to the account conf file.
|
||||
_saveaccountconf_mutable Active24_ApiKey "$Active24_ApiKey"
|
||||
_saveaccountconf_mutable Active24_ApiSecret "$Active24_ApiSecret"
|
||||
|
||||
_debug "A24 API CHECK"
|
||||
if ! _active24_rest GET "/v2/check"; then
|
||||
_err "A24 API check failed with: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! echo "$response" | tr -d " " | grep \"verified\":true >/dev/null; then
|
||||
_err "A24 API check failed with: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
_active24_get_service_id "$_domain"
|
||||
_debug _service_id "$_service_id"
|
||||
}
|
||||
|
||||
_active24_get_service_id() {
|
||||
_d=$1
|
||||
if ! _active24_rest GET "/v1/user/self/zone/${_d}"; then
|
||||
return 1
|
||||
else
|
||||
response=$(echo "$response" | _json_decode)
|
||||
_service_id=$(echo "$response" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2)
|
||||
fi
|
||||
}
|
||||
|
||||
_active24_rest() {
|
||||
m=$1
|
||||
ep_qs=$2 # with query string
|
||||
# ep=$2
|
||||
ep=$(printf "%s" "$ep_qs" | cut -d '?' -f1) # no query string
|
||||
ep="$2"
|
||||
data="$3"
|
||||
_debug "$ep"
|
||||
|
||||
_debug "A24 $ep"
|
||||
_debug "A24 $Active24_ApiKey"
|
||||
_debug "A24 $Active24_ApiSecret"
|
||||
|
||||
timestamp=$(_time)
|
||||
datez=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
canonicalRequest="${m} ${ep} ${timestamp}"
|
||||
signature=$(printf "%s" "$canonicalRequest" | _hmac sha1 "$(printf "%s" "$Active24_ApiSecret" | _hex_dump | tr -d " ")" hex)
|
||||
authorization64="$(printf "%s:%s" "$Active24_ApiKey" "$signature" | _base64)"
|
||||
|
||||
export _H1="Date: ${datez}"
|
||||
export _H2="Accept: application/json"
|
||||
export _H3="Content-Type: application/json"
|
||||
export _H4="Authorization: Basic ${authorization64}"
|
||||
|
||||
_debug2 H1 "$_H1"
|
||||
_debug2 H2 "$_H2"
|
||||
_debug2 H3 "$_H3"
|
||||
_debug2 H4 "$_H4"
|
||||
|
||||
# _sleep 1
|
||||
export _H1="Authorization: Bearer $ACTIVE24_Token"
|
||||
|
||||
if [ "$m" != "GET" ]; then
|
||||
_debug2 "${m} $Active24_Api${ep_qs}"
|
||||
_debug "data" "$data"
|
||||
response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")"
|
||||
response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")"
|
||||
else
|
||||
if [ -z "$data" ]; then
|
||||
_debug2 "GET $Active24_Api${ep_qs}"
|
||||
response="$(_get "$Active24_Api${ep_qs}")"
|
||||
else
|
||||
_debug2 "GET $Active24_Api${ep_qs} with data: ${data}"
|
||||
response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")"
|
||||
fi
|
||||
response="$(_get "$ACTIVE24_Api/$ep")"
|
||||
fi
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "error $ep"
|
||||
return 1
|
||||
|
|
@ -204,3 +119,23 @@ _active24_rest() {
|
|||
_debug2 response "$response"
|
||||
return 0
|
||||
}
|
||||
|
||||
_active24_init() {
|
||||
ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}"
|
||||
if [ -z "$ACTIVE24_Token" ]; then
|
||||
ACTIVE24_Token=""
|
||||
_err "You didn't specify a Active24 api token yet."
|
||||
_err "Please create the token and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token"
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_ad_info='AlwaysData.com
|
||||
Site: AlwaysData.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ad
|
||||
Options:
|
||||
AD_API_KEY API Key
|
||||
Issues: github.com/acmesh-official/acme.sh/pull/503
|
||||
Author: Paul Koppen
|
||||
'
|
||||
|
||||
#
|
||||
#AD_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||
|
||||
#This is the Alwaysdata api wrapper for acme.sh
|
||||
#
|
||||
#Author: Paul Koppen
|
||||
#Report Bugs here: https://github.com/wpk-/acme.sh
|
||||
|
||||
AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1"
|
||||
|
||||
|
|
@ -95,7 +94,7 @@ _get_root() {
|
|||
if _ad_rest GET "domain/"; then
|
||||
response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
|
|
@ -106,7 +105,7 @@ _get_root() {
|
|||
if [ "$hostedzone" ]; then
|
||||
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
|
||||
if [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,56 +1,15 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_ali_info='AlibabaCloud.com
|
||||
Domains: Aliyun.com
|
||||
Site: AlibabaCloud.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ali
|
||||
Options:
|
||||
Ali_Key API Key
|
||||
Ali_Secret API Secret
|
||||
'
|
||||
|
||||
# NOTICE:
|
||||
# This file is referenced by Alibaba Cloud Services deploy hooks
|
||||
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
|
||||
# Be careful when modifying this file, especially when making breaking changes for common functions
|
||||
Ali_API="https://alidns.aliyuncs.com/"
|
||||
|
||||
Ali_DNS_API="https://alidns.aliyuncs.com/"
|
||||
#Ali_Key="LTqIA87hOKdjevsf5"
|
||||
#Ali_Secret="0p5EYueFNq501xnCPzKNbx6K51qPH2"
|
||||
|
||||
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_ali_add() {
|
||||
# the API only accepts punycode for IDN domains, and a raw UTF-8 domain
|
||||
# also breaks the request signature (issue 4733)
|
||||
fulldomain=$(_idn "$1")
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
_prepare_ali_credentials || return 1
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Add record"
|
||||
_add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record"
|
||||
}
|
||||
|
||||
dns_ali_rm() {
|
||||
fulldomain=$(_idn "$1")
|
||||
txtvalue=$2
|
||||
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
|
||||
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_clean
|
||||
}
|
||||
|
||||
#################### Alibaba Cloud common functions below ####################
|
||||
|
||||
_prepare_ali_credentials() {
|
||||
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
|
||||
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
|
||||
if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
|
||||
|
|
@ -63,74 +22,38 @@ _prepare_ali_credentials() {
|
|||
#save the api key and secret to the account conf file.
|
||||
_saveaccountconf_mutable Ali_Key "$Ali_Key"
|
||||
_saveaccountconf_mutable Ali_Secret "$Ali_Secret"
|
||||
}
|
||||
|
||||
# act ign mtd
|
||||
_ali_rest() {
|
||||
act="$1"
|
||||
ign="$2"
|
||||
mtd="${3:-GET}"
|
||||
|
||||
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _ali_urlencode_upper)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
|
||||
signature=$(printf "%s" "$signature" | _ali_urlencode_upper)
|
||||
url="$endpoint?Signature=$signature"
|
||||
|
||||
if [ "$mtd" = "GET" ]; then
|
||||
url="$url&$query"
|
||||
response="$(_get "$url")"
|
||||
else
|
||||
response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")"
|
||||
fi
|
||||
|
||||
_ret="$?"
|
||||
_debug2 response "$response"
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Error <$act>"
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$ign" ]; then
|
||||
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
|
||||
if [ "$message" ]; then
|
||||
_err "$message"
|
||||
return 1
|
||||
fi
|
||||
_debug "Add record"
|
||||
_add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record"
|
||||
}
|
||||
|
||||
dns_ali_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
|
||||
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_clean
|
||||
}
|
||||
|
||||
# stdin stdout
|
||||
# The Aliyun signature requires percent-encoding with upper-case hex.
|
||||
# Do not use "_url_encode upper-hex" here: this file is also bundled by
|
||||
# third parties (e.g. Proxmox VE proxmox-acme) whose older copies of the
|
||||
# acme.sh function library ignore the upper-hex argument and output
|
||||
# lower-case hex, which invalidates the signature.
|
||||
# https://github.com/acmesh-official/acme.sh/issues/6272
|
||||
_ali_urlencode_upper() {
|
||||
{
|
||||
_url_encode
|
||||
echo
|
||||
} | sed 's/%a/%A/g;s/%b/%B/g;s/%c/%C/g;s/%d/%D/g;s/%e/%E/g;s/%f/%F/g;s/%\(.\)a/%\1A/g;s/%\(.\)b/%\1B/g;s/%\(.\)c/%\1C/g;s/%\(.\)d/%\1D/g;s/%\(.\)e/%\1E/g;s/%\(.\)f/%\1F/g'
|
||||
}
|
||||
|
||||
_ali_nonce() {
|
||||
if [ "$ACME_OPENSSL_BIN" ]; then
|
||||
"$ACME_OPENSSL_BIN" rand -hex 16 2>/dev/null && return 0
|
||||
fi
|
||||
printf "%s" "$(date +%s)$$$(date +%N)" | _digest sha256 hex | cut -c 1-32
|
||||
}
|
||||
|
||||
_ali_timestamp() {
|
||||
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
|
||||
}
|
||||
|
||||
#################### Private functions below ####################
|
||||
#################### Private functions below ##################################
|
||||
|
||||
_get_root() {
|
||||
domain=$1
|
||||
i=1
|
||||
i=2
|
||||
p=1
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
return 1
|
||||
|
|
@ -142,7 +65,7 @@ _get_root() {
|
|||
fi
|
||||
|
||||
if _contains "$response" "PageNumber"; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_domain="$h"
|
||||
_debug _domain "$_domain"
|
||||
|
|
@ -154,10 +77,52 @@ _get_root() {
|
|||
return 1
|
||||
}
|
||||
|
||||
_ali_rest() {
|
||||
signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
|
||||
signature=$(_ali_urlencode "$signature")
|
||||
url="$Ali_API?$query&Signature=$signature"
|
||||
|
||||
if ! response="$(_get "$url")"; then
|
||||
_err "Error <$1>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 response "$response"
|
||||
if [ -z "$2" ]; then
|
||||
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
|
||||
if [ "$message" ]; then
|
||||
_err "$message"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
_ali_urlencode() {
|
||||
_str="$1"
|
||||
_str_len=${#_str}
|
||||
_u_i=1
|
||||
while [ "$_u_i" -le "$_str_len" ]; do
|
||||
_str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
|
||||
case $_str_c in [a-zA-Z0-9.~_-])
|
||||
printf "%s" "$_str_c"
|
||||
;;
|
||||
*)
|
||||
printf "%%%02X" "'$_str_c"
|
||||
;;
|
||||
esac
|
||||
_u_i="$(_math "$_u_i" + 1)"
|
||||
done
|
||||
}
|
||||
|
||||
_ali_nonce() {
|
||||
#_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
|
||||
#Not so good...
|
||||
date +"%s%N"
|
||||
}
|
||||
|
||||
_check_exist_query() {
|
||||
_qdomain="$1"
|
||||
_qsubdomain="$2"
|
||||
endpoint=$Ali_DNS_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=DescribeDomainRecords'
|
||||
|
|
@ -167,13 +132,12 @@ _check_exist_query() {
|
|||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Timestamp='$(_timestamp)
|
||||
query=$query'&TypeKeyWord=TXT'
|
||||
query=$query'&Version=2015-01-09'
|
||||
}
|
||||
|
||||
_add_record_query() {
|
||||
endpoint=$Ali_DNS_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=AddDomainRecord'
|
||||
|
|
@ -183,14 +147,13 @@ _add_record_query() {
|
|||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Timestamp='$(_timestamp)
|
||||
query=$query'&Type=TXT'
|
||||
query=$query'&Value='$3
|
||||
query=$query'&Version=2015-01-09'
|
||||
}
|
||||
|
||||
_delete_record_query() {
|
||||
endpoint=$Ali_DNS_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=DeleteDomainRecord'
|
||||
|
|
@ -199,12 +162,11 @@ _delete_record_query() {
|
|||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Timestamp='$(_timestamp)
|
||||
query=$query'&Version=2015-01-09'
|
||||
}
|
||||
|
||||
_describe_records_query() {
|
||||
endpoint=$Ali_DNS_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=DescribeDomainRecords'
|
||||
|
|
@ -213,7 +175,7 @@ _describe_records_query() {
|
|||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Timestamp='$(_timestamp)
|
||||
query=$query'&Version=2015-01-09'
|
||||
}
|
||||
|
||||
|
|
@ -235,3 +197,7 @@ _clean() {
|
|||
fi
|
||||
|
||||
}
|
||||
|
||||
_timestamp() {
|
||||
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_alviy_info='Alviy.com
|
||||
Site: Alviy.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_alviy
|
||||
Options:
|
||||
Alviy_token API token. Get it from the https://cloud.alviy.com/token
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/5115
|
||||
'
|
||||
|
||||
Alviy_Api="https://cloud.alviy.com/api/v1"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#Usage: dns_alviy_add _acme-challenge.www.domain.com "content"
|
||||
dns_alviy_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
Alviy_token="${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}"
|
||||
if [ -z "$Alviy_token" ]; then
|
||||
Alviy_token=""
|
||||
_err "Please specify Alviy token."
|
||||
return 1
|
||||
fi
|
||||
|
||||
#save the api key and email to the account conf file.
|
||||
_saveaccountconf_mutable Alviy_token "$Alviy_token"
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
_debug "Getting existing records"
|
||||
if _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then
|
||||
_info "This record already exists, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_add_data="{\"content\":\"$txtvalue\",\"type\":\"TXT\"}"
|
||||
_debug2 _add_data "$_add_data"
|
||||
_info "Adding record"
|
||||
if _alviy_rest POST "zone/$_domain/domain/$fulldomain/" "$_add_data"; then
|
||||
_debug "Checking updated records of '${fulldomain}'"
|
||||
|
||||
if ! _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then
|
||||
_err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
else
|
||||
_err "Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_sleep 10
|
||||
_info "Added TXT record '${txtvalue}' for '${fulldomain}'."
|
||||
return 0
|
||||
}
|
||||
|
||||
#fulldomain
|
||||
dns_alviy_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
Alviy_token="${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}"
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
if ! _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then
|
||||
_info "The record does not exist, skip"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_add_data=""
|
||||
uuid=$(echo "$response" | tr "{" "\n" | grep "$txtvalue" | tr "," "\n" | grep uuid | cut -d \" -f4)
|
||||
# delete record
|
||||
_debug "Delete TXT record for '${fulldomain}'"
|
||||
if ! _alviy_rest DELETE "zone/$_domain/record/$uuid" "{\"confirm\":1}"; then
|
||||
_err "Cannot delete empty TXT record for '$fulldomain'"
|
||||
return 1
|
||||
fi
|
||||
_info "The record '$fulldomain'='$txtvalue' deleted"
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
#_acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
_get_root() {
|
||||
domain=$1
|
||||
i=3
|
||||
a="init"
|
||||
while [ -n "$a" ]; do
|
||||
a=$(printf "%s" "$domain" | cut -d . -f $i-)
|
||||
i=$((i + 1))
|
||||
done
|
||||
n=$((i - 3))
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $n-)
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
_alviy_rest GET "zone/$domain/"
|
||||
_debug "can't get host from $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _alviy_rest GET "zone/$h/"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$response" '"code":"NOT_FOUND"'; then
|
||||
_debug "$h not found"
|
||||
else
|
||||
s=$((n - 1))
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f -$s)
|
||||
_domain="$h"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
_alviy_txt_exists() {
|
||||
zone=$1
|
||||
domain=$2
|
||||
content_data=$3
|
||||
_debug "Getting existing records"
|
||||
|
||||
if ! _alviy_rest GET "zone/$zone/domain/$domain/TXT/"; then
|
||||
_info "The record does not exist"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _contains "$response" "$3"; then
|
||||
_info "The record has other value"
|
||||
return 1
|
||||
fi
|
||||
# GOOD code return - TRUE function
|
||||
return 0
|
||||
}
|
||||
|
||||
_alviy_rest() {
|
||||
method=$1
|
||||
path="$2"
|
||||
content_data="$3"
|
||||
_debug "$path"
|
||||
|
||||
export _H1="Authorization: Bearer $Alviy_token"
|
||||
export _H2="Content-Type: application/json"
|
||||
|
||||
if [ "$content_data" ] || [ "$method" = "DELETE" ]; then
|
||||
_debug "data ($method): " "$content_data"
|
||||
response="$(_post "$content_data" "$Alviy_Api/$path" "" "$method")"
|
||||
else
|
||||
response="$(_get "$Alviy_Api/$path")"
|
||||
fi
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
|
||||
if [ "$_code" = "401" ]; then
|
||||
_err "It seems that your api key or secret is not correct."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_code" != "200" ]; then
|
||||
_err "API call error ($method): $path Response code $_code"
|
||||
fi
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "error on rest call ($method): $path. Response:"
|
||||
_err "$response"
|
||||
return 1
|
||||
fi
|
||||
_debug2 response "$response"
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_anx_info='Anexia.com CloudDNS
|
||||
Site: Anexia.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_anx
|
||||
Options:
|
||||
ANX_Token API Token
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/3238
|
||||
'
|
||||
|
||||
# Anexia CloudDNS acme.sh hook
|
||||
# Author: MA
|
||||
|
||||
#ANX_Token="xxxx"
|
||||
|
||||
ANX_API='https://engine.anexia-it.com/api/clouddns/v1'
|
||||
|
||||
|
|
@ -130,17 +127,18 @@ _get_root() {
|
|||
i=1
|
||||
p=1
|
||||
|
||||
_anx_rest GET "zone.json"
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
return 1
|
||||
fi
|
||||
|
||||
_anx_rest GET "zone.json/${h}"
|
||||
if _contains "$response" "\"name\":\"$h\""; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,177 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_artfiles_info='ArtFiles.de
|
||||
Site: ArtFiles.de
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_artfiles
|
||||
Options:
|
||||
AF_API_USERNAME API Username
|
||||
AF_API_PASSWORD API Password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/4718
|
||||
Author: Martin Arndt <https://troublezone.net/>
|
||||
'
|
||||
|
||||
########## API configuration ###################################################
|
||||
|
||||
AF_API_SUCCESS='status":"OK'
|
||||
AF_URL_DCP='https://dcp.c.artfiles.de/api/'
|
||||
AF_URL_DNS=${AF_URL_DCP}'dns/{*}_dns.html?domain='
|
||||
AF_URL_DOMAINS=${AF_URL_DCP}'domain/get_domains.html'
|
||||
|
||||
########## Public functions ####################################################
|
||||
|
||||
# Adds a new TXT record for given ACME challenge value & domain.
|
||||
# Usage: dns_artfiles_add _acme-challenge.www.example.com "ACME challenge value"
|
||||
dns_artfiles_add() {
|
||||
domain="$1"
|
||||
txtValue="$2"
|
||||
_info 'Using ArtFiles.de DNS addition API…'
|
||||
_debug 'Domain' "$domain"
|
||||
_debug 'txtValue' "$txtValue"
|
||||
|
||||
_set_credentials
|
||||
_saveaccountconf_mutable 'AF_API_USERNAME' "$AF_API_USERNAME"
|
||||
_saveaccountconf_mutable 'AF_API_PASSWORD' "$AF_API_PASSWORD"
|
||||
|
||||
_set_headers
|
||||
_get_zone "$domain"
|
||||
_dns 'GET'
|
||||
if ! _contains "$response" 'TXT'; then
|
||||
_err 'Retrieving TXT records failed.'
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
_clean_records
|
||||
_dns 'SET' "$(printf -- '%s\n_acme-challenge "%s"' "$response" "$txtValue")"
|
||||
if ! _contains "$response" "$AF_API_SUCCESS"; then
|
||||
_err 'Adding ACME challenge value failed.'
|
||||
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Removes the existing TXT record for given ACME challenge value & domain.
|
||||
# Usage: dns_artfiles_rm _acme-challenge.www.example.com "ACME challenge value"
|
||||
dns_artfiles_rm() {
|
||||
domain="$1"
|
||||
txtValue="$2"
|
||||
_info 'Using ArtFiles.de DNS removal API…'
|
||||
_debug 'Domain' "$domain"
|
||||
_debug 'txtValue' "$txtValue"
|
||||
|
||||
_set_credentials
|
||||
_set_headers
|
||||
_get_zone "$domain"
|
||||
if ! _dns 'GET'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _contains "$response" "$txtValue"; then
|
||||
_err 'Retrieved TXT records are missing given ACME challenge value.'
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
_clean_records
|
||||
response="$(printf -- '%s' "$response" | sed '/_acme-challenge "'"$txtValue"'"/d')"
|
||||
_dns 'SET' "$response"
|
||||
if ! _contains "$response" "$AF_API_SUCCESS"; then
|
||||
_err 'Removing ACME challenge value failed.'
|
||||
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
########## Private functions ###################################################
|
||||
|
||||
# Cleans awful TXT records response of ArtFiles's API & pretty prints it.
|
||||
# Usage: _clean_records
|
||||
_clean_records() {
|
||||
_info 'Cleaning TXT records…'
|
||||
# Extract TXT part, strip trailing quote sign (ACME.sh API guidelines forbid
|
||||
# usage of SED's GNU extensions, hence couldn't omit it via regex), strip '\'
|
||||
# from '\"' & turn '\n' into real LF characters.
|
||||
# Yup, awful API to use - but that's all we got to get this working, so… ;)
|
||||
_debug2 'Raw ' "$response"
|
||||
response="$(printf -- '%s' "$response" | sed 's/^.*TXT":"\([^}]*\).*$/\1/;s/,".*$//;s/.$//;s/\\"/"/g;s/\\n/\n/g')"
|
||||
_debug2 'Clean' "$response"
|
||||
}
|
||||
|
||||
# Executes an HTTP GET or POST request for getting or setting DNS records,
|
||||
# containing given payload upon POST.
|
||||
# Usage: _dns [GET | SET] [payload]
|
||||
_dns() {
|
||||
_info 'Executing HTTP request…'
|
||||
action="$1"
|
||||
payload="$(printf -- '%s' "$2" | _url_encode)"
|
||||
url="$(printf -- '%s%s' "$AF_URL_DNS" "$domain" | sed 's/{\*}/'"$(printf -- '%s' "$action" | _lower_case)"'/')"
|
||||
|
||||
if [ "$action" = 'SET' ]; then
|
||||
_debug2 'Payload' "$payload"
|
||||
response="$(_post '' "$url&TXT=$payload" '' 'POST' 'application/x-www-form-urlencoded')"
|
||||
else
|
||||
response="$(_get "$url" '' 10)"
|
||||
fi
|
||||
|
||||
if ! _contains "$response" "$AF_API_SUCCESS"; then
|
||||
_err "DNS API error: $response"
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug 'Response' "$response"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Gets the root domain zone for given domain.
|
||||
# Usage: _get_zone _acme-challenge.www.example.com
|
||||
_get_zone() {
|
||||
fqdn="$1"
|
||||
domains="$(_get "$AF_URL_DOMAINS" '' 10)"
|
||||
_info 'Getting domain zone…'
|
||||
_debug2 'FQDN' "$fqdn"
|
||||
_debug2 'Domains' "$domains"
|
||||
|
||||
while _contains "$fqdn" "."; do
|
||||
if _contains "$domains" "$fqdn"; then
|
||||
domain="$fqdn"
|
||||
_info "Found root domain zone: $domain"
|
||||
break
|
||||
else
|
||||
fqdn="${fqdn#*.}"
|
||||
_debug2 'FQDN' "$fqdn"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$domain" = "$fqdn" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
_err 'Couldn'\''t find root domain zone.'
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Sets the credentials for accessing ArtFiles's API
|
||||
# Usage: _set_credentials
|
||||
_set_credentials() {
|
||||
_info 'Setting credentials…'
|
||||
AF_API_USERNAME="${AF_API_USERNAME:-$(_readaccountconf_mutable AF_API_USERNAME)}"
|
||||
AF_API_PASSWORD="${AF_API_PASSWORD:-$(_readaccountconf_mutable AF_API_PASSWORD)}"
|
||||
if [ -z "$AF_API_USERNAME" ] || [ -z "$AF_API_PASSWORD" ]; then
|
||||
_err 'Missing ArtFiles.de username and/or password.'
|
||||
_err 'Please ensure both are set via export command & try again.'
|
||||
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Adds the HTTP Authorization & Content-Type headers to a follow-up request.
|
||||
# Usage: _set_headers
|
||||
_set_headers() {
|
||||
_info 'Setting headers…'
|
||||
encoded="$(printf -- '%s:%s' "$AF_API_USERNAME" "$AF_API_PASSWORD" | _base64)"
|
||||
export _H1="Authorization: Basic $encoded"
|
||||
export _H2='Content-Type: application/json'
|
||||
}
|
||||
|
|
@ -1,490 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
dns_arubabusiness_info='ArubaBusiness
|
||||
Site: business.aruba.it
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_arubabusiness
|
||||
Options:
|
||||
AB_Key Your ArubaBusiness API Key
|
||||
AB_User Your account user
|
||||
AB_Pass Your account password
|
||||
'
|
||||
|
||||
#
|
||||
# A word of warning: as of this writing, api.arubabusiness.it only supports oauth authentication using the "password" grant type.
|
||||
# If you are REALLY sure you want to use it, it would be wise set up a dedicated technical user without administrative privileges
|
||||
#
|
||||
|
||||
ARUBABUSINESS_API='https://api.arubabusiness.it'
|
||||
|
||||
######## Public functions ########
|
||||
|
||||
#
|
||||
# Usage: dns_arubabusiness_add _acme-challenge.www.domain.com aaaabbbbcccc111122223333
|
||||
#
|
||||
# Add a new TXT record whose name and value match the given domain and value
|
||||
#
|
||||
# Variables
|
||||
# _full_domain: $1 - the name of the TXT record
|
||||
# _txt_value: $2 - the value of the TXT record
|
||||
# _body
|
||||
# dns_details
|
||||
# domain_id
|
||||
# dns_record_id
|
||||
# response
|
||||
#
|
||||
dns_arubabusiness_add() {
|
||||
_full_domain=$1
|
||||
_txt_value=$2
|
||||
|
||||
if ! _ab_authenticate; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _ab_domain_id "$_full_domain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details"; then
|
||||
# This is very unlikely, but allow the process to use the existing record
|
||||
_info "A TXT record with name: $_full_domain and value: $_txt_value already exists (id: $dns_record_id)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_body="{ \"IdDomain\": $domain_id, \"Type\": \"TXT\", \"Name\": \"$_full_domain\", \"Content\": \"\\\"$_txt_value\\\"\" }"
|
||||
|
||||
_debug "Adding TXT record with name: $_full_domain and value: $_txt_value"
|
||||
|
||||
if ! _ab_rest POST "api/domains/dns/record" "$_body" || ! _contains "$response" "DomainId"; then
|
||||
_err "Failed to add TXT record with name: $_full_domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Sleeping 10 seconds to let ArubaBusiness do its magic"
|
||||
_sleep 10
|
||||
|
||||
# Refresh dns details and check that the record was really added
|
||||
if ! _ab_dns_details "$root_domain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details"; then
|
||||
# This should never happen
|
||||
_err "The TXT record with name: $_full_domain was not set"
|
||||
_err "Please check that the dns records are clean"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Added TXT record with id: $dns_record_id"
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: dns_arubabusiness_rm _acme-challenge.www.domain.com aaaabbbbcccc111122223333
|
||||
#
|
||||
# Remove the TXT record whose name and value match the given domain and value
|
||||
#
|
||||
# Variables
|
||||
# _full_domain: $1 - the name of the TXT record
|
||||
# _txt_value: $2 - the value of the TXT record
|
||||
# dns_details
|
||||
# dns_record_id
|
||||
#
|
||||
dns_arubabusiness_rm() {
|
||||
_full_domain=$1
|
||||
_txt_value=$2
|
||||
|
||||
if ! _ab_authenticate; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _ab_domain_id "$_full_domain"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details" || [ -z "$dns_record_id" ]; then
|
||||
_err "Could not retrieve the record id for: $_full_domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Deleting TXT record: $dns_record_id"
|
||||
if ! _ab_rest DELETE "api/domains/dns/record/$dns_record_id" || ! _contains "$response" "DomainId"; then
|
||||
_err "Failed to delete TXT record: $dns_record_id"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Deleted TXT record: $dns_record_id"
|
||||
return 0
|
||||
}
|
||||
|
||||
######## Private functions ########
|
||||
|
||||
#
|
||||
# Usage: _ab_domain_id _acme-challenge.www.domain.com
|
||||
#
|
||||
# Split the input domain into subdomain + root domain and get the id of the root domain
|
||||
#
|
||||
# Variables
|
||||
# _full_domain: $1 - the domain whose root needs to be extracted
|
||||
# _domain_sections
|
||||
# _current_index
|
||||
# _candidate_subdomain
|
||||
# _candidate_domain
|
||||
# sub_domain
|
||||
# root_domain
|
||||
# domain_id
|
||||
# dns_details: a json containing all dns records registered on the root domain
|
||||
#
|
||||
# Example
|
||||
# _get_root _acme-challenge.www.domain.com
|
||||
#
|
||||
# Should return
|
||||
# sub_domain=_acme-challenge.www
|
||||
# root_domain=domain.com
|
||||
# domain_id=123123123123
|
||||
# dns_details="{JSON_CONTENT}"
|
||||
#
|
||||
_ab_domain_id() {
|
||||
_full_domain=$1
|
||||
|
||||
_info "Attempting to retrieve root domain details for: $_full_domain"
|
||||
|
||||
_domain_sections=$(_math "$(printf "%s" "$_full_domain" | tr '.' '\n' | wc -l)" + 1)
|
||||
|
||||
if [ "$_domain_sections" -lt 1 ]; then
|
||||
_err "Invalid input $_full_domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_current_index=1
|
||||
while true; do
|
||||
_candidate_subdomain=$(if [ "$_current_index" = "1" ]; then printf ""; else printf "%s" "$_full_domain" | cut -d . -f 1-"$(_math "$_current_index" - 1)"; fi)
|
||||
_candidate_domain=$(printf "%s" "$_full_domain" | cut -d . -f "$_current_index"-"$_domain_sections")
|
||||
|
||||
if ! _ab_dns_details "$_candidate_domain"; then
|
||||
_debug2 "Could not fetch dns details for: $_candidate_domain"
|
||||
_current_index=$(_math "$_current_index" + 1)
|
||||
|
||||
# Fail if there are no candidates left
|
||||
if [ "$_current_index" -gt "$_domain_sections" ]; then
|
||||
_err "Could not determine the root domain for: $_full_domain"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
sub_domain="$_candidate_subdomain"
|
||||
root_domain="$_candidate_domain"
|
||||
# Extract the domain id, which is an integer and contains no commas
|
||||
domain_id="$(printf "%s" "$dns_details" | _egrep_o '"Id":[^,]*' | _head_n 1 | cut -d : -f 2 | tr -d ' "')"
|
||||
|
||||
if [ -z "$domain_id" ]; then
|
||||
_err "Could not determine the domain id for: $root_domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Retrieved root domain id: $domain_id"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_dns_record_id _acme-challenge.www.domain.com "aaaabbbbcccc111122223333" "{JSON_CONTENT}"
|
||||
#
|
||||
# Extract the record id of the first TXT record whose name and content match the input values
|
||||
#
|
||||
# Variables
|
||||
# _record_name: $1
|
||||
# _txt_value: $2
|
||||
# _dns_details: $3 - the json returned by a previous call to '_ab_dns_details() $root_domain'
|
||||
# _record_ids
|
||||
# _record_names
|
||||
# _record_types
|
||||
# _record_contents
|
||||
# _record_ids_count
|
||||
# _record_names_count
|
||||
# _record_types_count
|
||||
# _record_contents_count
|
||||
# _i
|
||||
# dns_record_id
|
||||
#
|
||||
# Notes
|
||||
# TXT correspond to record type 5
|
||||
# ArubaBusiness appends a terminating dot (.) to the record name
|
||||
# The content field may contain the following character sequence: \"
|
||||
# All record names are always converted to lowercase
|
||||
#
|
||||
_ab_dns_record_id() {
|
||||
_record_name=$1
|
||||
_txt_value=$2
|
||||
_dns_details=$3
|
||||
|
||||
_record_name_lowercase=$(printf "%s" "$_record_name" | _lower_case)
|
||||
|
||||
# Extract the record ids, which are integers and contain no commas, colons or spaces
|
||||
# The first id is skipped because it refers to the domain id
|
||||
_record_ids=$(printf "%s" "$_dns_details" | sed 's/"Id":/\n"Id":/g' | _egrep_o '"Id":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' ' | tr '\n' ' ')
|
||||
|
||||
# Extract the record names, which are strings but cannot contain commas, colons, spaces and quotes
|
||||
# The first name is skipped because it refers to the domain name
|
||||
_record_names=$(printf "%s" "$_dns_details" | sed 's/"Name":/\n"Name":/g' | _egrep_o '"Name":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' "' | tr '\n' ' ')
|
||||
|
||||
# Extract the record types, which are integers (except for the first one) and contain no commas, colons or spaces
|
||||
# The first type is skipped because it refers to the domain type
|
||||
_record_types=$(printf "%s" "$_dns_details" | sed 's/"Type":/\n"Type":/g' | _egrep_o '"Type":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' ' | tr '\n' ' ')
|
||||
|
||||
# Extract the record contents, which are strings and may contain no quotes except for TXT records, which must be delimited by two \" literals
|
||||
# Note: There is no domain related entry here
|
||||
# Note: A " character is appended at the end of each content to make it easier to process the list later
|
||||
_record_contents=$(printf "%s" "$_dns_details" | sed 's/"Content":/\n"Content":/g' | sed 's/\\"//g' | _egrep_o '"Content": *"[^"]*"' | cut -d : -f 2- | sed -n 's/"\(.*\)"/\1/p' | tr '\n' '#')
|
||||
|
||||
_info "IDS: $_record_ids"
|
||||
_info "NAMES: $_record_names"
|
||||
_info "TYPEs: $_record_types"
|
||||
_info "CONTENTS: $_record_contents"
|
||||
|
||||
_record_ids_count=$(printf "%s" "$_record_ids" | tr ' ' '\n' | wc -l)
|
||||
_record_names_count=$(printf "%s" "$_record_names" | tr ' ' '\n' | wc -l)
|
||||
_record_types_count=$(printf "%s" "$_record_types" | tr ' ' '\n' | wc -l)
|
||||
_record_contents_count=$(printf "%s" "$_record_contents" | tr '#' '\n' | wc -l)
|
||||
|
||||
_info "Ids: $_record_ids_count, names: $_record_names_count, types: $_record_types_count, contents: $_record_contents_count"
|
||||
|
||||
if [ "$_record_ids_count" != "$_record_names_count" ] || [ "$_record_ids_count" != "$_record_types_count" ] || [ "$_record_ids_count" != "$_record_contents_count" ]; then
|
||||
_err "Failed to parse record elements. Ids: $_record_ids_count, names: $_record_names_count, types: $_record_types_count, contents: $_record_contents_count"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Looking for a TXT record matching inputs - name: $_record_name_lowercase value: $_txt_value"
|
||||
|
||||
_i=1
|
||||
while [ "$_i" -le "$_record_ids_count" ]; do
|
||||
_current_name=$(printf "%s" "$_record_names" | cut -d " " -f "$_i")
|
||||
_current_type=$(printf "%s" "$_record_types" | cut -d " " -f "$_i")
|
||||
_current_content=$(printf "%s" "$_record_contents" | cut -d "#" -f "$_i")
|
||||
|
||||
if [ "$_record_name_lowercase." = "$_current_name" ] && [ "5" = "$_current_type" ] && [ "$_txt_value" = "$_current_content" ]; then
|
||||
dns_record_id=$(printf "%s" "$_record_ids" | cut -d " " -f "$_i")
|
||||
_info "Found matching record with id: $dns_record_id"
|
||||
return 0
|
||||
else
|
||||
_debug2 "Record does not match - type: '$_current_type' name: '$_current_name' value: '$_current_content'; Expected '$_record_name_lowercase.' '5' '$_txt_value'"
|
||||
fi
|
||||
_i=$(_math "$_i" + 1)
|
||||
done
|
||||
|
||||
_debug2 "No matching record was found in $_dns_details"
|
||||
return 1
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_dns_details domain.com
|
||||
#
|
||||
# Retrieve dns info for the given input domain
|
||||
#
|
||||
# Variables
|
||||
# _domain: $1
|
||||
# dns_details: the json returned by the call to $ARUBABUSINESS_API/api/domains/dns/$_domain/details (if return status is 0)
|
||||
# response
|
||||
#
|
||||
_ab_dns_details() {
|
||||
_domain=$1
|
||||
|
||||
if ! _ab_rest GET "api/domains/dns/$_domain/details" || ! _contains "$response" "DomainId"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
dns_details="$response"
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_authenticate
|
||||
#
|
||||
# Read account conf, update domain conf and perform user authentication to acquire an access token
|
||||
#
|
||||
# Variables
|
||||
# AB_Key
|
||||
# AB_User
|
||||
# AB_Pass
|
||||
# AB_Token
|
||||
#
|
||||
_ab_authenticate() {
|
||||
AB_Key="${AB_Key:-$(_readaccountconf_mutable AB_Key)}"
|
||||
AB_User="${AB_User:-$(_readaccountconf_mutable AB_User)}"
|
||||
AB_Pass="${AB_Pass:-$(_readaccountconf_mutable AB_Pass)}"
|
||||
|
||||
if [ -z "$AB_Key" ] || [ -z "$AB_User" ] || [ -z "$AB_Pass" ]; then
|
||||
AB_Key=""
|
||||
AB_User=""
|
||||
AB_Pass=""
|
||||
_err "Either the ArubaBusiness API key, the user or the password has not been defined yet."
|
||||
_err "Please configure them and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable AB_Key "$AB_Key"
|
||||
_saveaccountconf_mutable AB_User "$AB_User"
|
||||
_saveaccountconf_mutable AB_Pass "$AB_Pass"
|
||||
|
||||
if ! _ab_get_token || [ -z "$AB_Token" ]; then
|
||||
_err "Failed to acquire an access token"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_get_token
|
||||
#
|
||||
# Try acquiring a temporary access token. The token should have a 24h lifespan
|
||||
#
|
||||
# Variables
|
||||
# _ab_user_enc
|
||||
# _ab_pass_enc
|
||||
# _ab_authdata
|
||||
# AB_User
|
||||
# AB_Pass
|
||||
# AB_Token
|
||||
# response
|
||||
# _H2
|
||||
#
|
||||
_ab_get_token() {
|
||||
_ab_user_enc=$(printf "%s" "$AB_User" | _url_encode)
|
||||
_ab_pass_enc=$(printf "%s" "$AB_Pass" | _url_encode)
|
||||
_ab_authdata="grant_type=password&username=$_ab_user_enc&password=$_ab_pass_enc"
|
||||
|
||||
_H2="Content-Type: application/x-www-form-urlencoded"
|
||||
|
||||
if ! _ab_rest POST "auth/token" "$_ab_authdata" || ! _contains "$response" "access_token"; then
|
||||
_err "Authentication failure"
|
||||
return 1
|
||||
fi
|
||||
|
||||
AB_Token="$(printf "%s" "$response" | _egrep_o '"access_token":"[^\"]*"' | cut -d : -f 2 | tr -d '"')"
|
||||
|
||||
if [ -z "$AB_Token" ]; then
|
||||
_err "Could not extract access token"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Acquired access token"
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_rest POST "example/endpoint" "password=123"
|
||||
#
|
||||
# Perform a REST request using the given method, endpoint and data
|
||||
#
|
||||
# Variables
|
||||
# _method: $1 - The http method
|
||||
# _endpoint: $2 - The api path (relative to $ARUBABUSINESS_API)
|
||||
# _data: $3 - The body of the request (optional)
|
||||
# _key_trimmed
|
||||
# _token_trimmed
|
||||
# _ret_code
|
||||
# AB_Key
|
||||
# AB_Token
|
||||
# ARUBABUSINESS_API
|
||||
# _H1
|
||||
# _H2
|
||||
# _H3
|
||||
# _H4
|
||||
#
|
||||
_ab_rest() {
|
||||
_method=$1
|
||||
_endpoint="$2"
|
||||
_data="$3"
|
||||
|
||||
_key_trimmed=$(printf "%s" "$AB_Key" | tr -d '"')
|
||||
_token_trimmed=$(printf "%s" "$AB_Token" | tr -d '"')
|
||||
|
||||
_H1="Accept: application/json"
|
||||
|
||||
if [ -z "$_H2" ]; then
|
||||
# Default to application/json
|
||||
_H2="Content-Type: application/json"
|
||||
fi
|
||||
|
||||
if [ "$_key_trimmed" ]; then
|
||||
_H3="Authorization-Key: $_key_trimmed"
|
||||
else
|
||||
_err "Missing Api Key"
|
||||
_ab_cleanup_headers
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_token_trimmed" ]; then
|
||||
_H4="Authorization: Bearer $_token_trimmed"
|
||||
else
|
||||
_debug "No access token set"
|
||||
fi
|
||||
|
||||
if [ "$_method" != "GET" ]; then
|
||||
response="$(_post "$_data" "$ARUBABUSINESS_API/$_endpoint" "" "$_method")"
|
||||
else
|
||||
response="$(_get "$ARUBABUSINESS_API/$_endpoint")"
|
||||
fi
|
||||
|
||||
_ret_code=$?
|
||||
|
||||
if [ "$_ret_code" = "0" ] && _ab_call_is_success; then
|
||||
# Normalize the json response
|
||||
response="$(printf "%s" "$response" | _normalizeJson)"
|
||||
_ret_code=0
|
||||
else
|
||||
_err "Failed to call endpoint: $_endpoint"
|
||||
_ret_code=1
|
||||
fi
|
||||
|
||||
_ab_cleanup_headers
|
||||
|
||||
return $_ret_code
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_cleanup_headers
|
||||
#
|
||||
# Unset header variables to avoid interfering with other calls
|
||||
#
|
||||
# Variables
|
||||
# _H1
|
||||
# _H2
|
||||
# _H3
|
||||
# _H4
|
||||
#
|
||||
_ab_cleanup_headers() {
|
||||
# Cleanup request headers
|
||||
unset _H1 _H2 _H3 _H4 _H5
|
||||
|
||||
# Cleanup response headers
|
||||
if [ -f "$HTTP_HEADER" ]; then
|
||||
: >"$HTTP_HEADER"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Usage: _ab_call_is_success
|
||||
#
|
||||
# Check whether a call's response http status is one of 200, 201, 202 or 204 (other 2xx are not handled)
|
||||
#
|
||||
# Variables
|
||||
# _status
|
||||
# _http_status
|
||||
# _success_http_codes
|
||||
# HTTP_HEADER
|
||||
#
|
||||
_ab_call_is_success() {
|
||||
_success_http_codes="200 201 202 204"
|
||||
if [ -f "$HTTP_HEADER" ]; then
|
||||
_http_status=$(_egrep_o "^HTTP[\/0-9. ]*" <"$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)
|
||||
for _status in $_success_http_codes; do
|
||||
if [ "$_status" = "$_http_status" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
|
@ -1,16 +1,11 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_arvan_info='ArvanCloud.ir
|
||||
Site: ArvanCloud.ir
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_arvan
|
||||
Options:
|
||||
Arvan_Token API Token
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/2796
|
||||
Author: Vahid Fardi
|
||||
'
|
||||
|
||||
ARVAN_API_URL="https://napi.arvancloud.ir/cdn/4.0/domains"
|
||||
#Arvan_Token="Apikey xxxx"
|
||||
|
||||
ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains"
|
||||
#Author: Vahid Fardi
|
||||
#Report Bugs here: https://github.com/Neilpang/acme.sh
|
||||
#
|
||||
######## Public functions #####################
|
||||
|
||||
#Usage: dns_arvan_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
|
|
@ -23,7 +18,7 @@ dns_arvan_add() {
|
|||
|
||||
if [ -z "$Arvan_Token" ]; then
|
||||
_err "You didn't specify \"Arvan_Token\" token yet."
|
||||
_err "You can get yours from here https://npanel.arvancloud.ir/profile/api-keys"
|
||||
_err "You can get yours from here https://npanel.arvancloud.com/profile/api-keys"
|
||||
return 1
|
||||
fi
|
||||
#save the api token to the account conf file.
|
||||
|
|
@ -45,7 +40,7 @@ dns_arvan_add() {
|
|||
_info "response id is $response"
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
elif _contains "$response" "Record Data is duplicate"; then
|
||||
elif _contains "$response" "Record Data is Duplicated"; then
|
||||
_info "Already exists, OK"
|
||||
return 0
|
||||
else
|
||||
|
|
@ -107,7 +102,7 @@ _get_root() {
|
|||
i=2
|
||||
p=1
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
|
|
@ -120,7 +115,7 @@ _get_root() {
|
|||
if _contains "$response" "\"domain\":\"$h\""; then
|
||||
_domain_id=$(echo "$response" | cut -d : -f 3 | cut -d , -f 1 | tr -d \")
|
||||
if [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -146,7 +141,6 @@ _arvan_rest() {
|
|||
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
|
||||
elif [ "$mtd" = "POST" ]; then
|
||||
export _H2="Content-Type: application/json"
|
||||
export _H3="Accept: application/json"
|
||||
_debug data "$data"
|
||||
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_aurora_info='versio.nl AuroraDNS
|
||||
Domains: pcextreme.nl
|
||||
Site: versio.nl
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_aurora
|
||||
Options:
|
||||
AURORA_Key API Key
|
||||
AURORA_Secret API Secret
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/3459
|
||||
Author: Jasper Zonneveld
|
||||
'
|
||||
|
||||
#
|
||||
#AURORA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||
#
|
||||
#AURORA_Secret="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||
|
||||
AURORA_Api="https://api.auroradns.eu"
|
||||
|
||||
|
|
@ -117,7 +111,7 @@ _get_root() {
|
|||
p=1
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
|
|
@ -132,7 +126,7 @@ _get_root() {
|
|||
_domain_id=$(echo "$response" | _normalizeJson | tr -d "{}" | tr "," "\n" | grep "\"id\": *\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
|
||||
_debug _domain_id "$_domain_id"
|
||||
if [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_autodns_info='InternetX autoDNS
|
||||
InternetX autoDNS XML API
|
||||
Site: InternetX.com/autodns/
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_autodns
|
||||
Options:
|
||||
AUTODNS_USER Username
|
||||
AUTODNS_PASSWORD Password
|
||||
AUTODNS_CONTEXT Context
|
||||
Author: <auerswald@gmail.com>
|
||||
'
|
||||
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
|
||||
|
||||
# This is the InternetX autoDNS xml api wrapper for acme.sh
|
||||
# Author: auerswald@gmail.com
|
||||
# Created: 2018-01-14
|
||||
#
|
||||
# export AUTODNS_USER="username"
|
||||
# export AUTODNS_PASSWORD="password"
|
||||
# export AUTODNS_CONTEXT="context"
|
||||
#
|
||||
# Usage:
|
||||
# acme.sh --issue --dns dns_autodns -d example.com
|
||||
|
||||
AUTODNS_API="https://gateway.autodns.com"
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ _get_autodns_zone() {
|
|||
p=1
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
|
||||
if [ -z "$h" ]; then
|
||||
|
|
@ -128,7 +129,7 @@ _get_autodns_zone() {
|
|||
if _contains "$autodns_response" "<summary>1</summary>" >/dev/null; then
|
||||
_zone="$(echo "$autodns_response" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
|
||||
_system_ns="$(echo "$autodns_response" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
|
@ -139,21 +140,12 @@ _get_autodns_zone() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Escape the XML special characters (& < > ' ") so that credentials
|
||||
# containing them do not break the request document (issue 5317).
|
||||
_autodns_xml_encode() {
|
||||
sed "s/&/\&/g;s/</\</g;s/>/\>/g;s/'/\'/g;s/\"/\"/g"
|
||||
}
|
||||
|
||||
_build_request_auth_xml() {
|
||||
_autodns_user_xml="$(printf "%s" "$AUTODNS_USER" | _autodns_xml_encode)"
|
||||
_autodns_password_xml="$(printf "%s" "$AUTODNS_PASSWORD" | _autodns_xml_encode)"
|
||||
_autodns_context_xml="$(printf "%s" "$AUTODNS_CONTEXT" | _autodns_xml_encode)"
|
||||
printf "<auth>
|
||||
<user>%s</user>
|
||||
<password>%s</password>
|
||||
<context>%s</context>
|
||||
</auth>" "$_autodns_user_xml" "$_autodns_password_xml" "$_autodns_context_xml"
|
||||
</auth>" "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT"
|
||||
}
|
||||
|
||||
# Arguments:
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_aws_info='Amazon AWS Route53 domain API
|
||||
Site: docs.aws.amazon.com/route53/
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_aws
|
||||
Options:
|
||||
AWS_ACCESS_KEY_ID API Key ID
|
||||
AWS_SECRET_ACCESS_KEY API Secret
|
||||
'
|
||||
|
||||
# All `_sleep` commands are included to avoid Route53 throttling, see
|
||||
# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
|
||||
#
|
||||
#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||
#
|
||||
#AWS_SECRET_ACCESS_KEY="xxxxxxx"
|
||||
|
||||
# Updated from "route53.amazonaws.com"
|
||||
AWS_HOST="route53.global.api.aws"
|
||||
#This is the Amazon Route53 api wrapper for acme.sh
|
||||
#All `_sleep` commands are included to avoid Route53 throttling, see
|
||||
#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
|
||||
|
||||
AWS_HOST="route53.amazonaws.com"
|
||||
AWS_URL="https://$AWS_HOST"
|
||||
|
||||
AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
|
||||
|
|
@ -148,6 +145,7 @@ dns_aws_rm() {
|
|||
fi
|
||||
_sleep 1
|
||||
return 1
|
||||
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
|
@ -159,10 +157,10 @@ _get_root() {
|
|||
|
||||
# iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100 | sed 's/\./\\./g')
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug "Checking domain: $h"
|
||||
if [ -z "$h" ]; then
|
||||
_err "invalid domain"
|
||||
_error "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
@ -175,7 +173,7 @@ _get_root() {
|
|||
if [ "$hostedzone" ]; then
|
||||
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>")
|
||||
if [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -209,40 +207,24 @@ _use_container_role() {
|
|||
}
|
||||
|
||||
_use_instance_role() {
|
||||
_instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
|
||||
if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then
|
||||
_debug "Using IMDSv2"
|
||||
_token_url="http://169.254.169.254/latest/api/token"
|
||||
export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600"
|
||||
_token="$(_post "" "$_token_url" "" "PUT")"
|
||||
_secure_debug3 "_token" "$_token"
|
||||
if [ -z "$_token" ]; then
|
||||
_debug "Unable to fetch IMDSv2 token from instance metadata"
|
||||
return 1
|
||||
fi
|
||||
export _H1="X-aws-ec2-metadata-token: $_token"
|
||||
fi
|
||||
|
||||
if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then
|
||||
_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
_debug "_url" "$_url"
|
||||
if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then
|
||||
_debug "Unable to fetch IAM role from instance metadata"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_instance_role_name=$(_get "$_instance_role_name_url" "" 1)
|
||||
_debug "_instance_role_name" "$_instance_role_name"
|
||||
_use_metadata "$_instance_role_name_url$_instance_role_name" "$_token"
|
||||
|
||||
_aws_role=$(_get "$_url" "" 1)
|
||||
_debug "_aws_role" "$_aws_role"
|
||||
_use_metadata "$_url$_aws_role"
|
||||
}
|
||||
|
||||
_use_metadata() {
|
||||
export _H1="X-aws-ec2-metadata-token: $2"
|
||||
_aws_creds="$(
|
||||
_get "$1" "" 1 |
|
||||
_normalizeJson |
|
||||
tr '{,}' '\n' |
|
||||
while read -r _line; do
|
||||
_key="$(echo "${_line%%:*}" | tr -d '\"')"
|
||||
_key="$(echo "${_line%%:*}" | tr -d '"')"
|
||||
_value="${_line#*:}"
|
||||
_debug3 "_key" "$_key"
|
||||
_secure_debug3 "_value" "$_value"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_azion_info='Azion.om
|
||||
Site: Azion.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_azion
|
||||
Options:
|
||||
AZION_Email Email
|
||||
AZION_Password Password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/3555
|
||||
'
|
||||
|
||||
#
|
||||
#AZION_Email=""
|
||||
#AZION_Password=""
|
||||
#
|
||||
|
||||
AZION_Api="https://api.azionapi.net"
|
||||
|
||||
|
|
@ -100,7 +96,7 @@ _get_root() {
|
|||
fi
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
# not valid
|
||||
|
|
@ -111,7 +107,7 @@ _get_root() {
|
|||
_domain_id=$(echo "$response" | tr '{' "\n" | grep "\"domain\":\"$h\"" | _egrep_o "\"id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
_debug _domain_id "$_domain_id"
|
||||
if [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,25 +1,13 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_azure_info='Azure
|
||||
Site: Azure.microsoft.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_azure
|
||||
Options:
|
||||
AZUREDNS_SUBSCRIPTIONID Subscription ID
|
||||
AZUREDNS_TENANTID Tenant ID
|
||||
AZUREDNS_APPID App ID. App ID of the service principal
|
||||
AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal
|
||||
AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false"
|
||||
AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Optional.
|
||||
'
|
||||
|
||||
wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS
|
||||
WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
# Used to add txt record
|
||||
#
|
||||
# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/create-or-update?view=rest-dns-2018-05-01&tabs=HTTP
|
||||
# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate
|
||||
#
|
||||
|
||||
dns_azure_add() {
|
||||
|
|
@ -32,7 +20,6 @@ dns_azure_add() {
|
|||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure Subscription ID"
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -47,45 +34,37 @@ dns_azure_add() {
|
|||
_saveaccountconf_mutable AZUREDNS_TENANTID ""
|
||||
_saveaccountconf_mutable AZUREDNS_APPID ""
|
||||
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET ""
|
||||
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN ""
|
||||
else
|
||||
_info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token"
|
||||
_info "You didn't ask to use Azure managed identity, checking service principal credentials"
|
||||
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
|
||||
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
|
||||
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
|
||||
AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
|
||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||
if [ -z "$AZUREDNS_TENANTID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure Tenant ID "
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_APPID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure App ID"
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$AZUREDNS_TENANTID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
_err "You didn't specify the Azure Tenant ID "
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure Client Secret"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_info "Using provided bearer token"
|
||||
if [ -z "$AZUREDNS_APPID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
_err "You didn't specify the Azure App ID"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
_err "You didn't specify the Azure Client Secret"
|
||||
return 1
|
||||
fi
|
||||
|
||||
#save account details to account conf file, don't opt in for azure manages identity check.
|
||||
|
|
@ -93,14 +72,9 @@ dns_azure_add() {
|
|||
_saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
|
||||
_saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
|
||||
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
|
||||
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$AZUREDNS_BEARERTOKEN"
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
|
||||
else
|
||||
accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g")
|
||||
fi
|
||||
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
|
||||
|
||||
if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
|
||||
_err "invalid domain"
|
||||
|
|
@ -150,7 +124,7 @@ dns_azure_add() {
|
|||
# Usage: fulldomain txtvalue
|
||||
# Used to remove the txt record after validation
|
||||
#
|
||||
# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/delete?view=rest-dns-2018-05-01&tabs=HTTP
|
||||
# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete
|
||||
#
|
||||
dns_azure_rm() {
|
||||
fulldomain=$1
|
||||
|
|
@ -162,7 +136,6 @@ dns_azure_rm() {
|
|||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure Subscription ID "
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -171,51 +144,40 @@ dns_azure_rm() {
|
|||
if [ "$AZUREDNS_MANAGEDIDENTITY" = true ]; then
|
||||
_info "Using Azure managed identity"
|
||||
else
|
||||
_info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token"
|
||||
_info "You didn't ask to use Azure managed identity, checking service principal credentials"
|
||||
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
|
||||
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
|
||||
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
|
||||
AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
|
||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||
if [ -z "$AZUREDNS_TENANTID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure Tenant ID "
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_APPID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure App ID"
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$AZUREDNS_TENANTID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
_err "You didn't specify the Azure Tenant ID "
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
AZUREDNS_BEARERTOKEN=""
|
||||
_err "You didn't specify the Azure Client Secret"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_info "Using provided bearer token"
|
||||
if [ -z "$AZUREDNS_APPID" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
_err "You didn't specify the Azure App ID"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
|
||||
AZUREDNS_SUBSCRIPTIONID=""
|
||||
AZUREDNS_TENANTID=""
|
||||
AZUREDNS_APPID=""
|
||||
AZUREDNS_CLIENTSECRET=""
|
||||
_err "You didn't specify the Azure Client Secret"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
|
||||
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
|
||||
else
|
||||
accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g")
|
||||
fi
|
||||
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
|
||||
|
||||
if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
|
||||
_err "invalid domain"
|
||||
|
|
@ -294,10 +256,10 @@ _azure_rest() {
|
|||
if [ "$_code" = "401" ]; then
|
||||
# we have an invalid access token set to expired
|
||||
_saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "0"
|
||||
_err "Access denied. Invalid access token. Make sure your Azure settings are correct. See: $wiki"
|
||||
_err "access denied make sure your Azure settings are correct. See $WIKI"
|
||||
return 1
|
||||
fi
|
||||
# See https://learn.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes
|
||||
# See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes
|
||||
if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then
|
||||
_request_retry_times="$(_math "$_request_retry_times" + 1)"
|
||||
_info "REST call error $_code retrying $ep in $_request_retry_times s"
|
||||
|
|
@ -315,14 +277,14 @@ _azure_rest() {
|
|||
return 0
|
||||
}
|
||||
|
||||
## Ref: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#request-an-access-token
|
||||
## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token
|
||||
_azure_getaccess_token() {
|
||||
managedIdentity=$1
|
||||
tenantID=$2
|
||||
clientID=$3
|
||||
clientSecret=$4
|
||||
|
||||
accesstoken="${AZUREDNS_ACCESSTOKEN:-$(_readaccountconf_mutable AZUREDNS_ACCESSTOKEN)}"
|
||||
accesstoken="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
|
||||
expires_on="${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}"
|
||||
|
||||
# can we reuse the bearer token?
|
||||
|
|
@ -339,18 +301,9 @@ _azure_getaccess_token() {
|
|||
_debug "getting new bearer token"
|
||||
|
||||
if [ "$managedIdentity" = true ]; then
|
||||
# https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
|
||||
if [ -n "$IDENTITY_ENDPOINT" ]; then
|
||||
# Some Azure environments may set IDENTITY_ENDPOINT (formerly MSI_ENDPOINT) to have an alternative metadata endpoint
|
||||
url="$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/"
|
||||
headers="X-IDENTITY-HEADER: $IDENTITY_HEADER"
|
||||
else
|
||||
url="http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
|
||||
headers="Metadata: true"
|
||||
fi
|
||||
|
||||
export _H1="$headers"
|
||||
response="$(_get "$url")"
|
||||
# https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
|
||||
export _H1="Metadata: true"
|
||||
response="$(_get http://169.254.169.254/metadata/identity/oauth2/token\?api-version=2018-02-01\&resource=https://management.azure.com/)"
|
||||
response="$(echo "$response" | _normalizeJson)"
|
||||
accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
|
|
@ -368,14 +321,14 @@ _azure_getaccess_token() {
|
|||
fi
|
||||
|
||||
if [ -z "$accesstoken" ]; then
|
||||
_err "No acccess token received. Check your Azure settings. See: $wiki"
|
||||
_err "no acccess token received. Check your Azure settings see $WIKI"
|
||||
return 1
|
||||
fi
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "error $response"
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable AZUREDNS_ACCESSTOKEN "$accesstoken"
|
||||
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$accesstoken"
|
||||
_saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "$expires_on"
|
||||
printf "%s" "$accesstoken"
|
||||
return 0
|
||||
|
|
@ -388,18 +341,15 @@ _get_root() {
|
|||
i=1
|
||||
p=1
|
||||
|
||||
## Ref: https://learn.microsoft.com/en-us/rest/api/dns/zones/list?view=rest-dns-2018-05-01&tabs=HTTP
|
||||
## returns up to 100 zones in one response. Handling more results is not implemented
|
||||
## (ZoneListResult with continuation token for the next page of results)
|
||||
##
|
||||
## TODO: handle more than 100 results, as per:
|
||||
## https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-dns-limits
|
||||
## The new limit is 250 Public DNS zones per subscription, while the old limit was only 100
|
||||
## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
|
||||
## returns up to 100 zones in one response therefore handling more results is not not implemented
|
||||
## (ZoneListResult with continuation token for the next page of results)
|
||||
## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
|
||||
##
|
||||
_azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\$top=500&api-version=2017-09-01" "" "$accesstoken"
|
||||
# Find matching domain name in Json response
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug2 "Checking domain: $h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
|
|
@ -414,7 +364,7 @@ _get_root() {
|
|||
#create the record at the domain apex (@) if only the domain name was provided as --domain-alias
|
||||
_sub_domain="@"
|
||||
else
|
||||
_sub_domain=$(echo "$domain" | cut -d . -f 1-"$p")
|
||||
_sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
|
||||
fi
|
||||
_domain=$h
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -1,775 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
# Global variables for returning results (avoid stdout pollution from logging)
|
||||
_BAIDU_FIND_RESULT=""
|
||||
_BAIDU_BCE_AUTH_RESULT=""
|
||||
|
||||
: "${BAIDU_LOG_LEVEL:=2}"
|
||||
|
||||
_baidu_log_ts() {
|
||||
date
|
||||
}
|
||||
|
||||
_baidu_log_ge() {
|
||||
_want="$1"
|
||||
[ "${BAIDU_LOG_LEVEL:-0}" -ge "$_want" ]
|
||||
}
|
||||
|
||||
_baidu_log() {
|
||||
_lvl="$1"
|
||||
_tag="$2"
|
||||
_msg="$3"
|
||||
if [ "$_lvl" = "0" ] || _baidu_log_ge "$_lvl"; then
|
||||
printf -- "[%s] %s %s\n" "$(_baidu_log_ts)" "$_tag" "$_msg"
|
||||
fi
|
||||
}
|
||||
|
||||
_baidu_err() {
|
||||
_baidu_log 0 "baidu_bcd.err" "$1"
|
||||
return 1
|
||||
}
|
||||
|
||||
_baidu_info() {
|
||||
_baidu_log 1 "baidu_bcd.info" "$1"
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_debug() {
|
||||
_baidu_log 2 "$1" "$2"
|
||||
return 0
|
||||
}
|
||||
|
||||
dns_baidu_info='Baidu Cloud BCD DNS
|
||||
Site: cloud.baidu.com
|
||||
Docs: https://cloud.baidu.com/doc/BCD/
|
||||
Signature: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
|
||||
Options:
|
||||
Baidu_AK AccessKeyId
|
||||
Baidu_SK SecretAccessKey
|
||||
OptionsAlt:
|
||||
Baidu_BCD_Host API host, default: bcd.baidubce.com
|
||||
Baidu_DNS_Host New DNS API host, default: dns.baidubce.com
|
||||
Baidu_API_Preference Engine preference, default: auto
|
||||
Baidu_BCD_Version API version number, default: 1
|
||||
Baidu_BCD_Expire Signature expiration seconds, default: 3600
|
||||
Baidu_View Resolve view, default: DEFAULT
|
||||
Baidu_Line New DNS line, default: default
|
||||
Baidu_TTL Resolve ttl seconds, default: 300
|
||||
Baidu_RM_Max Max records to delete in one run, default: 20
|
||||
'
|
||||
|
||||
BAIDU_BCD_DEFAULT_HOST="bcd.baidubce.com"
|
||||
BAIDU_DNS_DEFAULT_HOST="dns.baidubce.com"
|
||||
|
||||
# --- Public API ---
|
||||
dns_baidu_add() {
|
||||
fulldomain=$(_idn "$1")
|
||||
txtvalue=$2
|
||||
|
||||
if ! _baidu_run_with_fallback "add" "$fulldomain" "$txtvalue"; then
|
||||
_baidu_err "all baidu api engines failed for add: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dns_baidu_rm() {
|
||||
fulldomain=$(_idn "$1")
|
||||
txtvalue=$2
|
||||
|
||||
if ! _baidu_run_with_fallback "rm" "$fulldomain" "$txtvalue"; then
|
||||
_baidu_err "all baidu api engines failed for delete: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_run_with_fallback() {
|
||||
_action="$1"
|
||||
_fulldomain="$2"
|
||||
_txtvalue="$3"
|
||||
|
||||
if ! _baidu_load_credentials; then
|
||||
_baidu_err "baidu_load_credentials failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for _baidu_api_engine in $(_baidu_engine_order); do
|
||||
if ! _baidu_prepare_record "$_fulldomain"; then
|
||||
_baidu_info "prepare failed for engine: $_baidu_api_engine"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$_action" = "add" ]; then
|
||||
if _baidu_add_record "$_txtvalue"; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if _baidu_rm_record "$_txtvalue"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
_baidu_info "engine failed, try next if available: $_baidu_api_engine"
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
_baidu_engine_order() {
|
||||
_pref="$(_lower_case "$(_baidu_trim_ws "${Baidu_API_Preference:-auto}")")"
|
||||
case "$_pref" in
|
||||
legacy)
|
||||
printf "%s" "legacy new"
|
||||
;;
|
||||
new)
|
||||
printf "%s" "new legacy"
|
||||
;;
|
||||
*)
|
||||
printf "%s" "new legacy"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_baidu_add_record() {
|
||||
_txtvalue="$1"
|
||||
|
||||
if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
|
||||
_baidu_err "baidu_find_record_ids failed for add: $_record_domain.$_zone_name"
|
||||
return 1
|
||||
fi
|
||||
_existing_ids="$_BAIDU_FIND_RESULT"
|
||||
if [ "$_existing_ids" ]; then
|
||||
_baidu_info "txt exists, skip add: $_record_domain.$_zone_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_ttl="${Baidu_TTL:-300}"
|
||||
_ttl="$(_baidu_trim_ws "$_ttl")"
|
||||
case "$_ttl" in
|
||||
"" | *[!0-9]*)
|
||||
_ttl="300"
|
||||
;;
|
||||
esac
|
||||
|
||||
txtvalue="$(_baidu_trim_ws "$_txtvalue")"
|
||||
_record_domain="$(_baidu_trim_ws "$_record_domain")"
|
||||
_zone_name="$(_baidu_trim_ws "$_zone_name")"
|
||||
|
||||
if [ "$_baidu_api_engine" = "new" ]; then
|
||||
_line="$(_baidu_trim_ws "${Baidu_Line:-default}")"
|
||||
if [ -z "$_line" ]; then
|
||||
_line="default"
|
||||
fi
|
||||
_body="$(_baidu_payload_add_txt_dns "$_record_domain" "$txtvalue" "$_ttl" "$_line")"
|
||||
if ! _baidu_dns_call "POST" "/v1/dns/zone/${_zone_name}/record" "$_body"; then
|
||||
_baidu_err "baidu_dns_call failed: add record"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_view="$(_baidu_trim_ws "${Baidu_View:-DEFAULT}")"
|
||||
_body="$(_baidu_payload_add_txt "$_zone_name" "$_record_domain" "$txtvalue" "$_ttl" "$_view")"
|
||||
if ! _baidu_bcd_post "/domain/resolve/add" "$_body"; then
|
||||
_baidu_err "baidu_bcd_post failed: add record"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if _baidu_is_api_error "$response"; then
|
||||
_baidu_err "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_rm_record() {
|
||||
_txtvalue="$1"
|
||||
|
||||
if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
|
||||
_baidu_err "baidu_find_record_ids failed for delete: $_record_domain.$_zone_name"
|
||||
return 1
|
||||
fi
|
||||
_ids="$_BAIDU_FIND_RESULT"
|
||||
if [ -z "$_ids" ]; then
|
||||
_baidu_info "no matching txt to delete: $_record_domain.$_zone_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_rm_max="${Baidu_RM_Max:-20}"
|
||||
_rm_max="$(_baidu_trim_ws "$_rm_max")"
|
||||
case "$_rm_max" in
|
||||
"" | *[!0-9]*)
|
||||
_rm_max="20"
|
||||
;;
|
||||
esac
|
||||
_rm_cnt="$(printf "%s\n" "$_ids" | sed '/^$/d' | wc -l | tr -d ' ')"
|
||||
if [ "$_rm_cnt" ] && [ "$_rm_cnt" -gt "$_rm_max" ]; then
|
||||
_baidu_err "Refusing to delete $_rm_cnt records (limit: $_rm_max)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for _rid in $_ids; do
|
||||
if [ "$_baidu_api_engine" = "new" ]; then
|
||||
if ! _baidu_dns_call "DELETE" "/v1/dns/zone/${_zone_name}/record/${_rid}" ""; then
|
||||
_baidu_err "baidu_dns_call failed: delete recordId=$_rid"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_body="$(_baidu_payload_delete "$_zone_name" "$_rid")"
|
||||
if ! _baidu_bcd_post "/domain/resolve/delete" "$_body"; then
|
||||
_baidu_err "baidu_bcd_post failed: delete recordId=$_rid"
|
||||
return 1
|
||||
fi
|
||||
if _baidu_is_api_error "$response"; then
|
||||
_baidu_err "$response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$_baidu_api_engine" = "legacy" ]; then
|
||||
if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
|
||||
_baidu_err "baidu_find_record_ids failed for delete verify: $_record_domain.$_zone_name"
|
||||
return 1
|
||||
fi
|
||||
_left_ids="$_BAIDU_FIND_RESULT"
|
||||
if [ -z "$_left_ids" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$_left_ids" ]; then
|
||||
_baidu_err "delete verification failed: $_record_domain.$_zone_name still has TXT records"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Config / Record Context ---
|
||||
_baidu_load_credentials() {
|
||||
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
|
||||
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
|
||||
|
||||
Baidu_AK="$(_baidu_trim_ws "$Baidu_AK")"
|
||||
Baidu_SK="$(_baidu_trim_ws "$Baidu_SK")"
|
||||
|
||||
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
|
||||
_baidu_err "Baidu_AK and Baidu_SK are required"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
|
||||
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
|
||||
|
||||
BAIDU_BCD_HOST="${Baidu_BCD_Host:-$BAIDU_BCD_DEFAULT_HOST}"
|
||||
BAIDU_DNS_HOST="${Baidu_DNS_Host:-$BAIDU_DNS_DEFAULT_HOST}"
|
||||
BAIDU_BCD_VERSION="${Baidu_BCD_Version:-1}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_prepare_record() {
|
||||
_fulldomain="$1"
|
||||
if [ "$_baidu_api_engine" = "new" ]; then
|
||||
if ! _baidu_get_root_dns "$_fulldomain"; then
|
||||
_baidu_err "Could not find zone by new dns api for $_fulldomain"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
if ! _baidu_get_root "$_fulldomain"; then
|
||||
_baidu_err "Could not find zone by legacy bcd api for $_fulldomain"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
_record_domain="$_sub_domain"
|
||||
_zone_name="$_domain"
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Zone / Records ---
|
||||
_baidu_get_root() {
|
||||
domain=$1
|
||||
i=1
|
||||
p=1
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
if [ -z "$h" ]; then
|
||||
_baidu_err "invalid domain: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$h" 1 1)"; then
|
||||
_baidu_err "baidu_bcd_post failed: list zones"
|
||||
return 1
|
||||
fi
|
||||
if ! _baidu_is_api_error "$response" && (_contains "$response" "\"totalCount\"" || _contains "$response" "\"result\""); then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_domain=$h
|
||||
if [ "$_sub_domain" = "$_domain" ]; then
|
||||
_sub_domain="@"
|
||||
fi
|
||||
_baidu_info "zone matched: $_domain (host: $_sub_domain)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
p=$i
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
}
|
||||
|
||||
_baidu_get_root_dns() {
|
||||
domain=$1
|
||||
i=1
|
||||
p=1
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
if [ -z "$h" ]; then
|
||||
_baidu_err "invalid domain: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _baidu_dns_call "GET" "/v1/dns/zone/${h}/record" ""; then
|
||||
_baidu_info "baidu_dns_call failed: list zones"
|
||||
elif ! _baidu_is_api_error "$response" && (_contains "$response" "\"records\"" || _contains "$response" "\"maxKeys\""); then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_domain=$h
|
||||
if [ "$_sub_domain" = "$_domain" ]; then
|
||||
_sub_domain="@"
|
||||
fi
|
||||
_baidu_info "zone matched by dns api: $_domain (host: $_sub_domain)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
p=$i
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
}
|
||||
|
||||
_baidu_find_record_ids_current() {
|
||||
if [ "$_baidu_api_engine" = "new" ]; then
|
||||
_baidu_find_record_ids_dns "$@"
|
||||
else
|
||||
_baidu_find_record_ids "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
_baidu_find_record_ids() {
|
||||
_zone_name="$1"
|
||||
_record_domain="$2"
|
||||
_rdtype="$3"
|
||||
_rdata="$4"
|
||||
|
||||
# Reset global result variable
|
||||
_BAIDU_FIND_RESULT=""
|
||||
|
||||
_zone_name_e="$(_baidu_json_escape "$_zone_name")"
|
||||
_record_domain_e="$(_baidu_json_escape "$_record_domain")"
|
||||
_rdtype_e="$(_baidu_json_escape "$_rdtype")"
|
||||
_rdata_e="$(_baidu_json_escape "$_rdata")"
|
||||
|
||||
_page=1
|
||||
_page_size=100
|
||||
_ids=""
|
||||
|
||||
_max_page=""
|
||||
while true; do
|
||||
if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$_zone_name" "$_page" "$_page_size")"; then
|
||||
_baidu_err "baidu_bcd_post failed: list records"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _baidu_is_api_error "$response"; then
|
||||
_baidu_err "baidu_bcd error: $(_baidu_json_get_str "$response" "code") $(_baidu_json_get_str "$response" "message")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_normalized="$(
|
||||
printf "%s" "$response" | _normalizeJson
|
||||
)"
|
||||
|
||||
if [ -z "$_max_page" ]; then
|
||||
_total="$(_baidu_parse_totalcount "$_normalized")"
|
||||
_max_page="$(_baidu_calc_max_page "$_total" "$_page_size")"
|
||||
fi
|
||||
|
||||
_records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g')
|
||||
while IFS= read -r _line; do
|
||||
_id="$(_baidu_match_record_id "$_line" "$_record_domain_e" "$_rdtype_e" "$_rdata_e")"
|
||||
if [ "$_id" ]; then
|
||||
_ids="$_ids $_id"
|
||||
fi
|
||||
done <<EOF
|
||||
$_records
|
||||
EOF
|
||||
|
||||
if [ "$_page" -ge "$_max_page" ]; then
|
||||
break
|
||||
fi
|
||||
_page=$(_math "$_page" + 1)
|
||||
done
|
||||
|
||||
# Store result in global variable instead of stdout
|
||||
_BAIDU_FIND_RESULT="$_ids"
|
||||
}
|
||||
|
||||
_baidu_find_record_ids_dns() {
|
||||
_zone_name="$1"
|
||||
_record_domain="$2"
|
||||
_rdtype="$3"
|
||||
_rdata="$4"
|
||||
_BAIDU_FIND_RESULT=""
|
||||
|
||||
if ! _baidu_dns_call "GET" "/v1/dns/zone/${_zone_name}/record" ""; then
|
||||
_baidu_err "baidu_dns_call failed: list records"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _baidu_is_api_error "$response"; then
|
||||
_baidu_err "baidu_dns error: $(_baidu_json_get_str "$response" "code") $(_baidu_json_get_str "$response" "message")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_normalized="$(printf "%s" "$response" | _normalizeJson)"
|
||||
_records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g')
|
||||
_ids=""
|
||||
|
||||
while IFS= read -r _line; do
|
||||
_id="$(_baidu_match_record_id_dns "$_line" "$_record_domain" "$_rdtype" "$_rdata")"
|
||||
if [ "$_id" ]; then
|
||||
_ids="$_ids $_id"
|
||||
fi
|
||||
done <<EOF
|
||||
$_records
|
||||
EOF
|
||||
|
||||
_BAIDU_FIND_RESULT="$_ids"
|
||||
}
|
||||
|
||||
# --- HTTP ---
|
||||
_baidu_bcd_post() {
|
||||
_api_path="$1"
|
||||
_payload="$2"
|
||||
|
||||
# BCD API requires JSON payload. Some call sites build fragments; normalize defensively.
|
||||
_payload="$(_baidu_normalize_payload "$_payload")"
|
||||
|
||||
_ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
_expire="${Baidu_BCD_Expire:-3600}"
|
||||
_content_type="application/json; charset=utf-8"
|
||||
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
|
||||
|
||||
_uri="/v${BAIDU_BCD_VERSION}${_api_path}"
|
||||
if ! _baidu_bce_auth "POST" "$_uri" "" "$BAIDU_BCD_HOST" "$_ts" "$_expire" "$_content_type" "$_payload_hash"; then
|
||||
_baidu_err "baidu_bcd auth failed"
|
||||
return 1
|
||||
fi
|
||||
_auth="$_BAIDU_BCE_AUTH_RESULT"
|
||||
if [ -z "$_auth" ]; then
|
||||
_baidu_err "baidu_bcd auth failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_url="https://${BAIDU_BCD_HOST}${_uri}"
|
||||
_signed_headers_dbg="$(printf "%s" "$_auth" | cut -d / -f 5)"
|
||||
_baidu_info "POST ${_uri}"
|
||||
_baidu_info "signedHeaders: $_signed_headers_dbg"
|
||||
_baidu_info "payload_sha256: $_payload_hash"
|
||||
_baidu_debug "baidu_bcd.http.payload" "$(_baidu_dbg_trim "$(_baidu_redact_txt "$_payload")")"
|
||||
_H1="Authorization: $_auth"
|
||||
_H2="x-bce-date: $_ts"
|
||||
_H3="x-bce-content-sha256: $_payload_hash"
|
||||
_H4="Host: $BAIDU_BCD_HOST"
|
||||
_H5=""
|
||||
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
|
||||
_ret="$?"
|
||||
_baidu_info "ret: $_ret"
|
||||
_req_id="$(_baidu_json_get_str "$response" "requestId")"
|
||||
_code="$(_baidu_json_get_str "$response" "code")"
|
||||
_msg="$(_baidu_json_get_str "$response" "message")"
|
||||
_baidu_info "response: requestId=${_req_id:-"-"} code=${_code:-"-"} message=$(_baidu_dbg_trim "${_msg:-"-"}")"
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_baidu_err "baidu_bcd_post failed: $_uri"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_dns_call() {
|
||||
_method="$1"
|
||||
_uri="$2"
|
||||
_payload="$3"
|
||||
_content_type="application/json"
|
||||
_attempt=1
|
||||
_max_attempts=3
|
||||
|
||||
while [ "$_attempt" -le "$_max_attempts" ]; do
|
||||
_ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
|
||||
|
||||
if ! _baidu_bce_auth "$_method" "$_uri" "" "$BAIDU_DNS_HOST" "$_ts" "${Baidu_BCD_Expire:-3600}" "$_content_type" "$_payload_hash"; then
|
||||
_baidu_err "baidu_dns auth failed"
|
||||
return 1
|
||||
fi
|
||||
_auth="$_BAIDU_BCE_AUTH_RESULT"
|
||||
_url="https://${BAIDU_DNS_HOST}${_uri}"
|
||||
|
||||
# Route through acme.sh's _get/_post (they honor _H1.._H5); no raw curl.
|
||||
_H1="Authorization: $_auth"
|
||||
_H2="x-bce-date: $_ts"
|
||||
_H3="x-bce-content-sha256: $_payload_hash"
|
||||
_H4="Host: $BAIDU_DNS_HOST"
|
||||
_H5="Content-Type: $_content_type"
|
||||
|
||||
if [ "$_method" = "GET" ]; then
|
||||
response="$(_get "$_url")"
|
||||
elif [ "$_method" = "DELETE" ]; then
|
||||
response="$(_post "" "$_url" "" "DELETE")"
|
||||
else
|
||||
response="$(_post "$_payload" "$_url")"
|
||||
fi
|
||||
_ret="$?"
|
||||
_baidu_info "${_method} ${_uri} ret=${_ret}"
|
||||
|
||||
# Baidu may return a business error (Exception / 平台服务繁忙) inside HTTP 200.
|
||||
if [ "$_ret" = "0" ] && ! _contains "$response" "\"code\":\"Exception\"" && ! _contains "$response" "平台服务繁忙"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$_attempt" -lt "$_max_attempts" ]; then
|
||||
sleep 2
|
||||
fi
|
||||
_attempt=$(_math "$_attempt" + 1)
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- Auth / Signing ---
|
||||
_baidu_bce_auth() {
|
||||
# Signing algorithm (bce-auth-v1):
|
||||
# - SigningKey = HMAC-SHA256-HEX(sk, authStringPrefix)
|
||||
# - Signature = HMAC-SHA256-HEX(SigningKey, CanonicalRequest)
|
||||
# Reference: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
|
||||
_method="$1"
|
||||
_uri="$2"
|
||||
_query="$3"
|
||||
_host="$4"
|
||||
_ts="$5"
|
||||
_expire="$6"
|
||||
_ct="$7"
|
||||
_payload_hash="$8"
|
||||
|
||||
_BAIDU_BCE_AUTH_RESULT=""
|
||||
|
||||
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
|
||||
|
||||
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
|
||||
_canonical_uri="$(_baidu_bce_encode_path "$_uri")"
|
||||
_canonical_query=""
|
||||
|
||||
_host_v="$(_baidu_trim_ws "$_host")"
|
||||
_date_v="$(_baidu_trim_ws "$_ts")"
|
||||
_ct_v="$(_baidu_trim_ws "$_ct")"
|
||||
_host_e="$(printf "%s" "$_host_v" | _url_encode upper-hex)"
|
||||
_date_e="$(printf "%s" "$_date_v" | _url_encode upper-hex)"
|
||||
_ct_e="$(printf "%s" "$_ct_v" | _url_encode upper-hex)"
|
||||
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
|
||||
|
||||
_canonical_headers="content-type:${_ct_e}
|
||||
host:${_host_e}
|
||||
x-bce-content-sha256:${_hash_e}
|
||||
x-bce-date:${_date_e}"
|
||||
|
||||
_canonical_request="${_method}
|
||||
${_canonical_uri}
|
||||
${_canonical_query}
|
||||
${_canonical_headers}"
|
||||
|
||||
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
|
||||
_signing_key="$(_baidu_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
|
||||
_signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
|
||||
_signature="$(_baidu_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
|
||||
|
||||
_baidu_debug "baidu_bcd.auth" "bce_auth"
|
||||
_baidu_debug "baidu_bcd.auth.auth_prefix" "bce-auth-v1/[ak]/${_ts}/${_expire}/$_signed_headers/[signature]"
|
||||
_baidu_debug "baidu_bcd.auth.canonical_request_l" "$(printf "%s" "$_canonical_request" | sed -n 'l')"
|
||||
_baidu_debug "baidu_bcd.auth.signature" "$(printf "%s" "$_signature" | cut -c 1-16)..."
|
||||
_BAIDU_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_bce_encode_path() {
|
||||
_p="$1"
|
||||
_out=""
|
||||
if [ "${_p#"/"}" != "$_p" ]; then
|
||||
_out="/"
|
||||
fi
|
||||
|
||||
_rest="${_p#/}"
|
||||
while [ -n "$_rest" ]; do
|
||||
_seg="${_rest%%/*}"
|
||||
if [ "$_seg" ]; then
|
||||
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
|
||||
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
||||
else
|
||||
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
||||
fi
|
||||
fi
|
||||
if [ "${_rest#*/}" = "$_rest" ]; then
|
||||
break
|
||||
fi
|
||||
_rest="${_rest#*/}"
|
||||
done
|
||||
|
||||
if [ -z "$_out" ]; then
|
||||
_out="/"
|
||||
fi
|
||||
printf "%s" "$_out"
|
||||
}
|
||||
|
||||
# --- Utils ---
|
||||
_baidu_trim() {
|
||||
printf "%s" "$1" | sed 's/^ *//;s/ *$//'
|
||||
}
|
||||
|
||||
_baidu_trim_ws() {
|
||||
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
|
||||
}
|
||||
|
||||
_baidu_dbg_trim() {
|
||||
printf "%s" "$1" | tr '\r\n' ' ' | cut -c 1-800
|
||||
}
|
||||
|
||||
_baidu_is_api_error() {
|
||||
_contains "$1" "\"code\"" && _contains "$1" "\"message\""
|
||||
}
|
||||
|
||||
_baidu_normalize_payload() {
|
||||
_p="$(_baidu_trim "$(printf "%s" "$1" | tr -d '\r')")"
|
||||
if [ -z "$_p" ]; then
|
||||
printf "%s" ""
|
||||
return 0
|
||||
fi
|
||||
case "$_p" in
|
||||
\{* | \[*)
|
||||
printf "%s" "$_p"
|
||||
;;
|
||||
*)
|
||||
printf "%s" "{$_p}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_baidu_redact_txt() {
|
||||
printf "%s" "$1" | sed 's/"rdata" *: *"[^"]*"/"rdata":"[redacted]"/g'
|
||||
}
|
||||
|
||||
_baidu_json_get_str() {
|
||||
_json="$1"
|
||||
_key="$2"
|
||||
printf "%s" "$_json" | _normalizeJson | sed -n "s/.*\"${_key}\" *: *\"\\([^\"]*\\)\".*/\\1/p" | _head_n 1
|
||||
}
|
||||
|
||||
_baidu_json_escape() {
|
||||
_s="$1"
|
||||
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
|
||||
printf "%s" "$_s" |
|
||||
sed 's/\\/\\\\/g; s/ /\\t/g' |
|
||||
_baidu_json_encode
|
||||
}
|
||||
|
||||
_baidu_json_encode() {
|
||||
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
|
||||
printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
|
||||
}
|
||||
|
||||
_baidu_payload_list() {
|
||||
_domain="$(_baidu_json_escape "$1")"
|
||||
_pageNo="$2"
|
||||
_pageSize="$3"
|
||||
printf "%s" "{\"domain\":\"${_domain}\",\"pageNo\":${_pageNo},\"pageSize\":${_pageSize}}"
|
||||
}
|
||||
|
||||
_baidu_payload_add_txt() {
|
||||
_zoneName="$(_baidu_json_escape "$1")"
|
||||
_domain="$(_baidu_json_escape "$2")"
|
||||
_rdata="$(_baidu_json_escape "$3")"
|
||||
_ttl="$4"
|
||||
_view="$(_baidu_json_escape "$5")"
|
||||
printf "%s" "{\"domain\":\"${_domain}\",\"view\":\"${_view}\",\"rdType\":\"TXT\",\"ttl\":${_ttl},\"rdata\":\"${_rdata}\",\"zoneName\":\"${_zoneName}\"}"
|
||||
}
|
||||
|
||||
_baidu_payload_add_txt_dns() {
|
||||
_rr="$(printf "%s" "$1" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
_value="$(printf "%s" "$2" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
_ttl="$3"
|
||||
_line="$(printf "%s" "$4" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
printf "%s" "{\"rr\":\"${_rr}\",\"type\":\"TXT\",\"value\":\"${_value}\",\"ttl\":${_ttl},\"line\":\"${_line}\",\"description\":\"acme.sh\"}"
|
||||
}
|
||||
|
||||
_baidu_payload_delete() {
|
||||
_zoneName="$(_baidu_json_escape "$1")"
|
||||
_recordId="$2"
|
||||
printf "%s" "{\"zoneName\":\"${_zoneName}\",\"recordId\":${_recordId}}"
|
||||
}
|
||||
|
||||
_baidu_parse_totalcount() {
|
||||
_json="$1"
|
||||
printf "%s" "$_json" | _egrep_o "\"totalCount\": *[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d " "
|
||||
}
|
||||
|
||||
_baidu_calc_max_page() {
|
||||
_total="$1"
|
||||
_page_size="$2"
|
||||
if [ -z "$_total" ]; then
|
||||
printf "%s" "1"
|
||||
return 0
|
||||
fi
|
||||
_max=$(((_total + _page_size - 1) / _page_size))
|
||||
if [ "$_max" -lt 1 ]; then
|
||||
_max=1
|
||||
fi
|
||||
printf "%s" "$_max"
|
||||
}
|
||||
|
||||
_baidu_match_record_id() {
|
||||
_line="$1"
|
||||
_domain_e="$2"
|
||||
_rdtype_e="$3"
|
||||
_rdata_e="$4"
|
||||
if ! _contains "$_line" "\"recordId\"" || (! _contains "$_line" "\"domain\":\"$_domain_e\"" && ! _contains "$_line" "\"domain\":\"${_domain_e}.\""); then
|
||||
return 0
|
||||
fi
|
||||
if ! _contains "$_line" "\"rdtype\":\"$_rdtype_e\"" && ! _contains "$_line" "\"rdType\":\"$_rdtype_e\""; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$_rdata_e" ] && ! _contains "$_line" "\"rdata\":\"$_rdata_e\""; then
|
||||
return 0
|
||||
fi
|
||||
printf "%s" "$_line" | _egrep_o "\"recordId\": *[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d " "
|
||||
}
|
||||
|
||||
_baidu_match_record_id_dns() {
|
||||
_line="$1"
|
||||
_rr="$(printf "%s" "$2" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
_type="$(printf "%s" "$3" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
_value="$(printf "%s" "$4" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
case "$_line" in
|
||||
*"\"rr\":\"${_rr}\""*"\"type\":\"${_type}\""*"\"value\":\"${_value}\""*)
|
||||
printf "%s" "$_line" | sed -n 's/.*"id":"\{0,1\}\([^",}]*\)"\{0,1\}.*/\1/p' | _head_n 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_baidu_hmac_sha256_hexkey() {
|
||||
_key_hex="$1"
|
||||
_msg="$2"
|
||||
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
|
||||
}
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_beget_info='Beget.com
|
||||
Site: Beget.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_beget
|
||||
Options:
|
||||
BEGET_User API user
|
||||
BEGET_Password API password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/6200
|
||||
Author: ARNik <arnik@arnik.ru>
|
||||
'
|
||||
|
||||
Beget_Api="https://api.beget.com/api"
|
||||
|
||||
#################### Public functions ####################
|
||||
|
||||
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
# Used to add txt record
|
||||
dns_beget_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
_debug "dns_beget_add() $fulldomain $txtvalue"
|
||||
fulldomain=$(echo "$fulldomain" | _lower_case)
|
||||
|
||||
Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}"
|
||||
Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}"
|
||||
|
||||
if [ -z "$Beget_Username" ] || [ -z "$Beget_Password" ]; then
|
||||
Beget_Username=""
|
||||
Beget_Password=""
|
||||
_err "You must export variables: Beget_Username, and Beget_Password"
|
||||
return 1
|
||||
fi
|
||||
|
||||
#save the credentials to the account conf file.
|
||||
_saveaccountconf_mutable Beget_Username "$Beget_Username"
|
||||
_saveaccountconf_mutable Beget_Password "$Beget_Password"
|
||||
|
||||
_info "Prepare subdomain."
|
||||
if ! _prepare_subdomain "$fulldomain"; then
|
||||
_err "Can't prepare subdomain."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Get domain records"
|
||||
data="{\"fqdn\":\"$fulldomain\"}"
|
||||
res=$(_api_call "$Beget_Api/dns/getData" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't get domain records."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Add new TXT record"
|
||||
data="{\"fqdn\":\"$fulldomain\",\"records\":{"
|
||||
data=${data}$(_parce_records "$res" "A")
|
||||
data=${data}$(_parce_records "$res" "AAAA")
|
||||
data=${data}$(_parce_records "$res" "CAA")
|
||||
data=${data}$(_parce_records "$res" "MX")
|
||||
data=${data}$(_parce_records "$res" "SRV")
|
||||
data=${data}$(_parce_records "$res" "TXT")
|
||||
data=$(echo "$data" | sed 's/,$//')
|
||||
data=${data}'}}'
|
||||
|
||||
str=$(_txt_to_dns_json "$txtvalue")
|
||||
data=$(_add_record "$data" "TXT" "$str")
|
||||
|
||||
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't change domain records."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Usage: fulldomain txtvalue
|
||||
# Used to remove the txt record after validation
|
||||
dns_beget_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
_debug "dns_beget_rm() $fulldomain $txtvalue"
|
||||
fulldomain=$(echo "$fulldomain" | _lower_case)
|
||||
|
||||
Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}"
|
||||
Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}"
|
||||
|
||||
_info "Get current domain records"
|
||||
data="{\"fqdn\":\"$fulldomain\"}"
|
||||
res=$(_api_call "$Beget_Api/dns/getData" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't get domain records."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Remove TXT record"
|
||||
data="{\"fqdn\":\"$fulldomain\",\"records\":{"
|
||||
data=${data}$(_parce_records "$res" "A")
|
||||
data=${data}$(_parce_records "$res" "AAAA")
|
||||
data=${data}$(_parce_records "$res" "CAA")
|
||||
data=${data}$(_parce_records "$res" "MX")
|
||||
data=${data}$(_parce_records "$res" "SRV")
|
||||
data=${data}$(_parce_records "$res" "TXT")
|
||||
data=$(echo "$data" | sed 's/,$//')
|
||||
data=${data}'}}'
|
||||
|
||||
str=$(_txt_to_dns_json "$txtvalue")
|
||||
data=$(_rm_record "$data" "$str")
|
||||
|
||||
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't change domain records."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#################### Private functions below ####################
|
||||
|
||||
# Create subdomain if needed
|
||||
# Usage: _prepare_subdomain [fulldomain]
|
||||
_prepare_subdomain() {
|
||||
fulldomain=$1
|
||||
|
||||
_info "Detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain_id "$_domain_id"
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
if [ -z "$_sub_domain" ]; then
|
||||
_debug "$fulldomain is a root domain."
|
||||
return 0
|
||||
fi
|
||||
|
||||
_info "Get subdomain list"
|
||||
res=$(_api_call "$Beget_Api/domain/getSubdomainList")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't get subdomain list."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$res" "\"fqdn\":\"$fulldomain\""; then
|
||||
_debug "Subdomain $fulldomain already exist."
|
||||
return 0
|
||||
fi
|
||||
|
||||
_info "Subdomain $fulldomain does not exist. Let's create one."
|
||||
data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}"
|
||||
res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't create subdomain."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Cleanup subdomen records"
|
||||
data="{\"fqdn\":\"$fulldomain\",\"records\":{}}"
|
||||
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_debug "Can't cleanup $fulldomain records."
|
||||
fi
|
||||
|
||||
data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}"
|
||||
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_debug "Can't cleanup www.$fulldomain records."
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Usage: _get_root _acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
# _domain_id=32436365
|
||||
_get_root() {
|
||||
fulldomain=$1
|
||||
i=1
|
||||
p=1
|
||||
|
||||
_debug "Get domain list"
|
||||
res=$(_api_call "$Beget_Api/domain/getList")
|
||||
if ! _is_api_reply_ok "$res"; then
|
||||
_err "Can't get domain list."
|
||||
return 1
|
||||
fi
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100)
|
||||
_debug h "$h"
|
||||
|
||||
if [ -z "$h" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$res" "$h"; then
|
||||
_domain_id=$(echo "$res" | _egrep_o "\"id\":[0-9]*,\"fqdn\":\"$h\"" | cut -d , -f1 | cut -d : -f2)
|
||||
if [ "$_domain_id" ]; then
|
||||
if [ "$h" != "$fulldomain" ]; then
|
||||
_sub_domain=$(echo "$fulldomain" | cut -d . -f 1-"$p")
|
||||
else
|
||||
_sub_domain=""
|
||||
fi
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
p="$i"
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Parce DNS records from json string
|
||||
# Usage: _parce_records [j_str] [record_name]
|
||||
_parce_records() {
|
||||
j_str=$1
|
||||
record_name=$2
|
||||
res="\"$record_name\":["
|
||||
res=${res}$(echo "$j_str" | _egrep_o "\"$record_name\":\[.*" | cut -d '[' -f2 | cut -d ']' -f1)
|
||||
res=${res}"],"
|
||||
echo "$res"
|
||||
}
|
||||
|
||||
# Usage: _add_record [data] [record_name] [record_data]
|
||||
_add_record() {
|
||||
data=$1
|
||||
record_name=$2
|
||||
record_data=$3
|
||||
echo "$data" | sed "s/\"$record_name\":\[/\"$record_name\":\[$record_data,/" | sed "s/,\]/\]/"
|
||||
}
|
||||
|
||||
# Usage: _rm_record [data] [record_data]
|
||||
_rm_record() {
|
||||
data=$1
|
||||
record_data=$2
|
||||
echo "$data" | sed "s/$record_data//g" | sed "s/,\+/,/g" |
|
||||
sed "s/{,/{/g" | sed "s/,}/}/g" |
|
||||
sed "s/\[,/\[/g" | sed "s/,\]/\]/g"
|
||||
}
|
||||
|
||||
_txt_to_dns_json() {
|
||||
echo "{\"ttl\":600,\"txtdata\":\"$1\"}"
|
||||
}
|
||||
|
||||
# Usage: _api_call [api_url] [input_data]
|
||||
_api_call() {
|
||||
api_url="$1"
|
||||
input_data="$2"
|
||||
|
||||
_debug "_api_call $api_url"
|
||||
_debug "Request: $input_data"
|
||||
|
||||
# res=$(curl -s -L -D ./http.header \
|
||||
# "$api_url" \
|
||||
# --data-urlencode login=$Beget_Username \
|
||||
# --data-urlencode passwd=$Beget_Password \
|
||||
# --data-urlencode input_format=json \
|
||||
# --data-urlencode output_format=json \
|
||||
# --data-urlencode "input_data=$input_data")
|
||||
|
||||
url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json"
|
||||
if [ -n "$input_data" ]; then
|
||||
url=${url}"&input_data="
|
||||
url=${url}$(echo "$input_data" | _url_encode)
|
||||
fi
|
||||
res=$(_get "$url")
|
||||
|
||||
_debug "Reply: $res"
|
||||
echo "$res"
|
||||
}
|
||||
|
||||
# Usage: _is_api_reply_ok [api_reply]
|
||||
_is_api_reply_ok() {
|
||||
_contains "$1" '^{"status":"success","answer":{"status":"success","result":.*}}$'
|
||||
}
|
||||
202
dnsapi/dns_bh.sh
202
dnsapi/dns_bh.sh
|
|
@ -1,202 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_bh_info='Best-Hosting.cz
|
||||
Site: best-hosting.cz
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bh
|
||||
Options:
|
||||
BH_API_USER API User identifier.
|
||||
BH_API_KEY API Secret key.
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/6854
|
||||
Author: @heximcz
|
||||
'
|
||||
|
||||
BH_Api="https://best-hosting.cz/api/v1"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
# Usage: dns_bh_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_bh_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
# --- 1. Credentials ---
|
||||
BH_API_USER="${BH_API_USER:-$(_readaccountconf_mutable BH_API_USER)}"
|
||||
BH_API_KEY="${BH_API_KEY:-$(_readaccountconf_mutable BH_API_KEY)}"
|
||||
|
||||
if [ -z "$BH_API_USER" ] || [ -z "$BH_API_KEY" ]; then
|
||||
BH_API_USER=""
|
||||
BH_API_KEY=""
|
||||
_err "You must specify BH_API_USER and BH_API_KEY."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable BH_API_USER "$BH_API_USER"
|
||||
_saveaccountconf_mutable BH_API_KEY "$BH_API_KEY"
|
||||
|
||||
# --- 2. Add TXT record ---
|
||||
_info "Adding TXT record for $fulldomain"
|
||||
|
||||
json_payload="{\"fulldomain\":\"$fulldomain\",\"txtvalue\":\"$txtvalue\"}"
|
||||
if ! _bh_rest POST "dns" "$json_payload"; then
|
||||
_err "Failed to add DNS record."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_norm_add=$(printf "%s" "$response" | tr -d '[:space:]')
|
||||
if ! _contains "$_norm_add" '"status":"success"'; then
|
||||
_err "API error: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_id=$(printf "%s" "$_norm_add" | _egrep_o '"id":[0-9]+' | cut -d':' -f2)
|
||||
_debug record_id "$record_id"
|
||||
|
||||
if [ -z "$record_id" ]; then
|
||||
_err "Could not parse record ID from response."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Sanitize key — replace dots and hyphens with underscores
|
||||
_conf_key=$(printf "%s" "BH_record_ids_${fulldomain}" | tr '.-' '_')
|
||||
|
||||
# Wildcard support: store space-separated list of IDs
|
||||
# First call stores "111", second call stores "111 222"
|
||||
_existing_ids=$(_readdomainconf "$_conf_key")
|
||||
if [ -z "$_existing_ids" ]; then
|
||||
_savedomainconf "$_conf_key" "$record_id"
|
||||
else
|
||||
_savedomainconf "$_conf_key" "$_existing_ids $record_id"
|
||||
fi
|
||||
|
||||
_info "DNS TXT record added successfully."
|
||||
return 0
|
||||
}
|
||||
|
||||
# Usage: dns_bh_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_bh_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
# --- 1. Credentials ---
|
||||
BH_API_USER="${BH_API_USER:-$(_readaccountconf_mutable BH_API_USER)}"
|
||||
BH_API_KEY="${BH_API_KEY:-$(_readaccountconf_mutable BH_API_KEY)}"
|
||||
|
||||
if [ -z "$BH_API_USER" ] || [ -z "$BH_API_KEY" ]; then
|
||||
BH_API_USER=""
|
||||
BH_API_KEY=""
|
||||
_err "You must specify BH_API_USER and BH_API_KEY."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Sanitize key — same as in add
|
||||
_conf_key=$(printf "%s" "BH_record_ids_${fulldomain}" | tr '.-' '_')
|
||||
|
||||
# --- 2. Load stored record ID(s) ---
|
||||
_existing_ids=$(_readdomainconf "$_conf_key")
|
||||
_debug _existing_ids "$_existing_ids"
|
||||
|
||||
if [ -z "$_existing_ids" ]; then
|
||||
_err "Could not find record ID for $fulldomain."
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_id=""
|
||||
_remaining_ids=""
|
||||
|
||||
# Find the record ID that matches both the name and txtvalue
|
||||
for _id in $_existing_ids; do
|
||||
if ! _bh_rest GET "dns/$_id"; then
|
||||
_debug "Failed to query record id $_id, skipping."
|
||||
|
||||
# Keep it in the list so a later run can try again
|
||||
if [ -z "$_remaining_ids" ]; then
|
||||
_remaining_ids="$_id"
|
||||
else
|
||||
_remaining_ids="$_remaining_ids $_id"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
_match_name=0
|
||||
_match_content=0
|
||||
_norm_response=$(printf "%s" "$response" | tr -d '[:space:]')
|
||||
|
||||
case "$_norm_response" in
|
||||
*"\"name\":\"$fulldomain\""*)
|
||||
_match_name=1
|
||||
;;
|
||||
esac
|
||||
case "$_norm_response" in
|
||||
*"\"content\":\"$txtvalue\""*)
|
||||
_match_content=1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$_match_name" -eq 1 ] && [ "$_match_content" -eq 1 ]; then
|
||||
record_id="$_id"
|
||||
_debug "Matched record id" "$record_id"
|
||||
# Do not add this ID to _remaining_ids; it will be deleted
|
||||
continue
|
||||
fi
|
||||
|
||||
# Not a match — keep ID for potential future cleanups
|
||||
if [ -z "$_remaining_ids" ]; then
|
||||
_remaining_ids="$_id"
|
||||
else
|
||||
_remaining_ids="$_remaining_ids $_id"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$record_id" ]; then
|
||||
_err "Could not find matching TXT record for $fulldomain with the given value."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# --- 3. Delete record ---
|
||||
_info "Removing TXT record for $fulldomain"
|
||||
|
||||
if ! _bh_rest DELETE "dns/$record_id"; then
|
||||
_err "Failed to remove DNS record."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Update stored list — remove used ID
|
||||
if [ -z "$_remaining_ids" ]; then
|
||||
_cleardomainconf "$_conf_key"
|
||||
else
|
||||
_savedomainconf "$_conf_key" "$_remaining_ids"
|
||||
fi
|
||||
|
||||
_info "DNS TXT record removed successfully."
|
||||
return 0
|
||||
}
|
||||
|
||||
#################### Private functions #####################
|
||||
|
||||
_bh_rest() {
|
||||
m="$1"
|
||||
ep="$2"
|
||||
data="$3"
|
||||
_debug "$ep"
|
||||
|
||||
_credentials="$(printf "%s:%s" "$BH_API_USER" "$BH_API_KEY" | _base64)"
|
||||
|
||||
export _H1="Authorization: Basic $_credentials"
|
||||
export _H2="Content-Type: application/json"
|
||||
export _H3="Accept: application/json"
|
||||
|
||||
if [ "$m" = "GET" ]; then
|
||||
response="$(_get "$BH_Api/$ep")"
|
||||
else
|
||||
_debug data "$data"
|
||||
response="$(_post "$data" "$BH_Api/$ep" "" "$m")"
|
||||
fi
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Error calling $m $BH_Api/$ep"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 response "$response"
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,373 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
dns_bhosted_info='bHosted.nl DNS API
|
||||
Site: bHosted.nl
|
||||
Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bhosted
|
||||
Options:
|
||||
BHOSTED_Username API username
|
||||
BHOSTED_Password API password (MD5 hash like bHosted web services example)
|
||||
BHOSTED_TTL TTL for TXT record (default: 300)
|
||||
BHOSTED_SLD Optional override (useful for multi-part TLDs like co.uk)
|
||||
BHOSTED_TLD Optional override (useful for multi-part TLDs like co.uk)
|
||||
Notes:
|
||||
- Plugin uses addrecord + delrecord for DNS-01 challenge
|
||||
- Record ID is retrieved from addrecord XML response and cached for cleanup
|
||||
'
|
||||
|
||||
BHOSTED_API_ROOT="https://webservices.bhosted.com/dns"
|
||||
|
||||
############ Public functions #####################
|
||||
|
||||
# Usage: dns_bhosted_add _acme-challenge.www.example.com "txt-value"
|
||||
dns_bhosted_add() {
|
||||
fulldomain="$1"
|
||||
txtvalue="$2"
|
||||
|
||||
_debug "fulldomain" "$fulldomain"
|
||||
_debug "txtvalue" "$txtvalue"
|
||||
|
||||
_bhosted_load_credentials || return 1
|
||||
_bhosted_get_root "$fulldomain" || return 1
|
||||
|
||||
_info "Adding TXT record: ${_bhosted_name}.${_domain}"
|
||||
|
||||
BHOSTED_TTL="${BHOSTED_TTL:-$(_readaccountconf_mutable BHOSTED_TTL)}"
|
||||
BHOSTED_TTL="${BHOSTED_TTL:-300}"
|
||||
_saveaccountconf_mutable BHOSTED_TTL "$BHOSTED_TTL"
|
||||
|
||||
_bhosted_api_add_txt "$_bhosted_sld" "$_bhosted_tld" "$_bhosted_name" "$txtvalue" "$BHOSTED_TTL" || return 1
|
||||
|
||||
# Extract and cache record id in-memory for cleanup in this run
|
||||
_rec_id="$(_bhosted_extract_id "$response")"
|
||||
if [ -n "$_rec_id" ]; then
|
||||
_hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")"
|
||||
_debug "_hash" "$_hash"
|
||||
_debug "_rec_id" "$_rec_id"
|
||||
_bhosted_mem_set_id "$_hash" "$_rec_id"
|
||||
else
|
||||
_err "TXT record added but no record id found in response."
|
||||
_err "Cleanup may fail unless bHosted addrecord returns <id>...</id>."
|
||||
_debug2 "add response" "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Usage: dns_bhosted_rm _acme-challenge.www.example.com "txt-value"
|
||||
dns_bhosted_rm() {
|
||||
fulldomain="$1"
|
||||
txtvalue="$2"
|
||||
|
||||
_debug "fulldomain" "$fulldomain"
|
||||
_debug "txtvalue" "$txtvalue"
|
||||
|
||||
_bhosted_load_credentials || return 1
|
||||
_bhosted_get_root "$fulldomain" || return 1
|
||||
|
||||
_hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")"
|
||||
_rec_id="$(_bhosted_mem_get_id "$_hash")"
|
||||
|
||||
if [ -z "$_rec_id" ]; then
|
||||
_err "No cached bHosted record id found for cleanup."
|
||||
_err "Please delete TXT manually in bHosted DNS for: ${_bhosted_name}.${_domain}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Removing TXT record id=${_rec_id}: ${_bhosted_name}.${_domain}"
|
||||
_bhosted_api_del_record "$_bhosted_sld" "$_bhosted_tld" "$_rec_id" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
######## Private functions #####################
|
||||
|
||||
_bhosted_load_credentials() {
|
||||
BHOSTED_Username="${BHOSTED_Username:-$(_readaccountconf_mutable BHOSTED_Username)}"
|
||||
BHOSTED_Password="${BHOSTED_Password:-$(_readaccountconf_mutable BHOSTED_Password)}"
|
||||
|
||||
if [ -z "$BHOSTED_Username" ] || [ -z "$BHOSTED_Password" ]; then
|
||||
BHOSTED_Username=""
|
||||
BHOSTED_Password=""
|
||||
_err "You didn't specify bHosted credentials."
|
||||
_err "Please export BHOSTED_Username and BHOSTED_Password (MD5 hash)."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable BHOSTED_Username "$BHOSTED_Username"
|
||||
_saveaccountconf_mutable BHOSTED_Password "$BHOSTED_Password"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Determine root zone and host part
|
||||
# Supports simple domains automatically (example.com, example.nl)
|
||||
# For multi-part TLDs (example.co.uk), set:
|
||||
# BHOSTED_SLD=example
|
||||
# BHOSTED_TLD=co.uk
|
||||
_bhosted_get_root() {
|
||||
domain="$1"
|
||||
|
||||
BHOSTED_SLD="${BHOSTED_SLD:-$(_readdomainconf BHOSTED_SLD)}"
|
||||
BHOSTED_TLD="${BHOSTED_TLD:-$(_readdomainconf BHOSTED_TLD)}"
|
||||
|
||||
if [ -n "$BHOSTED_SLD" ] && [ -n "$BHOSTED_TLD" ]; then
|
||||
_savedomainconf BHOSTED_SLD "$BHOSTED_SLD"
|
||||
_savedomainconf BHOSTED_TLD "$BHOSTED_TLD"
|
||||
|
||||
_domain="${BHOSTED_SLD}.${BHOSTED_TLD}"
|
||||
case "$domain" in
|
||||
*."$_domain") ;;
|
||||
"$_domain") ;;
|
||||
*)
|
||||
_err "BHOSTED_SLD/BHOSTED_TLD do not match requested domain: $domain"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
_bhosted_sld="$BHOSTED_SLD"
|
||||
_bhosted_tld="$BHOSTED_TLD"
|
||||
_bhosted_name="${domain%."$_domain"}"
|
||||
if [ "$_bhosted_name" = "$domain" ]; then
|
||||
_bhosted_name=""
|
||||
fi
|
||||
|
||||
[ -n "$_bhosted_name" ] || _bhosted_name="@"
|
||||
|
||||
_debug "_domain" "$_domain"
|
||||
_debug "_bhosted_sld" "$_bhosted_sld"
|
||||
_debug "_bhosted_tld" "$_bhosted_tld"
|
||||
_debug "_bhosted_name" "$_bhosted_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Auto-parse: assume last label = tld, label before = sld
|
||||
# Works for .nl / .com / .org etc.
|
||||
_bhosted_tld="$(printf "%s" "$domain" | awk -F. '{print $NF}')"
|
||||
_bhosted_sld="$(printf "%s" "$domain" | awk -F. '{print $(NF-1)}')"
|
||||
|
||||
if [ -z "$_bhosted_sld" ] || [ -z "$_bhosted_tld" ]; then
|
||||
_err "Could not parse SLD/TLD from domain: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_domain="${_bhosted_sld}.${_bhosted_tld}"
|
||||
_bhosted_name="${domain%."$_domain"}"
|
||||
if [ "$_bhosted_name" = "$domain" ]; then
|
||||
_bhosted_name=""
|
||||
fi
|
||||
|
||||
[ -n "$_bhosted_name" ] || _bhosted_name="@"
|
||||
|
||||
_debug "_domain" "$_domain"
|
||||
_debug "_bhosted_sld" "$_bhosted_sld"
|
||||
_debug "_bhosted_tld" "$_bhosted_tld"
|
||||
_debug "_bhosted_name" "$_bhosted_name"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_bhosted_api_add_txt() {
|
||||
_sld="$1"
|
||||
_tld="$2"
|
||||
_name="$3"
|
||||
_content="$4"
|
||||
_ttl="$5"
|
||||
|
||||
_u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)"
|
||||
_u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)"
|
||||
_u_sld="$(printf "%s" "$_sld" | _url_encode)"
|
||||
_u_tld="$(printf "%s" "$_tld" | _url_encode)"
|
||||
_u_name="$(printf "%s" "$_name" | _url_encode)"
|
||||
_u_content="$(printf "%s" "$_content" | _url_encode)"
|
||||
_u_ttl="$(printf "%s" "$_ttl" | _url_encode)"
|
||||
|
||||
_data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&type=TXT&name=${_u_name}&content=${_u_content}&ttl=${_u_ttl}"
|
||||
|
||||
_debug "bHosted add endpoint" "${BHOSTED_API_ROOT}/addrecord"
|
||||
response="$(_post "$_data" "${BHOSTED_API_ROOT}/addrecord")"
|
||||
_ret="$?"
|
||||
|
||||
_debug2 "bHosted add response" "$response"
|
||||
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "bHosted addrecord request failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _bhosted_response_has_error "$response"; then
|
||||
_err "bHosted addrecord returned an error"
|
||||
_debug2 "response" "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_bhosted_api_del_record() {
|
||||
_sld="$1"
|
||||
_tld="$2"
|
||||
_id="$3"
|
||||
|
||||
_u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)"
|
||||
_u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)"
|
||||
_u_sld="$(printf "%s" "$_sld" | _url_encode)"
|
||||
_u_tld="$(printf "%s" "$_tld" | _url_encode)"
|
||||
_u_id="$(printf "%s" "$_id" | _url_encode)"
|
||||
|
||||
_url="${BHOSTED_API_ROOT}/delrecord"
|
||||
_data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&id=${_u_id}"
|
||||
|
||||
_debug "bHosted delete endpoint" "$_url"
|
||||
response="$(_post "$_data" "$_url")"
|
||||
_ret="$?"
|
||||
|
||||
_debug2 "bHosted delete response" "$response"
|
||||
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "bHosted delrecord request failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _bhosted_response_has_error "$response"; then
|
||||
_err "bHosted delrecord returned an error"
|
||||
_debug2 "response" "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Extract XML tag value from response, e.g. <id>12345</id>
|
||||
_bhosted_xml_value() {
|
||||
_tag="$1"
|
||||
_resp="$2"
|
||||
|
||||
# Flatten response to simplify parsing
|
||||
_flat="$(printf "%s" "$_resp" | tr -d '\r\n\t')"
|
||||
printf "%s" "$_flat" | sed -n "s:.*<${_tag}>\\([^<]*\\)</${_tag}>.*:\\1:p" | _head_n 1
|
||||
}
|
||||
|
||||
# Return code convention:
|
||||
# return 0 => response HAS error
|
||||
# return 1 => response has NO error (success)
|
||||
_bhosted_response_has_error() {
|
||||
_resp="$1"
|
||||
|
||||
# Empty response = error
|
||||
if [ -z "$_resp" ]; then
|
||||
_debug "Empty API response"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Prefer explicit bHosted XML response fields
|
||||
if _contains "$_resp" "<response>"; then
|
||||
_errors="$(_bhosted_xml_value "errors" "$_resp")"
|
||||
_done="$(_bhosted_xml_value "done" "$_resp")"
|
||||
_subcommand="$(_bhosted_xml_value "subcommand" "$_resp")"
|
||||
_id="$(_bhosted_xml_value "id" "$_resp")"
|
||||
|
||||
_debug "bHosted XML subcommand" "$_subcommand"
|
||||
_debug "bHosted XML id" "$_id"
|
||||
_debug "bHosted XML errors" "$_errors"
|
||||
_debug "bHosted XML done" "$_done"
|
||||
|
||||
# Success according to provided format
|
||||
if [ "$_errors" = "0" ] && [ "$_done" = "true" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "bHosted XML indicates failure"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fallback for unexpected/non-XML responses
|
||||
_resp_lc="$(_lower_case "$_resp")"
|
||||
|
||||
if _contains "$_resp_lc" "error"; then
|
||||
_debug "Detected 'error' in response"
|
||||
return 0
|
||||
fi
|
||||
if _contains "$_resp_lc" "fout"; then
|
||||
_debug "Detected 'fout' in response"
|
||||
return 0
|
||||
fi
|
||||
if _contains "$_resp_lc" "invalid"; then
|
||||
_debug "Detected 'invalid' in response"
|
||||
return 0
|
||||
fi
|
||||
if _contains "$_resp_lc" "failed"; then
|
||||
_debug "Detected 'failed' in response"
|
||||
return 0
|
||||
fi
|
||||
if _contains "$_resp_lc" "denied"; then
|
||||
_debug "Detected 'denied' in response"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If no explicit error markers found, assume success
|
||||
return 1
|
||||
}
|
||||
|
||||
# Extract record id from response
|
||||
# Supports bHosted XML first, then generic fallbacks
|
||||
_bhosted_extract_id() {
|
||||
_resp="$1"
|
||||
|
||||
# bHosted XML: <id>12345</id>
|
||||
_id="$(_bhosted_xml_value "id" "$_resp" | tr -cd '0-9')"
|
||||
if [ -n "$_id" ]; then
|
||||
printf "%s" "$_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# JSON: "id":12345
|
||||
_id="$(printf "%s" "$_resp" | _egrep_o '"id"[ ]*:[ ]*[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')"
|
||||
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')"
|
||||
if [ -n "$_id" ]; then
|
||||
printf "%s" "$_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Create a unique config key for cached record ids
|
||||
_bhosted_cache_hash() {
|
||||
_fd="$1"
|
||||
_tv="$2"
|
||||
# md5 hex of fulldomain|txtvalue
|
||||
printf "%s|%s" "$_fd" "$_tv" | _digest md5 hex
|
||||
}
|
||||
|
||||
_bhosted_cache_key() {
|
||||
_hash="$1"
|
||||
printf "%s" "BHOSTED_TXT_ID_${_hash}"
|
||||
}
|
||||
|
||||
_bhosted_mem_set_id() {
|
||||
_hash="$1"
|
||||
_id="$2"
|
||||
_key="$(_bhosted_cache_key "$_hash")"
|
||||
_savedomainconf "$_key" "$_id"
|
||||
}
|
||||
|
||||
_bhosted_mem_get_id() {
|
||||
_hash="$1"
|
||||
_key="$(_bhosted_cache_key "$_hash")"
|
||||
_readdomainconf "$_key"
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_bookmyname_info='BookMyName.com
|
||||
Site: BookMyName.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bookmyname
|
||||
Options:
|
||||
BOOKMYNAME_USERNAME Username
|
||||
BOOKMYNAME_PASSWORD Password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/3209
|
||||
Author: @Neilpang
|
||||
'
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
# BookMyName urls:
|
||||
# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=add&value="XXXXXXXX"'
|
||||
# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=remove&value="XXXXXXXX"'
|
||||
|
||||
# Output:
|
||||
#good: update done, cid 123456, domain id 456789, type txt, ip XXXXXXXX
|
||||
#good: remove done 1, cid 123456, domain id 456789, ttl 300, type txt, ip XXXXXXXX
|
||||
|
||||
# Be careful, BMN DNS servers can be slow to pick up changes; using dnssleep is thus advised.
|
||||
|
||||
# Usage:
|
||||
# export BOOKMYNAME_USERNAME="ABCDE-FREE"
|
||||
# export BOOKMYNAME_PASSWORD="MyPassword"
|
||||
# /usr/local/ssl/acme.sh/acme.sh --dns dns_bookmyname --dnssleep 600 --issue -d domain.tld
|
||||
|
||||
#Usage: dns_bookmyname_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_bookmyname_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
_info "Using bookmyname"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}"
|
||||
BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}"
|
||||
|
||||
if [ -z "$BOOKMYNAME_USERNAME" ] || [ -z "$BOOKMYNAME_PASSWORD" ]; then
|
||||
BOOKMYNAME_USERNAME=""
|
||||
BOOKMYNAME_PASSWORD=""
|
||||
_err "You didn't specify BookMyName username and password yet."
|
||||
_err "Please specify them and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
#save the credentials to the account conf file.
|
||||
_saveaccountconf_mutable BOOKMYNAME_USERNAME "$BOOKMYNAME_USERNAME"
|
||||
_saveaccountconf_mutable BOOKMYNAME_PASSWORD "$BOOKMYNAME_PASSWORD"
|
||||
|
||||
uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/"
|
||||
data="?hostname=${fulldomain}&type=TXT&ttl=300&do=add&value=${txtvalue}"
|
||||
result="$(_get "${uri}${data}")"
|
||||
_debug "Result: $result"
|
||||
|
||||
if ! _startswith "$result" 'good: update done, cid '; then
|
||||
_err "Can't add $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#Usage: fulldomain txtvalue
|
||||
#Remove the txt record after validation.
|
||||
dns_bookmyname_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
_info "Using bookmyname"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}"
|
||||
BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}"
|
||||
|
||||
uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/"
|
||||
data="?hostname=${fulldomain}&type=TXT&ttl=300&do=remove&value=${txtvalue}"
|
||||
result="$(_get "${uri}${data}")"
|
||||
_debug "Result: $result"
|
||||
|
||||
if ! _startswith "$result" 'good: remove done 1, cid '; then
|
||||
_info "Can't remove $fulldomain"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_bunny_info='Bunny.net
|
||||
Site: Bunny.net/dns/
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bunny
|
||||
Options:
|
||||
BUNNY_API_KEY API Key
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/4296
|
||||
Author: <nosilver4u@ewww.io>
|
||||
'
|
||||
|
||||
## Will be called by acme.sh to add the TXT record via the Bunny DNS API.
|
||||
## returns 0 means success, otherwise error.
|
||||
|
||||
## Author: nosilver4u <nosilver4u at ewww.io>
|
||||
## GitHub: https://github.com/nosilver4u/acme.sh
|
||||
|
||||
##
|
||||
## Environment Variables Required:
|
||||
##
|
||||
## BUNNY_API_KEY="75310dc4-ca77-9ac3-9a19-f6355db573b49ce92ae1-2655-3ebd-61ac-3a3ae34834cc"
|
||||
##
|
||||
|
||||
##################### Public functions #####################
|
||||
|
||||
|
|
@ -196,7 +199,7 @@ _get_base_domain() {
|
|||
_debug2 domain_list "$domain_list"
|
||||
|
||||
i=1
|
||||
while [ "$i" -gt 0 ]; do
|
||||
while [ $i -gt 0 ]; do
|
||||
## get next longest domain
|
||||
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
|
||||
## check we got something back from our cut (or are we at the end)
|
||||
|
|
@ -208,7 +211,7 @@ _get_base_domain() {
|
|||
## check if it exists
|
||||
if [ -n "$found" ]; then
|
||||
## exists - exit loop returning the parts
|
||||
sub_point=$(_math "$i" - 1)
|
||||
sub_point=$(_math $i - 1)
|
||||
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
|
||||
_domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
|
||||
_debug _domain_id "$_domain_id"
|
||||
|
|
@ -218,11 +221,11 @@ _get_base_domain() {
|
|||
return 0
|
||||
fi
|
||||
## increment cut point $i
|
||||
i=$(_math "$i" + 1)
|
||||
i=$(_math $i + 1)
|
||||
done
|
||||
|
||||
if [ -z "$found" ]; then
|
||||
page=$(_math "$page" + 1)
|
||||
page=$(_math $page + 1)
|
||||
nextpage="https://api.bunny.net/dnszone?page=$page"
|
||||
## Find the next page if we don't have a match.
|
||||
hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")"
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_calrissia_info='Calrissia.be DNS API
|
||||
Site: calrissia.be
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_calrissia
|
||||
Options:
|
||||
CALRISSIA_TOKEN Personal access token
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/6809
|
||||
Author: Ward Hus
|
||||
'
|
||||
|
||||
CALRISSIA_API="https://my.calrissia.com/api"
|
||||
|
||||
dns_calrissia_add() {
|
||||
fulldomain="$1"
|
||||
txtvalue="$2"
|
||||
|
||||
_calrissia_load_token || return 1
|
||||
|
||||
if ! _calrissia_get_root "$fulldomain"; then
|
||||
_err "Unable to find domain in Calrissia account for: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'"
|
||||
_info "Adding TXT record for $fulldomain"
|
||||
|
||||
_body="{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120,\"prio\":0}"
|
||||
_response="$(_calrissia_request POST "/domain/$_domain_id/record" "$_body")"
|
||||
|
||||
if ! _contains "$_response" '"id"'; then
|
||||
_err "Failed to create TXT record: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dns_calrissia_rm() {
|
||||
fulldomain="$1"
|
||||
txtvalue="$2"
|
||||
|
||||
_calrissia_load_token || return 1
|
||||
|
||||
if ! _calrissia_get_root "$fulldomain"; then
|
||||
_err "Unable to find domain in Calrissia account for: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'"
|
||||
|
||||
# Look the record up from the API instead of relying on local state.
|
||||
# The record list is embedded in the domain object.
|
||||
_response="$(_calrissia_request GET "/domain/$_domain_id")"
|
||||
_debug2 "Response: $_response"
|
||||
|
||||
# Split the record objects onto separate lines, then match on both the
|
||||
# subdomain name and the TXT value to find the record id to delete.
|
||||
_record_id="$(printf "%s" "$_response" |
|
||||
tr '{}' '\n' |
|
||||
grep "\"name\" *: *\"$_sub_domain\"" |
|
||||
grep "\"content\" *: *\"$txtvalue\"" |
|
||||
_egrep_o '"id" *: *[0-9]+' |
|
||||
_head_n 1 |
|
||||
_egrep_o '[0-9]+')"
|
||||
|
||||
if [ -z "$_record_id" ]; then
|
||||
_info "No matching TXT record found for $fulldomain; nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_info "Removing TXT record id=$_record_id from domain id=$_domain_id"
|
||||
if ! _response="$(_calrissia_request DELETE "/domain/$_domain_id/record/$_record_id")" || _contains "$_response" '"error"'; then
|
||||
_err "Failed to remove TXT record: $_response"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
####################
|
||||
# Private helpers #
|
||||
####################
|
||||
|
||||
_calrissia_load_token() {
|
||||
CALRISSIA_TOKEN="${CALRISSIA_TOKEN:-$(_readaccountconf_mutable CALRISSIA_TOKEN)}"
|
||||
if [ -z "$CALRISSIA_TOKEN" ]; then
|
||||
_err "CALRISSIA_TOKEN is not set. Generate one at https://identity.calrissia.com under API Keys."
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable CALRISSIA_TOKEN "$CALRISSIA_TOKEN"
|
||||
}
|
||||
|
||||
# Sets _domain, _domain_id, _sub_domain for a given FQDN.
|
||||
_calrissia_get_root() {
|
||||
_fqdn="$1"
|
||||
|
||||
i=1
|
||||
while true; do
|
||||
_candidate="$(printf "%s" "$_fqdn" | cut -d . -f "$i"-)"
|
||||
[ -z "$_candidate" ] && return 1
|
||||
|
||||
_debug "Trying root domain: $_candidate"
|
||||
_response="$(_calrissia_request GET "/domain?full_domain_name=$_candidate")"
|
||||
_debug2 "Response: $_response"
|
||||
|
||||
_domain_id="$(printf "%s" "$_response" |
|
||||
_egrep_o '"id" *: *[0-9]+' |
|
||||
_head_n 1 |
|
||||
_egrep_o '[0-9]+')"
|
||||
|
||||
if [ -n "$_domain_id" ]; then
|
||||
if [ "$i" = "1" ]; then
|
||||
# The FQDN itself is the zone apex, e.g. a challenge-alias domain.
|
||||
_sub_domain=""
|
||||
else
|
||||
_sub_domain="$(printf "%s" "$_fqdn" | cut -d . -f "1-$((i - 1))")"
|
||||
fi
|
||||
_domain="$_candidate"
|
||||
return 0
|
||||
fi
|
||||
|
||||
i=$((i + 1))
|
||||
done
|
||||
}
|
||||
|
||||
_calrissia_request() {
|
||||
_method="$1"
|
||||
_path="$2"
|
||||
_body="$3"
|
||||
export _H1="Authorization: Bearer $CALRISSIA_TOKEN"
|
||||
export _H2="Accept: application/json"
|
||||
if [ "$_method" = "GET" ]; then
|
||||
_get "$CALRISSIA_API$_path"
|
||||
else
|
||||
_post "$_body" "$CALRISSIA_API$_path" "" "$_method" "application/json"
|
||||
fi
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue