#!/usr/bin/env bash # memory-export.sh — Export Neuron engram store as a portable encrypted .neuronmem bundle # # Usage: # ./tools/memory-export.sh [output-path] [--passphrase "your passphrase"] # # If no passphrase is given, a random one is generated and printed — write it down. # If no output path is given, defaults to ./neuron-export-.neuronmem set -euo pipefail # ── Config ───────────────────────────────────────────────────────────────────── ENGRAM_SNAPSHOT="${HOME}/.neuron/engram/snapshot.json" SOUL_VERSION="1.1.0" FORMAT_VERSION="1" # ── Parse args ───────────────────────────────────────────────────────────────── OUTPUT_PATH="" PASSPHRASE="" PASSPHRASE_SET=0 while [[ $# -gt 0 ]]; do case "$1" in --passphrase) PASSPHRASE="$2" PASSPHRASE_SET=1 shift 2 ;; --passphrase=*) PASSPHRASE="${1#*=}" PASSPHRASE_SET=1 shift ;; -*) echo "Unknown option: $1" >&2 echo "Usage: $0 [output-path] [--passphrase \"...\"]" >&2 exit 1 ;; *) if [[ -z "$OUTPUT_PATH" ]]; then OUTPUT_PATH="$1" else echo "Unexpected argument: $1" >&2 exit 1 fi shift ;; esac done # ── Default output path ──────────────────────────────────────────────────────── TIMESTAMP="$(date -u +"%Y%m%dT%H%M%SZ")" if [[ -z "$OUTPUT_PATH" ]]; then OUTPUT_PATH="./neuron-export-${TIMESTAMP}.neuronmem" fi # Ensure .neuronmem extension if [[ "${OUTPUT_PATH}" != *.neuronmem ]]; then OUTPUT_PATH="${OUTPUT_PATH%.neuronmem}.neuronmem" fi # ── Validate source ──────────────────────────────────────────────────────────── if [[ ! -f "$ENGRAM_SNAPSHOT" ]]; then echo "ERROR: Engram snapshot not found at: $ENGRAM_SNAPSHOT" >&2 exit 1 fi echo "Neuron Memory Export" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Source: $ENGRAM_SNAPSHOT" echo "Output: $OUTPUT_PATH" echo "" # ── Generate passphrase if not provided ──────────────────────────────────────── if [[ $PASSPHRASE_SET -eq 0 ]]; then PASSPHRASE="$(openssl rand -base64 32)" echo "⚠ No passphrase provided. Generated passphrase:" echo "" echo " ${PASSPHRASE}" echo "" echo "⚠ WRITE THIS DOWN. You will need it to import this file." echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi # ── Count nodes and edges ────────────────────────────────────────────────────── echo "Analyzing snapshot..." NODE_COUNT="$(python3 -c " import json, sys with open('${ENGRAM_SNAPSHOT}') as f: d = json.load(f) nodes = d.get('nodes', d if isinstance(d, list) else []) edges = d.get('edges', []) print(len(nodes) if isinstance(nodes, list) else len(nodes)) " 2>/dev/null || echo "unknown")" echo " Nodes: ${NODE_COUNT}" # ── Compute checksum of source file ─────────────────────────────────────────── echo "Computing checksum..." CHECKSUM="$(openssl dgst -sha256 "$ENGRAM_SNAPSHOT" | awk '{print $NF}')" echo " SHA256: ${CHECKSUM:0:16}..." # ── Build bundle in temp dir ─────────────────────────────────────────────────── WORK_DIR="$(mktemp -d)" BUNDLE_DIR="${WORK_DIR}/neuronmem-v${FORMAT_VERSION}" mkdir -p "$BUNDLE_DIR" echo "Building bundle..." # Copy snapshot as nodes.json cp "$ENGRAM_SNAPSHOT" "${BUNDLE_DIR}/nodes.json" # Write metadata.json ISO_TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" cat > "${BUNDLE_DIR}/metadata.json" << METAEOF { "version": "${FORMAT_VERSION}", "exported_at": "${ISO_TIMESTAMP}", "node_count": ${NODE_COUNT}, "soul_version": "${SOUL_VERSION}", "sha256": "${CHECKSUM}", "format": "neuronmem-v1", "encryption": "aes-256-cbc-pbkdf2", "source_host": "$(hostname -s 2>/dev/null || echo unknown)" } METAEOF echo " metadata.json written" echo " nodes.json copied ($(du -sh "${BUNDLE_DIR}/nodes.json" | cut -f1))" # ── Create tar.gz ────────────────────────────────────────────────────────────── TAR_PATH="${WORK_DIR}/bundle.tar.gz" echo "Compressing..." (cd "$WORK_DIR" && tar czf "$TAR_PATH" "neuronmem-v${FORMAT_VERSION}/") COMPRESSED_SIZE="$(du -sh "$TAR_PATH" | cut -f1)" echo " Compressed size: ${COMPRESSED_SIZE}" # ── Encrypt ──────────────────────────────────────────────────────────────────── echo "Encrypting (AES-256-CBC, PBKDF2, 600k iterations)..." openssl enc -aes-256-cbc \ -pbkdf2 \ -iter 600000 \ -salt \ -in "$TAR_PATH" \ -out "$OUTPUT_PATH" \ -pass "pass:${PASSPHRASE}" # ── Cleanup ──────────────────────────────────────────────────────────────────── rm -rf "$WORK_DIR" # ── Report ───────────────────────────────────────────────────────────────────── FINAL_SIZE="$(du -sh "$OUTPUT_PATH" | cut -f1)" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Export complete." echo " File: $OUTPUT_PATH" echo " Size: ${FINAL_SIZE}" echo " Nodes: ${NODE_COUNT}" echo " Checksum: ${CHECKSUM:0:32}..." echo " Timestamp: ${ISO_TIMESTAMP}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"