#!/usr/bin/env bash
# nbshell — drive nixbuild.net's interactive admin shell non-interactively.
#
# nixbuild.net's account administration (settings, ssh-keys, tokens, billing)
# lives ONLY behind `ssh <host> shell`, and that shell is stubborn:
#   - `ssh -T <host> shell` with piped stdin produces NO output (needs a PTY).
#   - `ssh -tt <host> shell` is rejected: "pty not supported".
#   - `ssh <host> shell <cmd>` (command as ssh args) hits the *build-runner*
#     channel, which needs the `run:write` permission and fails with
#     "Authorization failed".
# The only thing that works is a real local PTY wrapping `ssh -T ... shell`,
# fed one command at a time and synchronised on the `nixbuild.net> ` prompt.
# `expect` provides exactly that. (The HTTP API at api.nixbuild.net is
# read-only — no settings endpoints — so it cannot substitute for this.)
#
# Usage:
#   nbshell 'settings substituters --show'
#   nbshell 'settings substituters --add https://cache.example.org' \
#           'settings substituters --show'
#   printf '%s\n' 'settings usage' | nbshell -        # commands on stdin
#
# Each argument (or each stdin line) is one shell command line, run in order
# in a single session. Output is cleaned: banner and `nixbuild.net> ` prompts
# stripped, CRs removed. Exit code is expect's (0 unless the session failed).
#
# Env:
#   NIXBUILD_SSH_HOST   override host (default: eu.nixbuild.net / userConfig)
#   NBSHELL_RAW=1       do not strip banner/prompts (debug)
#   NBSHELL_TIMEOUT     per-prompt timeout seconds (default 30)
#
# Note: `exit` is NOT a valid shell command — the session ends on EOF, which
# this script handles via expect `close`.

set -euo pipefail

HOST="${NIXBUILD_SSH_HOST:-eu.nixbuild.net}"
TIMEOUT="${NBSHELL_TIMEOUT:-30}"

# Collect commands from argv, or from stdin if the sole arg is "-".
cmds=()
if [ "$#" -eq 1 ] && [ "$1" = "-" ]; then
	while IFS= read -r line; do
		[ -n "$line" ] && cmds+=("$line")
	done
else
	cmds=("$@")
fi

if [ "${#cmds[@]}" -eq 0 ]; then
	echo "nbshell: no commands given" >&2
	echo "usage: nbshell 'settings substituters --show' [more commands...]" >&2
	exit 2
fi

# Locate expect: prefer a system binary, else run it ephemerally from nixpkgs
# (no profile/-env install — matches the declarative-only house rule).
if command -v expect >/dev/null 2>&1; then
	EXPECT=(expect)
elif command -v nix >/dev/null 2>&1; then
	EXPECT=(nix run nixpkgs#expect --)
else
	echo "nbshell: need either 'expect' on PATH or 'nix' to fetch it" >&2
	exit 3
fi

script="$(mktemp "${TMPDIR:-/tmp}/nbshell.XXXXXX.exp")"
trap 'rm -f "$script"' EXIT

{
	echo "set timeout ${TIMEOUT}"
	echo 'log_user 1'
	echo 'proc wp {} {'
	echo '  expect {'
	echo '    -re {nixbuild\.net>\s*$} { return }'
	echo '    timeout { puts stderr "nbshell: TIMEOUT waiting for prompt"; return }'
	echo '  }'
	echo '}'
	# -T: do NOT request a remote PTY (nixbuild rejects that); expect itself
	# provides the local PTY the shell needs to render. Disable mux so we get
	# a clean dedicated channel.
	echo "spawn ssh -T -o ControlMaster=no -o ControlPath=none ${HOST} shell"
	echo 'wp'
	for c in "${cmds[@]}"; do
		# Escape backslashes and double-quotes for the Tcl string literal.
		esc=${c//\\/\\\\}
		esc=${esc//\"/\\\"}
		echo "send \"${esc}\r\""
		echo 'wp'
	done
	# The remote shell has no `exit` command and won't EOF on its own; close the
	# PTY ourselves. `close` then a bare `expect eof` races and throws
	# "spawn id ... not open" — wrap both so the session always ends cleanly
	# with exit status 0.
	echo 'catch { close }'
	echo 'catch { expect eof }'
	echo 'exit 0'
} >"$script"

run_expect() { "${EXPECT[@]}" "$script" 2>&1; }

if [ "${NBSHELL_RAW:-0}" = "1" ]; then
	run_expect
	exit "${PIPESTATUS[0]}"
fi

# Clean output: drop the static welcome banner, SSH PQ/MOTD warnings, the
# command listing, the spawn echo, any Tcl close/eof traceback, and the
# `nixbuild.net> ` prompt prefix; normalise CRs and squeeze blank lines.
run_expect | tr -d '\r' | grep -vE \
	'WARNING|store now, decrypt|may need to be upgraded|openssh\.com/pq|^\*\*|^•|nixbuild\.net •|^Welcome to nixbuild|^This shell allows|^and retrieve information|^Account id:|^Storage used:|^You have no free|^Free build time left:|^Available shell commands:|^  (help|settings|usage|ssh-keys|billing|tokens|builds) |^Run .help COMMAND|^For help on subcommands|^For more documentation|^https://docs\.nixbuild\.net/configuration|^spawn ssh |^spawn id |expect: spawn id|^ *while executing|^ *invoked from within|^"(expect eof|close)"|^ *\(file ' \
	| sed -E 's/^nixbuild\.net> ?//' \
	| cat -s
