#!/bin/bash
# This file is part of KyCH.
# Copyright (C) 2026 Taler Systems SA
#
# KyCH is free software; you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later version.
#
# KyCH is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with KyCH; see the file COPYING.  If not, see
# <http://www.gnu.org/licenses/>
#
#
# Create the PostgreSQL role and database that kych.conf names and load the
# schema into it.  This is the one-time setup step; afterwards the schema is
# kept up to date by kych-dbinit, which the Debian package runs on every
# upgrade.
#
# Error checking on
set -eu

RESET_DB=0
SKIP_DBINIT=0
DBUSER="kych"
CFGFILE="/etc/kych/kych.conf"

while getopts 'c:hrsu:' OPTION; do
  case "$OPTION" in
  c)
    CFGFILE="$OPTARG"
    ;;
  h)
    echo 'Supported options:'
    echo "  -c FILENAME  -- use configuration FILENAME (default: $CFGFILE)"
    echo "  -h           -- print this help text"
    echo "  -r           -- reset database (dangerous, destroys all data)"
    echo "  -s           -- skip database initialization"
    echo "  -u USER      -- kych to be run by USER (default: $DBUSER)"
    exit 0
    ;;
  r)
    RESET_DB="1"
    ;;
  s)
    SKIP_DBINIT="1"
    ;;
  u)
    DBUSER="$OPTARG"
    ;;
  ?)
    echo "Unrecognized command line option" 1>&2
    exit 1
    ;;
  esac
done

if [ "$(id -u)" -ne 0 ]; then
  echo "This script must be run as root" 1>&2
  exit 1
fi

if ! id postgres >/dev/null 2>&1; then
  echo "Could not find 'postgres' user. Please install PostgreSQL first" 1>&2
  exit 1
fi

if ! id "$DBUSER" >/dev/null 2>&1; then
  echo "Could not find '$DBUSER' user. Please set it up first" 1>&2
  exit 1
fi

if ! command -v kych-dbinit >/dev/null 2>&1; then
  echo "Required 'kych-dbinit' not found. Please fix your installation." 1>&2
  exit 1
fi

# kych-dbinit understands the full configuration format, including @inline@
# includes and $-expansion, so it is asked where the database is rather than
# this script guessing at it.  This sets PGDATABASE, PGHOST and PGPORT, which
# matters on a host whose PostgreSQL is not on the default port: everything
# below then acts on the same server the daemon will use.
eval "$(kych-dbinit -c "$CFGFILE" --print-connection)"

if [ -z "${PGDATABASE:-}" ]; then
  echo "Could not determine the database from $CFGFILE" 1>&2
  exit 1
fi
DBNAME="$PGDATABASE"

# A database on another host is not ours to create.
case "$PGHOST" in
/* | localhost | 127.0.0.1 | ::1) ;;
*)
  echo "DATABASE in $CFGFILE names the server '$PGHOST', which is not this" 1>&2
  echo "host.  Create the role and the database there by hand, then load the" 1>&2
  echo "schema with 'kych-dbinit -c $CFGFILE'." 1>&2
  exit 1
  ;;
esac

# The administrative work is done over the local socket as the postgres user,
# which is what peer authentication there expects; only the port is taken from
# the configuration.  Asking the postgres superuser to authenticate over TCP
# instead would stop to prompt for a password that nobody is there to type,
# which is also why -w is passed to psql throughout.  "sudo -i" resets the
# environment, so PGPORT is passed explicitly.
as_postgres() {
  sudo -i -u postgres env PGPORT="$PGPORT" PGCONNECT_TIMEOUT=10 "$@"
}

# Up front, so that a server which is not running is reported as that and not
# as a series of puzzling failures further down.
if ! as_postgres psql -w -tAc 'SELECT 1' postgres >/dev/null 2>&1; then
  echo "Cannot reach PostgreSQL on port $PGPORT as the postgres user over the" 1>&2
  echo "local socket.  Check that the server is running and that $CFGFILE" 1>&2
  echo "names its port." 1>&2
  exit 1
fi

# The daemon connects over the local socket and authenticates as the
# operating-system user it runs as, which is what peer authentication in a
# stock Debian PostgreSQL expects: the role must have the same name.
if as_postgres psql -w -tAc \
  "SELECT 1 FROM pg_roles WHERE rolname = '$DBUSER'" postgres | grep -q 1; then
  echo "Database user '$DBUSER' already exists, continuing anyway." 1>&2
else
  echo "Setting up database user '$DBUSER'." 1>&2
  if ! as_postgres createuser "$DBUSER"; then
    echo "Failed to create database user '$DBUSER'" 1>&2
    exit 1
  fi
fi

DO_CREATE=1
if as_postgres psql -w -lqtA -F: | cut -d: -f1 | grep -qx "$DBNAME"; then
  if [ 1 = "$RESET_DB" ]; then
    echo "Deleting existing database '$DBNAME'." 1>&2
    if ! as_postgres dropdb "$DBNAME"; then
      echo "Failed to delete existing database '$DBNAME'" 1>&2
      exit 1
    fi
  else
    echo "Database '$DBNAME' already exists, continuing anyway." 1>&2
    DO_CREATE=0
  fi
fi

if [ 1 = "$DO_CREATE" ]; then
  echo "Creating database '$DBNAME'." 1>&2
  if ! as_postgres createdb -O "$DBUSER" "$DBNAME"; then
    echo "Failed to create database '$DBNAME'" 1>&2
    exit 1
  fi
fi

if [ 0 = "$SKIP_DBINIT" ]; then
  echo "Initializing database '$DBNAME'." 1>&2
  if ! sudo -u "$DBUSER" kych-dbinit -c "$CFGFILE"; then
    echo "Failed to initialize database schema" 1>&2
    exit 1
  fi
fi

echo "Database configuration finished." 1>&2

exit 0
