#!/bin/bash -e

function get_answer {
    local var_to_set=$1
    local prompt="$2"
    local default="$3"
    # Read spaces literally
    local IFS=

    echo "$prompt:"
    read -r -p "[$default]: " res
    res=${res:-$default}

    # Manually expand ~ since read doesn't do it.
    case "$res" in
        "~/"*) res="${HOME}/${res#"~/"}" ;;
        # Quote everything after tilde, then use eval to expand it.
        "~"*) eval res=`printf '~%q' "${res:1}"` ;;
    esac

    # echo "[*] Setting $var_to_set to" \"$res\" # debug
    eval $var_to_set=\"'$res'\"
}

function read_password {
    local var_to_set=$1
    local prompt="$2"
    # Read spaces literally
    local IFS=

    while true; do
        echo "$prompt:"
        read -sr password
        if [ -z "$password" ]; then
            echo "Password can't be blank!"
            if y_or_n "Would you like to disable authentication via password for this user?" "n"; then
		password=""
                break
	    fi
        else
            break
        fi
    done
    eval $var_to_set=\"'$password'\"
}

function y_or_n {
    local prompt="$1"
    local default=$2
    if [ "$default" ]; then
        local read_prompt="[$default]: "
    fi

    while true; do
        echo "$prompt:"
        read -rp "$read_prompt" y_or_n
        y_or_n=${y_or_n:-$default}

	case "$y_or_n" in
	    y|yes)
		return 0
		;;
	    n|no)
		return 1
		;;
	    *)
		echo Please answer either y or n
		;;
	esac
    done
}

function prompt_for_path_and_create {
    local mode="$1"
    local var_to_set="$2"
    local prompt="$3"
    local default="$4"
    local file
    local dir

    while true; do
	get_answer $var_to_set "$prompt" "$default"
	if [ $mode = file ]; then
	    eval file=\$$var_to_set
	    default="$file"
	    dir="`dirname $file`"
	else 
	    eval dir=\$$var_to_set
            if [ "/" == "${dir:(-1)}" ] ; then
                # remove trailing slash
                eval $var_to_set="${dir:0:${#dir}-1}"
            fi
	    default="$dir"
	fi
	# don't check for existence of path
	if [ "$create" = "0" ]; then
	    return
	fi

	if [ -d "$dir" ]; then
	    return
	elif  [ -e "$dir" ]; then
	    cat <<EOF

$dir already exists but is not a directory.
Please choose another location or move 
$dir out of the way.

EOF
	else
	    echo "$dir" does not exist.
	    if y_or_n "Would you like me to create it?" "y"; then
		    mkdir -p "$dir"
	    fi
	    return
	fi
    done
}

function non_interactive_verify_dir {
    local dir="$1"

    if [ -d "$dir" ]; then
        :
    elif  [ -e "$dir" ]; then
	cat <<EOF

$dir already exists but is not a directory.
Please choose another location or move 
$dir out of the way.

EOF
        exit 1
    else 
	mkdir -p "$dir"
    fi
}

function valid_port_number {
    local port="$1"

    # Allows for port number to be 0, in which case the OS will select the port number at runtime.
    if expr "$port" : '[0-9][0-9]*$' >/dev/null && [ "$port" -ge 0 ] && [ "$port" -lt 65536 ]; then
	return 0
    else
	echo Invalid port number: "$port"
	return 1
    fi
}

