#!/usr/bin/env bash
#
# gopass_bernina_service_setup — make a *service/automation* account's
# (e.g. gac-bernina) access to the "bernina" gopass store independent of
# any particular host's $HOME. See gopass.md.
#
# Why this exists, and why it's different from gopass_bernina_setup
# (the script for human accounts): this account needs a passphrase-less
# GPG key (unattended use) and a repo-scoped SSH Deploy Key instead of a
# personal SSH key - everything else (storage backend, the network_drives
# chmod workaround) is the same idea as the human script.
#
# Storage backend: prefers ~/network_drives (SMB) - the account's GPG
# keyring, gopass config, and store clone all end up there. Falls back to
# /sf/bernina/config/personal/<account> (NFS4) only if network_drives
# isn't reachable AND the account has group access to create a directory
# there (unlike a human's /sf/bernina/config/personal, this needs
# `unx-sf_bernina_bs` group membership, which a service account often
# doesn't have - confirmed for gac-bernina: `create_personal_dir`
# fails with "Permission denied"). Check which of the two actually works
# for THIS account before relying on either - see gopass.md.
#
# THE WORKAROUND THIS SCRIPT USES for network_drives (confirmed by hand -
# see gopass.md): network_drives (SMB) rejects the chmod calls that
# `git init`/`git clone`/`git config`/`gpg --gen-key`/`ssh-keygen` make
# internally, so those must never run directly there. An
# ALREADY-initialized repo, keyring, or key pair, copied on with `cp -r`
# (rsync also works on some shares but confirmed to fail outright on at
# least one - see copy_tree() below), keeps working normally afterward.
# So: do the actual init/clone/keygen in a local scratch directory first,
# then copy the result onto network_drives and use it from there.
#
# Run this AS the service account (e.g. via `become <account>`, or however
# you get a shell as it). Safe to re-run - every step checks current state
# first. If the account already has a working GPG/SSH key (e.g. in a
# shared $HOME that still works for now), this OFFERS to copy it into the
# new location rather than generating a fresh one - keeps the same
# recipient/deploy-key registration, no re-authorization needed.
#
# Usage: gopass_bernina_service_setup [account-email]
#   Defaults to <current-username>@psi.ch.
#
set -euo pipefail

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
    cat <<'USAGE'
gopass_bernina_service_setup [account-email]

For: the non-interactive SERVICE/AUTOMATION account (e.g. gac-bernina) -
NOT a human. This account can READ secrets (its GPG key is authorized as
a recipient) but does not need write/admin access - see gopass.md for the
3-role split (admin / normal user / gac-bernina). Its git access is a
repo-scoped Deploy Key, which should be added READ-ONLY.

Run this AS the service account. Puts everything (passphrase-less GPG
key, SSH deploy key, gopass config, store clone) under
~/network_drives/home/gopass/ if reachable, else
/sf/bernina/config/personal/<account>/ - same auto-detection as
gopass_bernina_setup, the human onboarding script (use that one
instead for a human, including the admin).

Note: /sf/bernina/config/personal needs unx-sf_bernina_bs group
membership, which a service account often doesn't have - if
create_personal_dir fails with "Permission denied" for this account,
network_drives is your only option; check it's reachable on every host
this account needs to run from first.
USAGE
    exit 0
fi

GITEA_REMOTE="git@gitea.psi.ch:Bernina/bernina-secrets.git"
STORE_ALIAS="bernina"
PERSONAL_ROOT="/sf/bernina/config/personal"
BIN_DIR="/sf/bernina/bin"

log()  { echo "==> $*"; }
warn() { echo "!!  $*" >&2; }
ask() {
    local reply
    read -r -p "$1 [y/N] " reply
    [[ "$reply" =~ ^[Yy]$ ]]
}
copy_tree() {
    # `rsync -rlt` (no -p/-a - permission preservation doesn't work on
    # network_drives) works on some network_drives/SMB shares but
    # confirmed to fail on at least one (gac-bernina's: exit 3, "Errors
    # selecting input/output files, dirs", zero output even with -v and
    # stderr combined - an rsync/gvfs-fuse incompatibility specific to
    # that share). `cp -r` confirmed to work there, so use it everywhere.
    mkdir -p "$2"
    cp -r "$1/." "$2/"
}

