#!/usr/bin/env bash # nc-make-jump.sh — generate the 3-thread jump pattern for cross-environment # data replay during a Cloverleaf migration. Matches Bryan's house pattern # (linux__out / windows__in / windows__out). # # Topology: # # OLD env (e.g. windows) NEW env (linux) # ── adt site (existing) ── ── server_jump site (new) ── ── adt site (cloned, unchanged) ── # ┌─────────────────────┐ ┌────────────────────────────┐ ┌─────────────────────────┐ # │ │ │ windows__in (NEW) │ │ │ # │ ├─→ existing dests │ │ tcpip-server, ISSERVER=1 │ │ listens on ORIG_PORT │ # │ └─→ linux__ │ ──TCP──→ │ PORT = jump_port │ │ (UNCHANGED — no route │ # │ out (NEW) │ │ │ internal route │ │ changes here) │ # │ tcpip-client │ │ ▼ │ │ │ # │ → jump_port │ │ windows__out (NEW) │ │ ▲ │ # └─────────────────────┘ │ tcpip-client │ │ │ │ # │ HOST=127.0.0.1 │ │ │ TCP localhost │ # │ PORT=ORIG_PORT ─────────┼───────┘ │ ──────────────────── │ # └────────────────────────────┘ │ # │ # (so OLD's existing inbound gets ONE new route; NEW's existing inbound is UNTOUCHED) # # Three new protocol blocks: # 1. `linux__out` → add to OLD env NetConfig (same process as original inbound). # 2. `windows__in` → add to NEW env server_jump/NetConfig. # 3. `windows__out` → add to NEW env server_jump/NetConfig. # # Plus one route-add snippet for OLD's existing inbound's DATAXLATE block. # # Tag = inbound thread name (auto-derived per Bryan's preference). # # Usage: # nc-make-jump.sh --inbound NAME --new-host HOST --jump-port PORT # [--inbound-host HOST] # default 127.0.0.1 # [--process-jump PROC] # process for NEW-side threads, default server_jump # [--encoding ENC] # default = ENCODING from existing inbound # [--out-prefix PREFIX] # write files instead of stdout set -u set -o pipefail NC_SELF="$0" LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)" NCP="$LIB_DIR/nc-parse.sh" die() { printf 'nc-make-jump: %s\n' "$*" >&2; exit 1; } NC="" INBOUND="" NEW_HOST="" JUMP_PORT="" INBOUND_HOST="127.0.0.1" PROC_JUMP="server_jump" ENC_OVERRIDE="" OUT_PREFIX="" while [ $# -gt 0 ]; do case "$1" in --inbound) shift; INBOUND="$1" ;; --new-host) shift; NEW_HOST="$1" ;; --jump-port) shift; JUMP_PORT="$1" ;; --inbound-host) shift; INBOUND_HOST="$1" ;; --process-jump) shift; PROC_JUMP="$1" ;; --encoding) shift; ENC_OVERRIDE="$1" ;; --out-prefix) shift; OUT_PREFIX="$1" ;; -h|--help) sed -n '2,50p' "$NC_SELF"; exit 0 ;; -*) die "unknown flag: $1" ;; *) [ -z "$NC" ] && NC="$1" || die "extra arg: $1" ;; esac shift done [ -n "$NC" ] || die "usage: see --help" [ -n "$INBOUND" ] || die "missing --inbound" [ -n "$NEW_HOST" ] || die "missing --new-host" [ -n "$JUMP_PORT" ] || die "missing --jump-port" [ -f "$NC" ] || die "not a file: $NC" # Read fields from the existing inbound T_PROCESS=$("$NCP" protocol-field "$NC" "$INBOUND" PROCESSNAME 2>/dev/null | head -1) [ -n "$T_PROCESS" ] || die "no such protocol in $NC: $INBOUND" T_ENC=$("$NCP" protocol-field "$NC" "$INBOUND" ENCODING 2>/dev/null | head -1) [ -z "$T_ENC" ] && T_ENC="ASCII" ENC="${ENC_OVERRIDE:-$T_ENC}" ORIG_PORT=$("$NCP" protocol-nested "$NC" "$INBOUND" PROTOCOL.PORT 2>/dev/null | head -1) # F-2 fix (2026-06-08): protocol-nested returns the literal string "{}" for # file/ICL inbounds whose PORT block is empty ({ PORT {} }). The old guard # only tested for empty string, so "{}" (non-empty) slipped through and the # generated thread carried "{ PORT {} }" — a broken TCP client with no port. # Treat empty, "{}", and any non-numeric value as "no port" and die clearly. case "$ORIG_PORT" in ''|'{}'|*[!0-9]*) die "could not read a numeric PROTOCOL.PORT for inbound '$INBOUND' (got: '${ORIG_PORT:-}'). Is it a TCP listener? File/ICL inbounds do not use this jump pattern." ;; esac # tag = the inbound name itself (Bryan's "auto-derived" preference) TAG="$INBOUND" OLD_OUT_NAME="linux_${TAG}_out" NEW_IN_NAME="windows_${TAG}_in" NEW_OUT_NAME="windows_${TAG}_out" # ───────────────────────────────────────────────────────────────────────────── # Common helpers — emit the standard set of fields that every protocol block # carries. Differences between the three threads are isolated below. # ───────────────────────────────────────────────────────────────────────────── emit_dataformat_passthrough() { cat <<'EOF' { DATAFORMAT { { FRLTYPE offlen } { OFFLEN { { LEN 0 } { OFF 0 } } } { TYPE frl } } } EOF } emit_edibatch_empty() { cat <<'EOF' { EDIBATCH { { IN_DATA { { TYPE {} } { VERSION {} } } } { OUT_DATA { { HEADER {} } { TRIGGER { { COUNT {} } { SCHEDULER {} } { TIMER {} } } } { TYPE {} } { VERSION {} } } } } } EOF } emit_errdbtps_default() { cat <<'EOF' { ERRDBTPS { { ERRTPSPROCS { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } } { RETRIES -1 } } } EOF } emit_proc_blocks_empty() { cat <<'EOF' { RECVCONTROL { { ACKCONTROL { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } } { EOMSG {} } { HOLDMSGS 0 } { MSGPRIO 5120 } } } { SAVECONTROL { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } } { TPS_INBOUND { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } } { TPS_OUTBOUND { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } } { TRACING 0 } EOF } emit_inner_protocol_tcp_client() { local host="$1" port="$2" cat <_out # Outbound TCP client, same process as the existing inbound. # No DATAXLATE (pass-through). Receives data via the route-add on the original inbound. # ───────────────────────────────────────────────────────────────────────────── emit_old_out() { printf 'protocol %s {\n' "$OLD_OUT_NAME" cat <_in # Inbound TCP server. Routes internally to windows__out (same site). # ───────────────────────────────────────────────────────────────────────────── emit_new_in() { printf 'protocol %s {\n' "$NEW_IN_NAME" cat <_out # Outbound TCP client, connects to localhost on the ORIGINAL inbound port. # Receives via internal route from windows__in. # ───────────────────────────────────────────────────────────────────────────── emit_new_out() { printf 'protocol %s {\n' "$NEW_OUT_NAME" cat < "${OUT_PREFIX}.old_out.tcl" emit_new_in > "${OUT_PREFIX}.new_in.tcl" emit_new_out > "${OUT_PREFIX}.new_out.tcl" emit_route_add > "${OUT_PREFIX}.route_add.tcl" printf '%s\n%s\n%s\n%s\n' \ "${OUT_PREFIX}.old_out.tcl" \ "${OUT_PREFIX}.new_in.tcl" \ "${OUT_PREFIX}.new_out.tcl" \ "${OUT_PREFIX}.route_add.tcl" else cat <