From 2de51b119cca9adbcfdc0df22e78f3a13a2c93c5 Mon Sep 17 00:00:00 2001 From: Bagas Purwa Sentika Date: Mon, 15 Jun 2026 23:26:34 +0800 Subject: [PATCH] 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) --- ngn-agent/setup-ngn-agent.sh | 210 +++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100755 ngn-agent/setup-ngn-agent.sh diff --git a/ngn-agent/setup-ngn-agent.sh b/ngn-agent/setup-ngn-agent.sh new file mode 100755 index 0000000..9b629a7 --- /dev/null +++ b/ngn-agent/setup-ngn-agent.sh @@ -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 +}