#!/usr/bin/bash

#DESC

###
#ENV
###

VERSION="1.7"
OSB=rh
MAJOR=8
OS=$OSB$MAJOR
HOSTNAME=$(hostname)
# Use the RPM runtime on RHEL 8 instead of its older system python3.
if [ -x /usr/bin/python3.9 ]; then MYKOS_PYTHON=/usr/bin/python3.9; else MYKOS_PYTHON=python3; fi
[ -f /etc/mykos/pos.env ] && . /etc/mykos/pos.env
REPOSERVER=${REPOSERVER:-mykol.com}
NETDEV=$(ip route get 8.8.8.8 | awk -- '{printf $5}')
MERAKI_API_KEY=${MERAKI_API_KEY:-}
MERAKI_ORG_ID=${MERAKI_ORG_ID:-}
# Only the fixed host entry point selects host VPN setup.
MYKOS_VPN_HOST=0
BUILDDATE=$(date +%Y%m%d)
BUILDROOT=/var/tmp/containers/$BUILDDATE/$KSNAME
mkdir -p $BUILDROOT/docker
HOST_USER=`whoami`
#[ "$HOST_USER" != "root" ] && echo "You must be root or in sudoers to run $0." && exit 1
export ROOTDIR="$(pwd)"
[ -f .shopcode ] && SHOPCODE="`cat .shopcode`"
[ -f .$SHOPCODE.container ] && CONTAINER=`cat .$SHOPCODE.container`
[ -f .container ] && CONTAINER=`cat .container`
LGGRN='\033[1;32m'
BLUE='\033[1;34m'
TEAL='\033[38;5;31m'
GREY='\033[0;37m'
NC='\033[0m'


###
#FUNCS
###

check_image_builder() {
  case "$OS" in
    rh10|rocky8|rocky9|rocky10|alma8|alma9|alma10|oracle8|oracle9|oracle10|centos8|centos9|centos10|fedora43|fedora44)
      command -v ksvalidator >/dev/null 2>&1 || { echo 'Install pykickstart on the builder.' >&2; return 1; }
      # Validate the temporary copy with the installed parser, independent of host release.
      create_ks || return 1
      ksvalidator "/var/tmp/$OS.ks" || return 1 ;;
  esac
}

create_ks() {
"$MYKOS_PYTHON" ./prepare_ks.py "ks/$OS.ks" "/var/tmp/$OS.ks"
}

create_dockerfile() {
local os_version os_vendor update_step update_tmp
case "$OS" in
  rocky*) os_vendor='Rocky Linux'; update_step= ;;
  alma*) os_vendor='AlmaLinux'; update_step= ;;
  oracle*) os_vendor='Oracle Linux'; update_step= ;;
  centos*) os_vendor='CentOS Stream'; update_step= ;;
  fedora*) os_vendor='Fedora'; update_step= ;;
  *) os_vendor=RHEL
     # Fetch and validate on the builder; minimal guests need no download client.
     update_tmp=$(mktemp .mykos-updateos.XXXXXX) || return 1
     if ! curl --fail --show-error --location --retry 2 --connect-timeout 15 --max-time 120 "${MYKOS_REPO_ORIGIN:-https://$REPOSERVER}/updateos/updateos" -o "$update_tmp" ||
        ! test -s "$update_tmp" || ! bash -n "$update_tmp"; then
       rm -f -- "$update_tmp"
       echo 'Could not stage the updateos script; Podman build was not started.' >&2
       return 1
     fi
     if ! chmod 0755 "$update_tmp" || ! mv -f -- "$update_tmp" updateos; then
       rm -f -- "$update_tmp"
       return 1
     fi
     update_step='COPY updateos /usr/bin/updateos' ;;
esac
os_version=$("$MYKOS_PYTHON" "$ROOTDIR/image_version.py" "$OS-docker.tar.xz") || return 1
cat << EOF > Dockerfile
FROM scratch
ADD $OS-docker.tar.xz /
LABEL io.mykos.os="$OS"
LABEL io.mykos.os.version="$os_version"
LABEL org.label-schema.schema-version="1.0" \
    org.label-schema.name="$OS Base Image" \
    org.label-schema.vendor="$os_vendor" \
    org.label-schema.license="GPLv2" \
    org.label-schema.build-date="20261003"
MAINTAINER “Mike Green" <mgreen@teleflora.com>
ENV container $OS-docker
VOLUME /sys/fs/cgroup
$update_step
CMD ["/usr/bin/bash"]
EOF
}

stop_container() {
  if "$MYKOS_PYTHON" ./vm info "$SHOPCODE" >/dev/null 2>&1; then
    "$MYKOS_PYTHON" ./vm stop "$SHOPCODE"
    return $?
  fi
  /usr/local/sbin/mykos-linux stop "$SHOPCODE"

}

