#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-3.0-only
set -euo pipefail

usage() {
    cat <<'EOF'
Usage: eniatos-useradd [--plain] [--migrate] USER

  --plain    unencrypted home dataset; user is not added to encrypted-home
  --migrate  USER already exists; move current home onto a new dataset
EOF
    exit "${1:-0}"
}

PLAIN=0
MIGRATE=0
USER_NAME=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help) usage 0 ;;
        --plain) PLAIN=1; shift ;;
        --migrate) MIGRATE=1; shift ;;
        --) shift; break ;;
        -*) echo "unknown option: $1" >&2; usage 1 ;;
        *) USER_NAME="$1"; shift; break ;;
    esac
done

[[ $# -eq 0 ]] || { echo "too many arguments" >&2; usage 1; }
[[ -n "$USER_NAME" ]] || usage 1
[[ "$USER_NAME" =~ ^[a-z_][a-z0-9_-]*$ ]] || {
    echo "invalid username: $USER_NAME" >&2
    exit 1
}

[[ $(id -u) -eq 0 ]] || { echo "must be root" >&2; exit 1; }

POOL_NAME=$(tr -d '[:space:]' < /etc/hostname)
POOL_NAME=${POOL_NAME,,}
HOME_PARENT="${POOL_NAME}/home"
DS="${HOME_PARENT}/${USER_NAME}"
HOME_DIR="/home/${USER_NAME}"
GROUP="encrypted-home"
MINLEN=8
SHELL_PATH="/usr/bin/bash"

read_password() {
    local p1 p2
    read -r -s -p "Password: " p1
    echo >&2
    read -r -s -p "Password (again): " p2
    echo >&2
    [[ "$p1" == "$p2" ]] || { echo "passwords do not match" >&2; exit 1; }
    [[ ${#p1} -ge $MINLEN ]] || {
        echo "password must be at least ${MINLEN} characters" >&2
        exit 1
    }
    printf '%s' "$p1"
}

ensure_parent() {
    if zfs list -H -o name "$HOME_PARENT" &>/dev/null; then
        return 0
    fi
    if [[ -d /home ]] && [[ -n $(find /home -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null) ]]; then
        echo "${HOME_PARENT} does not exist and /home is not empty." >&2
        echo "Move existing homes aside first, then create the parent dataset." >&2
        echo "See EniatOS.md (Adding User)." >&2
        exit 1
    fi
    zfs create -o mountpoint=/home "$HOME_PARENT"
}

create_dataset() {
    local pass="$1"
    if [[ $PLAIN -eq 1 ]]; then
        zfs create -o canmount=on -o mountpoint="$HOME_DIR" "$DS"
    else
        printf '%s\n%s\n' "$pass" "$pass" | zfs create \
            -o encryption=aes-256-gcm \
            -o keyformat=passphrase \
            -o keylocation=prompt \
            -o canmount=on \
            -o mountpoint="$HOME_DIR" \
            "$DS"
        if ! zfs get -H -o value mounted "$DS" | grep -qx yes; then
            printf '%s\n' "$pass" | zfs load-key "$DS"
            zfs mount "$DS"
        fi
    fi
}

groupadd -f "$GROUP"
ensure_parent

if zfs list -H -o name "$DS" &>/dev/null; then
    echo "dataset already exists: $DS" >&2
    exit 1
fi

if [[ $MIGRATE -eq 1 ]]; then
    getent passwd "$USER_NAME" >/dev/null || {
        echo "user does not exist: $USER_NAME" >&2
        exit 1
    }
else
    if getent passwd "$USER_NAME" >/dev/null; then
        echo "user already exists: $USER_NAME" >&2
        exit 1
    fi
    useradd -M -d "$HOME_DIR" -s "$SHELL_PATH" "$USER_NAME"
fi

PASS=""
if [[ $PLAIN -eq 0 ]] || [[ $MIGRATE -eq 0 ]]; then
    PASS=$(read_password)
fi

STASH=""
if [[ $MIGRATE -eq 1 && -e "$HOME_DIR" ]]; then
    STASH="${HOME_DIR}.migrating"
    [[ ! -e "$STASH" ]] || { echo "leftover exists: $STASH" >&2; exit 1; }
    mv "$HOME_DIR" "$STASH"
fi

create_dataset "$PASS"

if [[ -n "$STASH" ]]; then
    rsync -aHAX "$STASH"/ "$HOME_DIR"/
    rm -rf "$STASH"
elif [[ -d /etc/skel ]]; then
    rsync -aHAX /etc/skel/ "$HOME_DIR"/
fi

chown -R "${USER_NAME}:${USER_NAME}" "$HOME_DIR"
chmod 700 "$HOME_DIR"

if [[ -n "$PASS" ]]; then
    printf '%s:%s\n' "$USER_NAME" "$PASS" | chpasswd
fi

if [[ $PLAIN -eq 0 ]]; then
    usermod -aG "$GROUP" "$USER_NAME"
fi

echo "created ${DS} (plain=${PLAIN}) for ${USER_NAME}"
