feat(lib): helper ini_set idempotent pour merge de fichiers INI #24
+107
@@ -17,6 +17,9 @@
|
||||
# config`. Reusable as-is by any LXC creator script.
|
||||
# - refresh_os_packages(): Alpine only (apk update && apk upgrade). A
|
||||
# future Debian-based script needs its own apt-get variant.
|
||||
# - ini_set(): OS-agnostic (plain awk/sed, no OS-specific assumptions).
|
||||
# Reusable as-is by any script that manages an INI-style config file,
|
||||
# regardless of the underlying distro.
|
||||
#
|
||||
# Does not set shell options (set -e/-u/-o pipefail): a sourced file must
|
||||
# not impose those on the caller's shell. Both openbao/install.sh and
|
||||
@@ -118,3 +121,107 @@ refresh_os_packages() {
|
||||
log_info "Refreshing Alpine packages..."
|
||||
apk update >/dev/null && apk upgrade >/dev/null
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# #18 - Idempotently set KEY = VALUE in SECTION of an INI-style config file
|
||||
# (e.g. Gitea's app.ini). Merges key by key rather than overwriting the
|
||||
# whole file, so a rejoué script can add newly-required keys to an already
|
||||
# customized config without clobbering it.
|
||||
#
|
||||
# Usage: ini_set <file> <section> <key> <value>
|
||||
#
|
||||
# Behavior:
|
||||
# - Missing file/section/key: created.
|
||||
# - Key present with a different value: replaced in place.
|
||||
# - Key present with the same value: no-op (byte-identical output).
|
||||
# - Other sections/keys: never touched — the match is scoped to the
|
||||
# given section, so the same key name in a different section (e.g.
|
||||
# ENABLED in both [metrics] and [actions]) is left alone.
|
||||
# Comments, blank lines and section order are preserved. Written atomically
|
||||
# (tmpfile + mv) so an interrupted run can't leave a corrupt config.
|
||||
# ============================================================
|
||||
ini_set() {
|
||||
local file="$1" section="$2" key="$3" value="$4"
|
||||
local tmp
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
mkdir -p "$(dirname "$file")"
|
||||
: > "$file"
|
||||
fi
|
||||
|
||||
tmp=$(mktemp "${file}.tmp.XXXXXX")
|
||||
|
||||
# mktemp defaults to 0600 root:root, which would silently lock the
|
||||
# service account that owns $file (e.g. gitea:www-data on Gitea's
|
||||
# app.ini) out of the config this function just wrote. Carry the
|
||||
# original file's mode/ownership onto the replacement before it lands.
|
||||
# `stat -c` works identically on GNU coreutils and BusyBox.
|
||||
chmod "$(stat -c '%a' "$file")" "$tmp" 2>/dev/null || true
|
||||
chown "$(stat -c '%u:%g' "$file")" "$tmp" 2>/dev/null || true
|
||||
|
||||
# awk writes into $tmp regardless of its own exit status — a mid-stream
|
||||
# death (OOM, signal, an exotic value tripping the regex) would still
|
||||
# leave a truncated-but-nonempty $tmp for `mv` to install over $file.
|
||||
# Gate the mv on awk's exit code so a failure leaves the original config
|
||||
# untouched instead of silently destroying it.
|
||||
if ! awk -v section="$section" -v key="$key" -v value="$value" '
|
||||
# Blank lines are buffered rather than printed immediately while a
|
||||
# section is still awaiting insertion: without this, a key inserted
|
||||
# right before the next section header lands *after* that section'"'"'s
|
||||
# trailing blank line(s) instead of before them. Flushed as soon as
|
||||
# either a non-blank line or the insertion itself happens, so this
|
||||
# never reorders anything except relative to that pending insertion.
|
||||
function flush_blanks() {
|
||||
while (blank_count > 0) { print ""; blank_count-- }
|
||||
}
|
||||
/^\[.*\]$/ {
|
||||
if (in_section && !done) {
|
||||
printf "%s = %s\n", key, value
|
||||
done = 1
|
||||
}
|
||||
flush_blanks()
|
||||
cur = $0
|
||||
gsub(/^\[|\]$/, "", cur)
|
||||
in_section = (cur == section)
|
||||
if (in_section) section_found = 1
|
||||
print
|
||||
next
|
||||
}
|
||||
{
|
||||
if (in_section && !done) {
|
||||
if (match($0, "^[ \t]*" key "[ \t]*=")) {
|
||||
flush_blanks()
|
||||
printf "%s = %s\n", key, value
|
||||
done = 1
|
||||
next
|
||||
}
|
||||
if ($0 ~ /^[ \t]*$/) {
|
||||
blank_count++
|
||||
next
|
||||
}
|
||||
flush_blanks()
|
||||
print
|
||||
next
|
||||
}
|
||||
print
|
||||
}
|
||||
END {
|
||||
if (in_section && !done) {
|
||||
printf "%s = %s\n", key, value
|
||||
done = 1
|
||||
}
|
||||
flush_blanks()
|
||||
if (!section_found) {
|
||||
if (NR > 0) print ""
|
||||
printf "[%s]\n", section
|
||||
printf "%s = %s\n", key, value
|
||||
}
|
||||
}
|
||||
' "$file" > "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
log_error "ini_set: awk failed on ${file} (${section}.${key}), config left untouched."
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user