stage_image() {
  SHOPCODE=$(cat .shopcode) && POSTYPE=$(cat .$SHOPCODE.postype) && OS=$(cat .$SHOPCODE.imagetype)
  case "$OS" in win*|wns*|mac*|ubuntu*) "$MYKOS_PYTHON" ./vm start "$SHOPCODE" "$OS"; return $? ;; esac
  if podman container exists "$SHOPCODE.$(hostname)"; then
    /usr/local/sbin/mykos-linux start "$SHOPCODE"
    return $?
  fi
  restore=no
  if [ "$("$MYKOS_PYTHON" -c 'import sys; sys.path.insert(0,"/usr/local/sbin"); import mykos_profiles; print(mykos_profiles.has_backup(sys.argv[1]))' "$SHOPCODE")" = True ]; then
    read -r -p 'Use the configured application backup? (y/n): ' ans
    [ "$ans" = y ] && restore=yes
  fi
  /usr/local/sbin/mykos-stage-pos "$SHOPCODE" "$restore" "$OS" || return $?
  CONTAINER=$(cat ".$SHOPCODE.container")
  IP=$(/usr/local/sbin/mykos-linux ip "$SHOPCODE")

}

create-image() {
  local image_selection
  printf '%s\n' \
    'OS choices:' \
    '  All EL images   all' \
    '  RedHat          rh7 rh8 rh9 rh10' \
    '  Rocky Linux     rocky8 rocky9 rocky10' \
    '  AlmaLinux       alma8 alma9 alma10' \
    '  Oracle Linux    oracle8 oracle9 oracle10' \
    '  CentOS Stream   centos8 centos9 centos10' \
    '  Fedora          fedora43 fedora44' \
    '  Windows         win11 win10 win8e win7u winxp winme win2k win98' \
    '  Windows Server  wns2025 wns2022 wns2019 wns2016' \
    '  macOS           mac15 mac14 mac13 mac12' \
    '  Ubuntu          ubuntu20.04 ubuntu22.04 ubuntu24.04' \
    '  SQL Server      mssql2025 mssql2022' && printf 'OS: ' && read -r image_selection || return 1
  if [ "$image_selection" = all ]; then
    build-all-images
  else
    OS="$image_selection"
    build-image "$OS"
  fi
}

build-image() (
  local OS="$1"
  case "$OS" in
    win*|wns*|mac*|ubuntu*)
      renice -n 1 -p "$BASHPID" >/dev/null || return 1
      "$MYKOS_PYTHON" ./vm create "$OS"; return $? ;;
  esac
  case "$OS" in rh7|rh8|rh9|rh10|oracle8|oracle9|oracle10|centos8|centos9|centos10|fedora43|fedora44|rocky8|rocky9|rocky10|alma8|alma9|alma10|mssql2025|mssql2022) ;; *) echo 'Invalid OS selection' >&2; return 1 ;; esac
  renice -n 1 -p "$BASHPID" >/dev/null || return 1
  if podman image exists "$OS"; then echo "Image already built: $OS"; return 0; fi
  check_image_builder || return 1
  rm -f "/var/tmp/$OS-docker.tar.xz" || return 1
  create_ks || return 1
  if ! "$MYKOS_PYTHON" ./build_rootfs.py "$OS" "/var/tmp/$OS.ks"; then
    echo "Image creation failed; see the installer diagnostics directory printed above. Podman build was not started." >&2
    return 1
  fi
  [ -s "/var/tmp/$OS-docker.tar.xz" ] || { echo 'Image creator did not produce a rootfs archive' >&2; return 1; }
  mkdir -p "$BUILDROOT/docker" || return 1
  mv "/var/tmp/$OS-docker.tar.xz" "$BUILDROOT/docker" || return 1
  (
    cd "$BUILDROOT/docker" || exit 1
    create_dockerfile && podman build --tag "$OS" .
  ) || return 1
  CONTAINER=$(/usr/local/sbin/mykos-native-image-start "$OS") || return 1
  podman exec "$CONTAINER" /bin/sh -c 'if [ -f /etc/yum/pluginconf.d/subscription-manager.conf ]; then sed -i "s/enabled=1/enabled=0/g" /etc/yum/pluginconf.d/subscription-manager.conf; fi' || return 1
  podman exec "$CONTAINER" systemctl restart sshd || return 1
  podman commit "$CONTAINER" "$OS:latest"
)

build-all-images() {
  local image completed=0
  local images=(rh7 rh8 rh9 rh10 rocky8 rocky9 rocky10 alma8 alma9 alma10 oracle8 oracle9 oracle10 centos8 centos9 centos10)
  for image in "${images[@]}"; do
    printf '\n[%s/%s] Build EL image: %s\n' "$((completed+1))" "${#images[@]}" "$image"
    if build-image "$image"; then
      completed=$((completed+1))
    else
      printf 'Build stopped at %s; remaining images were not started.\n' "$image" >&2
      printf 'Built or reused: %s/%s\n' "$completed" "${#images[@]}" >&2
      return 1
    fi
  done
  printf '\nAll EL images built or reused: %s/%s\n' "$completed" "${#images[@]}"
}

