ohs to nginx

#!/usr/bin/env bash
# ohs-to-nginx.sh
# Convert Oracle HTTP Server (OHS/Apache httpd) configs into a single Nginx config.
#
# Input layout (in ./ohs_config by default):
#   HTTPD.conf, ssl.conf, mod_wl_ohs.conf
# Optional multi-instance files are supported via prefixes, e.g. OHS1_httpd.conf, OHS1_ssl.conf, OHS1_mod_wl_ohs.conf
# You can mix default (unprefixed) and prefixed sets. The prefix is everything before the first underscore.
#
# Output: ./nginx_from_ohs/nginx.conf
#
# What is translated:
# - Listen, ServerName, DocumentRoot, ErrorLog, CustomLog (best-effort)
# - SSL: SSLCertificateFile, SSLCertificateKeyFile, SSLProtocol, SSLCipherSuite
# - WebLogic upstreams from mod_wl_ohs: WebLogicCluster or WebLogicHost/WebLogicPort
# - <Location ...> SetHandler weblogic-handler → proxy_pass to upstream
# - Common timeout/keepalive hints (ConnectTimeoutSecs, KeepAliveEnabled) mapped to proxy_* directives
#
# Limitations (by design to keep this portable and dependency-free):
# - Complex conditional blocks, Includes, IfDefine, and per-virtualhost overrides are not fully modeled
# - Regex MatchExpression is mapped as a location ~ regex when found, otherwise path prefix
# - PathTrim/PathPrepend is applied in comments for manual adjustment (advanced rewrites not auto-generated)
# - Multiple VirtualHost blocks aren’t individually split; we emit one server per instance for :80 and :443
# - Use this as a starting point and review the generated nginx.conf before production use
#
set -euo pipefail

OHSDIR=${1:-"./ohs_config"}
OUTDIR=${2:-"./nginx_from_ohs"}
OUTFILE="$OUTDIR/nginx.conf"

mkdir -p "$OUTDIR"

# --- Helpers -----------------------------------------------------------------
trim() { sed -e 's/^\s*//' -e 's/\s*$//'; }
val() { awk '{sub(/^\s+/,""); print}'; }
# safe print (escape # for nginx comments only when needed)
pecho() { printf '%s\n' "$*"; }

# Map of instance → files
# For each file in $OHSDIR, determine instance prefix (part before first underscore) if present.
# Recognized basenames: httpd.conf, HTTPD.conf, ssl.conf, mod_wl_ohs.conf

declare -A HTTPD_FILE SSL_FILE WL_FILE

