Skip to content

Commit a4c37fc

Browse files
committed
feat: Add certificate download functionality with fallback support
🆕 New Features: - Add --cert-download command to download certificates as ZIP files - Implement automatic fallback from new API (JSON) to legacy API (ZIP) - Add support for wildcard file matching (cert*.pem, privkey*.pem, etc.) - Create standardized output format with .crt, .key, .chain.crt files - Generate ZIP archive containing all certificate files 🔧 Technical Improvements: - New API: Uses /nginx/certificates/{id}/certificates endpoint with JSON response - Legacy API: Uses /nginx/certificates/{id}/download endpoint with ZIP response - Automatic detection of certificate files with wildcards (supports cert8.pem, cert9.pem, etc.) - Proper error handling and cleanup of temporary files - Enhanced help messages and documentation 📚 Documentation Updates: - Update CHANGELOG.md with detailed feature description - Update README.md version to 3.0.6 - Add comprehensive usage examples for --cert-download - Add acknowledgments section for PR #20 contributor ✅ Resolves certificate download issues on newer NPM installations ✅ Supports both old and new NPM API versions ✅ Creates standardized certificate files and ZIP archives Resolves: #20 (PR #20) Version: 3.0.6
1 parent 9f4fa85 commit a4c37fc

File tree

3 files changed

+267
-4
lines changed

3 files changed

+267
-4
lines changed

CHANGELOG.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,42 @@
1-
## [3.0.5] - 2025-01-20e changes to the npm-api.sh script will be documented in this file.
1+
# Changelog
2+
3+
All notable changes to the npm-api.sh script will be documented in this file.
4+
5+
## [3.0.6] - 2025-01-20
6+
7+
### 🆕 New Features
8+
9+
- **Added certificate download functionality with fallback support** ([PR #20](https://github.com/Erreur32/nginx-proxy-manager-Bash-API/pull/20))
10+
- **Issue**: Certificate download failed on newer NPM installations due to API changes
11+
- **Solution**:
12+
- Added `--cert-download` command to download certificates as ZIP files
13+
- Implemented automatic fallback from new API (JSON) to legacy API (ZIP)
14+
- Added support for wildcard file matching (cert*.pem, privkey*.pem, etc.)
15+
- Created standardized output format with .crt, .key, .chain.crt files
16+
- **Technical Details**:
17+
- New API: Uses `/nginx/certificates/{id}/certificates` endpoint with JSON response
18+
- Legacy API: Uses `/nginx/certificates/{id}/download` endpoint with ZIP response
19+
- Automatic detection of certificate files with wildcards (supports cert8.pem, cert9.pem, etc.)
20+
- Creates both individual files and ZIP archive for easy distribution
21+
- Proper error handling and cleanup of temporary files
22+
- **Usage Examples**:
23+
```bash
24+
# Download certificate with default settings
25+
./npm-api.sh --cert-download 123
26+
27+
# Download to specific directory
28+
./npm-api.sh --cert-download 123 ./certs
29+
30+
# Download with custom name
31+
./npm-api.sh --cert-download 123 ./certs mydomain
32+
```
33+
- **Output Files**:
34+
- `{cert_name}.crt` - Certificate file
35+
- `{cert_name}.key` - Private key file
36+
- `{cert_name}.chain.crt` - Intermediate certificate (if available)
37+
- `{cert_name}.fullchain.crt` - Full chain certificate (if available)
38+
- `{cert_name}_metadata.json` - Certificate metadata
39+
- `{cert_name}_certificate.zip` - ZIP archive containing all files
240

341
## [3.0.5] - 2025-01-20
442

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Stargazers][stars-shield]][stars]
77

88

9-
# Nginx Proxy Manager CLI Script V3.0.5 🚀
9+
# Nginx Proxy Manager CLI Script V3.0.6 🚀
1010

1111
## Table of Contents
1212

@@ -152,6 +152,7 @@ API_PASS="changeme"
152152
--cert-list List ALL SSL certificates
153153
--cert-show domain Or 🆔 List SSL certificates filtered by [domain name] (JSON)
154154
--cert-delete domain Or 🆔 Delete Certificate for the given 'domain'
155+
--cert-download 🆔 [output_dir] [cert_name] Download certificate as ZIP with fallback support
155156
--cert-generate domain [email] Generate Let's Encrypt Certificate or others Providers.
156157
• Standard domains: example.com, sub.example.com
157158
• Wildcard domains: *.example.com (requires DNS challenge)
@@ -227,7 +228,10 @@ API_PASS="changeme"
227228