set_working_shopcode() {
  echo -n "ID: " && read SHOPCODE && [ "$SHOPCODE" != "" ] && echo $SHOPCODE >.shopcode && CONTAINER=""
  [ -f .$SHOPCODE.container ] && CONTAINER=$(cat .$SHOPCODE.container)
  [ -f .$SHOPCODE.imagetype ] && OS=$(cat .$SHOPCODE.imagetype)
  [ -f .$SHOPCODE.postype ] && POSTYPE=$(cat .$SHOPCODE.postype) && return
  [[ "$SHOPCODE" =~ ^[0-9]{8}$ ]] || { echo "ID must be eight digits"; return 1; }
  printf '%s\n' \
    'OS choices:' \
    '  RedHat          rh7 rh8 rh9 rh10' \
    '  Rocky Linux     rocky8 rocky9 rocky10' \
    '  AlmaLinux       alma8 alma9 alma10' \
    '  Oracle Linux    oracle8 oracle9 oracle10' \
    '  CentOS Stream   centos8 centos9 centos10' \
    '  Fedora          fedora43 fedora44' \
    '  Windows         win11 win10 win8e win7u winxp winme win2k win98' \
    '  Windows Server  wns2025 wns2022 wns2019 wns2016' \
    '  macOS           mac15 mac14 mac13 mac12' \
    '  Ubuntu          ubuntu20.04 ubuntu22.04 ubuntu24.04' \
    '  SQL Server      mssql2025 mssql2022' && printf 'OS: ' && read -r OS || return 1
  case "$OS" in rh7|rh8|rh9|rh10|oracle8|oracle9|oracle10|centos8|centos9|centos10|fedora43|fedora44|rocky8|rocky9|rocky10|alma8|alma9|alma10|win11|win10|win8e|win7u|winxp|winme|win2k|win98|wns2025|wns2022|wns2019|wns2016|mac15|mac14|mac13|mac12|ubuntu20.04|ubuntu22.04|ubuntu24.04|mssql2025|mssql2022) ;; *) echo 'Unsupported OS'; return 1 ;; esac
  printf '%s\n' "$OS" >".$SHOPCODE.imagetype"
  POSTYPE="$OS"; printf '%s\n' "$POSTYPE" >".$SHOPCODE.postype"
}

install() {
  [ ! -f ../.envtype ] && echo -n "Env Name: " && read ENVTYPE && echo "$ENVTYPE" >../.envtype
  echo -n "Create base image? y/n: " && read answer
  [ "$answer" != "y" ] && return
  create-image || return 1
  echo "Done!"
}

prune-docker() {
  /usr/local/sbin/mykos-prune purge || return $?
  unset SHOPCODE CONTAINER POSTYPE

}

