Files
monok8s/alpine/merge-rootfs.sh

242 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Usage:
# export HOSTNAME=mybox
# ./merge-rootfs.sh rootfs-extra /out/rootfs
#
# Naming rules:
# foo -> normal file
# foo.tmpl -> render with envsubst, then normal handling
# foo.override -> replace target directly
# foo.tmpl.override -> render with envsubst, then replace target directly
#
# Default handling:
# etc/* -> merge missing lines
# opt/scripts/* -> replace
# everything else -> copy only if missing
SRC_ROOT="${1:?source rootfs path required}"
DST_ROOT="${2:?target rootfs path required}"
if [[ ! -d "$SRC_ROOT" ]]; then
echo "Source rootfs does not exist or is not a directory: $SRC_ROOT" >&2
exit 1
fi
if [[ ! -d "$DST_ROOT" ]]; then
echo "Target rootfs does not exist or is not a directory: $DST_ROOT" >&2
exit 1
fi
if ! command -v envsubst >/dev/null 2>&1; then
echo "envsubst not found. Install gettext or gettext-envsubst." >&2
exit 1
fi
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
append_missing_lines() {
local src="$1"
local dst="$2"
local changed=1
local line
while IFS= read -r line || [[ -n "$line" ]]; do
if ! grep -Fqx -- "$line" "$dst"; then
printf '%s\n' "$line" >> "$dst"
changed=0
fi
done < "$src"
return "$changed"
}
check_template_vars() {
local src="$1"
local missing=0
local vars var
vars="$(
grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$[A-Za-z_][A-Za-z0-9_]*' "$src" \
| sed -E 's/^\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?$/\1/' \
| sort -u || true
)"
while IFS= read -r var; do
[[ -z "$var" ]] && continue
if [[ -z "${!var+x}" ]]; then
echo "Missing required env var: $var (used in $src)" >&2
missing=1
fi
done <<< "$vars"
[[ "$missing" -eq 0 ]]
}
preserve_exec_bit() {
local old="$1"
local new="$2"
if [[ -e "$old" && -x "$old" ]]; then
chmod +x "$new"
fi
}
get_envsubst_vars() {
local rel="$1"
case "$rel" in
bin/flash-emmc.sh.tmpl)
echo '${BUILD_TAG}'
;;
*)
echo ''
;;
esac
}
render_template() {
local src="$1"
local out="$2"
local allowed="${3:-}"
mkdir -p "$(dirname "$out")"
if [[ -n "$allowed" ]]; then
envsubst "$allowed" < "$src" > "$out"
else
check_template_vars "$src"
envsubst < "$src" > "$out"
fi
if [[ -x "$src" ]]; then
chmod +x "$out"
fi
}
replace_file() {
local src="$1"
local dst="$2"
local had_exec=0
[[ -e "$dst" && -x "$dst" ]] && had_exec=1
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
if [[ "$had_exec" -eq 1 ]]; then
chmod +x "$dst"
fi
echo "Replaced file: $dst"
}
merge_or_copy_file() {
local src="$1"
local dst="$2"
local rel="$3"
mkdir -p "$(dirname "$dst")"
# scripts: replace
if [[ "$rel" == opt/scripts/* ]]; then
local had_exec=0
[[ -e "$dst" && -x "$dst" ]] && had_exec=1
cp -a "$src" "$dst"
if [[ "$had_exec" -eq 1 ]]; then
chmod +x "$dst"
fi
echo "Replaced script: $dst"
return
fi
# /etc: merge missing lines
if [[ "$rel" == etc/* ]]; then
if [[ ! -e "$dst" ]]; then
cp -a "$src" "$dst"
echo "Copied new config: $dst"
return
fi
if [[ ! -f "$dst" ]]; then
echo "Skipping existing non-regular path: $dst" >&2
return
fi
if append_missing_lines "$src" "$dst"; then
echo "Appended missing lines: $dst"
else
echo "No changes needed: $dst"
fi
return
fi
# default: copy only if missing
if [[ ! -e "$dst" ]]; then
cp -a "$src" "$dst"
echo "Copied new file: $dst"
else
echo "Skipped existing file: $dst"
fi
}
find "$SRC_ROOT" -mindepth 1 | while IFS= read -r src_path; do
rel_path="${src_path#"$SRC_ROOT"/}"
if [[ -d "$src_path" ]]; then
mkdir -p "$DST_ROOT/$rel_path"
continue
fi
if [[ -L "$src_path" ]]; then
dst_path="$DST_ROOT/$rel_path"
if [[ -e "$dst_path" || -L "$dst_path" ]]; then
echo "Symlink exists, skipping: $dst_path"
else
mkdir -p "$(dirname "$dst_path")"
ln -s "$(readlink "$src_path")" "$dst_path"
echo "Created symlink: $dst_path"
fi
continue
fi
if [[ ! -f "$src_path" ]]; then
echo "Skipping unsupported file type: $src_path" >&2
continue
fi
src_for_merge="$src_path"
rel_for_target="$rel_path"
mode="default"
if [[ "$rel_for_target" == *.tmpl.override ]]; then
mode="override"
rel_for_target="${rel_for_target%.tmpl.override}"
rendered="$TMP_DIR/$rel_for_target"
render_template "$src_path" "$rendered"
src_for_merge="$rendered"
elif [[ "$rel_for_target" == *.override ]]; then
mode="override"
rel_for_target="${rel_for_target%.override}"
elif [[ "$rel_for_target" == *.tmpl ]]; then
rel_for_target="${rel_for_target%.tmpl}"
rendered="$TMP_DIR/$rel_for_target"
render_template "$src_path" "$rendered" "$(get_envsubst_vars "$rel_path")"
src_for_merge="$rendered"
fi
dst_path="$DST_ROOT/$rel_for_target"
if [[ "$mode" == "override" ]]; then
replace_file "$src_for_merge" "$dst_path"
else
merge_or_copy_file "$src_for_merge" "$dst_path" "$rel_for_target"
fi
done