shopt -s nullglob nocaseglob
for f in "$OHSDIR"/*; do
  base=$(basename "$f")
  lower=${base,,}
  inst="default"
  namepart="$lower"
  if [[ "$lower" == *_httpd.conf || "$lower" == *_ssl.conf || "$lower" == *_mod_wl_ohs.conf ]]; then
    inst=${lower%%_*}
    namepart=${lower#*_}
  fi
  case "$namepart" in
    httpd.conf) HTTPD_FILE[$inst]="$f" ;;
    ssl.conf)   SSL_FILE[$inst]="$f" ;;
    mod_wl_ohs.conf) WL_FILE[$inst]="$f" ;;
    *) ;; # ignore other files
  esac
done
shopt -u nocaseglob

# Collect unique instances
instances=()
for k in "${!HTTPD_FILE[@]}"; do instances+=("$k"); done
for k in "${!SSL_FILE[@]}";   do [[ " ${instances[*]} " == *" $k "* ]] || instances+=("$k"); done
for k in "${!WL_FILE[@]}";    do [[ " ${instances[*]} " == *" $k "* ]] || instances+=("$k"); done

if [[ ${#instances[@]} -eq 0 ]]; then
  echo "No recognizable OHS config files found in $OHSDIR" >&2
  exit 1
fi

# Data structures: per-instance associative arrays in bash via namerefs
parse_httpd() {
  local inst=$1 file=$2
  # Defaults
  eval "LISTEN_$inst='' SERVERNAME_$inst='' DOCROOT_$inst='' ERRLOG_$inst='' ACCLOG_$inst=''"
  [[ -f "$file" ]] || return 0
  while IFS= read -r line; do
    line=${line%%#*}
    line=$(echo "$line" | trim)
    [[ -z "$line" ]] && continue
    key=${line%% *}
    rest=${line#* }
    case "${key,,}" in
      listen)
        # Could be ip:port or port
        port=$(echo "$rest" | awk -F: '{print $NF}' | tr -d ' ') 
        eval "LISTEN_$inst='${port}'" ;;
      servername)
        eval "SERVERNAME_$inst='$(echo "$rest" | val)'" ;;
      documentroot)
        eval "DOCROOT_$inst='$(echo "$rest" | val | sed "s/\"//g")'" ;;
      errorlog)
        eval "ERRLOG_$inst='$(echo "$rest" | val)'" ;;
      customlog)
        # CustomLog path format
        path=$(echo "$rest" | awk '{print $1}')
        eval "ACCLOG_$inst='${path}'" ;;
    esac
  done < "$file"
}

parse_ssl() {
  local inst=$1 file=$2
  eval "SSL_ON_$inst=0 SSL_CERT_$inst='' SSL_KEY_$inst='' SSL_CHAIN_$inst='' SSL_PROTOCOLS_$inst='' SSL_CIPHERS_$inst=''"
  [[ -f "$file" ]] || return 0
  eval "SSL_ON_$inst=1"
  while IFS= read -r line; do
    line=${line%%#*}; line=$(echo "$line" | trim); [[ -z "$line" ]] && continue
    key=${line%% *}; rest=${line#* }
    case "${key,,}" in
      sslcertificatefile)  eval "SSL_CERT_$inst='$(echo "$rest" | val)'" ;;
      sslcertificatekeyfile) eval "SSL_KEY_$inst='$(echo "$rest" | val)'" ;;
      sslcertificatechainfile) eval "SSL_CHAIN_$inst='$(echo "$rest" | val)'" ;;
      sslprotocol)         eval "SSL_PROTOCOLS_$inst='$(echo "$rest" | val)'" ;;
      sslciphersuite)      eval "SSL_CIPHERS_$inst='$(echo "$rest" | val)'" ;;
    esac
  done < "$file"
}

# Structures to hold mod_wl info per instance
# WL_MODE_$inst = cluster|single|none
# WL_CLUSTER_$inst = "host1:port1,host2:port2"
# WL_SINGLE_HOST_$inst, WL_SINGLE_PORT_$inst
# WL_LOCATIONS_$inst = file path to a temp file listing location mappings: path<TAB>type<TAB>pattern
# WL_TIMEOUT_$inst, WL_KEEPALIVE_$inst

parse_modwl() {
  local inst=$1 file=$2
  eval "WL_MODE_$inst='none' WL_CLUSTER_$inst='' WL_SINGLE_HOST_$inst='' WL_SINGLE_PORT_$inst='' WL_TIMEOUT_$inst='' WL_KEEPALIVE_$inst=''"
  local loc_tmp
  loc_tmp=$(mktemp)
  eval "WL_LOCATIONS_$inst='$loc_tmp'"
  [[ -f "$file" ]] || return 0

  # First, fetch global cluster/single definitions (outside <Location>)
  while IFS= read -r line; do
    line=${line%%#*}; line=$(echo "$line" | trim); [[ -z "$line" ]] && continue
    key=${line%% *}; rest=${line#* }
    case "${key,,}" in
      weblogiccluster)
        eval "WL_MODE_$inst='cluster' WL_CLUSTER_$inst='$(echo "$rest" | val | tr -d '\"')'" ;;
      weblogichost)
        eval "WL_SINGLE_HOST_$inst='$(echo "$rest" | val | tr -d '\"')'" ;;
      weblogicport)
        eval "WL_SINGLE_PORT_$inst='$(echo "$rest" | val | tr -d '\"')'" ;;
      connecttimeoutsecs)
        eval "WL_TIMEOUT_$inst='$(echo "$rest" | val)'" ;;
      keepaliveenabled)
        eval "WL_KEEPALIVE_$inst='$(echo "$rest" | val | tr '[:upper:]' '[:lower:]')'" ;;
    esac
  done < "$file"

  # Determine mode if only host/port given
  eval "mode=\${WL_MODE_$inst} shost=\${WL_SINGLE_HOST_$inst} sport=\${WL_SINGLE_PORT_$inst}"
  if [[ "$mode" == "none" && -n "$shost" && -n "$sport" ]]; then
    eval "WL_MODE_$inst='single'"
  fi

  # Parse <Location ..> blocks that set weblogic-handler
  awk -v out="$loc_tmp" '
    BEGIN{inloc=0; path=""; matchExpr=""}
    {
      line=$0; sub(/#.*/, "", line);
      gsub(/^\s+|\s+$/, "", line);
      if (line ~ /^<Location[[:space:]]+/) {
        inloc=1;
        path=line;
        sub(/^<Location[[:space:]]+/, "", path);
        sub(/>$/, "", path);
        next;
      }
      if (inloc==1 && line ~ /^<\/Location>/) {
        # emit if weblogic-handler was seen for this block
        if (seen){
          if (matchExpr!="") {printf("%s\tregex\t%s\n", path, matchExpr) >> out}
          else {printf("%s\tpath\t%s\n", path, path) >> out}
        }
        inloc=0; seen=0; matchExpr=""; path="";
        next;
      }
      if (inloc==1) {
        if (tolower(line) ~ /^sethandler[[:space:]]+weblogic-handler/) { seen=1 }
        if (tolower(line) ~ /^matchexpression[[:space:]]+/) {
          m=line; sub(/^[^ ]+[ ]+/, "", m); matchExpr=m;
        }
        # ignore other directives inside for now
      }
    }
  ' "$file"
}

