Files
nym/docker/localnet/localnet.sh
2026-02-17 08:46:42 +01:00

772 lines
24 KiB
Bash
Executable File

#!/bin/bash
set -ex
# Nym Localnet Orchestration Script
# Supports both Docker and Apple Container Runtime
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
IMAGE_NAME="nym-localnet:latest"
VOLUME_NAME="nym-localnet-data"
VOLUME_PATH="/tmp/nym-localnet-$$"
NYM_VOLUME_PATH="/tmp/nym-localnet-home-$$"
SUFFIX=${NYM_NODE_SUFFIX:-localnet}
# Detect container runtime: prefer Apple 'container' if available, fall back to docker
if command -v container &> /dev/null; then
RUNTIME="container"
HOST_INTERNAL="host.containers.internal"
else
RUNTIME="docker"
HOST_INTERNAL="host.docker.internal"
fi
# WireGuard: set to 1 only if you need VPN routing in localnet (requires NET_ADMIN and /dev/net/tun).
# Default 0: mixnet-only, no elevated capabilities required.
WIREGUARD_ENABLED=${WIREGUARD_ENABLED:-0}
# OpenTelemetry configuration
# Set OTEL_ENABLE=1 to enable OTel tracing on all nym-node instances.
# OTEL_ENDPOINT should point to the OTLP gRPC collector reachable from containers.
# When SigNoz runs in Docker (signoz-net), we route to its collector directly.
OTEL_ENABLE=${OTEL_ENABLE:-1}
if [ -z "${OTEL_ENDPOINT:-}" ]; then
SIGNOZ_NET=$(docker network ls --filter name=signoz-net --format '{{.Name}}' 2>/dev/null || true)
if [ "$RUNTIME" = "docker" ] && [ -n "$SIGNOZ_NET" ]; then
OTEL_ENDPOINT="http://signoz-otel-collector:4317"
OTEL_SIGNOZ_NET="$SIGNOZ_NET"
else
OTEL_ENDPOINT="http://${HOST_INTERNAL}:4317"
OTEL_SIGNOZ_NET=""
fi
fi
# Build OTel flags for nym-node run commands
otel_flags() {
if [ "$OTEL_ENABLE" = "1" ]; then
echo "--otel --otel-endpoint $OTEL_ENDPOINT"
fi
}
# WireGuard capability flags for gateway containers (only when WIREGUARD_ENABLED=1)
wireguard_cap_args() {
if [ "$WIREGUARD_ENABLED" = "1" ]; then
echo "--cap-add=NET_ADMIN --device /dev/net/tun"
fi
}
# --wireguard-enabled value for nym-node
wireguard_flag() {
[ "$WIREGUARD_ENABLED" = "1" ] && echo "true" || echo "false"
}
# Container names
INIT_CONTAINER="nym-localnet-init"
MIXNODE1_CONTAINER="nym-mixnode1"
MIXNODE2_CONTAINER="nym-mixnode2"
MIXNODE3_CONTAINER="nym-mixnode3"
GATEWAY_CONTAINER="nym-gateway"
GATEWAY2_CONTAINER="nym-gateway2"
REQUESTER_CONTAINER="nym-network-requester"
SOCKS5_CONTAINER="nym-socks5-client"
ALL_CONTAINERS=(
"$MIXNODE1_CONTAINER"
"$MIXNODE2_CONTAINER"
"$MIXNODE3_CONTAINER"
"$GATEWAY_CONTAINER"
"$GATEWAY2_CONTAINER"
"$REQUESTER_CONTAINER"
"$SOCKS5_CONTAINER"
)
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() {
echo -e "${BLUE}[INFO]${NC} $*"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $*"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*"
}
cleanup_host_state() {
log_info "Cleaning local nym-node state for suffix ${SUFFIX}"
for node in mix1 mix2 mix3 gateway gateway2; do
rm -rf "$HOME/.nym/nym-nodes/${node}-${SUFFIX}"
done
}
# Check prerequisites
check_prerequisites() {
if ! command -v docker &> /dev/null; then
log_error "Docker not found"
exit 1
fi
log_info "Using runtime: $RUNTIME"
}
# Build the Docker image
build_image() {
log_info "Building image: $IMAGE_NAME"
log_warn "This will take 15-30 minutes on first build..."
cd "$PROJECT_ROOT"
log_info "Building with Docker..."
if ! docker build \
-f "$SCRIPT_DIR/Dockerfile.localnet" \
-t "$IMAGE_NAME" \
"$PROJECT_ROOT"; then
log_error "Docker build failed"
exit 1
fi
# If using Apple container runtime, transfer image from Docker
if [ "$RUNTIME" = "container" ]; then
log_info "Transferring image to Apple container runtime..."
TEMP_IMAGE="/tmp/nym-localnet-image-$$.tar"
if ! docker save -o "$TEMP_IMAGE" "$IMAGE_NAME"; then
log_error "Failed to save Docker image"
exit 1
fi
if ! container image load --input "$TEMP_IMAGE"; then
rm -f "$TEMP_IMAGE"
log_error "Failed to load image into container runtime"
exit 1
fi
rm -f "$TEMP_IMAGE"
if ! container image inspect "$IMAGE_NAME" &>/dev/null; then
log_error "Image not found in container runtime after load"
exit 1
fi
fi
log_success "Image built and loaded: $IMAGE_NAME"
}
# Create shared volume directory
create_volume() {
log_info "Creating shared volume at: $VOLUME_PATH"
mkdir -p "$VOLUME_PATH"
chmod 777 "$VOLUME_PATH"
log_success "Volume created"
}
# Create shared nym home directory
create_nym_volume() {
log_info "Creating shared nym home volume at: $NYM_VOLUME_PATH"
mkdir -p "$NYM_VOLUME_PATH"
chmod 777 "$NYM_VOLUME_PATH"
log_success "Nym home volume created"
}
# Remove shared volume directory
remove_volume() {
if [ -d "$VOLUME_PATH" ]; then
log_info "Removing volume: $VOLUME_PATH"
rm -rf "$VOLUME_PATH"
log_success "Volume removed"
fi
if [ -d "$NYM_VOLUME_PATH" ]; then
log_info "Removing nym home volume: $NYM_VOLUME_PATH"
rm -rf "$NYM_VOLUME_PATH"
log_success "Nym home volume removed"
fi
}
# Network name
NETWORK_NAME="nym-localnet-network"
# Create container network
create_network() {
log_info "Creating container network: $NETWORK_NAME"
if $RUNTIME network create "$NETWORK_NAME" 2>/dev/null; then
log_success "Network created: $NETWORK_NAME"
else
log_info "Network $NETWORK_NAME already exists or creation failed"
fi
}
# Remove container network
remove_network() {
if $RUNTIME network list | grep -q "$NETWORK_NAME"; then
log_info "Removing network: $NETWORK_NAME"
$RUNTIME network rm "$NETWORK_NAME" 2>/dev/null || true
log_success "Network removed"
fi
}
# Start a mixnode
start_mixnode() {
local node_id=$1
local container_name=$2
log_info "Starting $container_name..."
# Calculate port numbers based on node_id
local mixnet_port="1000${node_id}"
local verloc_port="2000${node_id}"
local http_port="3000${node_id}"
local otel_args
otel_args=$(otel_flags)
$RUNTIME run \
--name "$container_name" \
-m 2G \
--network "$NETWORK_NAME" \
-p "${mixnet_port}:${mixnet_port}" \
-p "${verloc_port}:${verloc_port}" \
-p "${http_port}:${http_port}" \
-v "$VOLUME_PATH:/localnet" \
-v "$NYM_VOLUME_PATH:/root/.nym" \
-d \
-e "NYM_NODE_SUFFIX=$SUFFIX" \
"$IMAGE_NAME" \
sh -c '
CONTAINER_IP=$(hostname -i);
echo "Container IP: $CONTAINER_IP";
echo "Initializing mix'"${node_id}"'...";
nym-node run --id mix'"${node_id}"'-localnet --init-only \
--unsafe-disable-replay-protection \
--local \
--mixnet-bind-address=0.0.0.0:'"${mixnet_port}"' \
--verloc-bind-address=0.0.0.0:'"${verloc_port}"' \
--http-bind-address=0.0.0.0:'"${http_port}"' \
--http-access-token=lala \
--public-ips $CONTAINER_IP \
--output=json \
--bonding-information-output="/localnet/mix'"${node_id}"'.json";
echo "Waiting for network.json...";
while [ ! -f /localnet/network.json ]; do
sleep 2;
done;
echo "Starting mix'"${node_id}"'...";
exec nym-node '"${otel_args}"' run --id mix'"${node_id}"'-localnet --unsafe-disable-replay-protection --local
'
log_success "$container_name started"
}
# Start gateway
start_gateway() {
log_info "Starting $GATEWAY_CONTAINER..."
local otel_args wg_flag
otel_args=$(otel_flags)
wg_flag=$(wireguard_flag)
$RUNTIME run \
--name "$GATEWAY_CONTAINER" \
-m 2G \
$(wireguard_cap_args) \
--network "$NETWORK_NAME" \
-p 9000:9000 \
-p 10004:10004 \
-p 20004:20004 \
-p 30004:30004 \
-p 41264:41264 \
-p 51264:51264 \
-p 51822:51822/udp \
-v "$VOLUME_PATH:/localnet" \
-v "$NYM_VOLUME_PATH:/root/.nym" \
-d \
-e "NYM_NODE_SUFFIX=$SUFFIX" \
"$IMAGE_NAME" \
sh -c '
CONTAINER_IP=$(hostname -i);
echo "Container IP: $CONTAINER_IP";
echo "Initializing gateway...";
nym-node run --id gateway-localnet --init-only \
--unsafe-disable-replay-protection \
--local \
--mode entry-gateway \
--mode exit-gateway \
--mixnet-bind-address=0.0.0.0:10004 \
--entry-bind-address=0.0.0.0:9000 \
--verloc-bind-address=0.0.0.0:20004 \
--http-bind-address=0.0.0.0:30004 \
--http-access-token=lala \
--public-ips $CONTAINER_IP \
--lp-use-mock-ecash true \
--output=json \
--wireguard-enabled '"$wg_flag"' \
--bonding-information-output="/localnet/gateway.json";
echo "Waiting for network.json...";
while [ ! -f /localnet/network.json ]; do
sleep 2;
done;
echo "Starting gateway with LP listener (mock ecash)...";
exec nym-node '"${otel_args}"' run --id gateway-localnet --unsafe-disable-replay-protection --local --wireguard-enabled '"$wg_flag"' --lp-use-mock-ecash true
'
log_success "$GATEWAY_CONTAINER started"
# Wait for gateway to be ready
log_info "Waiting for gateway to listen on port 9000..."
local retries=0
local max_retries=30
while ! nc -z 127.0.0.1 9000 2>/dev/null; do
sleep 2
retries=$((retries + 1))
if [ $retries -ge $max_retries ]; then
log_error "Gateway failed to start on port 9000"
return 1
fi
done
log_success "Gateway is ready on port 9000"
}
# Start gateway2
start_gateway2() {
log_info "Starting $GATEWAY2_CONTAINER..."
local otel_args wg_flag
otel_args=$(otel_flags)
wg_flag=$(wireguard_flag)
$RUNTIME run \
--name "$GATEWAY2_CONTAINER" \
-m 2G \
$(wireguard_cap_args) \
--network "$NETWORK_NAME" \
-p 9001:9001 \
-p 10005:10005 \
-p 20005:20005 \
-p 30005:30005 \
-p 41265:41265 \
-p 51265:51265 \
-p 51823:51822/udp \
-v "$VOLUME_PATH:/localnet" \
-v "$NYM_VOLUME_PATH:/root/.nym" \
-d \
-e "NYM_NODE_SUFFIX=$SUFFIX" \
"$IMAGE_NAME" \
sh -c '
CONTAINER_IP=$(hostname -i);
echo "Container IP: $CONTAINER_IP";
echo "Initializing gateway2...";
nym-node run --id gateway2-localnet --init-only \
--unsafe-disable-replay-protection \
--local \
--mode entry-gateway \
--mode exit-gateway \
--mixnet-bind-address=0.0.0.0:10005 \
--entry-bind-address=0.0.0.0:9001 \
--verloc-bind-address=0.0.0.0:20005 \
--http-bind-address=0.0.0.0:30005 \
--http-access-token=lala \
--public-ips $CONTAINER_IP \
--lp-use-mock-ecash true \
--output=json \
--wireguard-enabled '"$wg_flag"' \
--bonding-information-output="/localnet/gateway2.json";
echo "Waiting for network.json...";
while [ ! -f /localnet/network.json ]; do
sleep 2;
done;
echo "Starting gateway2 with LP listener (mock ecash)...";
exec nym-node '"${otel_args}"' run --id gateway2-localnet --unsafe-disable-replay-protection --local --wireguard-enabled '"$wg_flag"' --lp-use-mock-ecash true
'
log_success "$GATEWAY2_CONTAINER started"
# Wait for gateway2 to be ready
log_info "Waiting for gateway2 to listen on port 9001..."
local retries=0
local max_retries=30
while ! nc -z 127.0.0.1 9001 2>/dev/null; do
sleep 2
retries=$((retries + 1))
if [ $retries -ge $max_retries ]; then
log_error "Gateway2 failed to start on port 9001"
return 1
fi
done
log_success "Gateway2 is ready on port 9001"
}
# Start network requester
start_network_requester() {
log_info "Starting $REQUESTER_CONTAINER..."
# Get gateway IP address (first IP only, in case container has multiple networks)
log_info "Getting gateway IP address..."
GATEWAY_IP=$($RUNTIME exec "$GATEWAY_CONTAINER" hostname -i | awk '{print $1}')
log_info "Gateway IP: $GATEWAY_IP"
$RUNTIME run \
--name "$REQUESTER_CONTAINER" \
--network "$NETWORK_NAME" \
-v "$VOLUME_PATH:/localnet" \
-v "$NYM_VOLUME_PATH:/root/.nym" \
-e "GATEWAY_IP=$GATEWAY_IP" \
-d \
"$IMAGE_NAME" \
sh -c '
while [ ! -f /localnet/network.json ]; do
echo "Waiting for network.json...";
sleep 2;
done;
while ! nc -z $GATEWAY_IP 9000 2>/dev/null; do
echo "Waiting for gateway on port 9000 ($GATEWAY_IP)...";
sleep 2;
done;
SUFFIX=$(date +%s);
nym-network-requester init \
--id "network-requester-$SUFFIX" \
--open-proxy=true \
--custom-mixnet /localnet/network.json \
--output=json > /localnet/network_requester.json;
exec nym-network-requester run \
--id "network-requester-$SUFFIX" \
--custom-mixnet /localnet/network.json
'
log_success "$REQUESTER_CONTAINER started"
}
# Start SOCKS5 client
start_socks5_client() {
log_info "Starting $SOCKS5_CONTAINER..."
$RUNTIME run \
--name "$SOCKS5_CONTAINER" \
--network "$NETWORK_NAME" \
-p 1080:1080 \
-v "$VOLUME_PATH:/localnet:ro" \
-v "$NYM_VOLUME_PATH:/root/.nym" \
-d \
"$IMAGE_NAME" \
sh -c '
while [ ! -f /localnet/network_requester.json ]; do
echo "Waiting for network requester...";
sleep 2;
done;
SUFFIX=$(date +%s);
PROVIDER=$(cat /localnet/network_requester.json | grep -o "\"client_address\":\"[^\"]*\"" | cut -d\" -f4);
if [ -z "$PROVIDER" ]; then
echo "Error: Could not extract provider address";
exit 1;
fi;
nym-socks5-client init \
--id "socks5-client-$SUFFIX" \
--provider "$PROVIDER" \
--custom-mixnet /localnet/network.json \
--no-cover;
exec nym-socks5-client run \
--id "socks5-client-$SUFFIX" \
--custom-mixnet /localnet/network.json \
--host 0.0.0.0
'
log_success "$SOCKS5_CONTAINER started"
# Wait for SOCKS5 to be ready
log_info "Waiting for SOCKS5 proxy on port 1080..."
sleep 5
local retries=0
local max_retries=15
while ! nc -z 127.0.0.1 1080 2>/dev/null; do
sleep 2
retries=$((retries + 1))
if [ $retries -ge $max_retries ]; then
log_warn "SOCKS5 proxy not responding on port 1080 yet"
return 0
fi
done
log_success "SOCKS5 proxy is ready on port 1080"
}
# Stop all containers
stop_containers() {
log_info "Stopping all containers..."
for container_name in "${ALL_CONTAINERS[@]}"; do
if $RUNTIME inspect "$container_name" &>/dev/null; then
log_info "Stopping $container_name"
$RUNTIME stop "$container_name" 2>/dev/null || true
$RUNTIME rm "$container_name" 2>/dev/null || true
fi
done
# Also clean up init container if it exists
$RUNTIME rm "$INIT_CONTAINER" 2>/dev/null || true
log_success "All containers stopped"
cleanup_host_state
remove_network
}
# Show $RUNTIME logs
show_logs() {
local container_name=${1:-}
if [ -z "$container_name" ]; then
# No container specified - launch tmux log viewer
log_info "Launching tmux log viewer for all containers..."
exec "$SCRIPT_DIR/localnet-logs.sh"
fi
# Show logs for specific container
if $RUNTIME inspect "$container_name" &>/dev/null; then
$RUNTIME logs -f "$container_name"
else
log_error "Container not found: $container_name"
log_info "Available containers:"
for name in "${ALL_CONTAINERS[@]}"; do
echo " - $name"
done
exit 1
fi
}
# Show container status
show_status() {
log_info "Container status:"
echo ""
for container_name in "${ALL_CONTAINERS[@]}"; do
if $RUNTIME inspect "$container_name" &>/dev/null; then
local status=$($RUNTIME inspect "$container_name" 2>/dev/null | grep -o '"Status":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
echo -e " ${GREEN}${NC} $container_name - $status"
else
echo -e " ${RED}${NC} $container_name - not running"
fi
done
echo ""
log_info "Port status:"
echo " Mixnet:"
for port in 10001 10002 10003 10004; do
if nc -z 127.0.0.1 $port 2>/dev/null; then
echo -e " ${GREEN}${NC} Port $port - listening"
else
echo -e " ${RED}${NC} Port $port - not listening"
fi
done
echo " Gateway:"
for port in 9000 30004; do
if nc -z 127.0.0.1 $port 2>/dev/null; then
echo -e " ${GREEN}${NC} Port $port - listening"
else
echo -e " ${RED}${NC} Port $port - not listening"
fi
done
echo " LP (Lewes Protocol):"
for port in 41264 51264; do
if nc -z 127.0.0.1 $port 2>/dev/null; then
echo -e " ${GREEN}${NC} Port $port - listening"
else
echo -e " ${RED}${NC} Port $port - not listening"
fi
done
echo " SOCKS5:"
if nc -z 127.0.0.1 1080 2>/dev/null; then
echo -e " ${GREEN}${NC} Port 1080 - listening"
else
echo -e " ${RED}${NC} Port 1080 - not listening"
fi
}
# Build network topology with container IPs
build_topology() {
log_info "Building network topology with container IPs..."
# Wait for all bonding JSON files to be created
log_info "Waiting for all nodes to complete initialization..."
for file in mix1.json mix2.json mix3.json gateway.json gateway2.json; do
while [ ! -f "$VOLUME_PATH/$file" ]; do
echo " Waiting for $file..."
sleep 1
done
log_success " $file created"
done
# Get container IPs (first IP only, containers may be on multiple networks)
log_info "Getting container IP addresses..."
MIX1_IP=$($RUNTIME exec "$MIXNODE1_CONTAINER" hostname -i | awk '{print $1}')
MIX2_IP=$($RUNTIME exec "$MIXNODE2_CONTAINER" hostname -i | awk '{print $1}')
MIX3_IP=$($RUNTIME exec "$MIXNODE3_CONTAINER" hostname -i | awk '{print $1}')
GATEWAY_IP=$($RUNTIME exec "$GATEWAY_CONTAINER" hostname -i | awk '{print $1}')
GATEWAY2_IP=$($RUNTIME exec "$GATEWAY2_CONTAINER" hostname -i | awk '{print $1}')
log_info "Container IPs:"
echo " mix1: $MIX1_IP"
echo " mix2: $MIX2_IP"
echo " mix3: $MIX3_IP"
echo " gateway: $GATEWAY_IP"
echo " gateway2: $GATEWAY2_IP"
# Run build_topology.py in a container with access to the volumes
$RUNTIME run \
--name "nym-localnet-topology-builder" \
--network "$NETWORK_NAME" \
-v "$VOLUME_PATH:/localnet" \
-v "$NYM_VOLUME_PATH:/root/.nym" \
--rm \
"$IMAGE_NAME" \
python3 /usr/local/bin/build_topology.py \
/localnet \
"$SUFFIX" \
"$MIX1_IP" \
"$MIX2_IP" \
"$MIX3_IP" \
"$GATEWAY_IP" \
"$GATEWAY2_IP"
# Verify network.json was created
if [ -f "$VOLUME_PATH/network.json" ]; then
log_success "Network topology created successfully"
else
log_error "Failed to create network topology"
exit 1
fi
}
# Start all services
start_all() {
log_info "Starting Nym Localnet..."
cleanup_host_state
create_network
create_volume
create_nym_volume
start_mixnode 1 "$MIXNODE1_CONTAINER"
start_mixnode 2 "$MIXNODE2_CONTAINER"
start_mixnode 3 "$MIXNODE3_CONTAINER"
start_gateway
start_gateway2
# Connect nym containers to SigNoz network for direct OTLP routing
if [ -n "${OTEL_SIGNOZ_NET:-}" ]; then
log_info "Connecting containers to SigNoz network ($OTEL_SIGNOZ_NET)..."
for c in "$MIXNODE1_CONTAINER" "$MIXNODE2_CONTAINER" "$MIXNODE3_CONTAINER" \
"$GATEWAY_CONTAINER" "$GATEWAY2_CONTAINER"; do
docker network connect "$OTEL_SIGNOZ_NET" "$c" 2>/dev/null && \
log_success " $c connected to $OTEL_SIGNOZ_NET" || true
done
fi
build_topology
# Configure networking for WireGuard VPN routing only when WIREGUARD_ENABLED=1
if [ "$WIREGUARD_ENABLED" = "1" ]; then
log_info "Configuring gateway networking (IP forwarding, NAT) for WireGuard..."
for gw in "$GATEWAY_CONTAINER" "$GATEWAY2_CONTAINER"; do
if $RUNTIME exec "$gw" sh -c "
echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null
iptables-legacy -t nat -A POSTROUTING -o eth0 -j MASQUERADE 2>/dev/null
" 2>/dev/null; then
log_success "Configured $gw"
else
log_warn "Could not configure NAT on $gw. WireGuard VPN routing may not work."
fi
done
fi
start_network_requester
start_socks5_client
echo ""
log_success "Nym Localnet is running!"
echo ""
echo "Test with:"
echo " curl -x socks5h://127.0.0.1:1080 https://nymtech.net"
echo ""
echo "View logs:"
echo " $0 logs # All containers in tmux"
echo " $0 logs gateway # Single container"
echo ""
echo "Stop:"
echo " $0 down"
echo ""
}
# Main command handler
main() {
check_prerequisites
local command=${1:-help}
shift || true
case "$command" in
build)
build_image
;;
up)
build_image
start_all
;;
start)
start_all
;;
down|stop)
stop_containers
remove_volume
;;
restart)
stop_containers
start_all
;;
logs)
show_logs "$@"
;;
status|ps)
show_status
;;
help|--help|-h)
cat <<EOF
Nym Localnet Orchestration Script
Usage: $0 <command> [options]
Commands:
build Build the localnet image
up Build image and start all services
start Start all services (requires built image)
down, stop Stop all services and clean up
restart Restart all services
logs [name] Show logs (no args = tmux overlay, with name = single container)
status, ps Show status of all containers and ports
help Show this help message
Examples:
$0 up # Build and start everything
$0 logs # View all logs in tmux overlay
$0 logs gateway # View gateway logs only
$0 status # Check what's running
$0 down # Stop and clean up
EOF
;;
*)
log_error "Unknown command: $command"
echo "Run '$0 help' for usage information"
exit 1
;;
esac
}
main "$@"