#!/bin/sh

# PROVIDE: cardano_node
# REQUIRE: DAEMON
# KEYWORD: shutdown
#
# -----------------------------------------------------------------------------
#
# This script supports running multiple instances of the daemon.
# To run additional instances make a symlink to script under different name:
#
# % ln -s /usr/local/etc/rc.d/cardano_node /usr/local/etc/rc.d/SOMENAME
#
# and define corresponding SOMENAME_* variables in /etc/rc.conf
# For example, if you linked the script to cardano_node_testnet, then each
# variable listed below should read as cardano_node_testnet_enable, etc.
#
# -----------------------------------------------------------------------------
#
# Add the following lines to /etc/rc.conf to enable this service:
#
# cardano_node_enable:      Set to YES to enable cardano-node.
#                           Default: "NO"
#
# cardano_node_jail_enable: Set to NO to disable running cardano-node in the jail.
#                           Default: "YES"
#
# cardano_node_home:        An absolute path to the daemon home directory.
#                           The directory will be created if not exists.
#                           Default: "/var/db/cardano_node"
#
# cardano_node_net:         A network name to connect to.
#                           Default: "mainnet"
#
# cardano_node_port:        Port to listen for connections on.
#                           Default: "6000"
#
# Advanced settings that usually don't need to be changed for simple usage cases:
#
# cardano_node_host:        Host address to bind to.
#                           Default: "0.0.0.0"
#
#
# cardano_node_socket:      An absolute path to the daemon socket file.
#                           Default: "${cardano_node_home}/cardano-node.sock"
#
# cardano_node_db:          An absolute path to the database directory.
#                           Default: "${cardano_node_home}/${cardano_node_net}-db"
#
# cardano_node_topology:    An absolute or a relative to ${cardano_node_home} path
#                           to the topology JSON file.
#                           Default: "${cardano_node_net}-configs/topology.json"
#
# cardano_node_config:      An absolute or a relative to ${cardano_node_home} path
#                           to the cardano-node config.json file.
#                           Default: "${cardano_node_net}-configs/config.json"
#
# cardano_node_rts_flags:   GHC runtime flags to be passed between "+RTS" and "-RTS".
#                           See https://downloads.haskell.org/ghc/latest/docs/html/users_guide/runtime_control.html
#                           for the meaning of these flags.
#                           Default: "-N -A64m -n4m -F1.2 -qg1"
#
# cardano_node_flags:       Any additional command line flags to pass to cardano-node.
#                           Default: ""
#

. /etc/rc.subr

# The following code snippet was taken from security/openvpn/files/openvpn.in rc script.

# service(8) does not create an authentic environment, try to guess,
# and as of 10.3-RELEASE-p0, it will not find the indented name=
# assignments below. So give it a default.
# Trailing semicolon also for service(8)'s benefit:
name="$file" ;

case "$0" in
/etc/rc*)
	# during boot (shutdown) $0 is /etc/rc (/etc/rc.shutdown),
	# so get the name of the script from $_file
	name="$_file"
	;;
*/service)
	# do not use this as $0
	;;
*)
	name="$0"
	;;
esac

# default name to "cardano_node" if guessing failed
# Trailing semicolon also for service(8)'s benefit:
name="${name:-cardano_node}" ;
name="${name##*/}"

desc="Cardano Node daemon"
rcvar="${name}_enable"
command=/usr/local/bin/cardano-node

cardano_deployment_url="https://raw.githubusercontent.com/cardano-bsd-alliance/freebsd-ports-cardano-artifacts/master/cardano-node"
cardano_config_files="config byron-genesis shelley-genesis alonzo-genesis conway-genesis topology submit-api-config"
cardano_networks="mainnet preview preprod"

start_cmd="cardano_node_start"
start_precmd="cardano_node_prestart"
stop_cmd="cardano_node_stop"
status_cmd="cardano_node_status"
reload_cmd="cardano_node_reload"
fetch_cmd="cardano_node_fetch"