update_meraki() {
(
[ -n "$MERAKI_API_KEY" ] && [ -n "$MERAKI_ORG_ID" ] && [ -n "${VPN_PSK:-}" ] || { echo "Configure Meraki credentials in /etc/mykos/pos.env and enter the VPN PSK during setup"; exit 1; }
[[ "$MERAKI_ORG_ID" =~ ^[0-9]+$ ]] || { echo "Invalid Meraki organization ID"; exit 1; }
[[ ! "$MERAKI_API_KEY" =~ [[:cntrl:]] ]] || { echo "Invalid Meraki API key"; exit 1; }
local work endpoint
umask 077
work=$(mktemp -d "${TMPDIR:-/tmp}/mykos-meraki.XXXXXXXX") || exit 1
trap 'rm -rf -- "$work"' EXIT
printf 'Authorization: Bearer %s\nAccept: application/json\n' "$MERAKI_API_KEY" > "$work/headers" || exit 1
endpoint="https://api.meraki.com/api/v1/organizations/$MERAKI_ORG_ID/appliance/vpn/thirdPartyVPNPeers"
echo "Downloading existing Meraki site-to-site peers...."
curl --fail --silent --show-error --connect-timeout 15 --max-time 60 \
  --request GET --url "$endpoint" --header "@$work/headers" --output "$work/existing.json" || exit 1
"$MYKOS_PYTHON" - "$work/existing.json" <<'VALIDATE_PY' || exit 1
import json,sys
try:
 v=json.load(open(sys.argv[1]))
 if not isinstance(v,dict) or not isinstance(v.get('peers'),list) or not all(isinstance(p,dict) for p in v['peers']):raise ValueError()
except (ValueError,OSError):raise SystemExit('Cannot read existing Meraki peers; configuration was not changed')
VALIDATE_PY
# Resolve the remote MX to one network in this organization; never fall back to all networks.
local next_url page=0 selected_network
next_url="https://api.meraki.com/api/v1/organizations/$MERAKI_ORG_ID/appliance/uplink/statuses?perPage=1000"
while [ -n "$next_url" ]; do
  page=$((page+1)); [ "$page" -le 100 ] || { echo 'Too many Meraki inventory pages'; exit 1; }
  curl --fail --silent --show-error --connect-timeout 15 --max-time 60 --request GET \
    --url "$next_url" --header "@$work/headers" --dump-header "$work/page.headers" --output "$work/sites-$page.json" || exit 1
  next_url=$("$MYKOS_PYTHON" - "$work/page.headers" "$MERAKI_ORG_ID" <<'PAGE_PY'
import re,sys,urllib.parse
text=open(sys.argv[1]).read()
links=re.findall(r'<([^>]+)>;\s*rel="?next"?',text,re.I)
if len(links)>1:raise SystemExit('Ambiguous Meraki pagination')
if links:
 u=urllib.parse.urlsplit(links[0])
 if u.scheme!='https' or u.netloc!='api.meraki.com' or u.path!='/api/v1/organizations/'+sys.argv[2]+'/appliance/uplink/statuses':
  raise SystemExit('Unexpected Meraki pagination URL')
 print(links[0])
PAGE_PY
) || exit 1
done
selected_network=$("$MYKOS_PYTHON" - "$work" "$REMOTE_PUBLIC" <<'SITE_PY'
import ipaddress,json,pathlib,socket,sys
try:
 addresses={a[4][0] for a in socket.getaddrinfo(sys.argv[2],None,socket.AF_INET)}
 rows=[]
 for p in pathlib.Path(sys.argv[1]).glob('sites-*.json'):
  value=json.loads(p.read_text())
  if not isinstance(value,list) or not all(isinstance(x,dict) for x in value):raise ValueError()
  rows+=value
 matches={r['networkId'] for r in rows if isinstance(r.get('networkId'),str) and any(u.get('publicIp') in addresses or u.get('ip') in addresses for u in r.get('uplinks',[]) if isinstance(u,dict))}
 if len(matches)!=1:raise ValueError()
 print(next(iter(matches)))
except (OSError,ValueError,KeyError):
 raise SystemExit('Could not uniquely match the remote Meraki site in this organization; no VPN peers were changed')
SITE_PY
) || exit 1
# Preserve the complete downloaded peer list. Never PUT an empty fallback on GET/JSON failure.
MERAKI_ROUTE_BASED="${VPN_ROUTE_BASED:-0}" VPN_PSK="$VPN_PSK" REMOTE_NAME="$REMOTE_NAME" VPN_LOCAL_CIDR="$VPN_LOCAL_CIDR" PUBLIC="$PUBLIC" MERAKI_NETWORK_ID="$selected_network" MERAKI_PEER_NAME="${SHOPCODE:+$SHOPCODE.}$REMOTE_NAME" VPN_LOCAL_ID="${VPN_LOCAL_ID:-192.168.222.0}" VPN_REMOTE_ID="${VPN_REMOTE_ID:-192.168.222.0}" "$MYKOS_PYTHON" - "$work/existing.json" "$work/payload.json" <<'JSON_PY'
import json, os, sys
try:
    with open(sys.argv[1]) as handle: current=json.load(handle)
    if not isinstance(current,dict) or not isinstance(current.get('peers'),list) or not all(isinstance(p,dict) for p in current['peers']):
        raise ValueError()
except (ValueError,OSError):
    raise SystemExit('Cannot read existing Meraki peers; configuration was not changed')
peer = {
    "name": os.environ["MERAKI_PEER_NAME"],
    "ikeVersion": "2", "secret": os.environ["VPN_PSK"],
    "privateSubnets": [os.environ["VPN_LOCAL_CIDR"]],
    "ipsecPolicies": {"ikeCipherAlgo": ["aes128"], "ikeAuthAlgo": ["sha1"],
        "ikePrfAlgo": ["prfsha1"], "ikeDiffieHellmanGroup": ["group21"], "ikeLifetime": 28800,
        "childCipherAlgo": ["aes128", "aes192", "aes256", "tripledes"],
        "childAuthAlgo": ["sha256", "sha1", "md5"], "childPfsGroup": ["disabled"], "childLifetime": 28800},
    "networkTags": ["none"], "localId": os.environ["VPN_REMOTE_ID"], "remoteId": os.environ["VPN_LOCAL_ID"],
    "isRouteBased": os.environ["MERAKI_ROUTE_BASED"]=="1", "publicIp": os.environ["PUBLIC"],
    "network": {"ids": [os.environ["MERAKI_NETWORK_ID"]]}}
matches=[p for p in current['peers'] if p.get('name')==peer['name']]
if matches:
    if len(matches)==1 and all(matches[0].get(k)==v for k,v in peer.items()):
        raise SystemExit(3)
    raise SystemExit('A Meraki peer already uses this name with different settings; configuration was not changed')
with open(sys.argv[2],'w') as handle:
    json.dump({'peers':current['peers']+[peer]},handle)
JSON_PY
local merge_status=$?
if [ "$merge_status" = 3 ]; then echo "Meraki peer already configured; no changes needed."; exit 0; fi
[ "$merge_status" = 0 ] || exit "$merge_status"
# Refuse to overwrite edits made while the user was choosing a remote site.
curl --fail --silent --show-error --connect-timeout 15 --max-time 60 --request GET \
  --url "$endpoint" --header "@$work/headers" --output "$work/recheck.json" || exit 1
"$MYKOS_PYTHON" - "$work/existing.json" "$work/recheck.json" <<'RECHECK_PY' || exit 1
import json,sys
try:
 if json.load(open(sys.argv[1]))!=json.load(open(sys.argv[2])):raise ValueError()
except (OSError,ValueError):raise SystemExit('Meraki peers changed during setup; nothing was uploaded. Review and retry.')
RECHECK_PY
echo "Appending site-to-site peer on Meraki...."
curl --fail --silent --show-error --connect-timeout 15 --max-time 60 --request PUT \
  --url "$endpoint" --header "@$work/headers" \
  --header 'Content-Type: application/json' --data-binary "@$work/payload.json" --output "$work/result.json" || exit 1
# A successful HTTP code alone does not verify the returned configuration.
"$MYKOS_PYTHON" - "$work/payload.json" "$work/result.json" <<'VERIFY_PY' || exit 1
import json,sys
def subset(expected,actual):
 if isinstance(expected,dict):return isinstance(actual,dict) and all(k=='secret' or (k in actual and subset(v,actual[k])) for k,v in expected.items())
 return expected==actual
try:
 requested=json.load(open(sys.argv[1]))['peers'];returned=json.load(open(sys.argv[2]))['peers']
 if not isinstance(returned,list) or len(requested)!=len(returned):raise ValueError()
 remaining=list(returned)
 for peer in requested:
  match=next((i for i,value in enumerate(remaining) if subset(peer,value)),None)
  if match is None:raise ValueError()
  remaining.pop(match)
except (OSError,ValueError,KeyError,TypeError):
 raise SystemExit('Meraki reply could not be verified; local VPN remains unchanged. Check Dashboard before retrying.')
VERIFY_PY
echo "Done.... Existing peers retained."
)
}

