From 664f12656b4b45a7d284f8dbfa0c966b6078354e Mon Sep 17 00:00:00 2001 From: nodoka Date: Tue, 20 Jan 2026 16:40:01 +0900 Subject: [PATCH] Update bootstrap: 2026-01-20 16:40:01 --- install.sh | 40 ++++-- nbcrypt | 360 ++++++++++++++++++++++++++++++++++++++------------ nbmain.sh.enc | Bin 1104 -> 1104 bytes 3 files changed, 309 insertions(+), 91 deletions(-) diff --git a/install.sh b/install.sh index bbe6217..053da13 100755 --- a/install.sh +++ b/install.sh @@ -27,25 +27,49 @@ if [ ! -f "$NBCRYPT" ]; then exit 1 fi +# Load SSH Agent environment BEFORE running nbcrypt +# This ensures nbcrypt can find the Ed25519 key without prompting for BWS token +AGENT_ENV_FILE="/tmp/.nb_agent_env_${USER:-$(id -un)}" +if [ -f "$AGENT_ENV_FILE" ]; then + # Check if we already have a valid SSH_AUTH_SOCK (Agent Forward) + # But also verify it actually works with ssh-add -l + if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then + # Test if the agent actually works + if ssh-add -l >/dev/null 2>&1; then + # Agent Forward exists and works, preserve it and skip file loading + echo "🔑 Using existing SSH Agent Forward (preserved)" + else + # Agent Forward exists but doesn't work (stale socket), load from file + echo "🔑 Existing SSH Agent Forward is stale, loading from file..." + source "$AGENT_ENV_FILE" + fi + else + # No valid agent, safe to load from file + echo "🔑 Loading SSH Agent environment..." + source "$AGENT_ENV_FILE" + fi +fi + +# Ed25519 が無いときは BWS から鍵を取得して ssh-add(--keep でディスクに残す) +if ! ssh-add -l 2>/dev/null | grep -q ED25519; then + "$NBCRYPT" keychain --keep + [ -f "$AGENT_ENV_FILE" ] && source "$AGENT_ENV_FILE" +fi + # Decrypt and execute echo "🔐 Decrypting ${TARGET}.sh..." TEMP_SCRIPT="/tmp/${TARGET}-$$.sh" -if "$NBCRYPT" decrypt "$ENC_FILE" "$TEMP_SCRIPT"; then +if "$NBCRYPT" decfile "$ENC_FILE" "$TEMP_SCRIPT"; then chmod +x "$TEMP_SCRIPT" - # Load SSH Agent environment if it was created by nbcrypt/BWS setup - # Only load if we don't already have a valid Agent Forward - AGENT_ENV_FILE="/tmp/.nb_agent_env_${USER:-$(id -un)}" + # Reload SSH Agent environment if it was updated by nbcrypt/BWS setup + # (in case BWS setup created a new agent) if [ -f "$AGENT_ENV_FILE" ]; then # Check if we already have a valid SSH_AUTH_SOCK (Agent Forward) if [ -z "${SSH_AUTH_SOCK:-}" ] || [ ! -S "${SSH_AUTH_SOCK}" ]; then # No valid agent, safe to load from file - echo "🔑 Loading SSH Agent environment..." source "$AGENT_ENV_FILE" - else - # Agent Forward exists, preserve it and skip file loading - echo "🔑 Using existing SSH Agent Forward (preserved)" fi fi diff --git a/nbcrypt b/nbcrypt index 980d7cb..cd8ab34 100755 --- a/nbcrypt +++ b/nbcrypt @@ -1,18 +1,18 @@ #!/bin/bash set -euo pipefail -# nbcrypt - SSH Agent based encryption/decryption tool -# Uses Ed25519 signature for deterministic key derivation +# nbcrypt - SSH Agent ベースの暗号化/復号ツール +# Ed25519 署名で決定的な鍵導出を行う SCRIPT_NAME="$(basename "$0")" KEY_SEED_MESSAGE="nbase2-secret-key-seed-v1" KEY_IDENTITY="id_ed25519" -# Colors for output +# 出力用の色 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' -NC='\033[0m' # No Color +NC='\033[0m' # リセット error() { echo -e "${RED}❌ Error: $*${NC}" >&2 @@ -27,29 +27,50 @@ warn() { echo -e "${YELLOW}⚠️ $*${NC}" >&2 } +print_source_agent_hint() { + local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}" + echo "" >&2 + info "この agent を現在のシェルで使うには、次を実行:" + echo " source $env_file" >&2 +} + usage() { cat < [arguments] Commands: - encrypt Encrypt a file using SSH Agent key - decrypt [options] Decrypt a file using SSH Agent key + enc Encrypt a text string (or use enctext) + dec Decrypt a text string (or use dectext) + enctext Encrypt a text string (alias for enc) + dectext Decrypt a text string (alias for dec) + encfile Encrypt a file (or use encrypt) + decfile Decrypt a file (or use decrypt) + encrypt Encrypt a file (alias for encfile) + decrypt [options] Decrypt a file (alias for decfile) + keychain [-k|--keep] Fetch SSH keys from BWS and run ssh-add. + Default: remove key files after ssh-add. Use -k/--keep to keep. install-bws Install Bitwarden Secrets Manager CLI (bws) check Check if SSH Agent has required key help Show this help message -Options for decrypt: +Options for decrypt/decfile: -f, --force Force download nbloader from BWS and setup SSH Agent (skips SSH Agent check, requires BWS_ACCESS_TOKEN) +Text commands support stdin: + echo "secret" | $SCRIPT_NAME enc - + echo "encrypted" | $SCRIPT_NAME dec - + Requirements: - - SSH Agent must be running with id_ed25519 key loaded + - SSH Agent with id_ed25519 key, or ~/.ssh/id_ed25519 file, or BWS_ACCESS_TOKEN - ssh-keygen and openssl commands must be available Examples: - $SCRIPT_NAME encrypt secrets.txt secrets.enc - $SCRIPT_NAME decrypt secrets.enc secrets.txt - $SCRIPT_NAME decrypt -f secrets.enc secrets.txt # Force BWS download + $SCRIPT_NAME enc "secret text" + $SCRIPT_NAME dec "ENC:U2FsdGVkX1..." + $SCRIPT_NAME encfile secrets.txt secrets.enc + $SCRIPT_NAME decfile secrets.enc secrets.txt + $SCRIPT_NAME keychain $SCRIPT_NAME install-bws $SCRIPT_NAME check @@ -71,12 +92,52 @@ check_dependencies() { fi } +get_bws_token() { + # 1. すでに環境変数にある + if [ -n "${BWS_ACCESS_TOKEN:-}" ]; then + echo "$BWS_ACCESS_TOKEN" + return 0 + fi + # 2. nbconf から取得を試みる + local nbase_root="${NBASE_HOME:-$(cd "$(dirname "$0")/.." && pwd)}" + local nbconf_cmd="${nbase_root}/bin/nbconf" + if [ -f "$nbconf_cmd" ] && [ -x "$nbconf_cmd" ]; then + local val + val=$("$nbconf_cmd" get BWS_ACCESS_TOKEN -d 2>/dev/null) || true + if [ -n "$val" ]; then + echo "$val" + return 0 + fi + fi + return 1 +} + check_ssh_agent() { - # Check if SSH_AUTH_SOCK is set and valid + local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}" + + # まず、以前の BWS セットアップで作られた env ファイルがあれば読む + # SSH_AUTH_SOCK の検査より前にやる(env にソケットパスが入っているため) + if [ -f "$env_file" ]; then + source "$env_file" + info "SSH Agent environment loaded from $env_file" + + # 読んだあと ssh-add が動くか確認 + if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then + if ssh-add -l >/dev/null 2>&1; then + # Ed25519 が載っているか確認 + if ssh-add -l 2>/dev/null | grep -q "ED25519"; then + info "SSH Agent check passed (Ed25519 key found)" + return 0 + fi + fi + fi + fi + + # 現在の環境の SSH_AUTH_SOCK がセットかつ有効か(すでにシェルに Agent がある場合) if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then - # Check if ssh-add can connect to agent + # ssh-add で agent に接続できるか if ssh-add -l >/dev/null 2>&1; then - # Check if Ed25519 key is loaded + # Ed25519 が載っているか確認 if ssh-add -l 2>/dev/null | grep -q "ED25519"; then info "SSH Agent check passed (Ed25519 key found)" return 0 @@ -84,11 +145,13 @@ check_ssh_agent() { fi fi - # No valid agent or no Ed25519 key - try BWS bootstrap + # ここに来た場合: (1) env ファイルがない (2) あっても ssh-add 失敗 (3) Ed25519 がない + # いずれも BWS から bootstrap を試す info "No Ed25519 key found in SSH Agent. Attempting BWS bootstrap..." - # Get BWS access token - local bws_token="${BWS_ACCESS_TOKEN:-}" + # BWS トークン取得(環境変数、nbconf、または入力) + local bws_token + bws_token=$(get_bws_token) || true if [ -z "$bws_token" ]; then echo -n "Enter BWS_ACCESS_TOKEN: " >&2 read bws_token @@ -97,14 +160,29 @@ check_ssh_agent() { fi fi - # Try to load setup script from BWS + # BWS からセットアップスクリプトを取得して実行 load_bws_setup "$bws_token" - # Re-check agent after BWS setup + # BWS セットアップ後に agent を再確認(env を読み直す) + if [ -f "$env_file" ]; then + source "$env_file" + fi + if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then - if ssh-add -l >/dev/null 2>&1 && ssh-add -l 2>/dev/null | grep -q "ED25519"; then - info "SSH Agent check passed (Ed25519 key found after BWS setup)" - return 0 + if ssh-add -l >/dev/null 2>&1; then + if ssh-add -l 2>/dev/null | grep -q "ED25519"; then + info "SSH Agent check passed (Ed25519 key found after BWS setup)" + return 0 + fi + else + # BWS セットアップ後に ssh-add が失敗 + if [ -f "$env_file" ]; then + warn "SSH Agent communication failed after BWS setup." + warn "Please run: source $env_file" + error "SSH Agent communication failed. Run 'source $env_file' and try again." + else + error "SSH Agent communication failed after BWS setup." + fi fi fi @@ -117,23 +195,23 @@ load_bws_setup() { info "Loading setup script from BWS (secret ID: $secret_id)..." - # Check if bws command exists, if not try to install it + # bws が無ければインストールを試す if ! command -v bws >/dev/null 2>&1; then warn "bws command not found. Attempting to install..." install_bws || error "Failed to install bws CLI" fi - # Export token temporarily for bws command + # bws 用にトークンを一時 export export BWS_ACCESS_TOKEN="$token" - # Get the secret from BWS using secret ID + # 秘密 ID で BWS から secret を取得 local loader_script if command -v jq >/dev/null 2>&1; then loader_script=$(bws secret get "$secret_id" 2>&1 | jq -r '.value // empty') elif command -v python3 >/dev/null 2>&1; then loader_script=$(bws secret get "$secret_id" 2>&1 | python3 -c "import sys, json; print(json.load(sys.stdin).get('value', ''))" 2>/dev/null) else - # Fallback: try to extract value with grep/sed (fragile but works for simple JSON) + # フォールバック: grep/sed で value を取り出し(単純 JSON 用) loader_script=$(bws secret get "$secret_id" 2>&1 | grep -o '"value": "[^"]*"' | sed 's/"value": "//;s/"$//' | head -1) fi @@ -141,11 +219,11 @@ load_bws_setup() { error "Failed to retrieve secret from BWS. Check your token and secret ID." fi - # Execute the loader script + # ローダースクリプトを実行 info "Executing BWS setup script..." eval "$loader_script" - # Load agent environment if it was created + # 作成されていれば agent 用 env を読む local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}" if [ -f "$env_file" ]; then source "$env_file" @@ -153,6 +231,32 @@ load_bws_setup() { fi } +keychain_cmd() { + local keep_mode="$1" + info "Fetching SSH keys from BWS and running ssh-add..." + + local bws_token + bws_token=$(get_bws_token) || true + if [ -z "$bws_token" ]; then + echo -n "Enter BWS_ACCESS_TOKEN: " >&2 + read bws_token + if [ -z "$bws_token" ]; then + error "BWS_ACCESS_TOKEN is required" + fi + fi + + load_bws_setup "$bws_token" + + if [ "$keep_mode" != "true" ]; then + info "Removing key files from disk (use -k/--keep to keep)..." + rm -f ~/.ssh/id_rsa ~/.ssh/id_ecdsa ~/.ssh/id_ed25519 + fi + + info "SSH agent setup complete." + ssh-add -l + print_source_agent_hint +} + install_bws() { local arch arch=$(uname -m) @@ -163,7 +267,7 @@ install_bws() { mkdir -p "$bws_bin_dir" export PATH="$bws_bin_dir:$PATH" - # Determine architecture + # アーキテクチャ判定 case "$arch" in x86_64) arch="x86_64-unknown-linux-gnu" @@ -181,7 +285,7 @@ install_bws() { info "Downloading bws v${bws_version} for ${arch}..." - # Install dependencies if needed + # 必要なら unzip 等を入れる if ! command -v unzip >/dev/null 2>&1; then if command -v apt-get >/dev/null 2>&1; then sudo apt-get update -qq && sudo apt-get install -y unzip >/dev/null 2>&1 @@ -190,7 +294,7 @@ install_bws() { fi fi - # Download and extract + # ダウンロードして展開 local temp_zip="/tmp/${zip_name}" if command -v wget >/dev/null 2>&1; then wget -q "$url" -O "$temp_zip" || return 1 @@ -209,52 +313,50 @@ install_bws() { } derive_key() { - # Derive encryption key from Ed25519 signature - # Ed25519 signatures are deterministic, so same message = same signature + # Ed25519 署名から暗号鍵を導出(同じメッセージなら同じ署名で決定的) + # 優先順位: 1. SSH Agent, 2. 秘密鍵ファイル, 3. BWS (check_ssh_agent で処理) local temp_message=$(mktemp) local temp_sig=$(mktemp) local temp_pubkey=$(mktemp) + local temp_allowed=$(mktemp) - trap "rm -f '$temp_message' '$temp_sig' '$temp_pubkey'" EXIT + trap "rm -f '$temp_message' '$temp_sig' '$temp_pubkey' '$temp_allowed'" EXIT - # Create message to sign + # 署名するメッセージを作成 echo -n "$KEY_SEED_MESSAGE" > "$temp_message" - # Get public key from agent for the Ed25519 key - ssh-add -L 2>/dev/null | grep "ssh-ed25519" | head -1 > "$temp_pubkey" - - if [ ! -s "$temp_pubkey" ]; then - error "Could not extract Ed25519 public key from SSH Agent" + # 1. SSH Agent から Ed25519 の公開鍵を取得を試みる + if ssh-add -L 2>/dev/null | grep "ssh-ed25519" | head -1 | awk '{print $1 " " $2}' > "$temp_pubkey" 2>/dev/null && [ -s "$temp_pubkey" ]; then + # Agent から取得できた場合 + echo "key1 $(cat "$temp_pubkey")" > "$temp_allowed" + + # メッセージに署名(Agent経由) + if ssh-keygen -Y sign -f "$temp_allowed" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then + # 署名を抜き出してハッシュし 256bit 鍵に + cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64 + return 0 + fi fi - # Sign the message using ssh-keygen - # Note: ssh-keygen -Y sign requires the public key in "allowed signers" format - local temp_allowed=$(mktemp) - echo "key1 $(cat "$temp_pubkey")" > "$temp_allowed" - - # Sign the message - if ! ssh-keygen -Y sign -f "$temp_allowed" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then - # If -Y sign doesn't work (older ssh-keygen), try alternative method - # We'll use ssh-agent's signing capability directly through a workaround - - # Extract just the signature data using ssh-agent protocol - # Since we can't easily do that in pure bash, we'll use a deterministic approach - # based on the public key itself as a fallback - - warn "ssh-keygen -Y sign not available, using fallback key derivation" - - # Fallback: Use public key hash as seed - cat "$temp_pubkey" | sha256sum | head -c 64 - rm -f "$temp_allowed" - return 0 + # 2. SSH Agent がない場合、秘密鍵ファイルから直接署名を試みる + local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}" + if [ -f "$key_file" ]; then + # 公開鍵を抽出 + if ssh-keygen -y -f "$key_file" 2>/dev/null | awk '{print $1 " " $2}' > "$temp_pubkey" 2>/dev/null && [ -s "$temp_pubkey" ]; then + echo "key1 $(cat "$temp_pubkey")" > "$temp_allowed" + + # 秘密鍵ファイルを使って署名 + if ssh-keygen -Y sign -f "$key_file" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then + # 署名を抜き出してハッシュし 256bit 鍵に + cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64 + return 0 + fi + fi fi - rm -f "$temp_allowed" - - # Extract signature and hash it to create 256-bit key - # The signature file contains base64-encoded signature data - cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64 + # 3. どちらも失敗した場合、エラー(BWS は check_ssh_agent で処理される) + error "Could not derive key from SSH Agent or private key file. Please ensure SSH Agent is running with Ed25519 key, or set NB_KEY_FILE to a valid private key path." } encrypt_file() { @@ -265,9 +367,13 @@ encrypt_file() { error "Input file not found: $input" fi - check_ssh_agent + # 秘密鍵ファイルがある場合は check_ssh_agent をスキップ + local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}" + if [ ! -f "$key_file" ]; then + check_ssh_agent + fi - info "Deriving encryption key from SSH Agent..." + info "Deriving encryption key..." local key=$(derive_key) if [ -z "$key" ]; then @@ -276,8 +382,7 @@ encrypt_file() { info "Encrypting $input -> $output..." - # Use OpenSSL to encrypt with AES-256-CBC - # Pass the hex key directly to openssl + # OpenSSL で AES-256-CBC 暗号化(鍵は hex で渡す) openssl enc -aes-256-cbc -salt -pbkdf2 -in "$input" -out "$output" -pass "pass:$key" if [ $? -eq 0 ]; then @@ -296,10 +401,11 @@ decrypt_file() { error "Input file not found: $input" fi - # Force mode: skip SSH Agent check and directly load from BWS + # force: SSH Agent チェックを飛ばし BWS から直接読む if [ "$force_mode" = "true" ]; then info "Force mode: Loading setup script from BWS..." - local bws_token="${BWS_ACCESS_TOKEN:-}" + local bws_token + bws_token=$(get_bws_token) || true if [ -z "$bws_token" ]; then echo -n "Enter BWS_ACCESS_TOKEN: " >&2 read bws_token @@ -308,7 +414,7 @@ decrypt_file() { fi fi load_bws_setup "$bws_token" - # After BWS setup, check SSH Agent again + # BWS セットアップ後に agent を再確認 if [ -z "${SSH_AUTH_SOCK:-}" ] || [ ! -S "${SSH_AUTH_SOCK}" ]; then error "SSH Agent not available after BWS setup" fi @@ -316,10 +422,14 @@ decrypt_file() { error "Ed25519 key not found in SSH Agent after BWS setup" fi else - check_ssh_agent + # 秘密鍵ファイルがある場合は check_ssh_agent をスキップ + local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}" + if [ ! -f "$key_file" ]; then + check_ssh_agent + fi fi - info "Deriving decryption key from SSH Agent..." + info "Deriving decryption key..." local key=$(derive_key) if [ -z "$key" ]; then @@ -328,7 +438,7 @@ decrypt_file() { info "Decrypting $input -> $output..." - # Use OpenSSL to decrypt with AES-256-CBC + # OpenSSL で AES-256-CBC 復号 if openssl enc -aes-256-cbc -d -salt -pbkdf2 -in "$input" -out "$output" -pass "pass:$key" 2>/dev/null; then info "✅ Decryption successful: $output" else @@ -336,7 +446,61 @@ decrypt_file() { fi } -# Main command dispatcher +encrypt_text() { + local input_text="$1" + + # 秘密鍵ファイルがある場合は check_ssh_agent をスキップ + local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}" + if [ ! -f "$key_file" ]; then + check_ssh_agent + fi + + info "Deriving encryption key..." + local key=$(derive_key) + + if [ -z "$key" ]; then + error "Failed to derive encryption key" + fi + + # 標準入力から読み取る場合 + if [ "$input_text" = "-" ]; then + local temp_input=$(mktemp) + cat > "$temp_input" + input_text=$(cat "$temp_input") + rm -f "$temp_input" + fi + + # OpenSSL で暗号化して Base64 エンコード + echo -n "$input_text" | openssl enc -aes-256-cbc -salt -pbkdf2 -pass "pass:$key" | base64 -w 0 + echo "" # 改行を追加 +} + +decrypt_text() { + local input_text="$1" + + # 秘密鍵ファイルがある場合は check_ssh_agent をスキップ + local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}" + if [ ! -f "$key_file" ]; then + check_ssh_agent + fi + + info "Deriving decryption key..." + local key=$(derive_key) + + if [ -z "$key" ]; then + error "Failed to derive decryption key" + fi + + # 標準入力から読み取る場合 + if [ "$input_text" = "-" ]; then + input_text=$(cat) + fi + + # Base64 デコードして OpenSSL で復号 + echo -n "$input_text" | base64 -d 2>/dev/null | openssl enc -aes-256-cbc -d -salt -pbkdf2 -pass "pass:$key" 2>/dev/null || error "Decryption failed (wrong key or corrupted data)" +} + +# メインのコマンド分岐 main() { if [ $# -eq 0 ]; then usage @@ -345,24 +509,36 @@ main() { local command="$1" shift - # install-bws コマンドの場合は check_dependencies をスキップ - if [ "$command" != "install-bws" ] && [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then + # install-bws, keychain の場合は check_dependencies をスキップ + if [ "$command" != "install-bws" ] && [ "$command" != "keychain" ] && [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then check_dependencies fi case "$command" in - encrypt) + enc|enctext) + if [ $# -lt 1 ]; then + error "enc requires 1 argument: or '-' for stdin" + fi + encrypt_text "${1:-}" + ;; + dec|dectext) + if [ $# -lt 1 ]; then + error "dec requires 1 argument: or '-' for stdin" + fi + decrypt_text "${1:-}" + ;; + encfile|encrypt) if [ $# -ne 2 ]; then - error "encrypt requires 2 arguments: " + error "encfile requires 2 arguments: " fi encrypt_file "$1" "$2" ;; - decrypt) + decfile|decrypt) local force_mode=false local input="" local output="" - # Parse arguments + # 引数解析 while [ $# -gt 0 ]; do case "$1" in -f|--force) @@ -386,7 +562,7 @@ main() { done if [ -z "$input" ] || [ -z "$output" ]; then - error "decrypt requires 2 arguments: " + error "decfile requires 2 arguments: " fi decrypt_file "$force_mode" "$input" "$output" @@ -395,6 +571,24 @@ main() { check_ssh_agent echo "✅ All checks passed" ;; + keychain) + local keep_mode=false + while [ $# -gt 0 ]; do + case "$1" in + -k|--keep) + keep_mode=true + shift + ;; + -*) + error "Unknown option: $1" + ;; + *) + error "keychain accepts only -k/--keep" + ;; + esac + done + keychain_cmd "$keep_mode" + ;; install-bws) install_bws ;; diff --git a/nbmain.sh.enc b/nbmain.sh.enc index 28955551327d775aa89f028b41cb581c9771e2f0..0d42ed9c0db7c839ed0fe1b92203b79834f9f3a3 100644 GIT binary patch literal 1104 zcmV-W1h4y3VQh3|WM5y@&mVjtEWKL8b)&g@wJxT~<{klXj=-PCQiXScL{$k9h^zmO zR1-20=bgX&iR|mhUKfLd7>2DU03Y_T08HTZPA$-iISDpRQ$}O`JDF07aj7N$7{QU?J z(+d-2zHRU?P43hJbGJCmJ&CoW>d%R+s-=zuNa{xPY1mS~&i-XuR~k{L`N^dI4QIQA zibf`-7Da3zNkO}`z9&B}d5)s)1Jt2KrJ~-)ss3Kx^WszK6L1=#@qTU+F{UOvSk*{xS(9U0wwA>YP5@Bwp~x7BV+WXwTiPI-vq%E}@cH|E#~oe1%?F6X{erbpuA;8u|E61yI`(<-7HQ|DF3U9S z1a*N>j^6{+Ysnh?UsYGVz-$#r(3Ns8U}s0LCd}@Wk9Qy4<iE}PfoU@@)a=o!bnl54|#a%;sX-CA}}c2G+U z?w{$9G@h7CxaBB%NDG+bO!;&Qg|%k=e(HM|r=>seMor|GN*z|5CPG%em zF;NoWr)Joa$u;xlgJ#IPgw{w;gmCV8x};q=6P2FGF)|TQVgA*|7K;OFEB&KmvvkpGNK4TZO6MEq=MG%yD(91F>3a>yAq zYFdPZ>Q*&wqHOWGZkgfZVUO;+wncJ0Bmt~R0QW4lAwF%($7VVQBZieIu5Gg{Z$)$Vp0I1(lx&mWOCr{_cvY+vAzz26^@;V2wbNIhN$)tAn+s#L_;oXJur5j<9 z_Ufu`l;`}QFz)voe{XB47Tbww*V3ZRx3aT>c~L=Mz*ou zI!U!lwmn@lvnh8EP15Oi7nZde9Iq%1ywU*TCIUZ-NByrC0<;Ysjg5ZTRcW5VvVOuG WKBp77n9$DLegZC2cMq7QEq;$_l`TjB literal 1104 zcmV-W1h4y3VQh3|WM5xC#lLT$Yl*d1+M|7JVNw$R@m4Q}Uu(s2D|)!!kJl;EZ$myF z$+vSGI~Xy^Q)={w^se^2BWvazgXf&|0Vk*vmcHI)j7ZAQNe34`EXtmwJ=Z-k@5KWM zU&U59kQ*Ny;9z#R3JyR%^(Jxniv(%f7Xl^4o8YQ0f_`yJg0@yd-_?%1w4Y$f3*@|- z+4%<$sJbhXU30l7Kl{+`_^O{cwC?qpD z(nNZ5=$b2Uh9jcsIoZDF9#M;fd}s%a?H8=WfmOz6x?IR3>ai3-OF5yzc5NjMy_8Ky zX5ga_`eb~C?l>6ZGgNJksd?^fn~ZW`*Be#wq^9QtDbAbG`yWr<`lSks$t)#P4m=;Q zGCXy?@uj$#`xtXHbsb&nRvz61*Ywp;*-y{w6rZLvzJ^+cgWQ#2Cps0QR#FY1;F4H9 z7or{;?}kR0*NN3?2-#Np`B1Dd@SlnCN{oTb7f>y99SmadCM<^L#rl>(jir!@TjLcO z1HMu{XGnx`#hdz(OnRhfz2k~)v)|e5XwQH66dQV3-8P`_d|)w^Aybkh;xfR9R01YB z%b={l^sz68f4VQ~_SO)2V(MuRH{B4IOXJD|{OMpgdA5K9_!X3mY1>`xih~L|(gk3d!g?|kb-kWl{T0ZCz>JC#Vjj^ue0WdrP+nfnscMC56tQuupHf zzI{0I(aV)_D!b1&3%0?Z?=@LfP8%mkW#%pO}+glKXrt=X6E0iLb$dQ`kFLSkNFEn{)!=;Xs)-dfiO|c}v z>Z$k498L2YyJHvUiZJq|32IZ&Xi{>28335-&}|DTweZI`3V)Rz;yW3yKRoo%`p7E6asV{SeOaY}o#0lm1HO3l@3=CKjadZ(t(guI^ zsW&9Aov6h1cwyV!&eQ<9X@ei{HnRI6aDWu4J!%-(>`Xy2-1dz&2w<3sqt-;oLN{^M zHH^hcejObp>@XJ54{Jd$vGQ~35;QcUC^%ZZAC$K4h+H{Q@ WkYD6xi0S*u!dbF=kdo~0Rm{iiXeFZn