penguin/monok8s

k8s image for Mono Gateway Dev Kit

alpine/merge-rootfs.sh

raw ยท 4846 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 ]]
}

render_template() {
    local src="$1"
    local out="$2"

    mkdir -p "$(dirname "$out")"
    check_template_vars "$src"
    envsubst < "$src" > "$out"
}

replace_file() {
    local src="$1"
    local dst="$2"

    mkdir -p "$(dirname "$dst")"
    cp -a "$src" "$dst"
    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
        cp -a "$src" "$dst"
        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"
        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