list_vpns() {
  if [ "$CONTAINER" != "" ]; then
    /usr/local/sbin/mykos-linux vpn "$SHOPCODE"
  fi
}

add_vpn() {
local VPN_PSK vpn_psk_escaped host_connection VPN_LOCAL_CIDR VPN_LOCAL_ID VPN_REMOTE_ID VPN_MODE=direct VPN_REMOTE_CIDR VPN_ROUTE_BASED=0
VPN_LOCAL_CIDR=192.168.222.0/24
if [ "${MYKOS_VPN_HOST:-0}" != 1 ]; then
[[ "${SHOPCODE:-}" =~ ^[0-9]{8}$ ]] || { echo "Invalid ID"; return 1; }
[ -z "${CONTAINER:-}" ] && { echo "No running container......"; return 1; }

fi
# gather user input
INPUTS="REMOTE_NAME REMOTE_PUBLIC REMOTE_NET"
for var in $INPUTS; do
  echo -n "Enter $var: "
  read -r REPLY
  export "$var=$REPLY"
done
# Names become filenames and connection names; reject traversal and config injection.
[[ "$REMOTE_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$ ]] || { echo "Invalid remote name"; return 1; }
[[ "$REMOTE_PUBLIC" =~ ^[A-Za-z0-9][A-Za-z0-9.-]{0,252}$ ]] || { echo "Invalid remote public IP or hostname"; return 1; }
VPN_REMOTE_CIDR=$("$MYKOS_PYTHON" -c 'import ipaddress,sys;v=sys.argv[1];print(ipaddress.IPv4Network(v if "/" in v else v+"/24",strict=True))' "$REMOTE_NET") || return 1
if [ "${MYKOS_VPN_HOST:-0}" != 1 ]; then
  read -r -p 'VPN mode: direct guest tunnel [1], gateway for all MYKOS guests [2]: ' VPN_MODE || return 1
  case "$VPN_MODE" in ''|1|direct) VPN_MODE=direct ;; 2|gateway) VPN_MODE=gateway ;; *) echo 'Choose 1 or 2'; return 1 ;; esac
else
  read -r -p 'Host VPN mode: host only [1], shared gateway for all guests and host [2]: ' VPN_MODE || return 1
  case "$VPN_MODE" in ''|1|direct) VPN_MODE=direct ;; 2|gateway) VPN_MODE=host-gateway ;; *) echo 'Choose 1 or 2'; return 1 ;; esac
fi
VPN_LOCAL_ID=192.168.222.0; VPN_REMOTE_ID=192.168.222.0
if [ "${MYKOS_VPN_HOST:-0}" != 1 ]; then
  VPN_LOCAL_ID="$SHOPCODE.$(hostname)"; VPN_REMOTE_ID="$REMOTE_PUBLIC"
elif [ "$VPN_MODE" = host-gateway ]; then
  VPN_LOCAL_ID="$(hostname)"; VPN_REMOTE_ID="$REMOTE_PUBLIC"
