feat(09-tooling-portable-setup-02): create setup script skeleton with args, prereqs, and interactive prompts
- Script header with D-06 through D-10 references and snapshot date - getopts argument parsing with defaults for all 9 parameters - Usage display with -h flag - Prerequisite checks (Hermes CLI, Docker, SSH keys, repo paths) - prompt_secret function for masked input (T-09-05 mitigation) - Directory creation for scripts, hindsight, skills, archive - Backup existing config.yaml before modification (T-09-07 mitigation)
This commit is contained in:
210
ngn-agent/setup-ngn-agent.sh
Executable file
210
ngn-agent/setup-ngn-agent.sh
Executable file
@@ -0,0 +1,210 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# setup-ngn-agent.sh — Portable ngn-agent configuration setup
|
||||||
|
#
|
||||||
|
# Phase 9, Plan 2 — recreates all configuration on a fresh macOS machine
|
||||||
|
# Assumes Hermes v0.16+ is installed (per D-07)
|
||||||
|
#
|
||||||
|
# Embedded file snapshots frozen at: 2026-06-15
|
||||||
|
# Regenerate by re-running this phase.
|
||||||
|
#
|
||||||
|
# D-06: Single script recreating all ngn-agent configuration
|
||||||
|
# D-07: Requires Hermes v0.16+ on PATH
|
||||||
|
# D-08: Interactive secrets: JIRA_API_TOKEN, JIRA_EMAIL, TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY
|
||||||
|
# D-09: Configurable paths via arguments (SSH keys, repos, timezone)
|
||||||
|
# D-10: Creates/updates: config.yaml, .env, hindsight/config.json, scripts, skills, cron jobs
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---- Usage ----
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
usage: setup-ngn-agent.sh [OPTIONS]
|
||||||
|
|
||||||
|
Portable ngn-agent configuration setup for macOS + Hermes v0.16+
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-s1, --ssh-key-1 PATH SSH private key path 1 (default: ~/.ssh/id_ed25519razer)
|
||||||
|
-s2, --ssh-key-2 PATH SSH private key path 2 (default: ~/.ssh/id_rsa)
|
||||||
|
-sc, --ssh-config PATH SSH config path (default: ~/.ssh/config)
|
||||||
|
-sh, --ssh-known-hosts PATH SSH known_hosts path (default: ~/.ssh/known_hosts)
|
||||||
|
-r1, --repo-ops PATH rai-ops repo path (default: ~/Razer/rai-ops)
|
||||||
|
-r2, --repo-deploy PATH rai-deployment repo path (default: ~/Razer/rai-deployment)
|
||||||
|
-r3, --repo-devtools PATH rai-devtools repo path (default: ~/Razer/rai-devtools)
|
||||||
|
-t, --timezone ZONE Timezone (default: Asia/Singapore)
|
||||||
|
-d, --docker-image TAG Docker image tag (default: ngn-agent:latest)
|
||||||
|
-y, --yes Non-interactive mode (skip prompts, use env vars)
|
||||||
|
-h, --help Show this help message
|
||||||
|
|
||||||
|
Secrets are prompted interactively with masked input unless -y is set,
|
||||||
|
in which case they are read from environment variables.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Argument defaults ----
|
||||||
|
SSH_KEY_1="${SSH_KEY_1:-$HOME/.ssh/id_ed25519razer}"
|
||||||
|
SSH_KEY_2="${SSH_KEY_2:-$HOME/.ssh/id_rsa}"
|
||||||
|
SSH_CONFIG="${SSH_CONFIG:-$HOME/.ssh/config}"
|
||||||
|
SSH_KNOWN_HOSTS="${SSH_KNOWN_HOSTS:-$HOME/.ssh/known_hosts}"
|
||||||
|
REPO_OPS="${REPO_OPS:-$HOME/Razer/rai-ops}"
|
||||||
|
REPO_DEPLOY="${REPO_DEPLOY:-$HOME/Razer/rai-deployment}"
|
||||||
|
REPO_DEVTOOLS="${REPO_DEVTOOLS:-$HOME/Razer/rai-devtools}"
|
||||||
|
TIMEZONE="${TIMEZONE:-Asia/Singapore}"
|
||||||
|
DOCKER_IMAGE="${DOCKER_IMAGE:-ngn-agent:latest}"
|
||||||
|
NONINTERACTIVE=false
|
||||||
|
|
||||||
|
# ---- Argument parsing (per D-09) ----
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-s1|--ssh-key-1)
|
||||||
|
SSH_KEY_1="$2"; shift 2 ;;
|
||||||
|
-s2|--ssh-key-2)
|
||||||
|
SSH_KEY_2="$2"; shift 2 ;;
|
||||||
|
-sc|--ssh-config)
|
||||||
|
SSH_CONFIG="$2"; shift 2 ;;
|
||||||
|
-sh|--ssh-known-hosts)
|
||||||
|
SSH_KNOWN_HOSTS="$2"; shift 2 ;;
|
||||||
|
-r1|--repo-ops)
|
||||||
|
REPO_OPS="$2"; shift 2 ;;
|
||||||
|
-r2|--repo-deploy)
|
||||||
|
REPO_DEPLOY="$2"; shift 2 ;;
|
||||||
|
-r3|--repo-devtools)
|
||||||
|
REPO_DEVTOOLS="$2"; shift 2 ;;
|
||||||
|
-t|--timezone)
|
||||||
|
TIMEZONE="$2"; shift 2 ;;
|
||||||
|
-d|--docker-image)
|
||||||
|
DOCKER_IMAGE="$2"; shift 2 ;;
|
||||||
|
-y|--yes)
|
||||||
|
NONINTERACTIVE=true; shift ;;
|
||||||
|
-h|--help)
|
||||||
|
usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- Interactive secret prompt (per D-08) ----
|
||||||
|
# T-09-05 mitigation: read -s for masked input, no echo to terminal
|
||||||
|
prompt_secret() {
|
||||||
|
local var_name="$1"
|
||||||
|
local prompt_text="$2"
|
||||||
|
local is_optional="${3:-false}"
|
||||||
|
local val=""
|
||||||
|
|
||||||
|
# If env var is already set (e.g., user exported it), skip prompt
|
||||||
|
if [ -n "${!var_name:-}" ]; then
|
||||||
|
echo " → ${var_name} already set (using environment value)"
|
||||||
|
echo "${!var_name}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [ -z "$val" ]; do
|
||||||
|
read -s -p "${prompt_text}" val
|
||||||
|
echo
|
||||||
|
if [ -z "$val" ] && [ "$is_optional" = "true" ]; then
|
||||||
|
# Optional and empty — return empty string
|
||||||
|
echo ""
|
||||||
|
return
|
||||||
|
elif [ -z "$val" ]; then
|
||||||
|
echo " ⚠ Value cannot be empty. Press Ctrl+C to cancel."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "$val"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Prerequisite checks ----
|
||||||
|
check_prerequisites() {
|
||||||
|
echo " → Checking prerequisites..."
|
||||||
|
|
||||||
|
# 1. Hermes CLI installed (per D-07)
|
||||||
|
if ! command -v hermes >/dev/null 2>&1; then
|
||||||
|
echo " ERROR: Hermes CLI not found — install v0.16+ first."
|
||||||
|
echo " See: https://github.com/nousresearch/hermes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✓ Hermes CLI found: $(hermes --version 2>/dev/null || echo 'unknown version')"
|
||||||
|
|
||||||
|
# 2. Docker running
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo " ERROR: Docker is not running."
|
||||||
|
echo " Start Docker Desktop or Orbstack first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✓ Docker is running"
|
||||||
|
|
||||||
|
# 3. SSH key files exist
|
||||||
|
if [ ! -f "$SSH_KEY_1" ]; then
|
||||||
|
echo " ⚠ SSH key not found: ${SSH_KEY_1}"
|
||||||
|
else
|
||||||
|
echo " ✓ SSH key 1: ${SSH_KEY_1}"
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SSH_KEY_2" ]; then
|
||||||
|
echo " ⚠ SSH key not found: ${SSH_KEY_2}"
|
||||||
|
else
|
||||||
|
echo " ✓ SSH key 2: ${SSH_KEY_2}"
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SSH_CONFIG" ]; then
|
||||||
|
echo " ⚠ SSH config not found: ${SSH_CONFIG}"
|
||||||
|
else
|
||||||
|
echo " ✓ SSH config: ${SSH_CONFIG}"
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SSH_KNOWN_HOSTS" ]; then
|
||||||
|
echo " ⚠ SSH known_hosts not found: ${SSH_KNOWN_HOSTS}"
|
||||||
|
else
|
||||||
|
echo " ✓ SSH known_hosts: ${SSH_KNOWN_HOSTS}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Repo paths exist
|
||||||
|
if [ ! -d "$REPO_OPS" ]; then
|
||||||
|
echo " ⚠ Repo not found: ${REPO_OPS}"
|
||||||
|
else
|
||||||
|
echo " ✓ Repo (ops): ${REPO_OPS}"
|
||||||
|
fi
|
||||||
|
if [ ! -d "$REPO_DEPLOY" ]; then
|
||||||
|
echo " ⚠ Repo not found: ${REPO_DEPLOY}"
|
||||||
|
else
|
||||||
|
echo " ✓ Repo (deploy): ${REPO_DEPLOY}"
|
||||||
|
fi
|
||||||
|
if [ ! -d "$REPO_DEVTOOLS" ]; then
|
||||||
|
echo " ⚠ Repo not found: ${REPO_DEVTOOLS}"
|
||||||
|
else
|
||||||
|
echo " ✓ Repo (devtools): ${REPO_DEVTOOLS}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Print path summary ----
|
||||||
|
print_summary() {
|
||||||
|
echo ""
|
||||||
|
echo " Configuration paths:"
|
||||||
|
echo " SSH key 1: ${SSH_KEY_1}"
|
||||||
|
echo " SSH key 2: ${SSH_KEY_2}"
|
||||||
|
echo " SSH config: ${SSH_CONFIG}"
|
||||||
|
echo " SSH known_hosts: ${SSH_KNOWN_HOSTS}"
|
||||||
|
echo " Repo (ops): ${REPO_OPS}"
|
||||||
|
echo " Repo (deploy): ${REPO_DEPLOY}"
|
||||||
|
echo " Repo (devtools): ${REPO_DEVTOOLS}"
|
||||||
|
echo " Timezone: ${TIMEZONE}"
|
||||||
|
echo " Docker image: ${DOCKER_IMAGE}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Create config directories ----
|
||||||
|
create_directories() {
|
||||||
|
echo " → Creating config directories..."
|
||||||
|
mkdir -p "$HOME/.hermes/scripts"
|
||||||
|
mkdir -p "$HOME/.hermes/hindsight"
|
||||||
|
mkdir -p "$HOME/.hermes/skills/ngn-agent"
|
||||||
|
mkdir -p "$HOME/.hermes/archive/sessions"
|
||||||
|
echo " ✓ Directories created"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Backup existing config (per Anti-Pattern 4, T-09-07 mitigation) ----
|
||||||
|
backup_config() {
|
||||||
|
if [ -f "$HOME/.hermes/config.yaml" ]; then
|
||||||
|
local bak_file="$HOME/.hermes/config.yaml.bak.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
cp "$HOME/.hermes/config.yaml" "$bak_file"
|
||||||
|
echo " ✓ Backed up config.yaml → $(basename ${bak_file})"
|
||||||
|
else
|
||||||
|
echo " → No existing config.yaml to backup"
|
||||||
|
fi
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user