extra_commands="status fetch reload"

load_rc_config $name
eval ": \${${name}_enable:=NO}"
eval ": \${${name}_jail_enable:=YES}"
eval ": \${${name}_home:=\"/var/db/cardano_node\"}"
eval ": \${${name}_net:=\"mainnet\"}"
eval ": \${${name}_host:=\"0.0.0.0\"}"
eval ": \${${name}_port:=\"6000\"}"
eval ": \${${name}_socket:=\"\${${name}_home}/cardano-node.sock\"}"
eval ": \${${name}_db:=\"\${${name}_home}/\${${name}_net}-db\"}"
eval ": \${${name}_topology:=\"\${${name}_net}-configs/topology.json\"}"
eval ": \${${name}_config:=\"\${${name}_net}-configs/config.json\"}"
eval ": \${${name}_rts_flags:=\"-N -A64m -n4m -F1.2 -qg1\"}"
eval ": \${${name}_flags:=\"\"}"

# aliases
eval "_jail_enable=\${${name}_jail_enable}"
eval "_home=\${${name}_home}"
eval "_topology=\${${name}_topology}"
eval "_config=\${${name}_config}"
eval "_socket=\${${name}_socket}"
eval "_db=\${${name}_db}"
eval "_rts_flags=\${${name}_rts_flags}"
eval "_host=\${${name}_host}"
eval "_port=\${${name}_port}"
eval "_flags=\${${name}_flags}"

jail_topology="/topology_dir/`basename ${_topology}`"
jail_config="/config_dir/`basename ${_config}`"
jail_socket="/socket/`basename ${_socket}`"
jail_args="name=${name}_jail exec.jail_user=cardano exec.system_jail_user host=inherit"
jail_command=/bin/cardano-node

jail_root="${_home}/jail"
jail_copy_resolv_conf=yes
jail_copy_services=yes
jail_copy_programs="$command /usr/sbin/nologin"
jail_mount_devfs=yes
jail_ip_inherit=yes
#TODO: daemon fails with "Network.Socket.bind: permission denied" without suid ;\
jail_prepare_inside_cmds="mkdir ./socket ;\
                          ln -s ${_home}/jail/${jail_socket} ${_socket} ;\
                          chmod +s .${jail_command}"
jail_nullfs_mounts="${_db} ${jail_root}/db rw \
                    ${_home}/logs ${jail_root}/logs rw"

if checkyesno "_jail_enable"; then
    _socket_arg="${jail_socket}"
    _topology_arg="${jail_topology}"
    _config_arg="${jail_config}"
    _db_arg="/db"
    # We need to override ${command} to make check_pidfile work correctly when
    # rc.subr calls it as "check_pidfile ${pidfile} ${command}"
    command=/usr/sbin/jail
else
    _socket_arg="${_socket}"
    _topology_arg="${_topology}"
    _config_arg="${_config}"
    _db_arg="${_db}"
fi

pidfile="/var/run/${name}.pid"
flags="run +RTS ${_rts_flags} -RTS \
        --database-path ${_db_arg} \
        --host-addr ${_host} \
        --port ${_port} \
        --socket-path ${_socket_arg} \
        --topology ${_topology_arg} \
        --config ${_config_arg} \
        ${_flags}"

. /usr/local/share/rc-subr-jail/rc.subr.jail

# realpath2 path
# Returns an absolute path to ${_home}/${path} if it exists, otherwise
# treats ${path} as absolute
realpath2()
{
    local _path _realpath
    _path=$1

    _realpath=$(/bin/sh -c "cd ${_home} && realpath ${_path}" 2> /dev/null)
    if [ $? != "0" ]
    then
        return 1
    else
        echo $_realpath
    fi
}