fi
if [ "${MYKOS_VPN_HOST:-0}" != 1 ] || [ "$VPN_MODE" = host-gateway ]; then VPN_ROUTE_BASED=1; fi
read -r -s -p "Enter VPN shared secret (PSK): " VPN_PSK || { echo; return 1; }
echo
[ -n "$VPN_PSK" ] || { echo "VPN shared secret cannot be empty"; return 1; }
[[ ! "$VPN_PSK" =~ [[:cntrl:]] ]] || { echo "VPN shared secret must be a single line"; return 1; }
# Escape quotes/backslashes for the quoted ipsec.secrets value.
vpn_psk_escaped=$(printf '%s' "$VPN_PSK" | sed 's/[\\"]/\\&/g')
#set public ip
PUBLIC=$(curl --fail --silent --show-error --connect-timeout 15 --max-time 20 https://ifconfig.me) || return 1
PUBLIC=$("$MYKOS_PYTHON" -c 'import ipaddress,sys;print(ipaddress.IPv4Address(sys.argv[1].strip()))' "$PUBLIC") || return 1
[ "${MYKOS_VPN_HOST:-0}" = 1 ] || IP=$(/usr/local/sbin/mykos-linux ip "$SHOPCODE")
#setup vpn-data vol
if [ "${MYKOS_VPN_HOST:-0}" = 1 ]; then
  [ -e "/etc/ipsec.d/mykos-$REMOTE_NAME.conn.conf" ] && { echo "VPN connection already exists."; return 1; }
else
[[ ! -L ".$SHOPCODE" ]] || { echo "VPN directory must not be a symlink"; return 1; }
[[ ! -L ".$SHOPCODE/ipsec.secrets" ]] || { echo "VPN secrets must not be a symlink"; return 1; }
# check if vpn exist
[ -f ".$SHOPCODE/$REMOTE_NAME.conn.conf" ] && { echo "VPN connection already exists."; return 1; }
fi
# verify input
if [ "$INPUTS" != "" ]; then
  echo "ipsec VPN Connection about to be created:"
  echo "--------------------"
  echo "Continue y/n?"
  read -r -p "" || return 1
  if [[ "$REPLY" != "y" && "$REPLY" != "Y" ]]; then
    return
  else
    echo "Validating VPN Connection......"
# Resolve/validate all local selectors before any optional remote update.
if [ "${MYKOS_VPN_HOST:-0}" != 1 ]; then
local vpn_ip
vpn_ip="$IP"
VPN_LOCAL_CIDR=$("$MYKOS_PYTHON" -c 'import ipaddress,sys;print(str(ipaddress.IPv4Address(sys.argv[1]))+"/32")' "$vpn_ip") || return 1
if [ "$VPN_MODE" = gateway ]; then
  VPN_LOCAL_CIDR=$("$MYKOS_PYTHON" -c 'import sys;sys.path.insert(0,"/usr/local/sbin");from mykos_network import address_pool;from mykos_runtime import config;print(address_pool(config()))') || return 1
fi
fi
if [ "$VPN_MODE" = host-gateway ]; then
  VPN_LOCAL_CIDR=$("$MYKOS_PYTHON" -c 'import sys;sys.path.insert(0,"/usr/local/sbin");from mykos_network import address_pool;from mykos_runtime import config;print(address_pool(config()))') || return 1
fi
# Read-only dependency/mount preflight must precede any remote peer update.
if [ "${MYKOS_VPN_HOST:-0}" != 1 ]; then
  /usr/local/sbin/mykos-linux vpn-preflight "$SHOPCODE" >/dev/null || return 1
fi
local check_mode=direct check_owner="${SHOPCODE:-host}" check_connection="$REMOTE_NAME"
case "$VPN_MODE" in gateway|host-gateway) check_mode=shared ;; esac
if [ "${MYKOS_VPN_HOST:-0}" = 1 ]; then check_owner=host;check_connection="mykos-$REMOTE_NAME";fi
/usr/local/sbin/mykos-vpn-gateway preflight "$check_owner" "$check_connection" "$check_mode" >/dev/null || return 1
# A failed selected upload is a no-op locally. No directory, config, secret,
# service or routing mutation occurs before this success gate.
    echo -n "Push peer config to meraki? y/n " && read -r ans || return 1
    case "$ans" in
      y|Y) update_meraki || return 1 ;;
      n|N|'') ;;
      *) echo 'Choose y or n'; return 1 ;;
    esac
echo "Creating VPN Connection......"
# make connection ipsec config file
if [ "${MYKOS_VPN_HOST:-0}" = 1 ]; then
  if [ "$VPN_MODE" = host-gateway ]; then
    host_connection=$(printf '%s\0' "$REMOTE_NAME" "$REMOTE_PUBLIC" "$VPN_LOCAL_CIDR" "$VPN_REMOTE_CIDR" "$VPN_PSK" "$VPN_LOCAL_ID" "$VPN_REMOTE_ID" | "$MYKOS_PYTHON" ./host-vpn --save-host-shared) || return 1
  else
  host_connection=$(printf '%s\0' "$REMOTE_NAME" "$REMOTE_PUBLIC" "$REMOTE_NET" "$VPN_PSK" | "$MYKOS_PYTHON" ./host-vpn --save) || return 1
  fi
else
mkdir -p -- ".$SHOPCODE" || return 1
printf '%s\0' "$SHOPCODE" "$REMOTE_NAME" "$REMOTE_PUBLIC" "$VPN_LOCAL_CIDR" "$VPN_REMOTE_CIDR" "$VPN_PSK" "$VPN_LOCAL_ID" "$VPN_REMOTE_ID" | "$MYKOS_PYTHON" ./host-vpn --save-native || return 1

fi
    if [ "${MYKOS_VPN_HOST:-0}" = 1 ]; then
      if [ "$VPN_MODE" = host-gateway ]; then systemctl start ipsec || return 1; fi
      /usr/sbin/ipsec auto --rereadsecrets || return 1
      /usr/sbin/ipsec auto --add "$host_connection" || return 1
      /usr/sbin/ipsec auto --up "$host_connection" || return 1
      if [ "$VPN_MODE" = host-gateway ]; then
        /usr/local/sbin/mykos-vpn-gateway configure-host "$host_connection" "$VPN_REMOTE_CIDR" || return 1
      fi
    else
      /usr/local/sbin/mykos-linux prepare-vpn "$SHOPCODE" || return 1
      podman exec "$CONTAINER" systemctl start ipsec || return 1
      vpn_peer=$(cat ".$SHOPCODE/$REMOTE_NAME.vpn-peer") || return 1
      podman exec "$CONTAINER" "/etc/ipsec.d/$REMOTE_NAME.updown" --allow-peer "$vpn_peer" || return 1
      podman exec "$CONTAINER" ipsec auto --rereadsecrets || return 1
      podman exec "$CONTAINER" ipsec auto --add "$REMOTE_NAME" || return 1
      podman exec "$CONTAINER" ipsec auto --asynchronous --up "$REMOTE_NAME" || return 1
      if [ "$VPN_MODE" = gateway ]; then
        /usr/local/sbin/mykos-vpn-gateway configure "$SHOPCODE" "$REMOTE_NAME" "$VPN_REMOTE_CIDR" || return 1
      fi
    fi
    echo "VPN Connection info..." && echo
    echo "Remote-side VPN Info:"
    echo "Cisco/Meraki Dash --> Security --> Site-to-site VPN --> IPsec VPN Peers --> Add a peer" && echo
    echo "Name: $REMOTE_NAME"
    echo "IKE Version: IKEv2"
    echo "Public IP or Hostname: $PUBLIC"
    echo "Local ID: $VPN_REMOTE_ID"
    echo "Remote ID: $VPN_LOCAL_ID"
    echo "Shared Secret: (entered during setup)"
    echo "Static Routing"
    echo "Private Subnets: $VPN_LOCAL_CIDR"
    echo "Availability: Selected remote Meraki site only"
    echo "IPsec Policy: Custom"
    echo "Phase 1 Encryption: AES128"
    echo "Authentication: SHA1"
    echo "Psuedo-random function: SHA1"
    echo "Diffie-Hellman group: 21"
    echo "Lifetime: 28800"
    echo "Phase 2 Encryption: AES128"
    echo "Authenticaion: SHA1"
    echo "PFS group: Off"
    echo "Lifetime: 28800" 
  fi
