#!/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 for the SWIYU verifier and store the
# generated password where the service can read it.
#
# Unlike KyCH, the verifier reaches PostgreSQL through the JDBC driver, which
# cannot use peer authentication over a Unix socket -- hence a password, and
# hence this script rather than a plain createdb.  The schema itself is not
# created here: the verifier runs its own Flyway migrations on every start.
#
# Error checking on
set -eu

RESET_DB=0
DBUSER=
DBNAME=
ENVFILE="/etc/swiyu-verifier/swiyu-verifier.env"
SECRETFILE="/etc/swiyu-verifier/secrets/db.env"
SERVICEUSER="swiyu-verifier"

while getopts 'c:d:hn:ru:' OPTION; do
  case "$OPTION" in
  c)
    ENVFILE="$OPTARG"
    ;;
  d)
    SECRETFILE="$OPTARG"
    ;;
  h)
    echo 'Supported options:'
    echo "  -c FILENAME  -- read the connection from FILENAME (default: $ENVFILE)"
    echo "  -d FILENAME  -- write the password to FILENAME (default: $SECRETFILE)"
    echo "  -h           -- print this help text"
    echo "  -n DBNAME    -- database to create (default: from POSTGRES_JDBC)"
    echo "  -r           -- reset database (dangerous, destroys all data)"
    echo "  -u DBUSER    -- database role to create (default: from POSTGRES_USER)"
    exit 0
    ;;
  n)
    DBNAME="$OPTARG"
    ;;
  r)
    RESET_DB="1"
    ;;
  u)
    DBUSER="$OPTARG"
    ;;
  ?)
    echo "Unrecognized command line option" 1>&2
    exit 1
    ;;
  esac
done

# Take the port and the database name from the service's own configuration, so
# that this script and the service cannot end up talking to different servers:
# on a host whose PostgreSQL is not on the default port, a hard-coded default
# would silently set the database up in the wrong place, or in no place at all.
#
#   POSTGRES_JDBC=jdbc:postgresql://host:port/dbname?params
jdbc=$(sed -n 's/^[[:space:]]*POSTGRES_JDBC=//p' "$ENVFILE" 2>/dev/null | tail -1)
JDBCHOST=localhost
PGPORT=5432
case "$jdbc" in
jdbc:postgresql://*)
  rest=${jdbc#jdbc:postgresql://}
  authority=${rest%%/*}
  path=${rest#*/}
  path=${path%%\?*}
  JDBCHOST=${authority%%:*}
  case "$authority" in
  *:*) PGPORT=${authority##*:} ;;
  esac
  [ -n "$DBNAME" ] || DBNAME="$path"
  ;;
*)
  echo "No POSTGRES_JDBC in $ENVFILE; assuming localhost port 5432." 1>&2
  ;;
esac

case "$JDBCHOST" in
localhost | 127.0.0.1 | ::1 | "") ;;
*)
  echo "POSTGRES_JDBC names the server '$JDBCHOST', which is not this host." 1>&2
  echo "Create the role and the database there by hand -- the verifier needs" 1>&2
  echo "only an empty database owned by its role, since it migrates its own" 1>&2
  echo "schema -- and put the password in $SECRETFILE." 1>&2
  exit 1
  ;;
esac

if [ -z "$DBUSER" ]; then
  DBUSER=$(sed -n 's/^[[:space:]]*POSTGRES_USER=//p' "$ENVFILE" 2>/dev/null | tail -1)
fi

: "${DBNAME:=swiyu_verifier}"
: "${DBUSER:=swiyu_verifier}"

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

# 32 bytes of base64 from the kernel's pool; no shell history, no temporary
# file, and never printed.
DBPASS=$(head -c 32 /dev/urandom | base64 | tr -d '\n=' | tr '+/' 'ab')

# The administrative work is done over the local socket as the postgres user,
# which is what peer authentication there expects.  Only the port comes from
# the configuration: the service's own TCP connection with a password is a
# different matter, and asking the postgres superuser to authenticate over TCP
# would stop to prompt for a password that nobody is there to type.  "sudo -i"
# resets the environment, so PGPORT is passed explicitly, and -w keeps psql
# from prompting in any case.
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" 1>&2
  echo "POSTGRES_JDBC in $ENVFILE names its port." 1>&2
  exit 1
fi

echo "Setting up database user '$DBUSER'." 1>&2
if as_postgres psql -w -tAc "SELECT 1 FROM pg_roles WHERE rolname = '$DBUSER'" postgres | grep -q 1; then
  echo "Database user '$DBUSER' already existed; setting a new password." 1>&2
fi
# Both branches in one statement, so the role ends up with the password we are
# about to write out either way.
as_postgres psql -w -q postgres <<SQL
DO \$\$
BEGIN
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$DBUSER') THEN
    ALTER ROLE "$DBUSER" WITH LOGIN PASSWORD '$DBPASS';
  ELSE
    CREATE ROLE "$DBUSER" WITH LOGIN PASSWORD '$DBPASS';
  END IF;
END
\$\$;
SQL

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' on port $PGPORT." 1>&2
  if ! as_postgres createdb -O "$DBUSER" "$DBNAME"; then
    echo "Failed to create database '$DBNAME'" 1>&2
    exit 1
  fi
fi

echo "Writing the generated password to $SECRETFILE." 1>&2
mkdir -p "$(dirname "$SECRETFILE")"
umask 027
cat >"$SECRETFILE" <<ENV
# Written by swiyu-verifier-dbconfig; read by swiyu-verifier.service.
# Keep this file mode 0640 and owned by root:$SERVICEUSER.
POSTGRES_PASSWORD=$DBPASS
ENV
chmod 0640 "$SECRETFILE"
if getent group "$SERVICEUSER" >/dev/null 2>&1; then
  chown "root:$SERVICEUSER" "$SECRETFILE"
fi

echo "Database configuration finished.  The schema itself is created by the" 1>&2
echo "service on its next start." 1>&2

exit 0
