Last active
September 15, 2025 09:12
-
-
Save Reiner030/8583a101148636e7368afc1c428593cb to your computer and use it in GitHub Desktop.
OPNSense ACME plugin tool to reorder within /conf/config.xml all (or by UUID defined) certificate(s) with their action tasks + backup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # POSIX /bin/sh – OPNsense kompatibel | |
| # Sortiert <restartActions> stabil: Copy-* → Sent-* → Reload-* → Restart-* | |
| # und sortiert JEDE Gruppe alphabetisch nach Action-Name. | |
| # Usage: | |
| # ./sort_acme_restart_actions_posix.sh # anwenden | |
| # ./sort_acme_restart_actions_posix.sh -n # Dry-Run | |
| # CERT_UUID=<uuid> ./sort_acme_restart_actions_posix.sh # nur dieses Zertifikat | |
| # vorsichtig sein, wenn gerade acme.sh läuft | |
| pgrep -f "[a]cme.sh" >/dev/null 2>&1 && exit 0 | |
| set -eu | |
| CFG="/conf/config.xml" | |
| TS="$(date +%Y%m%d-%H%M%S)" | |
| BAK="/conf/config.xml.bak.$TS" | |
| TMP="$(mktemp /tmp/config.xml.XXXXXX)" | |
| # Optional: -n = Dry-Run (nur anzeigen, nichts schreiben) | |
| DRYRUN=0 | |
| [ "${1:-}" = "-n" ] && DRYRUN=1 | |
| # Nur dieses Zertifikat anfassen? (UUID) | |
| TARGET_CERT="${CERT_UUID:-}" | |
| # Tools prüfen | |
| command -v xmllint >/dev/null 2>&1 || { | |
| echo "xmllint nicht gefunden. Bitte ggf. 'pkg install libxml2'." >&2 | |
| exit 1 | |
| } | |
| # Action-Name aus UUID ermitteln (leer => unbekannt) | |
| get_action_name() { | |
| uuid="$1" | |
| xmllint --xpath "normalize-space(//AcmeClient/actions/action[@uuid='$uuid']/name)" "$CFG" 2>/dev/null || true | |
| } | |
| # CSV-Gruppe nach Name sortieren (alphabetisch, case-insensitive) | |
| # Input: CSV; Output: CSV | |
| sort_group() { | |
| csv="$1" | |
| [ -z "$csv" ] && { echo ""; return; } | |
| tmpfile="$(mktemp /tmp/acmegrp.XXXXXX)" | |
| OLDIFS="$IFS"; IFS=, | |
| for u in $csv; do | |
| u="$(echo "$u" | tr -d '[:space:]')" | |
| [ -z "$u" ] && continue | |
| n="$(get_action_name "$u")" | |
| # sort key: name|uuid | |
| printf '%s|%s\n' "${n:-zzz_unknown_$u}" "$u" >> "$tmpfile" | |
| done | |
| IFS="$OLDIFS" | |
| # sortiere nach Name (case-insensitive), stabil | |
| # und gebe nur die UUIDs als CSV aus | |
| # shellcheck disable=SC2002 | |
| sorted="$(cat "$tmpfile" | sort -f | awk -F'|' 'BEGIN{first=1}{if(!first){printf(",")} printf $2; first=0}')" | |
| rm -f "$tmpfile" | |
| echo "$sorted" | |
| } | |
| # Zertifikats-UUIDs | |
| cer_uuids="$(xmllint --xpath "//AcmeClient/certificates/certificate/@uuid" "$CFG" \ | |
| | sed 's/uuid="/\n/g' | sed 's/"//g' | awk NF)" | |
| [ -z "$cer_uuids" ] && { echo "Keine Zertifikate gefunden."; exit 0; } | |
| changed=0 | |
| for cuuid in $cer_uuids; do | |
| [ -n "$TARGET_CERT" ] && [ "$cuuid" != "$TARGET_CERT" ] && continue | |
| csv="$(xmllint --xpath "string(//AcmeClient/certificates/certificate[@uuid='$cuuid']/restartActions)" "$CFG" 2>/dev/null || true)" | |
| [ -z "$csv" ] && continue | |
| copies=""; sents=""; reloads=""; restarts="" | |
| OLDIFS="$IFS"; IFS=, | |
| for u in $csv; do | |
| u="$(echo "$u" | tr -d '[:space:]')" | |
| [ -z "$u" ] && continue | |
| name="$(get_action_name "$u")" | |
| case "$name" in | |
| Copy-*) copies="${copies:+$copies,}$u" ;; | |
| Sent-*) sents="${sents:+$sents,}$u" ;; | |
| Restart-*) restarts="${restarts:+$restarts,}$u" ;; | |
| *) reloads="${reloads:+$reloads,}$u" ;; | |
| esac | |
| done | |
| IFS="$OLDIFS" | |
| # JEDE Gruppe sortieren (nach Name), dann in definierter Reihenfolge zusammenführen | |
| copies_sorted="$(sort_group "$copies")" | |
| sents_sorted="$(sort_group "$sents")" | |
| reloads_sorted="$(sort_group "$reloads")" | |
| restarts_sorted="$(sort_group "$restarts")" | |
| newcsv="$copies_sorted" | |
| [ -n "$sents_sorted" ] && newcsv="${newcsv:+$newcsv,}$sents_sorted" | |
| [ -n "$reloads_sorted" ] && newcsv="${newcsv:+$newcsv,}$reloads_sorted" | |
| [ -n "$restarts_sorted" ] && newcsv="${newcsv:+$newcsv,}$restarts_sorted" | |
| if [ "$newcsv" != "$csv" ]; then | |
| echo "Zertifikat $cuuid: Reihenfolge wird angepasst:" | |
| echo " ALT: $csv" | |
| echo " NEU: $newcsv" | |
| if [ $DRYRUN -eq 0 ]; then | |
| if [ $changed -eq 0 ]; then | |
| cp "$CFG" "$BAK" | |
| echo "Backup: $BAK" | |
| fi | |
| awk -v uuid="$cuuid" -v newcsv="$newcsv" ' | |
| /<certificate[[:space:]]+uuid="/ { | |
| if ($0 ~ "<certificate[[:space:]]+uuid=\"" uuid "\"") in_cert=1; else in_cert=0 | |
| } | |
| in_cert && match($0, /<restartActions>.*<\/restartActions>/) { | |
| sub(/<restartActions>.*<\/restartActions>/, "<restartActions>" newcsv "</restartActions>") | |
| } | |
| { print } | |
| in_cert && /<\/certificate>/ { in_cert=0 } | |
| ' "$CFG" > "$TMP" && mv "$TMP" "$CFG" | |
| changed=1 | |
| fi | |
| fi | |
| done | |
| if [ $DRYRUN -eq 1 ]; then | |
| echo "Dry-Run beendet. Nichts geschrieben." | |
| exit 0 | |
| fi | |
| if [ $changed -eq 1 ]; then | |
| echo "Konfiguration aktualisiert. Lade Config neu…" | |
| configctl config reload >/dev/null 2>&1 || true | |
| echo "Fertig. GUI kann weiter 'gemischt' aussehen; die Abarbeitung folgt jetzt: Copy → Sent → Reload → Restart (mit interner Sortierung)." | |
| else | |
| echo "Keine Änderung erforderlich." | |
| fi |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
OPNsense ACME Client – Automation Order Helper Scripts
These helper scripts address a bug in the OPNsense ACME client plugin (os-acme-client 4.9 on OPNsense Business 25.4.3) where the GUI does not preserve the order of automations.
On save/re-open, the
<restartActions>list in/conf/config.xmlmay be reordered (often UUID-driven), causingReload-*actions to run beforeCopy-/Sent-*.That can lead to services reloading with old certificates still in place.
The issue is tracked here: opnsense/plugins#4940
Scripts
Show current order
acme_show_certificate_orders.shLists all certificates, their automation UUIDs, and the mapped action names in the current execution order.
Sort
<restartActions>orderacme_reorder_sorted_certificate_tasks.shRewrites
/conf/config.xmlto enforce this stable order:Copy-*Sent-*Reload-*Restart-*(e.g. WebGUI)Each run makes a backup of
/conf/config.xmland reloads configd.Usage
Show current order
Example output:
Sort order
Dry run (no changes):
Apply (writes backup + new order):
Target a single certificate only:
Automate with cron
Run regularly to correct after GUI edits:
The script automatically aborts if
acme.shis currently running.Example Diff
Before:
After:
Notes