# Parse all instances
for inst in "${instances[@]}"; do
  parse_httpd "$inst" "${HTTPD_FILE[$inst]:-}"
  parse_ssl   "$inst" "${SSL_FILE[$inst]:-}"
  parse_modwl "$inst" "${WL_FILE[$inst]:-}"
done

# Generate nginx.conf
{
  pecho "# Generated by ohs-to-nginx.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)"
  pecho "# Source directory: $OHSDIR"
  echo
  pecho "user  nginx;"
  pecho "worker_processes  auto;"
  echo
  pecho "events { worker_connections  10240; }"
  echo
  pecho "http {"
  pecho "    include       mime.types;"
  pecho "    default_type  application/octet-stream;"
  pecho "    sendfile      on;"
  pecho "    tcp_nopush    on;"
  pecho "    tcp_nodelay   on;"
  pecho "    keepalive_timeout  65;"
  echo

  for inst in "${instances[@]}"; do
    iname=${inst}
    [[ "$iname" == "default" ]] && iname="ohs"

    # Upstream definition from WL
    eval "mode=\${WL_MODE_$inst} cluster=\${WL_CLUSTER_$inst} shost=\${WL_SINGLE_HOST_$inst} sport=\${WL_SINGLE_PORT_$inst}"
    if [[ "$mode" != "none" ]]; then
      upname="${iname}_weblogic"
      pecho "    upstream $upname {"
      if [[ "$mode" == "cluster" ]]; then
        IFS=',' read -r -a parts <<< "$cluster"
        for p in "${parts[@]}"; do
          hp=$(echo "$p" | trim)
          [[ -z "$hp" ]] && continue
          host=${hp%%:*}; port=${hp##*:}
          pecho "        server $host:$port;"
        done
      else
        pecho "        server $shost:$sport;"
      fi
      pecho "        keepalive 64;"
      pecho "    }"
      echo
    fi

    # Gather general settings
    eval "port=\${LISTEN_$inst} sname=\${SERVERNAME_$inst} docroot=\${DOCROOT_$inst}"
    [[ -z "$port" ]] && port=80

    # Access/Error log comments
    eval "elog=\${ERRLOG_$inst} alog=\${ACCLOG_$inst}"

    # HTTP server block (port 80)
    pecho "    server {"
    pecho "        listen 80;"
    [[ -n "$sname" ]] && pecho "        server_name $sname;" || pecho "        # server_name _;"
    [[ -n "$docroot" ]] && pecho "        root $docroot;" || pecho "        # root /usr/share/nginx/html;"
    [[ -n "$elog" ]] && pecho "        # error_log  $elog;"
    [[ -n "$alog" ]] && pecho "        # access_log $alog;"

    # Location mappings from mod_wl_ohs
    if [[ "$mode" != "none" ]]; then
      eval "locfile=\${WL_LOCATIONS_$inst} timeout=\${WL_TIMEOUT_$inst} keepal=\${WL_KEEPALIVE_$inst}"
      proxy_tout=${timeout:-60}
      [[ "${keepal:-on}" == "on" ]] && ka_comment="#" || ka_comment=""
      while IFS=$'\t' read -r lpath ltype lpat; do
        [[ -z "$lpath" ]] && continue
        if [[ "$ltype" == "regex" ]]; then
          pecho "        location ~ $lpat {"
        else
          pecho "        location ${lpath} {"
        fi
        pecho "            proxy_set_header Host \$host;"
        pecho "            proxy_set_header X-Real-IP \$remote_addr;"
        pecho "            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;"
        pecho "            proxy_set_header X-Forwarded-Proto \$scheme;"
        pecho "            proxy_http_version 1.1;"
        pecho "            ${ka_comment}proxy_set_header Connection \"\$connection_upgrade\"; # keepalive hint"
        pecho "            proxy_connect_timeout ${proxy_tout}s;"
        pecho "            proxy_read_timeout ${proxy_tout}s;"
        pecho "            proxy_send_timeout ${proxy_tout}s;"
        pecho "            proxy_pass http://${iname}_weblogic;"
        pecho "            # NOTE: If OHS used PathTrim/Prepend here, consider adding rewrite directives accordingly."
        pecho "        }"
      done < "${locfile:-/dev/null}"
    else
      pecho "        # No mod_wl_ohs upstream detected for instance '$inst'"
    fi

    pecho "    }" # end server 80
    echo

    # HTTPS server block if SSL present
    eval "ssl_on=\${SSL_ON_$inst} cert=\${SSL_CERT_$inst} key=\${SSL_KEY_$inst} chain=\${SSL_CHAIN_$inst} protos=\${SSL_PROTOCOLS_$inst} ciphers=\${SSL_CIPHERS_$inst}"
    if [[ "$ssl_on" == "1" ]]; then
      pecho "    server {"
      pecho "        listen 443 ssl;"
      [[ -n "$sname" ]] && pecho "        server_name $sname;"
      [[ -n "$docroot" ]] && pecho "        root $docroot;"
      if [[ -n "$cert" && -n "$key" ]]; then
        pecho "        ssl_certificate     $cert;"
        pecho "        ssl_certificate_key $key;"
      else
        pecho "        # ssl_certificate /path/to/fullchain.pem;"
        pecho "        # ssl_certificate_key /path/to/privkey.pem;"
      fi
      [[ -n "$chain" ]] && pecho "        # (chain file is typically bundled in ssl_certificate)  # $chain"
      # Map Apache SSLProtocol → nginx ssl_protocols (best-effort)
      if [[ -n "$protos" ]]; then
        # Example: +TLSv1.2 +TLSv1.3 -TLSv1 -SSLv3
        # We will include only listed positives
        wanted=$(echo "$protos" | grep -Eo 'TLSv1(\.[0-9])?' | tr '\n' ' ' | xargs)
        [[ -n "$wanted" ]] && pecho "        ssl_protocols $wanted;"
      fi
      [[ -n "$ciphers" ]] && pecho "        ssl_ciphers  $ciphers;"
      pecho "        ssl_prefer_server_ciphers on;"

      if [[ "$mode" != "none" ]]; then
        eval "locfile=\${WL_LOCATIONS_$inst} timeout=\${WL_TIMEOUT_$inst} keepal=\${WL_KEEPALIVE_$inst}"
        proxy_tout=${timeout:-60}
        [[ "${keepal:-on}" == "on" ]] && ka_comment="#" || ka_comment=""
        while IFS=$'\t' read -r lpath ltype lpat; do
          [[ -z "$lpath" ]] && continue
          if [[ "$ltype" == "regex" ]]; then
            pecho "        location ~ $lpat {"
          else
            pecho "        location ${lpath} {"
          fi
          pecho "            proxy_set_header Host \$host;"
          pecho "            proxy_set_header X-Real-IP \$remote_addr;"
          pecho "            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;"
          pecho "            proxy_set_header X-Forwarded-Proto \$scheme;"
          pecho "            proxy_http_version 1.1;"
          pecho "            ${ka_comment}proxy_set_header Connection \"\$connection_upgrade\"; # keepalive hint"
          pecho "            proxy_connect_timeout ${proxy_tout}s;"
          pecho "            proxy_read_timeout ${proxy_tout}s;"
          pecho "            proxy_send_timeout ${proxy_tout}s;"
          pecho "            proxy_pass http://${iname}_weblogic;"
          pecho "        }"
        done < "${locfile:-/dev/null}"
      fi

      pecho "    }" # end server 443
      echo
    fi

    # Cleanup tmp loc file
    eval "locfile=\${WL_LOCATIONS_$inst}"
    [[ -n "${locfile:-}" && -f "$locfile" ]] && rm -f "$locfile"
  done

  pecho "}" # end http
} > "$OUTFILE"

echo "Wrote: $OUTFILE"

# Also emit a brief README next to the file with guidance
cat > "$OUTDIR/README.txt" <<'EOT'
This nginx.conf was auto-generated from Oracle HTTP Server configs.

Checklist before going live:
1) Review upstream servers and ports under `upstream <instance>_weblogic`.
2) Confirm `server_name`, `root`, and logging directives for your environment.
3) If OHS used PathTrim/PathPrepend/MatchExpression, verify location blocks and add `rewrite` rules as needed.
4) Validate TLS cert/key paths and combine chain into the certificate file if required.
5) Test with: nginx -t && nginx -s reload (or your system’s service manager).

Regenerate anytime by re-running ohs-to-nginx.sh with your ohs_config directory.
EOT
Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments