#!/bin/bash

#
# Looks for a configuration file in the mounted block device and
# applies it via RPC if found and the device has not been configured yet.
#
# WARNING: this script runs with the fstab locks held
#

# Skip if this is not the "add" action
if [ "$1" != "add" ]; then
    exit 0
fi

# Skip if the device is already configured
if [ -f /etc/spxmanage/configured ]; then
    exit 0
fi

# Get the serial number data
[ -f /etc/spinetix/identifiers ] && . /etc/spinetix/identifiers

APPLIEDCONFIGCSUM=/var/cache/spxmanage/media-mount-config/applied-config.sha256sum

WORKDIR=
cleanup() {
    [ -z "$WORKDIR" ] || rm -rf "$WORKDIR"
}

trap cleanup EXIT


# Selects the config file to use, saving its absolute path in $CONFIG
# Returns non-zero if no config file is found.
select_config_file() {
    local names=()
    local n

    CONFIG=
    [ -z "$SPX_SN" ] || names+=(config-"$SPX_SN".cfg config-"$SPX_SN".xml)
    [ -z "$SPX_USER_SN" ] || names+=(config-"$SPX_USER_SN".cfg config-"$SPX_USER_SN".xml)
    [ -z "$SPX_BOARD_SN" ] || names+=(config-"$SPX_BOARD_SN".cfg config-"$SPX_BOARD_SN".xml)
    [ -z "$SPX_SYSTEM_SN" ] || names+=(config-"$SPX_SYSTEM_SN".cfg config-"$SPX_SYSTEM_SN".xml)
    [ -z "$SPX_CHASSIS_SN" ] || names+=(config-"$SPX_CHASSIS_SN".cfg config-"$SPX_CHASSIS_SN".xml)
    names+=(config.cfg config.xml)

    for n in "${names[@]}"; do
        if [ -f "$MNTDIR"/"$n" ]; then
            CONFIG="$MNTDIR"/"$n"
            echo "INFO: found configuration file $n"
            return
        fi
    done

    return 1
}

# Gets the XML configuration file, updating $CONFIG to point to it
# Returns non-zero if it cannot be found.
get_config_xml() {
    if [ "${CONFIG%.xml}" != "$CONFIG" ]; then
        return # already an XML file
    fi
    if ! (cd $WORKDIR && 7za e $CONFIG -aoa -bd -bb0 config.xml < /dev/null > /dev/null); then
        echo "ERROR: failed extracting cfg file" >&2
        return 1
    fi
    CONFIG="$WORKDIR"/config.xml
    if [ ! -f "$CONFIG" ]; then
        echo "ERROR: cfg file does not contain config.xml" >&2
        CONFIG=
        return 1
    fi
}

# Build the RPC request data, saving it to file $RPCREQUEST
# The file $RPCREQUEST is out of $WORKDIR and needs to be removed when done
# Returns non-zero on error
build_rpc_request() {
    if ! jq -sR '{"method":"set_config","params":[{"xmlconfig":.}], "id":null}' < "$CONFIG" > "$WORKDIR/rpc.json" ; then
    echo "ERROR: failed creating set_config RPC request" >&2
        return 1
    fi
    RPCREQUEST="$(mktemp)"
    if ! cat "$WORKDIR/rpc.json" > "$RPCREQUEST"; then
        echo "ERROR: failed saving set_config RPC request" >&2
        rm -f "$RPCREQUEST"
        RPCREQUEST=
        return 1
    fi
    # The $CONFIG will become the applied config, so save its checksum. When
    # the config is posted it may cause an immediate reboot that may interrupt
    # this script, so saving it after the config is successfully posted does
    # not work reliably.
    if ! sha256sum < "$CONFIG" > "$APPLIEDCONFIGCSUM"; then
        echo "ERROR: failed saving new config checksum" >&2
        rm -f "$RPCREQUEST"
        RPCREQUEST=
        return 1
    fi
    return 0
}

post_rpc_request() {
    # The RPC endpoint may not yet be operational, in which case an HTTP error is returned.
    # If the call fails (e.g., malformed configuration) a 200 is returned as the HTTP status
    # and the error is in the JSON response. So we repeat a few times until we get a sucess
    # or we time out. An unconfigured device always has user admin with password admin.
    for (( n=0; n < 12; n++ )); do
        curl --disable --fail --max-time 10 -o /dev/null --silent \
            --data-binary @"$RPCREQUEST" -H "Content-Type: application/json" \
            http://localhost/rpc
        if [ $? -eq 0 ]; then
            rm -f "$RPCREQUEST" # may contain secrets, do not leave it around
            echo "INFO: posted configuration via RPC"
            return
        fi
        sleep 5
    done
    rm -f "$RPCREQUEST" # may contain secrets, do not leave it around
    rm -f "$APPLIEDCONFIGCSUM" # it did not become the applied config
    echo "ERROR: failed posting configuration via RPC, too many retries" >&2
}

if ! select_config_file; then
    exit 0 # no configuration file to apply
fi

WORKDIR="$(mktemp -d)"
[ -n "$WORKDIR" -a -d "$WORKDIR" ] || exit

if ! get_config_xml; then
    exit 1 # no XML configuration found
fi

if [ -f "$APPLIEDCONFIGCSUM" ] && sha256sum --status -c "$APPLIEDCONFIGCSUM" < "$CONFIG"; then
    echo "INFO: skipping config identical to last applied config"
    exit 0
fi

if ! build_rpc_request; then
    exit 1
fi

# Posting the RPC request may take time waiting for the RPC to be available
# if this gets run during early boot, so run that detached in the background.
echo "INFO: will post configuration via RPC in the background"
( post_rpc_request 2>&1 | logger -p daemon.info -t "config-post [$$]"; rm -f "$RPCREQUEST" ) 2>&1 > /dev/null < /dev/null &
