Skip to content

v3.3.0 — aligned output, --purge, audit fixes, perf & CI

Latest

Choose a tag to compare

@Erreur32 Erreur32 released this 01 Jun 11:43
· 2 commits to main since this release
5165f8c

Output alignment overhaul, a new --cert-delete --purge flag, a full code/security audit pass (HIGH/MED/LOW fixes), performance work, and a GitHub Actions CI (ShellCheck + shfmt + Gitleaks).

✨ New Features

  • --cert-delete <id|domain> --purge — New optional --purge flag. After the API soft-delete (NPM sets is_deleted=1, which only removes the cert from the list/UI), --purge also deletes the leftover on-disk files: custom_ssl/npm-<id>, letsencrypt/live|archive/npm-<id> and letsencrypt/renewal/npm-<id>.conf. Requires NGINX_PATH_DOCKER to be set in npm-api.conf; without it (or if the path is not accessible) --purge is a safe no-op with a clear message. The DB row is intentionally left untouched to avoid referential-integrity risk and DB-schema drift between NPM versions.
  • --user-create … --admin — New optional --admin flag. Users are now created as standard (non-admin) by default; pass --admin to grant the admin role. Previously every created user was forced to roles: ["admin"].
  • --cert-show-all — Now works as an alias of --cert-list (it was shown in usage text but had no parser case).

💅 Output / Display

  • host_list() — long & multiple domains — Proxy hosts with several domains (or a very long one) no longer break column alignment. The first domain stays on the main row; the remaining domains are listed below, indented under the DOMAIN column. New truncate_pad() helper truncates over-long values with an ellipsis () instead of overflowing, applied to the DOMAIN and TARGET columns.
  • redirect_host_list() — same alignment fix — Applied the identical treatment to the Redirection Hosts table (it had the same pad-without-truncation overflow bug): truncate_pad() on the DOMAIN and FORWARD DOMAIN columns, plus multi-line display for hosts with multiple domains.
  • show_help() — aligned description column — New help_row() helper measures visible width (ignoring ANSI color codes and counting wide emoji such as 🆔 as 2 cells) and aligns every description to a fixed column. Lines whose left part is wider than the column (e.g. --cert-download) wrap their description onto the next line, still aligned. All option lines converted to help_row.

🐛 Bug Fixes

  • list_cert_all() — wrong Valid/Expired statistics — The stats used select(.expires_on > now), comparing an ISO date string to the numeric now; in jq every string sorts greater than every number, so "Valid" always equalled the total and "Expired" was always 0. Now uses the API's own .expired boolean (select(.expired == true) / != true).
  • cert_delete() — no check that the certificate is still in use — Before deleting, the script now lists the proxy/redirection hosts that still reference the certificate and warns that they will be left with a dangling certificate_id. When combined with --purge, the purge is now refused while the cert is still referenced (its on-disk files are in use), to avoid breaking live TLS.
  • --access-list-create — command ran during argument parsing; bottom dispatch was dead codeACCESS_LIST_CREATE was never set, so the dispatcher branch was unreachable and the function executed mid-parse. Now aligned with --access-list-update: arguments are stashed (ACCESS_LIST_CREATE_ARGS) and the function runs from the dispatch block.
  • --access-list-create --pass-auth — asymmetric parsing — The create parser set --pass-auth with no value while --access-list-update (and the documented usage --pass-auth true) expects a true|false argument, so --pass-auth true failed with "Unknown option true". Create now consumes and validates the value like update.
  • host_acl_enable / host_acl_disable — error handling — Read the error from the wrong jq path (.message instead of .error.message) and never checked the HTTP status. Now capture HTTPSTATUS, treat non-200 as failure (exit 1), and read .error.message.
  • host_show() — wrong fields — Read .websockets_enabled (always null) instead of the real field .allow_websocket_upgrade, and passed .forward_scheme (http/https) through colorize_boolean (mislabelled). Both fixed.
  • user_create — no longer writes the raw API response to /tmp/npm_debug.log (a world-readable path).
  • cert_delete — declining the confirmation now exits 0 (deliberate user choice) instead of 1.
  • Exit codeshost_enable / host_disable and redirect_host_enable / redirect_host_disable now return 1 on every failure path (previously they printed the error but still returned success).
  • full_backupsuccess_count double-counted — Each backed-up certificate incremented the success counter twice, inflating the final summary. Fixed to count once.

🛡️ Robustness

  • --info dispatch — Added a dedicated elif [ "$INFO" = true ] branch so --info works even when combined with other flags (it previously only ran as the no-argument fallback).
  • set -u safety — Initialized CERT_ID, GENERATED_CERT_ID, USER_ID and search_term in the top variable block (they were referenced in the dispatcher before being set on some code paths).
  • set -e safety — Guarded the 11 [ "$HTTP_STATUS" -eq … ] checks with ${VAR:-0} so an empty/garbled response falls into the error branch instead of aborting the script, and converted the 10 ((var++)) counters to var=$((var + 1)) (the ((x++)) form returns exit status 1 when x is 0, which aborts under set -e).
  • Consistent boolean defaultsHTTP2_SUPPORT, SSL_FORCED, HSTS_ENABLED and HSTS_SUBDOMAINS now default to false instead of 0 (same class as the Issue #23 fix), and the duplicate AUTO_YES=false declaration was removed.

⚡ Performance

  • Token read once per run — The API token was re-read from disk with $(cat "$TOKEN_FILE") on every single request (~81 call sites). It is now cached in a global $TOKEN (populated by check_token_notverbose); headers use ${TOKEN:-$(cat "$TOKEN_FILE")} so an empty cache still falls back safely to reading the file.
  • host_list — one certificate fetch instead of one per host — The CERT DOMAIN column previously triggered a GET /nginx/certificates/<id> call for every proxy host that had SSL. The full certificate list is now fetched once and resolved locally through an id → domains map, turning N API round-trips into 1.

♻️ Code Quality / Cleanup

  • Factored the duplicated certificate formatter — The three near-identical jq cert-formatting one-liners (in cert_show ID/domain branches and list_cert_all) now share a single CERT_JQ_FMT format string and a cert_colorize helper; this also fixes the inconsistent Provider : vs Provider: label.
  • access_list table — Guarded proxy_host_count against null (// 0), truncated long names to the column width via truncate_pad, and made the "Proxy Hosts" cell fixed-width so a 2-digit count no longer breaks the box border.
  • Consistent confirmation prompts — All (y/n) prompts now accept ^[Yy]$ (the two French leftovers accepting O/o were aligned).
  • English usage text — Translated the remaining French usage/error strings (--host-update, --access-list-show) to match the rest of the CLI.
  • Removed dead code — Dropped the unimplemented -O / -J flag stubs (they only printed todo) and a duplicated GET /nginx/certificates block in cert_generate.

🆕 Documentation / Help

  • --host-list-full — Now shown in --help (was a functional but hidden/commented command). Lists all proxy hosts with full details (JSON).
  • --cert-generate — Help now lists the new NPM v2.15.0 Certbot DNS plugins: hostinger, rcodezero, hoster.by.
  • Minor help wording fixes: Show Default… (double space) and Check Check current token info (duplicated word) cleaned up.

🔎 Compatibility

  • Reviewed against NPM v2.15.0: no proxy-host/certificate data-model changes, version is read dynamically, and DNS provider names are passed through to the API — so no breaking changes for the script.

Full changelog: CHANGELOG.md