ACCOUNT="$(whoami)"
EMAIL="${1:-${ACCOUNT}@psi.ch}"

# --- 0. pick a storage backend ----------------------------------------------
if [[ -d "${HOME}/network_drives/home" ]]; then
    BACKEND="network_drives"
    ROOT_DIR="${HOME}/network_drives/home/gopass"
    NEEDS_SCRATCH=1
else
    BACKEND="sf_bernina"
    ROOT_DIR="${PERSONAL_ROOT}/${ACCOUNT}"
    NEEDS_SCRATCH=0
    log "~/network_drives/home not reachable on this host - falling back to ${ROOT_DIR}"
fi
GOPASS_HOME_DIR="${ROOT_DIR}/gopass-home"
GNUPG_DIR="${ROOT_DIR}/gnupg"
SSH_DIR="${ROOT_DIR}/ssh"
STORE_DIR="${ROOT_DIR}/gopass-${STORE_ALIAS}"
ENV_FILE="${ROOT_DIR}/env.sh"

SCRATCH="$(mktemp -d)"
cleanup() { rm -rf "${SCRATCH}"; }
trap cleanup EXIT

echo "Setting up host-independent gopass access for ${ACCOUNT} (${EMAIL})"
echo "Backend: ${BACKEND}  (everything will live under ${ROOT_DIR})"
echo

# --- 1. target directory -----------------------------------------------------
log "Step 1/5: target directory (${ROOT_DIR})"
if [[ "${BACKEND}" == "sf_bernina" ]]; then
    "${BIN_DIR}/create_personal_dir" "${ACCOUNT}"
fi
mkdir -p "${GOPASS_HOME_DIR}"
mkdir -m 700 -p "${GNUPG_DIR}"
mkdir -m 700 -p "${SSH_DIR}"

# --- 2. GPG key: migrate existing if present, else generate passphrase-less -
log "Step 2/5: GPG key for ${EMAIL}"
if GNUPGHOME="${GNUPG_DIR}" gpg --list-secret-keys "${EMAIL}" >/dev/null 2>&1; then
    log "  already present under ${GNUPG_DIR}"
elif gpg --list-secret-keys "${EMAIL}" >/dev/null 2>&1; then
    log "  found an existing key for ${EMAIL} in this shell's current \$HOME/.gnupg"
    if ask "  Copy it to ${GNUPG_DIR} (keeps the SAME key/recipient — no re-authorization needed)?"; then
        copy_tree "${HOME}/.gnupg" "${GNUPG_DIR}"
        log "  copied"
    else
        warn "  without this, decrypt calls will fail until a key exists under ${GNUPG_DIR}"
    fi
else
    warn "  no GPG key found anywhere for ${EMAIL}"
    if ask "  Generate a new passphrase-less one now (needed for unattended use — see gopass.md for the security trade-off)?"; then
        KEYGEN_HOME="${GNUPG_DIR}"
        [[ "${NEEDS_SCRATCH}" -eq 1 ]] && KEYGEN_HOME="${SCRATCH}/gnupg" && mkdir -m 700 -p "${KEYGEN_HOME}"
        GNUPGHOME="${KEYGEN_HOME}" gpg --batch --gen-key <<EOF
%no-protection
Key-Type: RSA
Key-Length: 4096
Name-Real: ${ACCOUNT}
Name-Email: ${EMAIL}
Expire-Date: 1y
%commit
EOF
        [[ "${NEEDS_SCRATCH}" -eq 1 ]] && copy_tree "${KEYGEN_HOME}" "${GNUPG_DIR}"
        PUBKEY_FILE="${ROOT_DIR}/${ACCOUNT}-gopass-pubkey.asc"
        GNUPGHOME="${GNUPG_DIR}" gpg --armor --export "${EMAIL}" > "${PUBKEY_FILE}"
        log "  generated, exported public key to ${PUBKEY_FILE}"
        log "  send it to the store admin — they run 'gopass recipients add --store ${STORE_ALIAS} ${EMAIL}' (gopass.md)"
    fi