else
  echo "Nothing to do."
fi
}

rm_vpn() {
[[ "$SHOPCODE" =~ ^[0-9]{8}$ ]] || { echo 'Invalid ID'; return 1; }
[[ "$CONTAINER" =~ ^[0-9]{8}(\.[A-Za-z0-9_.-]+)?$ && "${CONTAINER:0:8}" = "$SHOPCODE" ]] || { echo 'Invalid container'; return 1; }
local directory=".$SHOPCODE" path CONN found=0
[ -d "$directory" ] && [ ! -L "$directory" ] || { echo 'No connections.'; return 1; }
for path in "$directory"/*.conn.conf; do
  [ -f "$path" ] && [ ! -L "$path" ] || continue
  path=${path##*/};printf '%s\n' "${path%.conn.conf}";found=1
done
[ "$found" = 1 ] || { echo 'No connections.'; return; }
read -r -p 'Connection to stop and remove: ' CONN || return 1
[[ "$CONN" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$ ]] || { echo 'Invalid connection'; return 1; }
path="$directory/$CONN.conn.conf"
[ -f "$path" ] && [ ! -L "$path" ] || { echo 'Select a listed connection'; return 1; }
local confirmation
read -r -p "Type $CONN to confirm removal: " confirmation || return 1
[ "$confirmation" = "$CONN" ] || { echo 'Cancelled'; return 1; }
# Use the existing text-admin workflow: remove the selected configuration and reload IPsec.
/usr/local/sbin/mykos-vpn-gateway remove "$SHOPCODE" "$CONN" || return 1
if [ -f "$directory/$CONN.updown" ] && [ ! -L "$directory/$CONN.updown" ]; then
  podman exec "$CONTAINER" "/etc/ipsec.d/$CONN.updown" --remove "$CONN" || return 1
fi
podman exec "$CONTAINER" ipsec auto --delete "$CONN" || return 1
rm -f -- "$path" "$directory/$CONN.secrets" "$directory/$CONN.updown" "$directory/$CONN.default.conf" "$directory/$CONN.vpn-peer" || return 1
podman exec "$CONTAINER" ipsec auto --rereadsecrets || return 1
echo "Removed VPN $CONN"
}

clear_vpns() {
[ -f .$SHOPCODE.container ] && echo "Running container. Stop it first....." && return 
[ ! -d .$SHOPCDE ] && echo "No VPNs configured......" && return
echo "About to wipe all connections for $SHOPCODE."
echo "Continue y/n?" && read -p ""
[ "$REPLY" == "y" ] && [ "$REPLY" != "" ] && rm -rf .$SHOPCODE
}



###
#MAIN
###

# Fixed entry point for the authenticated web terminal; never evaluate shell input.
if [ "${1:-}" = "--add-host-vpn" ]; then
  [ "$#" -eq 1 ] || { echo "Invalid host VPN selection"; exit 1; }
  MYKOS_VPN_HOST=1
  unset SHOPCODE CONTAINER
  add_vpn
  exit $?
fi
if [ "${1:-}" = "--add-vpn" ] || [ "${1:-}" = "--remove-vpn" ]; then
  [[ "$#" -eq 3 && "$2" =~ ^[0-9]{8}$ && "$3" =~ ^[0-9]{8}(\.[A-Za-z0-9_.-]+)?$ && "${3:0:8}" = "$2" ]] || { echo "Invalid container selection"; exit 1; }
  SHOPCODE="$2"
  CONTAINER="$3"
  if [ "$1" = --remove-vpn ]; then rm_vpn; exit $?; fi
  add_vpn
  exit $?
fi

