penguin/monok8s

k8s image for Mono Gateway Dev Kit

alpine/merge-rootfs.sh

raw ยท 5612 bytes

#!/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