#!/bin/sh set -eu cd "$(dirname "$0")/.." CONFIG_FILE="${DEPLOY_CONFIG:-deploy-routeros.env}" if [ -f "$CONFIG_FILE" ]; then # shellcheck disable=SC1090 case "$CONFIG_FILE" in /*) . "$CONFIG_FILE" ;; *) . "./$CONFIG_FILE" ;; esac fi : "${ROUTER_HOST:=192.168.88.1}" : "${ROUTER_USER:=admin}" : "${ROUTER_PORT:=22}" : "${ROUTER_IDENTITY_FILE:=}" : "${ROUTER_ARCH:=auto}" : "${ROUTER_DISK:=disk1}" : "${ROUTER_SCP_DIR:=$ROUTER_DISK}" : "${CONTAINER_NAME:=suricata-ids}" : "${CONTAINER_IP:=172.31.255.2/30}" : "${CONTAINER_GATEWAY:=172.31.255.1}" : "${CONTAINER_SUBNET:=172.31.255.0/30}" : "${CONTAINER_BRIDGE:=br-ids}" : "${CONTAINER_VETH:=veth-ids}" : "${VLAN_ID:=100}" : "${TZSP_PORT:=37008}" : "${CONFIGURE_SNIFFER:=true}" : "${START_SNIFFER:=true}" : "${SURICATA_HOME_NET:=[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]}" : "${MONITORED_NETWORKS:=192.168.100.0/24}" : "${AUTO_BLOCK:=false}" : "${AUTO_BLOCK_MAX_SEVERITY:=1}" : "${BLOCK_TIMEOUT:=1h}" : "${UPDATE_RULES_ON_START:=false}" : "${CREATE_REST_USER:=false}" : "${ENABLE_WWW_SSL:=false}" : "${ROUTEROS_REST_USER:=suricata-api}" : "${ROUTEROS_REST_PASSWORD:=CHANGE_ME}" : "${ROUTEROS_VERIFY_TLS:=false}" : "${ROUTEROS_ADDRESS_LIST:=IDS-BLOCK}" : "${REPLACE_EXISTING:=true}" : "${KEEP_REMOTE_TAR:=true}" : "${ENGINE:=}" need() { command -v "$1" >/dev/null 2>&1 || { echo "$1 is required" >&2 exit 2 } } need ssh need scp case "$ROUTER_PORT" in *[!0-9]*|'') echo "ROUTER_PORT must be numeric" >&2; exit 2 ;; esac case "$VLAN_ID" in *[!0-9]*|'') echo "VLAN_ID must be numeric" >&2; exit 2 ;; esac case "$TZSP_PORT" in *[!0-9]*|'') echo "TZSP_PORT must be numeric" >&2; exit 2 ;; esac # Values are inserted into a RouterOS script inside quoted strings. Reject # characters that would require RouterOS-specific escaping. check_ros_value() { label="$1" value="$2" case "$value" in *'"'*|*'\\'*|*'$'*|*';'*|*'`'*) echo "$label contains a character not supported by the deploy script: $value" >&2 exit 3 ;; esac case "$value" in *'\n'*|*'\r'*) echo "$label contains a newline" >&2 exit 3 ;; esac } for pair in \ "ROUTER_DISK=$ROUTER_DISK" \ "CONTAINER_NAME=$CONTAINER_NAME" \ "CONTAINER_IP=$CONTAINER_IP" \ "CONTAINER_GATEWAY=$CONTAINER_GATEWAY" \ "CONTAINER_SUBNET=$CONTAINER_SUBNET" \ "CONTAINER_BRIDGE=$CONTAINER_BRIDGE" \ "CONTAINER_VETH=$CONTAINER_VETH" \ "SURICATA_HOME_NET=$SURICATA_HOME_NET" \ "MONITORED_NETWORKS=$MONITORED_NETWORKS" \ "BLOCK_TIMEOUT=$BLOCK_TIMEOUT" \ "ROUTEROS_REST_USER=$ROUTEROS_REST_USER" \ "ROUTEROS_REST_PASSWORD=$ROUTEROS_REST_PASSWORD" \ "ROUTEROS_ADDRESS_LIST=$ROUTEROS_ADDRESS_LIST" do check_ros_value "${pair%%=*}" "${pair#*=}" done SSH_TARGET="${ROUTER_USER}@${ROUTER_HOST}" ssh_run() { if [ -n "$ROUTER_IDENTITY_FILE" ]; then ssh -i "$ROUTER_IDENTITY_FILE" -p "$ROUTER_PORT" "$SSH_TARGET" "$1" else ssh -p "$ROUTER_PORT" "$SSH_TARGET" "$1" fi } scp_put() { src="$1" dst="$2" if [ -n "$ROUTER_IDENTITY_FILE" ]; then scp -i "$ROUTER_IDENTITY_FILE" -P "$ROUTER_PORT" "$src" "${SSH_TARGET}:$dst" else scp -P "$ROUTER_PORT" "$src" "${SSH_TARGET}:$dst" fi } echo "[deploy] RouterOS preflight" if ! ssh_run '/container/print' >/dev/null; then echo "RouterOS container menu is unavailable. Install the matching container package and enable container device-mode first." >&2 exit 4 fi if [ "$CONFIGURE_SNIFFER" = "true" ]; then if ! ssh_run '/tool/sniffer/print' >/dev/null; then echo "RouterOS sniffer is unavailable. Check device-mode sniffer=yes before deployment." >&2 exit 4 fi fi if [ "$ROUTER_ARCH" = "auto" ]; then echo "[deploy] detecting RouterOS architecture" RESOURCE="$(ssh_run '/system/resource/print without-paging')" DETECTED="$(printf '%s\n' "$RESOURCE" | awk -F: '/architecture-name/ {gsub(/[[:space:]]/, "", $2); print $2; exit}')" case "$DETECTED" in arm64) ROUTER_ARCH=arm64 ;; x86_64|x86|amd64) ROUTER_ARCH=amd64 ;; arm) ROUTER_ARCH=arm ;; *) echo "Unsupported or undetected RouterOS architecture: ${DETECTED:-unknown}" >&2 echo "Set ROUTER_ARCH manually to arm64, amd64 or arm." >&2 exit 4 ;; esac echo "[deploy] RouterOS architecture: $DETECTED -> image target $ROUTER_ARCH" fi case "$ROUTER_ARCH" in arm64|amd64|arm) ;; *) echo "ROUTER_ARCH must be auto, arm64, amd64 or arm" >&2; exit 4 ;; esac if [ -n "$ENGINE" ]; then ENGINE="$ENGINE" ./scripts/build-routeros.sh "$ROUTER_ARCH" else ./scripts/build-routeros.sh "$ROUTER_ARCH" fi LOCAL_TAR="build/routeros-suricata-tzsp-${ROUTER_ARCH}.tar" [ -f "$LOCAL_TAR" ] || { echo "Missing $LOCAL_TAR" >&2; exit 5; } DEPLOY_ID="$(date -u +%Y%m%d%H%M%S)" REMOTE_TAR_NAME="routeros-suricata-tzsp-${ROUTER_ARCH}-${DEPLOY_ID}.tar" REMOTE_TAR_ROS="${ROUTER_DISK}/${REMOTE_TAR_NAME}" REMOTE_TAR_SCP="${ROUTER_SCP_DIR%/}/${REMOTE_TAR_NAME}" LOCAL_RSC="build/deploy-${DEPLOY_ID}.rsc" REMOTE_RSC_NAME="deploy-${DEPLOY_ID}.rsc" REMOTE_RSC_ROS="${ROUTER_DISK}/${REMOTE_RSC_NAME}" REMOTE_RSC_SCP="${ROUTER_SCP_DIR%/}/${REMOTE_RSC_NAME}" ROOT_DIR="${ROUTER_DISK}/containers/${CONTAINER_NAME}-${DEPLOY_ID}" DATA_DIR="${ROUTER_DISK}/containers/${CONTAINER_NAME}-data" LOG_DIR="${ROUTER_DISK}/containers/${CONTAINER_NAME}-logs" RULES_DIR="${ROUTER_DISK}/containers/${CONTAINER_NAME}-rules" REST_URL="https://${CONTAINER_GATEWAY}" CONTAINER_IP_ONLY="${CONTAINER_IP%/*}" if [ "$CREATE_REST_USER" = "true" ] && [ "$ROUTEROS_REST_PASSWORD" = "CHANGE_ME" ]; then echo "CREATE_REST_USER=true requires a real ROUTEROS_REST_PASSWORD" >&2 exit 6 fi cat > "$LOCAL_RSC" <> "$LOCAL_RSC" <> "$LOCAL_RSC" <<'RSC' /ip/service/enable www-ssl RSC fi cat >> "$LOCAL_RSC" < 0) do={ RSC if [ "$REPLACE_EXISTING" = "true" ]; then cat >> "$LOCAL_RSC" <<'RSC' :if ([/container/get $old status] = "running") do={ /container/stop $old :delay 3s } /container/remove $old RSC else cat >> "$LOCAL_RSC" <<'RSC' :error "Container already exists and REPLACE_EXISTING=false" RSC fi cat >> "$LOCAL_RSC" <> "$LOCAL_RSC" <> "$LOCAL_RSC" <<'RSC' /tool/sniffer/start RSC fi fi cat >> "$LOCAL_RSC" < %s:%s\n' "$LOCAL_TAR" "$SSH_TARGET" "$REMOTE_TAR_SCP" scp_put "$LOCAL_TAR" "$REMOTE_TAR_SCP" printf '[deploy] uploading RouterOS deployment script: %s\n' "$REMOTE_RSC_SCP" scp_put "$LOCAL_RSC" "$REMOTE_RSC_SCP" echo "[deploy] importing configuration and starting container" ssh_run "/import file-name=\"${REMOTE_RSC_ROS}\"" if [ "$KEEP_REMOTE_TAR" != "true" ]; then ssh_run "/file/remove [find where name=\"${REMOTE_TAR_ROS}\"]" || true fi # The generated RSC can contain the REST password, so remove it after import. ssh_run "/file/remove [find where name=\"${REMOTE_RSC_ROS}\"]" || true echo "[deploy] final status" ssh_run "/container/print detail where name=\"${CONTAINER_NAME}\"" echo "[deploy] recent container log lines" ssh_run "/log/print without-paging where message~\"suricata|TZSP|IDS\"" || true echo "[deploy] done" echo "Container IP: ${CONTAINER_IP_ONLY}" echo "Dashboard: http://${CONTAINER_IP_ONLY}:8080/" echo "AUTO_BLOCK=${AUTO_BLOCK}; firewall DROP rules remain disabled by design."