fi

# --- 3. SSH deploy key: migrate existing if present, else offer to generate -
log "Step 3/5: SSH key for gitea.psi.ch"
if [[ -f "${SSH_DIR}/id_ed25519" ]]; then
    log "  already present at ${SSH_DIR}/id_ed25519"
elif [[ -f "${HOME}/.ssh/id_ed25519" ]]; then
    log "  found an existing key at \$HOME/.ssh/id_ed25519 (this shell's current home)"
    if ask "  Copy it to ${SSH_DIR} (keeps the SAME key — no re-registering the deploy key needed)?"; then
        cp "${HOME}/.ssh/id_ed25519" "${SSH_DIR}/id_ed25519"
        [[ -f "${HOME}/.ssh/id_ed25519.pub" ]] && cp "${HOME}/.ssh/id_ed25519.pub" "${SSH_DIR}/id_ed25519.pub"
        log "  copied"
    else
        warn "  without this, git/gitea access will fail until a key exists under ${SSH_DIR}"
    fi
else
    warn "  no SSH key found anywhere"
    if ask "  Generate a new passphrase-less deploy key now?"; then
        if [[ "${NEEDS_SCRATCH}" -eq 1 ]]; then
            # ssh-keygen chmods the private key file after writing it -
            # fails directly on network_drives like git/gpg do.
            ssh-keygen -t ed25519 -f "${SCRATCH}/id_ed25519" -N "" -C "${ACCOUNT} deploy key"
            cp "${SCRATCH}/id_ed25519" "${SCRATCH}/id_ed25519.pub" "${SSH_DIR}/"
        else
            ssh-keygen -t ed25519 -f "${SSH_DIR}/id_ed25519" -N "" -C "${ACCOUNT} deploy key"
        fi
        log "  generated. Register the PUBLIC half as a READ-ONLY Deploy Key on the repo (gopass.md):"
        echo
        cat "${SSH_DIR}/id_ed25519.pub"
        echo
        log "  https://gitea.psi.ch/Bernina/bernina-secrets/settings/keys -> Add Deploy Key (leave 'Allow write access' UNCHECKED)"
    fi
fi

# --- 4. write env.sh ----------------------------------------------------------
log "Step 4/5: writing ${ENV_FILE}"
if [[ "${BACKEND}" == "network_drives" ]]; then
    cat > "${ENV_FILE}" <<EOF
# Source this from ~/.bashrc (LOCAL to each host - not shared) to make
# ${ACCOUNT}'s gopass access work the same everywhere this account runs,
# regardless of whether \$HOME is shared. Generated by
# gopass_bernina_service_setup - see gopass.md for what each of these
# does and why.
#
# ~/network_drives (SMB, "Partially Shared Home") is mounted per login
# session, asynchronously - on a freshly started session it can take a
# moment to appear. Without this wait, the first gopass command of a new
# session can race the automount and silently fall back to an empty
# LOCAL store instead of erroring. Wait briefly (up to 5s) here instead,
# once per shell. See gopass.md Troubleshooting.
__gopass_wait_n=0
while [ ! -d "${GNUPG_DIR}" ] && [ "\${__gopass_wait_n}" -lt 20 ]; do
    sleep 0.25
    __gopass_wait_n=\$((__gopass_wait_n + 1))
done
unset __gopass_wait_n

export GOPASS_HOMEDIR="${GOPASS_HOME_DIR}"
export GNUPGHOME="${GNUPG_DIR}"
export GIT_SSH_COMMAND="ssh -i ${SSH_DIR}/id_ed25519 -o IdentitiesOnly=yes"
EOF
else
    cat > "${ENV_FILE}" <<EOF
# Source this from ~/.bashrc (LOCAL to each host - not shared) to make
# ${ACCOUNT}'s gopass access work the same everywhere this account runs,
# regardless of whether \$HOME is shared. Generated by
# gopass_bernina_service_setup - see gopass.md for what each of these
# does and why.
export GOPASS_HOMEDIR="${GOPASS_HOME_DIR}"
export GNUPGHOME="${GNUPG_DIR}"
export GIT_SSH_COMMAND="ssh -i ${SSH_DIR}/id_ed25519 -o IdentitiesOnly=yes"
EOF
fi
log "  done"