sanity_check()
{
    realpath2 ${_topology} > /dev/null
    if [ $? != "0" ]
    then
        echo "Invalid value for ${name}_topology: missing file ${_topology}"
        echo "You might want to run service ${name} onefetch to download this file into default dir"
        exit 1
    fi
    realpath2 ${_config} > /dev/null
    if [ $? != "0" ]
    then
        echo "Invalid value for cardano_node_config: missing file ${_config}"
        echo "You might want to run service ${name} onefetch to download this file into the default dir"
        exit 1
    fi
    return 0
}

cardano_node_prestart()
{
    # Create Cardano home directory, if not exists
    if [ ! -d "${_home}" ]; then
        mkdir -p "${_home}"
        chown cardano:cardano "${_home}"
    fi
    # Do the same for the logs directory
    if [ ! -d "${_home}/logs" ]; then
        mkdir -p "${_home}/logs"
        chown cardano:cardano "${_home}/logs"
    fi

    # Remove the symlink to the socket file if there is no pid file
    if [ -L "${_socket}" -a ! -f $pidfile ]; then
        rm "${_socket}"
    fi

    sanity_check
}

cardano_node_start()
{
    check_startmsgs && echo "Starting ${name}."

    local _topo _conf
    _topo=$(realpath2 ${_topology})
    _conf=$(realpath2 ${_config})

    jail_nullfs_mounts="$jail_nullfs_mounts $(dirname ${_topo}) ${jail_root}/topology_dir ro"
    jail_nullfs_mounts="$jail_nullfs_mounts $(dirname ${_conf}) ${jail_root}/config_dir ro"

    if checkyesno "_jail_enable"; then
        prepare_jail $jail_root
        if [ "$?" != "0" ]; then
            echo "Failed to start ${name}: jail creation error"
            return 1
        fi

        cd $_home && /bin/sh -c "/usr/sbin/daemon -p $pidfile -S -T cardano-node \
            ${command} -c ${jail_prepared_args} ${jail_args} command=${jail_command} ${flags}"
    else
        cd $_home && /usr/sbin/daemon -p $pidfile -S -T cardano-node \
            ${command} ${flags}
    fi
}

cardano_node_stop()
{
    local _topo _conf _ret
    _topo=$(realpath2 ${_topology})
    _conf=$(realpath2 ${_config})

    jail_nullfs_mounts="$jail_nullfs_mounts $(dirname ${_topo}) ${jail_root}/topology_dir ro"
    jail_nullfs_mounts="$jail_nullfs_mounts $(dirname ${_conf}) ${jail_root}/config_dir ro"

    pid=$(check_pidfile "${pidfile}" "$command")

    if [ -z "${pid}" ]
    then
        echo "${name} is not running"
        _ret=1
    else
        echo "Stopping ${name}."
        kill_jail "$pid" -INT "_jail_enable"
        wait_for_pids "$pid"
        _ret=0
    fi

    if checkyesno "_jail_enable"; then
            destroy_jail $jail_root 2> /dev/null
    fi
    rm -rf ${_socket}

    return $_ret
}

cardano_node_status()
{
    pid=$(check_pidfile "${pidfile}" "$command")

    if [ -z "${pid}" ]
    then
        echo "${name} is not running"
        return 1
    else
        echo ${name} is running as pid $pid
    fi
}

cardano_node_reload()
{
    pid=$(check_pidfile "${pidfile}" "$command")

    if [ -z "${pid}" ]
    then
        echo "${name} is not running"
        return 1
    else
        kill_jail "$pid" -HUP "_jail_enable"
    fi
}

cardano_node_fetch()
{
    for net in ${cardano_networks}
    do
        echo "===> Fetching configuration files for ${net}"
        mkdir -p "${_home}/${net}-configs"
        mkdir -p "${_home}/${net}-db"
        /usr/bin/apply "/usr/bin/fetch -a -o \
        ${_home}/${net}-configs ${cardano_deployment_url}/${net}-configs/%1.json" $cardano_config_files
        chown -R cardano:cardano "${_home}/${net}-configs"
        chown -R cardano:cardano "${_home}/${net}-db"
    done
}

run_rc_command "$1"