function valid_port_range {
    local range="$1"

    # Extract ports from the range (note that the whole range can be a single port).
    # The results are passed through echo to strip leading and trailing whitespace.
    local end=$(echo -n ${range#*-})
    local start=$(echo -n ${range%-*})

    if valid_port_number "$start" && valid_port_number "$end" && [ "$start" -le "$end" ]; then
        return 0
    else 
        echo Invalid port range: $range
        return 1
    fi
}

function create_user {
    local user=$1
    local group=$user

    local additional_args

    if getent group $group >/dev/null; then
	# Group exists already
	additional_args="-g $group"
    fi

    /usr/sbin/useradd -c "AllegroGraph" -d "$datadir" -r $additional_args $user
    chown -R $user: "$datadir"
    chown -R $user: "$logdir"
}

function setup_superuser {
    local name=$1
    local pw=$2
    local hash

    mkdir -p "$settingsdir/user"
    # Bash's built-in echo command will be used so the password
    # will not appear on in 'ps' output.
    # echo "[*] setting up superuser $name :: \"$pw\"" # debug
    if [ -n "$pw" ]; then
        hash='#x'`echo -n agraph:"$name:$pw" | sha1sum | sed 's/\(\S*\).*/\1/'`
    else
        hash=nil
    fi
    echo '('$hash' (:super) nil nil nil)' > "$settingsdir/user/$name"
}

function configure_welcome {
    cat <<EOF

Welcome to the AllegroGraph configuration program.  This script will
help you establish a baseline AllegroGraph configuration. 

You will be prompted for a few settings.  In most cases, you can hit return
to accept the default value.

EOF
}

function configure_init {
    if [ -f /usr/bin/configure-agraph ] && [ "$0" -ef /usr/bin/configure-agraph ]; then
        rpminstall=t
    fi

    if [ `id -u` = 0 ]; then
        root=t
    fi

    if [ "$root" ] && [ "$rpminstall" ]; then
    # RPM-install, running as root.  Use the standard config
        configfile=/etc/agraph/agraph.cfg
        datadir=/var/lib/agraph
        logdir=/var/log/agraph
        pidfile=/var/run/agraph/agraph.pid
    else
        if [ "$rpminstall" ]; then
            # RPM-install but not running as root.
            basedir=~/agraph
            configdir="$basedir/etc"
        else
            # Tarball install.  Use the location of this script to determine
            # default paths.
            configdir=`dirname "$0"`
            basedir="`cd "$configdir/.." && pwd`"
        fi
        configfile="$configdir/agraph.cfg"
        datadir="$basedir/data"
        logdir="$basedir/log"
        # Note: this needs to be re-evaluated if the user provides a
        # different datadir.
        pidfile="$datadir/agraph.pid"
    fi
    interactive=1
    superuser=super
    instancetimeout=3600
    port=10035
    create=1
    # Note: this needs to be re-evaluated if the user provides a
    # different datadir.
    settingsdir="$datadir/settings"
}

function usage {
    cat <<EOF
Usage: $0 [OPTIONS]

This script is typically run without arguments, in which case it will
prompt you for values to interactively set up an AllegroGraph
configuration.

To use the script for configuring AllegroGraph without user
interaction, pass the --non-interactive option, and use the following
options to set your parameters:

  --config-file <path>
      Location of the configuration file to create.
      (defaults to $configfile)
  --data-dir <path>
      Directory to store data and settings.
      (defaults to $datadir)
  --log-dir <path>
      Directory to write log files.
      (defaults to $logdir)
  --pid-file <path>
      Where to store the AllegroGraph PID file.
      (defaults to $pidfile)
  --port <number>
      Port number on which AllegroGraph runs its HTTP server.
      (defaults to 10035)
  --session-ports <start>-<end>
      Restrict the range of ports to be used by dedicated sessions. 
      By default a session can start on any port.
  --runas-user <user>
      Which user to run the server under. Only applicable when
      starting AllegroGraph as root.
  --create-runas-user
      Tells the script to create the user named in --runas-user if it
      does not already exists.
  --super-user <name>
      Name of AllegroGraph-internal user that gets super user
      privileges. Defaults to 'super'.
  --super-password <password>
      Password of this super user.
  --super-password-file <path>
      Allows you to specify a file containing the super user password,
      so that it doesn't appear in the command line.
  --instance-timeout <seconds>
      Number of seconds instances linger when no client has the
      corresponding repository open.
      (defaults to $instancetimeout seconds)
  --no-create
      Do not attempt to create local files/directories as a part
      of generating agraph.cfg. Useful if generating a config file
      intended for another host.
  --license-file <path>
      Install the License Key from file.
EOF
    exit 1
}

function agraph_usage {
    echo
    echo "You can start AllegroGraph by running: "
    if [ "$rpminstall" ] && [ "$root" ]; then
        echo /sbin/service agraph start
    elif [ "$rpminstall" ]; then
        echo /usr/bin/agraph-control --config "$configfile" start
    else 
        echo "$basedir"/bin/agraph-control --config "$configfile" start
    fi
    
    echo
    
    echo "You can stop AllegroGraph by running: "
    if [ "$rpminstall" ] && [ "$root" ]; then
        echo /sbin/service agraph stop
    elif [ "$rpminstall" ]; then
        echo /usr/bin/agraph-control --config "$configfile" stop
    else
        echo "$basedir"/bin/agraph-control --config "$configfile" stop
    fi
}

if [ $(uname -m) != x86_64 ]; then
    echo AllegroGraph Server requires 64-bit Linux
    exit 1
fi

configure_init

while [ $# -gt 0 ] ; do
    case "$1" in
        --config-file)
            shift
            configfile="$1"
            shift
            configdir=`dirname "$configfile"`
            ;;
	# Undocumented. --config-file merely sets the default location
	# when asking the user where to save the new config file.
	# --config-file-np (no-prompt) saves the new config to the
	# indicated location w/o prompting the user.
	# This command is used by install-agraph for cluster installs,
	# in order to generate an agraph.cfg in a known location
	# that it can then copy to each node.
	--config-file-np)
	    shift
	    configfile="$1"
	    shift
            configdir=`dirname "$configfile"`
	    configfilenp="t"
	    ;;
        --data-dir)
            shift
            datadir="$1"
            shift
            settingsdir="$datadir/settings" # re-evaluate for the new datadir
            ;;
        --log-dir)
            shift
            logdir="$1"
            shift
            ;;
        --pid-file)
            shift
            pidfile="$1"
            shift
            pidfileconfigured=1
            ;;
        --port)
            shift
            port="$1"
            shift
	    if ! valid_port_number "$port"; then
		usage
	    fi
            ;;
        --session-ports)
            shift
            sessionports="$1"
            shift
            if ! valid_port_range "$sessionports"; then
                usage
            fi
            ;;
        --runas-user)
            shift
            runas="$1"
            shift
            ;;
        --create-runas-user)
            shift
            createuser=1
            ;;
        --super-user)
            shift
            superuser="$1"
            shift
            ;;
        --super-password)
            shift
            superpw="$1"
            shift
            passwordconfigured=1
            ;;
        --super-password-file)
            shift
            superpw="`head -1 "$1"`"
            shift
            passwordconfigured=1
            ;;
        --instance-timeout)
            shift
            instancetimeout="$1"
            shift
            ;;
        --non-interactive)
            shift
            interactive=0
            ;;
	--no-create)
	    shift
	    create=0
	    ;;
        --license-file)
            shift
            licensefile="$1"
            shift
            ;;
        --help)
            usage
            ;;
        *)
            echo "Unknown option: $1"
            usage
            ;;
    esac