# --- 5. root store + clone/mount the store using the new env -----------------
log "Step 5/5: '${STORE_ALIAS}' store at ${STORE_DIR}"
export GOPASS_HOMEDIR="${GOPASS_HOME_DIR}" GNUPGHOME="${GNUPG_DIR}"
export GIT_SSH_COMMAND="ssh -i ${SSH_DIR}/id_ed25519 -o IdentitiesOnly=yes"

if ! gopass mounts >/dev/null 2>&1; then
    if [[ "${NEEDS_SCRATCH}" -eq 1 ]]; then
        GNUPGHOME="${GNUPG_DIR}" GOPASS_HOMEDIR="${SCRATCH}/gopass-home" gopass init "${EMAIL}"
        (cd "${SCRATCH}/gopass-home/.local/share/gopass/stores/root" && git config core.fileMode false)
        copy_tree "${SCRATCH}/gopass-home" "${GOPASS_HOME_DIR}"
        sed -i "s|${SCRATCH}/gopass-home|${GOPASS_HOME_DIR}|g" "${GOPASS_HOME_DIR}/.config/gopass/config"
    else
        gopass init "${EMAIL}"
    fi
fi

if gopass mounts 2>/dev/null | grep -q "${STORE_ALIAS} ("; then
    log "  already mounted"
elif [[ -d "${STORE_DIR}/.git" ]]; then
    gopass mounts add "${STORE_ALIAS}" "${STORE_DIR}"
elif [[ -d "${STORE_DIR}" ]]; then
    warn "  ${STORE_DIR} exists but has no .git — see gopass.md Troubleshooting"
    exit 1
else
    # Plain `git clone`, deliberately NOT `gopass clone` - confirmed by
    # hand that `gopass clone` can hang indefinitely (Go-level deadlock at
    # its post-clone "Gathering information for the git repository"
    # bookkeeping, unrelated to repo size or network) - see gopass.md.
    CLONE_DEST="${STORE_DIR}"
    [[ "${NEEDS_SCRATCH}" -eq 1 ]] && CLONE_DEST="${SCRATCH}/${STORE_ALIAS}"
    CLONE_ERR="$(mktemp)"
    if ! git clone "${GITEA_REMOTE}" "${CLONE_DEST}" 2>"${CLONE_ERR}"; then
        cat "${CLONE_ERR}" >&2
        rm -f "${CLONE_ERR}"
        warn "  clone failed — check the deploy key is registered and has repo access (gopass.md)"
        exit 1
    fi
    rm -f "${CLONE_ERR}"
    (cd "${CLONE_DEST}" && git config core.fileMode false)
    if [[ "${NEEDS_SCRATCH}" -eq 1 ]]; then
        copy_tree "${CLONE_DEST}" "${STORE_DIR}"
    fi
    gopass mounts add "${STORE_ALIAS}" "${STORE_DIR}"
fi

if ! gopass mounts 2>/dev/null | grep -q "${STORE_ALIAS} ("; then
    warn "  mount verification failed — see gopass.md Troubleshooting"
    exit 1
fi
log "  mounted and verified OK"

echo
echo "======================================================================"
echo "Done for THIS shell. To make it stick on every host this account runs"
echo "on, add ONE line to that host's LOCAL ~/.bashrc (not shared — do this"
echo "once per host, and only where ${BACKEND} is reachable for this account)."
echo "Put it NEAR THE TOP - before any '[are we interactive?] || return'"
echo "guard - so it also reaches non-interactive invocations of this account"
echo "(cron, systemd units, etc.), which would otherwise silently fall back"
echo "to this host's own empty local gopass store instead of erroring:"
echo
echo "  source ${ENV_FILE}"
echo
echo "Then open a fresh shell (or 'source ~/.bashrc') and confirm:"
echo "  gopass mounts        # should show '${STORE_ALIAS}' with no error"
echo "  gopass ls             # should list without any gitea auth prompt"
echo "======================================================================"
