#!/bin/bash
set -x
exec >> /unas/tmp/dc-conf_sync-$2.log 2>> /unas/tmp/dc-conf_sync-$2-err.log
# Sync confs for different services
# This service should only be started by cluster, after data pool mounts and before services start.
echo Warning: This service should only be started by cluster, after data pool mounts and before services start.

if [ -z "$SSH" ];then
    source /unas/sbin/dualcontroller/config/prepare
fi

export BAK_DIR=/unas/etc/dualcontroller/cfg/dualcontroller

worker="$(dirname $(readlink -f "$0"))/sync_worker"

# Check backup dir
if [ ! -d "$BAK_DIR" ];then
    mkdir -p "$BAK_DIR"
fi

if [ -n "$SERVICE_RELOAD" ];then
    SERVICE="$SERVICE_RELOAD"
fi

ACTION=$1
NAME=$2
if [ "$NAME" == main ];then
    export SERVICE="$SERVICE ssh.service"
    CONFFILENAME={account,apache,desktop,system}
else
    CONFFILENAME=$NAME
fi

export NAME
export SERVICE

export RUN_DIR="/var/run/dualcontroller/$NAME"
export JOB_QUEUE="$RUN_DIR/dualcontroller_queue"

mount_list=()
mount_only_list=()
monitor_list=()
once_list=()

prepare() {
    if [ ! -d "$RUN_DIR" ];then
        mkdir -p "$RUN_DIR"
    fi

    if [ ! -p $JOB_QUEUE ];then
        mkfifo $JOB_QUEUE || :
    fi
    exec 6<>$JOB_QUEUE || :
}

get_conffiles() {
    for file in `eval echo /unas/etc/dualcontroller/syncconf-enabled/$CONFFILENAME`
    do
        while IFS= read -r conf_target
        do
            if [ z"`eval echo $conf_target`" == z ];then
                # Skip comments and blank lines
                continue
            elif [[ "$conf_target" == *" mount_only" ]];then
                conf_target="${conf_target% mount_only}"
                [ -e "$BAK_DIR$conf_target" ] || [ -e "$conf_target" ] && mount_only_list+=("${conf_target%/}")
            elif [[ "$conf_target" == *" mount" ]];then
                conf_target="${conf_target% mount}"
                [ -e "$BAK_DIR$conf_target" ] || [ -e "$conf_target" ] && mount_list+=("${conf_target%/}")
            elif [[ "$conf_target" == *" sync" ]];then
                conf_target="${conf_target% sync}"
                [ -e "$BAK_DIR$conf_target" ] || [ -e "$conf_target" ] && monitor_list+=("${conf_target%/}")
            elif [[ "$conf_target" == *" once" ]];then
                conf_target="${conf_target% once}"
                [ -e "$BAK_DIR$conf_target" ] || [ -e "$conf_target" ] && once_list+=("${conf_target%/}")
            elif [ -d "$conf_target" ];then
                [ -e "$BAK_DIR$conf_target" ] || [ -e "$conf_target" ] && mount_list+=("${conf_target%/}")
            else
                [ -e "$BAK_DIR$conf_target" ] || [ -e "$conf_target" ] && monitor_list+=("${conf_target%/}")
            fi
            # Neither exists may cause failure
        done < $file
    done
}

sync_file() {
    sync_script=/unas/sbin/dualcontroller/conf_sync.d/$NAME
    for conf_target in "${mount_list[@]}" "${mount_only_list[@]}"
    do
        if [ -e "$BAK_DIR$conf_target" ];then
            # Regard the conf in data pool as mainline
            src=`realpath "$BAK_DIR$conf_target"`
            target_dir=`dirname "$conf_target"`
            [ ! -d "$target_dir" ] && mkdir -p "$target_dir"
            $RSYNC "$src" "$target_dir"
            mount --bind "$BAK_DIR$conf_target" "$conf_target"
        elif [ -e "$conf_target" ];then
            # Copy the local conf as init
            src=`realpath "$conf_target"`
            target_dir=`dirname "$BAK_DIR$conf_target"`
            [ ! -d "$target_dir" ] && mkdir -p "$target_dir"
            $RSYNC "$src" "$target_dir"
            mount --bind "$BAK_DIR$conf_target" "$conf_target"
        fi
        # Neither exists may cause failure
    done

    for conf_target in "${monitor_list[@]}" "${once_list[@]}"
    do
        if [ -e "$BAK_DIR$conf_target" ];then
            # Regard the conf in data pool as mainline
            src=`realpath "$BAK_DIR$conf_target"`
            target_dir=`dirname "$conf_target"`
            [ ! -d "$target_dir" ] && mkdir -p "$target_dir"
            $RSYNC "$src" "$target_dir"
            if [ -x "$sync_script" ];then
                "$sync_script" "$src"
            fi
        elif [ -e "$conf_target" ];then
            # Copy the local conf as init
            src=`realpath "$conf_target"`
            target_dir=`dirname "$BAK_DIR$conf_target"`
            [ ! -d "$target_dir" ] && mkdir -p "$target_dir"
            $RSYNC "$src" "$target_dir"
            if [ -x "$sync_script" ];then
                "$sync_script" "$src"
            fi
        fi
    done
}

parent_dirs() {
    # 更严谨的去重方式，避免部分匹配误判
    declare -A dir_map
    for arg in "${mount_list[@]}" "${monitor_list[@]}"
    do
        dir=$([ -d "$arg" ] && echo "$arg" || dirname "$arg")
        dir_map["$dir"]=1
    done
    for dir in "${!dir_map[@]}"; do
        echo "$dir"
    done
}

wait_reload() {
    # 启动后台 worker 监控
    $worker "${monitor_list[@]}" &
    # 由于监控的是inode，所以直接监控文件的情况下，如果文件被删除再创建，监控会失效
    # 监控目标文件的父目录
    inotifywait -rm \
        -e attrib -e delete_self -e create -e modify -e delete -e move \
        $(parent_dirs) |
        while read path action file; do
            # 获取事件的相对路径
            rel="${path%/}/$file"
            if [[ "${mount_list[@]} ${monitor_list[@]}" =~ " $rel " ]]; then
                # 路径完全匹配到
                write_queue "{\"action\": \"$action\", \"path\": \"$path\",\"file\": \"$file\"}"
            else
                # 检查是否为关心的目录的子对象
                for arg in "${mount_list[@]}" "${monitor_list[@]}"; do
                    case "$rel" in
                        "$arg"/*) write_queue "{\"action\": \"$action\", \"path\": \"$path\",\"file\": \"$file\"}"; break;;
                    esac
                done
            fi
        done
}

# Write FIFO
write_queue() {
    echo "$1" >&6 || :
}

clean_() {
    for conf_target in "${mount_list[@]}"
    do
        if mountpoint "$conf_target";then
            umount -l -f "$conf_target"
        fi
    done

    rm -rf "$RUN_DIR" "$JOB_QUEUE"
}


case "$ACTION" in
    sync)
        get_conffiles
        sync_file
        ;;
    wait)
        prepare
        get_conffiles
        wait_reload
        ;;
    stop)
        get_conffiles
        clean_
        ;;
    *)
        echo Unknown action: "$ACTION"
        ;;
esac

exit 0