done

if [ $interactive -eq 1 ]; then

    configure_welcome

    if [ "$configfilenp" != "t" ]; then 
	prompt_for_path_and_create file configfile \
             "Location of configuration file to create" "$configfile"
    fi

    if [ -f "$configfile" ]; then
        cat <<EOF

It appears that you already have an AllegroGraph configuration file:
$configfile
EOF
        if ! y_or_n "Would you like to overwrite it with a new configuration?" n; then
	    cat <<EOF

Keeping existing configuration.

╔══════════════════════════════════════════════════════════════════════════╗
║  IMPORTANT                                                               ║
║                                                                          ║
║  Reusing an old configuration file may cause new AllegroGraph defaults   ║
║  to be missing.                                                          ║
║                                                                          ║
║  We strongly recommend letting the installation script generate a fresh  ║
║  configuration file, then manually migrating any relevant settings from  ║
║  your old configuration into the new one.                                ║
╚══════════════════════════════════════════════════════════════════════════╝
EOF
            agraph_usage
	    exit 1
        fi
    fi

    # Re-evaluate configdir in case the user provided configfile path.
    configdir=`dirname "$configfile"`

    prompt_for_path_and_create dir datadir \
        "Directory to store data and settings" "$datadir"

    # Re-evaluate pidfile and settingsdir in case user provided a
    # custom datadir path.
    pidfile="$datadir/agraph.pid"
    settingsdir="$datadir/settings"

    prompt_for_path_and_create dir logdir \
        "Directory to store log files" "$logdir"

    prompt_for_path_and_create file pidfile \
        "Location of file to write server process id" "$pidfile"

    default_port=$port
    while true; do
        get_answer port "Port" $default_port
        valid_port_number "$port" && break
    done

    if [ "$root" ]; then
        get_answer runas "User to run as" agraph

        if [ "$create" != "0" ] && ! id $runas >/dev/null 2>&1; then
	    cat <<EOF

User '$runas' doesn't exist on this system. 
EOF
	    while true; do
	        get_answer create_runas "Create $runas user" y
	        case "$create_runas" in
		        y)
                            createuser=1
		            break
		            ;;
		        n)
		            break
		            ;;
		        *)
		            echo "Please answer y or n"
		            ;;
	        esac
	    done
        fi
    fi

    cat <<EOF

Now you must set up an initial user account for AllegroGraph.  This
account will have "super user" privileges in AllegroGraph.