228229
🔒 SSL Management:
229230
# List all certificates
230-
./npm-api.sh --list-ssl-cert
231+
./npm-api.sh --list-ssl-cert
232+
# Download certificate as ZIP
233+
./npm-api.sh --cert-download 123
234+
./npm-api.sh --cert-download 123 ./certs mydomain
231235
# Generate standard Let's Encrypt certificate
232236
./npm-api.sh --cert-generate domain.com [email] [dns_provider] [dns_credentials] [-y]
233237
# Generate wildcard certificate with Cloudflare
@@ -615,3 +619,9 @@ MIT License - see the [LICENSE.md][license] file for details
615619
[stars-shield]: https://img.shields.io/github/stars/Erreur32/nginx-proxy-manager-Bash-API.svg
616620
[stars]: https://github.com/Erreur32/nginx-proxy-manager-Bash-API/stargazers
617621

622+
---
623+
624+
## 🙏 Acknowledgments
625+
626+
Special thanks to [@popy2k14](https://github.com/popy2k14) for identifying and reporting the certificate download issue in [PR #20](https://github.com/Erreur32/nginx-proxy-manager-Bash-API/pull/20). Their contribution helped improve the script's compatibility with newer NPM installations by highlighting the API changes and the need for fallback support.
627+

npm-api.sh

Lines changed: 216 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# NPM api https://github.com/NginxProxyManager/nginx-proxy-manager/tree/develop/backend/schema
77
# https://github.com/NginxProxyManager/nginx-proxy-manager/tree/develop/backend/schema/components
88

9-
VERSION="3.0.5"
9+
VERSION="3.0.6"
1010

1111
#################################
1212
# This script allows you to manage Nginx Proxy Manager via the API. It provides
@@ -205,6 +205,7 @@ LIST_CERT_ALL=false
205205
CERT_SHOW=false
206206
CERT_GENERATE=false
207207
CERT_DELETE=false
208+
CERT_DOWNLOAD=false
208209
CERT_DOMAIN=""
209210
CERT_EMAIL=""
210211
DNS_PROVIDER=""
@@ -595,6 +596,7 @@ show_help() {
595596
echo -e " --cert-list List ALL SSL certificates"
596597
echo -e " --cert-show ${COLOR_CYAN}domain${CoR} Or ${COLOR_CYAN}🆔${CoR} List SSL certificates filtered by [domain name] (${COLOR_YELLOW}JSON${CoR})${CoR}"
597598
echo -e " --cert-delete ${COLOR_CYAN}domain${CoR} Or ${COLOR_CYAN}🆔${CoR} Delete Certificate for the given '${COLOR_YELLOW}domain${CoR}'"
599+
echo -e " --cert-download ${COLOR_CYAN}🆔${CoR} ${COLOR_CYAN}[output_dir]${CoR} ${COLOR_CYAN}[cert_name]${CoR} Download certificate as ZIP with fallback support"
598600

599601
echo -e " --cert-generate ${COLOR_CYAN}domain${CoR} ${COLOR_CYAN}[email]${CoR} Generate Let's Encrypt Certificate or others Providers.${CoR}"
600602
echo -e "${COLOR_YELLOW}Standard domains:${CoR} example.com, sub.example.com"
@@ -670,6 +672,8 @@ examples_cli() {
670672

671673
echo -e "${COLOR_GREY} # List all certificates${CoR}"
672674
echo -e " $0 --cert-list"
675+
echo -e "${COLOR_GREY} # Download certificate as ZIP${CoR}"
676+
echo -e " $0 --cert-download 123"
673677
echo -e "${COLOR_GREY} # Generate SSL certificate${CoR}"
674678
echo -e " $0 --cert-generate example.com --cert-email admin@example.com"
675679

@@ -1398,6 +1402,185 @@ list_cert_all() {
13981402
echo -e "${COLOR_RED}Expired${CoR} : ${COLOR_RED}$EXPIRED_CERTS${CoR}\n"
13991403
}
14001404

1405+
################################
1406+
# Function to download certificate as ZIP with fallback support
1407+
cert_download() {
1408+
local cert_id="$1"
1409+
local output_dir="${2:-./certificates}"
1410+
local cert_name="${3:-certificate_$cert_id}"
1411+
1412+
check_token_notverbose
1413+
1414+
if [ -z "$cert_id" ]; then
1415+
echo -e "\n ⛔ ${COLOR_RED}ERROR: Certificate ID is required${CoR}"
1416+
echo -e " Usage: "
1417+
echo -e " ${COLOR_ORANGE}$0 --cert-download <cert_id> [output_dir] [cert_name]${CoR}"
1418+
echo -e " ${COLOR_ORANGE}$0 --cert-download 123 ./certs mydomain${CoR}\n"
1419+
exit 1
1420+
fi
1421+
1422+
# Create output directory
1423+
mkdir -p "$output_dir"
1424+
1425+
echo -e "\n 🔒 Downloading certificate (ID: ${COLOR_YELLOW}$cert_id${CoR})"
1426+
echo -e " Output directory: ${COLOR_CYAN}$output_dir${CoR}"
1427+
echo -e " Certificate name: ${COLOR_CYAN}$cert_name${CoR}"
1428+
1429+
# Get certificate metadata first
1430+
local CERT_META
1431+
CERT_META=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id" \
1432+
-H "Authorization: Bearer $(cat "$TOKEN_FILE")")
1433+
1434+
if [ -z "$CERT_META" ] || ! echo "$CERT_META" | jq empty 2>/dev/null; then
1435+
echo -e "${COLOR_RED}Error: Unable to retrieve certificate metadata${CoR}"
1436+
exit 1
1437+
fi
1438+
1439+
# Check if certificate exists
1440+
if echo "$CERT_META" | jq -e '.error' >/dev/null; then
1441+
echo -e "${COLOR_RED}Certificate not found with ID: $cert_id${CoR}"
1442+
exit 1
1443+
fi
1444+
1445+
# Extract certificate information
1446+
local domain_names=$(echo "$CERT_META" | jq -r '.domain_names | join(", ")')
1447+
local provider=$(echo "$CERT_META" | jq -r '.provider // "Unknown"')
1448+
local expires_on=$(echo "$CERT_META" | jq -r '.expires_on // "N/A"')
1449+
1450+
echo -e " Domain(s): ${COLOR_YELLOW}$domain_names${CoR}"
1451+
echo -e " Provider: ${COLOR_YELLOW}$provider${CoR}"
1452+
echo -e " Expires: ${COLOR_YELLOW}$expires_on${CoR}"
1453+
1454+
# Try new API first (JSON format)
1455+
echo -e "\n 🔄 Trying new API format..."
1456+
local CERT_CONTENT
1457+
CERT_CONTENT=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id/certificates" \
1458+
-H "Authorization: Bearer $(cat "$TOKEN_FILE")" \
1459+
-H "Accept: application/json")
1460+
1461+
if [ -n "$CERT_CONTENT" ] && echo "$CERT_CONTENT" | jq empty 2>/dev/null; then
1462+
# Check if we got valid certificate data
1463+
local cert_data=$(echo "$CERT_CONTENT" | jq -r '.certificate // empty')
1464+
local private_data=$(echo "$CERT_CONTENT" | jq -r '.private // empty')
1465+
1466+
if [ -n "$cert_data" ] && [ -n "$private_data" ]; then
1467+
echo -e "${COLOR_GREEN}New API format successful${CoR}"
1468+
1469+
# Save certificate files
1470+
echo "$cert_data" > "$output_dir/${cert_name}.crt"
1471+
echo "$private_data" > "$output_dir/${cert_name}.key"
1472+
chmod 600 "$output_dir/${cert_name}.key"
1473+
1474+
# Save intermediate certificate if available
1475+
local intermediate_data=$(echo "$CERT_CONTENT" | jq -r '.intermediate // empty')
1476+
if [ -n "$intermediate_data" ]; then
1477+
echo "$intermediate_data" > "$output_dir/${cert_name}.chain.crt"
1478+
fi
1479+
1480+
# Save metadata
1481+
echo "$CERT_META" | jq '.' > "$output_dir/${cert_name}_metadata.json"
1482+
1483+
# Create ZIP file
1484+
local zip_file="$output_dir/${cert_name}_certificate.zip"
1485+
cd "$output_dir" || exit 1
1486+
zip -q "$zip_file" "${cert_name}.crt" "${cert_name}.key" "${cert_name}_metadata.json"
1487+
[ -f "${cert_name}.chain.crt" ] && zip -q "$zip_file" "${cert_name}.chain.crt"
1488+
cd - > /dev/null || exit 1
1489+
1490+
echo -e "${COLOR_GREEN}Certificate downloaded successfully${CoR}"
1491+
echo -e " Files created:"
1492+
echo -e "${COLOR_CYAN}${cert_name}.crt${CoR} (Certificate)"
1493+
echo -e "${COLOR_CYAN}${cert_name}.key${CoR} (Private Key)"
1494+
[ -f "$output_dir/${cert_name}.chain.crt" ] && echo -e "${COLOR_CYAN}${cert_name}.chain.crt${CoR} (Chain)"
1495+
echo -e "${COLOR_CYAN}${cert_name}_metadata.json${CoR} (Metadata)"
1496+
echo -e "${COLOR_CYAN}${cert_name}_certificate.zip${CoR} (ZIP Archive)"
1497+
1498+
return 0
1499+
fi
1500+
fi
1501+
1502+
# Fallback to old API (ZIP download)
1503+
echo -e " ⚠️ ${COLOR_YELLOW}New API failed, trying legacy ZIP download...${CoR}"
1504+
1505+
local TMP_DIR=$(mktemp -d)
1506+
local zip_file="$TMP_DIR/certificate.zip"
1507+
1508+
# Download ZIP file
1509+
local download_response
1510+
download_response=$(curl -s -w "%{http_code}" -X GET "$BASE_URL/nginx/certificates/$cert_id/download" \
1511+
-H "Authorization: Bearer $(cat "$TOKEN_FILE")" \
1512+
-o "$zip_file")
1513+
1514+
local http_code="${download_response: -3}"
1515+
1516+
if [ "$http_code" = "200" ] && [ -f "$zip_file" ] && [ -s "$zip_file" ]; then
1517+
echo -e "${COLOR_GREEN}Legacy ZIP download successful${CoR}"
1518+
1519+
# Extract ZIP file
1520+
if command -v unzip >/dev/null 2>&1; then
1521+
unzip -q "$zip_file" -d "$TMP_DIR"
1522+
1523+
# Find certificate files with wildcards (supporting cert8.pem, cert9.pem, etc.)
1524+
local cert_file=$(find "$TMP_DIR" -name "cert*.pem" | head -1)
1525+
local key_file=$(find "$TMP_DIR" -name "privkey*.pem" | head -1)
1526+
local chain_file=$(find "$TMP_DIR" -name "chain*.pem" | head -1)
1527+
local fullchain_file=$(find "$TMP_DIR" -name "fullchain*.pem" | head -1)
1528+
1529+
if [ -n "$cert_file" ] && [ -n "$key_file" ]; then
1530+
# Copy files to output directory
1531+
cp "$cert_file" "$output_dir/${cert_name}.crt"
1532+
cp "$key_file" "$output_dir/${cert_name}.key"
1533+
chmod 600 "$output_dir/${cert_name}.key"
1534+
1535+
# Copy chain file if available
1536+
if [ -n "$chain_file" ]; then
1537+
cp "$chain_file" "$output_dir/${cert_name}.chain.crt"
1538+
fi
1539+
1540+
# Copy fullchain if available
1541+
if [ -n "$fullchain_file" ]; then
1542+
cp "$fullchain_file" "$output_dir/${cert_name}.fullchain.crt"
1543+
fi
1544+
1545+
# Save metadata
1546+
echo "$CERT_META" | jq '.' > "$output_dir/${cert_name}_metadata.json"
1547+
1548+
# Create new ZIP file with standardized names
1549+
local final_zip="$output_dir/${cert_name}_certificate.zip"
1550+
cd "$output_dir" || exit 1
1551+
zip -q "$final_zip" "${cert_name}.crt" "${cert_name}.key" "${cert_name}_metadata.json"
1552+
[ -f "${cert_name}.chain.crt" ] && zip -q "$final_zip" "${cert_name}.chain.crt"
1553+
[ -f "${cert_name}.fullchain.crt" ] && zip -q "$final_zip" "${cert_name}.fullchain.crt"
1554+
cd - > /dev/null || exit 1
1555+
1556+
echo -e "${COLOR_GREEN}Certificate downloaded successfully (legacy method)${CoR}"
1557+
echo -e " Files created:"
1558+
echo -e "${COLOR_CYAN}${cert_name}.crt${CoR} (Certificate)"
1559+
echo -e "${COLOR_CYAN}${cert_name}.key${CoR} (Private Key)"
1560+
[ -f "$output_dir/${cert_name}.chain.crt" ] && echo -e "${COLOR_CYAN}${cert_name}.chain.crt${CoR} (Chain)"
1561+
[ -f "$output_dir/${cert_name}.fullchain.crt" ] && echo -e "${COLOR_CYAN}${cert_name}.fullchain.crt${CoR} (Full Chain)"
1562+
echo -e "${COLOR_CYAN}${cert_name}_metadata.json${CoR} (Metadata)"
1563+
echo -e "${COLOR_CYAN}${cert_name}_certificate.zip${CoR} (ZIP Archive)"
1564+
1565+
# Cleanup
1566+
rm -rf "$TMP_DIR"
1567+
return 0
1568+
else
1569+
echo -e "${COLOR_RED}Error: Could not find certificate files in ZIP${CoR}"
1570+
fi
1571+
else
1572+
echo -e "${COLOR_RED}Error: unzip command not found${CoR}"
1573+
fi
1574+
else
1575+
echo -e "${COLOR_RED}Error: Failed to download certificate ZIP (HTTP $http_code)${CoR}"
1576+
fi
1577+
1578+
# Cleanup on failure
1579+
rm -rf "$TMP_DIR"
1580+
echo -e "${COLOR_RED}Certificate download failed${CoR}"
1581+
exit 1
1582+
}
1583+
14011584
# Verify Cloudflare API Key validity
14021585
verify_cloudflare_api_key() {
14031586
local api_key="$1"
@@ -4153,6 +4336,36 @@ while [[ "$#" -gt 0 ]]; do
41534336
fi
41544337
;;
41554338
--cert-list) LIST_CERT_ALL=true; shift;;
4339+
--cert-download)
4340+
shift
4341+
if [ $# -eq 0 ] || [[ "$1" == -* ]]; then
4342+
echo -e "\n ⛔ ${COLOR_RED}The --cert-download option requires a certificate ID.${CoR}"
4343+
echo -e "\n ${COLOR_CYAN}Usage:${CoR}"
4344+
echo -e " ${COLOR_ORANGE}$0 --cert-download <cert_id> [output_dir] [cert_name]${CoR}"
4345+
echo -e "\n ${COLOR_CYAN}Examples:${CoR}"
4346+
echo -e " ${COLOR_GREEN}$0 --cert-download 240${CoR}"
4347+
echo -e " ${COLOR_GREEN}$0 --cert-download 240 ./certs${CoR}"
4348+
echo -e " ${COLOR_GREEN}$0 --cert-download 240 ./certs mydomain${CoR}"
4349+
echo -e "\n ${COLOR_YELLOW}💡 Tips:${CoR}"
4350+
echo -e " • Use ${COLOR_GREEN}--cert-list${CoR} to see all certificates and their IDs"
4351+
echo -e " • Supports both new API (JSON) and legacy API (ZIP) with automatic fallback"
4352+
echo -e " • Creates standardized certificate files (.crt, .key, .chain.crt) and ZIP archive\n"
4353+
exit 1
4354+
fi
4355+
CERT_DOWNLOAD=true
4356+
CERT_ID="$1"
4357+
shift
4358+
# Optional output directory
4359+
if [ $# -gt 0 ] && [[ "$1" != -* ]]; then
4360+
CERT_OUTPUT_DIR="$1"
4361+
shift
4362+
fi
4363+
# Optional certificate name
4364+
if [ $# -gt 0 ] && [[ "$1" != -* ]]; then
4365+
CERT_NAME="$1"
4366+
shift
4367+
fi
4368+
;;
41564369
--access-list) ACCESS_LIST=true; shift;;
41574370
--access-list-show)
41584371
ACCESS_LIST_SHOW=true
@@ -4267,6 +4480,8 @@ elif [ "$CERT_SHOW" = true ]; then
42674480
cert_show "$search_term"
42684481
elif [ "$LIST_CERT_ALL" = true ]; then
42694482
list_cert_all
4483+
elif [ "$CERT_DOWNLOAD" = true ]; then
4484+
cert_download "$CERT_ID" "${CERT_OUTPUT_DIR:-./certificates}" "${CERT_NAME:-certificate_$CERT_ID}"
42704485

42714486
elif [ "$ACCESS_LIST" = true ]; then
42724487
access_list

0 commit comments

Comments
 (0)