#!/bin/bash

set -e -o pipefail

PROGRAM_NAME="syshawk-mesh"
PROGRAM_VERSION="1.1.1"

# Defaults (overridable by config file)
CONFIG_FILE="/etc/syshawk/mesh/mesh-wg.conf"
MESH_DRY_RUN=0

MESH_DATABASE_DNS=""
# Generated with: openssl rand -out /etc/syshawk/mesh/dns-aes.key 32
MESH_DATABASE_AES_KEY_FILE="/etc/syshawk/mesh/dns-aes.key"
MESH_DATABASE_PRIMARYMASTER=""
MESH_DATABASE_UPDATEKEY="/etc/syshawk/mesh/dns.key"
MESH_DATABASE_TTL=60
MESH_DATABASE_PEER_MAX_AGE_DAYS=10
MESH_PUBLIC_HOSTNAME=""
MESH_ROUTER_ID=""
MESH_VPN_ID=""
MESH_WIREGUARD_PRIVATE_KEY_FILE="/etc/syshawk/mesh/wg-private.key"
# Generated with: wg genpsk
MESH_WIREGUARD_PSK_FILE="/etc/syshawk/mesh/wg-psk.static"
MESH_LOCKFILE_WAIT=5

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

# Development: ./lib
# Production: /usr/lib/mesh
if [[ -d "$SCRIPT_DIR/lib" ]]; then
	LIB_DIR="$SCRIPT_DIR/lib"
else
	LIB_DIR="/usr/lib/syshawk-mesh"
fi

# shellcheck disable=SC1090
. display.sh
# shellcheck disable=SC1090
. lockfile.sh
# shellcheck disable=SC1090
. tmpfiles.sh
# shellcheck disable=SC1090
. dict.sh
# shellcheck disable=SC1090
. /usr/share/bash-argsparse/argsparse.sh
# shellcheck disable=SC1090
. $LIB_DIR/database.sh
# shellcheck disable=SC1090
. $LIB_DIR/wireguard.sh

parse_args() {
	local display_mode="informational"

	argsparse_use_option "config:" "Path to mesh-wg.conf" "type:file" "default:$CONFIG_FILE"
	argsparse_use_option "dry-run" "Compute and log changes without applying them" "short:n"
	argsparse_use_option "verbose" "Enable verbose logs (debug)" "short:v" "exclude:quiet"
	argsparse_use_option "quiet" "Reduce logs (warnings and errors only)" "short:q" "exclude:verbose"
	argsparse_use_option "run" "" "alias:update-peer"
	argsparse_use_option "prune" "" "alias:delete-old-peer"
	argsparse_use_option "update-peer" "Update Wireguard tunnel configuration" "exclude:'delete-old-peer dump-database'"
	argsparse_use_option "delete-old-peer" "Delete DNS peer records older than max age" "exclude:'update-peer dump-database'"
	argsparse_use_option "dump-database" "Dump DNS peer database" "exclude:'update-peer delete-old-peer'"
	argsparse_use_option "full" "Enrich database with node IPs" "require:dump-database"
	argsparse_use_option "version" "Show version" "short:V"
	argsparse_parse_options "$@" || return $?

	if argsparse_is_option_set "version"; then
		printf '%s %s\n' "$PROGRAM_NAME" "$PROGRAM_VERSION"
		exit 0
	fi

	CONFIG_FILE="${program_options[config]}"

	if argsparse_is_option_set "dry-run"; then
		MESH_DRY_RUN="1"
	fi

	if argsparse_is_option_set "verbose"; then
		display_mode="debug"
	fi

	if argsparse_is_option_set "quiet"; then
		display_mode="warning"
	fi

	display_set_mode "$display_mode"
}

check_run_update_variables() {

	if [[ -z "$MESH_PUBLIC_HOSTNAME" ]]; then
		display_critical "MESH_PUBLIC_HOSTNAME is required"
		return 1
	fi

	if [[ -z "$MESH_ROUTER_ID" || ! "$MESH_ROUTER_ID" =~ ^[0-9]+$ ]]; then
		display_critical "MESH_ROUTER_ID is required"
		return 1
	fi

	if [[ -z "$MESH_VPN_ID" || ! "$MESH_VPN_ID" =~ ^[0-9]+$ ]]; then
		display_critical "MESH_VPN_ID is required"
		return 1
	fi

}

acquire_lock() {
	if ! lockfile_lock "$PROGRAM_NAME" $MESH_LOCKFILE_WAIT; then
		display_warning "another instance of %s is already running, exiting" "$PROGRAM_NAME"
		exit 0
	fi
}

run_update_peer() {
	mesh_database_check_variables
	mesh_wg_check_variables
	local public_key

	display_debug "updating peer record for %s (router-id=%s)" "$MESH_PUBLIC_HOSTNAME" "$MESH_ROUTER_ID"

	mesh_wg_ensure_private_key
	public_key=$(mesh_wg_get_public_key)
	mesh_database_update_myself "$MESH_PUBLIC_HOSTNAME" "$MESH_ROUTER_ID" "$public_key"
	mesh_database_load_with_ip

	mesh_wg_reconcile "$MESH_VPN_ID" "$MESH_PUBLIC_HOSTNAME"

}

run_delete_old_peer() {
	mesh_database_check_variables

	mesh_database_prune
}

main() {
	parse_args "$@" || exit 2

	if [[ -r "$CONFIG_FILE" ]]; then
		. "$CONFIG_FILE"
	fi

	if argsparse_is_option_set "dump-database"; then
		mesh_database_check_variables
		if argsparse_is_option_set "full"; then
			mesh_database_load_with_ip
		fi
		mesh_database_dump
		exit 0
	fi

	if argsparse_is_option_set "update-peer"; then
		acquire_lock
		check_run_update_variables
		run_update_peer
		exit 0
	fi

	if argsparse_is_option_set "delete-old-peer"; then
		run_delete_old_peer
		exit 0
	fi

	argsparse_usage
	exit 2
}

main "$@"