EOF

    get_answer superuser "SuperUser account name" $superuser
    if [ -f "$settingsdir/user/$superuser" ]; then
	if ! y_or_n "User \"$superuser\" already exists. Override?" "n"; then
            echo "Skipping superuser creation."
            passwordconfigured=1
	fi
    fi
    while [ ! "$passwordconfigured" ]; do
        read_password superpw  "SuperUser account password"
        if [ -n "$superpw" ]; then
            read_password superpw2  "SuperUser account password (again)"
        fi
        if [ "$superpw" = "$superpw2" ]; then
	    break
        else
	    echo "Passwords must match"
        fi
    done
    passwordconfigured=1

    get_answer instancetimeout "Instance timeout seconds" "$instancetimeout"

else
    if [ ! "$passwordconfigured" ]; then
        cat <<EOF
Please specify one of the --super-password or --super-password-file
options. These are needed to configure a super user account.
EOF
        exit 1
    fi

    if [ "$root" ] && [ ! "$runas" ]; then
        cat <<EOF
To run AllegroGraph as root, you have to specify the --runas-user
option.
EOF
        exit 1
    fi

    # Re-evaluate pidfile in case user provided the datadir, but only
    # if the pidfile itself has not been provided.
    if [ ! "$pidfileconfigured" ]; then
        pidfile="$datadir/agraph.pid"
    fi

    non_interactive_verify_dir "`dirname "$configfile"`"
    non_interactive_verify_dir "$datadir"
    non_interactive_verify_dir "$logdir"
    non_interactive_verify_dir "`dirname "$pidfile"`"
fi # end of [ $interactive -eq 1 ]

if [ -n "$runas" ] && [ -n "$createuser" ] && [ -n "$root" ]; then
    if ! id $runas >/dev/null 2>&1; then
        create_user "$runas"
    fi
fi
if [ "$create" = "1" ] && [ -n "$passwordconfigured" ]; then
    setup_superuser "$superuser" "$superpw"

    # otherwise $settingsdir will be owned by root
    if [ -n "$runas" ] && [ -n "$createuser" ] && [ -n "$root" ]; then
        chown -R $runas: "$settingsdir"
    fi
fi

echo "# AllegroGraph configuration file" > "$configfile"

# Put the link to the relevant documentation page.
if ! command -v curl &> /dev/null; then
    docsurl="https://franz.com/agraph/support/documentation/daemon-config.html"
else
    agversion=`"$basedir/bin/agraph" --version | grep -o -P '\d\.\d(\.\d)?'`
    docsurl="https://franz.com/agraph/support/documentation/$agversion/daemon-config.html"
    if ! curl --head --silent --fail "$docsurl" &> /dev/null
    then
        docsurl="https://franz.com/agraph/support/documentation/daemon-config.html"
    fi
fi

echo "# Documentation: $docsurl" >> "$configfile"

if [ "$runas" ]; then
    echo RunAs $runas >> "$configfile"
fi

if [ "$create" = "0" ] && [ -n "$passwordconfigured" ]; then
    echo Super "$superuser:$superpw" >> "$configfile"
fi

if [ "$sessionports" ]; then
    echo SessionPorts $sessionports >> "$configfile"
fi

function normalize_path {
    # If path is under $basedir, make it relative to $configfile, otherwise
    # return an absolute path.
    local path=`readlink -f "$1"`
    # >&2 echo [*] normalizing $path relative to $configdir # debug
    case $path/ in
        $basedir_abs*) realpath --relative-to="$basedir_abs" "$path" ;;
        *) echo "$path";;
    esac
}

if [ ! "$root" ] && [ ! "$rpminstall" ]; then
    # echo [*] Not root. \$configdir=$configdir
    basedir_abs=`readlink -f "$basedir"`
    case `readlink -f "$configdir"`/ in
        $basedir_abs*)
            # echo [*] Config in $basedir # debug
            basedir_relative=`realpath --relative-to="$configdir" "$basedir_abs"`
            basedir_directive="BaseDir $basedir_relative
"
            settingsdir=`normalize_path "$settingsdir"`
            logdir=`normalize_path "$logdir"`
            datadir=`normalize_path "$datadir"`
            pidfile=`normalize_path "$pidfile"`
            ;;
    esac
fi

cat <<EOF >>"$configfile"
${basedir_directive}\
SettingsDirectory $settingsdir
LogDir $logdir
PidFile $pidfile
Port $port
InstanceTimeout $instancetimeout
Auditing no