trap '' 2
while :; do
    clear
    echo -e "${GREY}$(date +'%x %l:%M %p') ${NC}"
    printf '\u250f''\u2501''\u2501''\u2501''\u2501''\u2501''\u2501''\u2501''\u2501''\n'
    echo -e "\u2503${BLUE} @ ${NC}`[ -f ../.envtype ] && cat ../.envtype || echo -e Not Installed`${NC}"
    printf '\u2503'${LGGRN}' )~ pos    '${GREY}v$VERSION${NC}'\n'
    printf '\u2503''\u2501''\n'
    printf '\u2503'${GREY}"  "${TEAL}"$(hostname)"${NC}'\n'
    printf '\u2503'${GREY}" Space    : $(df -h /backups | tail -1 | awk '{print $4}')"${NC}'\n'
    echo -e "\u2503${NC}"
    echo -e "\u2503${GREY} ID : `[ -f .shopcode ] && cat .shopcode`${NC}"
    echo -e "\u2503${GREY} Type : `[ -f .$SHOPCODE.postype ] && cat .$SHOPCODE.postype`${NC}"
    echo -e "\u2503${GREY} Stat : `podman container inspect $SHOPCODE.$(hostname) --format '{{.State.Status}}' 2>/dev/null`${NC}"
    printf '\u2503''\n'
    echo -e "\u2503${GREY}  $([ -f .$SHOPCODE.container ] && /usr/local/sbin/mykos-linux ip "$SHOPCODE" 2>/dev/null) ${NC}"
    echo -e "\u2503${GREY} $([ -f .$SHOPCODE.container ] && /usr/local/sbin/mykos-linux vpn "$SHOPCODE" 2>/dev/null) ${NC}"
    printf '\u2503''\n'
    printf '\u2503''\033[1m'"-\033[0mContainers"'\n'
    printf '\u2503''\033[1m'" 1.\033[0m Status"'\n'
    printf '\u2503''\033[1m'" 2.\033[0m Start/Stop"'\n'
    printf '\u2503''\033[1m'" 3.\033[0m Connect"'\n'
    printf '\u2503''\n'
    printf '\u2503''\033[1m'"-\033[0mVPNs"'\n'
    printf '\u2503''\033[1m'" 4.\033[0m Status"'\n'
    printf '\u2503''\033[1m'" 5.\033[0m Add"'\n'
    printf '\u2503''\033[1m'" 6.\033[0m Remove"'\n'
    printf '\u2503''\033[1m'" 7.\033[0m Clear"'\n'
    printf '\u2503''\n'
    printf '\u2503''\033[1m'"-\033[0mSystem"'\n'
    printf '\u2503''\033[1m'" s.\033[0m ID"'\n'
    printf '\u2503''\033[1m'" m.\033[0m Images"'\n'
    printf '\u2503''\033[1m'" c.\033[0m Build image"'\n'
    printf '\u2503''\033[1m'" f.\033[0m Capture/update image"'\n'
    printf '\u2503''\033[1m'" g.\033[0m Enable checkpoints"'\n'
    printf '\u2503''\033[1m'" v.\033[0m Images / consoles"'\n'
    printf '\u2503''\033[1m'" t.\033[0m Start/stop image"'\n'
    printf '\u2503''\033[1m'" b.\033[0m Guest memory"'\n'
    printf '\u2503''\033[1m'" i.\033[0m Install"'\n'
    printf '\u2503''\033[1m'" p.\033[0m Purge"'\n'
    printf '\u2503''\n'
    printf '\u2503''\033[1m'" r.\033[0m Readme"'\n'
    printf '\u2503''\033[1m'" x.\033[0m Exit"'\n'
    printf '\u2517''\u2501''\n'
        echo -ne "-> ${NC}"
        read selection
        case $selection in
          1) podman container ls ;;
	  2) case "$OS" in win*|wns*|mac*|ubuntu*) if "$MYKOS_PYTHON" ./vm info "$SHOPCODE" >/dev/null 2>&1; then "$MYKOS_PYTHON" ./vm toggle "$SHOPCODE"; else stage_image; fi ;; *) if podman container exists "$SHOPCODE.$(hostname)"; then /usr/local/sbin/mykos-linux toggle "$SHOPCODE"; else stage_image; fi ;; esac ;;
          3) case "$OS" in win*|wns*|mac*|ubuntu*) "$MYKOS_PYTHON" ./vm info "$SHOPCODE" ;; *) [ -f .$SHOPCODE.container ] && CONTAINER=$(cat .$SHOPCODE.container) && podman exec -it $CONTAINER su - root || echo "No running container.." ;; esac ;;
          4) list_vpns ;;
  	  5) add_vpn ;;
          6) rm_vpn ;;
          7) clear_vpns ;;
          s) set_working_shopcode ;;
          i) time install ;;
          p) prune-docker ;;
          m) podman image ls ;;
          c) create-image ;;
          f) read -r -p 'Image OS: ' VMOS; /usr/local/sbin/mykos-image-admin "$VMOS" --capture ;;
          g) read -r -p 'Image OS: ' VMOS; "$MYKOS_PYTHON" ./vm checkpoint-enable "$VMOS" ;;
          v) "$MYKOS_PYTHON" ./vm list ;;
          b) read -r -p 'Guest ID or image OS: ' VMKEY; read -r -p 'Target MiB (blank for status): ' VMMEM; if [ -z "$VMMEM" ]; then "$MYKOS_PYTHON" ./vm memory "$VMKEY"; else "$MYKOS_PYTHON" ./vm memory "$VMKEY" --target-mib "$VMMEM"; fi ;;
          t) read -r -p 'Image OS: ' VMOS; read -r -p 'resume or stop: ' VMACTION; case "$VMACTION" in resume|stop) "$MYKOS_PYTHON" ./vm "$VMACTION" "$VMOS" ;; *) echo 'Choose resume or stop' ;; esac ;;
          r) cat README.md | more ;;
          x) exit ;;
          *) ;;
        esac
       echo -ne "${CYAN}Press enter to continue..${NC}"
       read
done