<RootCatalog>
 Main $datadir/rootcatalog
</RootCatalog>

<SystemCatalog>
 Main $datadir/systemcatalog
 InstanceTimeout 240
</SystemCatalog>

<FedshardCatalog>
 Main $datadir/fedshardcatalog
</FedshardCatalog>

<llm ollama>
 chatModel llama3.1
 chatModel mistral
 chatModel qwen2
 embedderModel all-minilm embedding-can-set-length=true
 vendorfield length
 vendorfield cansetlength
 vendorfield scheme
 vendorfield host
 vendorfield port
 vendorfield embedding-length
 vendorfield embedding-can-set-length
 scheme http
 host   localhost
 port   11434
</llm>

## You can specify your default API key here using the ApiKey
## field name however this may not be a good idea if you wish
## to keep this key private.
## It's better to use a query option which is then not visible to
## those using webview.
## Note that while the field name ApiKey works in agraph.cfg, when
## assigning the value to a query option you use the name openaiApiKey
##

<llm openai>

 chatModel gpt-5.2
 chatModel gpt-5-mini
 chatModel gpt-4.1

 embedderModel text-embedding-3-large embedding-can-set-length=true
 embedderModel text-embedding-3-small embedding-can-set-length=true
 embedderModel text-embedding-ada-002 embedding-can-set-length=false

 needsApiKey yes
 vendorfield embedding-length 
 vendorfield embedding-can-set-length

 # apiKey your-key-here
</llm>

# the model is specified when the endpoint is created and can't
# be changed.
# so 'default' is just a dummy name here and not the model name
#
<llm databricks>
 needsApiKey yes
 embedderModel default endpoint-url="http://sample.embedserverxx.com"
 chatModel     default endpoint-url="http://sample.chatserverxx.com"
 vendorfield endpoint-url
 # apiKey your-key-here
</llm>


# no embedding models. It's suggested to use  voyage for embeddings
<llm claude>
 needsApiKey yes
 chatModel claude-opus-4-6
 chatModel claude-sonnet-4-6
 chatModel claude-haiku-4-5
 # apiKey your-key-here
</llm>

# like databricks you specify the model when creating the endpoint
# so 'default' is just a placeholder and not the actual model name
<llm azure>
 needsApiKey yes
 embedderModel default endpoint-url="http://sample.embedserverxx.com"
 chatModel default  endpoint-url="http://sample.chatserverxx.com"
 vendorfield endpoint-url
 vendorfield embedding-length
 vendorfield embedding-can-set-length
 # apiKey your-key-here
</llm>

# no embedder models
<llm deepseek>
 needsApiKey yes
 chatModel DeepSeek-R1-Zero
 chatModel DeepSeek-R1
 # apiKey your-key-here
</llm>

<llm gemini>
 needsApiKey yes
 embedderModel gemini-embedding-001 embedding-can-set-length=true
 chatModel gemini-2.5-pro
 chatModel gemini-2.5-flash
 chatModel gemini-2.5-flash-lite
 # apiKey your-key-here
 vendorfield embedding-length
 vendorfield embedding-can-set-length
</llm>

# no embedder models
<llm groq>
 needsApiKey yes
 chatModel deepseek-r1-distill-llama-70b 
 chatModel meta-llama/llama-4-maverick-17b-128e-instruct 
 chatModel mistral-saba-24b
 endpointUrl https://api.groq.com/openai/v1
 # apiKey your-key-here
</llm>


# no chat models
<llm voyage>
 needsApiKey yes
 embedderModel voyage-4-large
 embedderModel voyage-code-3
 embedderModel voyage-finance-2
 # apiKey your-key-here
</llm>






EOF

if [ -f "$licensefile" ]; then
    echo "Added license $licensefile."
    cat "$licensefile" >> "$configfile"
fi

cat <<EOF

$configfile has been created.

If desired, you may modify the configuration.  When you are satisfied,
you may start the agraph service.

╔══════════════════════════════════════════════════════════════════════════╗
║  IMPORTANT                                                               ║
║                                                                          ║
║  Reusing an old configuration file may cause new AllegroGraph defaults   ║
║  to be missing. Avoid replacing the newly generated configuration file   ║
║  with one from a previous installation.                                  ║
║                                                                          ║
║  We strongly recommend letting the installation script generate a fresh  ║
║  configuration file, then manually migrating any relevant settings from  ║
║  your old configuration into the new one.                                ║
╚══════════════════════════════════════════════════════════════════════════╝
EOF

agraph_usage
exit 0
