#!/usr/bin/env bash
set -euo pipefail

# ============================================================
# Interactive Squid HTTPS Proxy Installer with Let's Encrypt
# Debian 12/13
#
# Creates:
#   Firefox/FoxyProxy -> HTTPS proxy -> Squid -> Internet
#
# IMPORTANT:
#   In FoxyProxy, use the DOMAIN NAME, not the IP address.
# ============================================================

RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
RESET="\033[0m"

log()  { echo -e "${GREEN}[OK]${RESET} $*"; }
info() { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
die()  { echo -e "${RED}[ERROR]${RESET} $*" >&2; exit 1; }

SQUID_USER="proxy"
SQUID_GROUP="proxy"
STOPPED_SERVICES=()

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    die "Run this script as root."
  fi
}

ask() {
  local prompt="$1"
  local default="${2:-}"
  local value

  if [[ -n "$default" ]]; then
    read -rp "$prompt [$default]: " value
    echo "${value:-$default}"
  else
    read -rp "$prompt: " value
    echo "$value"
  fi
}

ask_password_confirmed() {
  local pass1 pass2

  while true; do
    read -rsp "Proxy password: " pass1
    printf '\n' >&2

    read -rsp "Confirm proxy password: " pass2
    printf '\n' >&2

    if [[ -z "$pass1" ]]; then
      warn "Password cannot be empty." >&2
      continue
    fi

    if [[ "$pass1" != "$pass2" ]]; then
      warn "Passwords do not match." >&2
      continue
    fi

    printf '%s' "$pass1"
    return 0
  done
}

validate_domain() {
  local domain="$1"

  if [[ ! "$domain" =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
    die "Invalid domain: $domain"
  fi

  if [[ "$domain" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    die "Do not use an IP address. Use a real hostname, e.g. proxy.example.com"
  fi
}

validate_port() {
  local port="$1"

  if ! [[ "$port" =~ ^[0-9]+$ ]]; then
    die "Invalid port: $port"
  fi

  if (( port < 1 || port > 65535 )); then
    die "Port must be between 1 and 65535."
  fi
}

detect_proxy_user_group() {
  if id proxy >/dev/null 2>&1; then
    SQUID_USER="proxy"
  elif id squid >/dev/null 2>&1; then
    SQUID_USER="squid"
  else
    SQUID_USER="proxy"
  fi

  if getent group proxy >/dev/null 2>&1; then
    SQUID_GROUP="proxy"
  elif getent group squid >/dev/null 2>&1; then
    SQUID_GROUP="squid"
  else
    SQUID_GROUP="$SQUID_USER"
  fi
}

install_packages() {
  info "Installing required packages..."

  apt update

  if apt-cache show squid-openssl >/dev/null 2>&1; then
    apt install -y squid-openssl apache2-utils certbot openssl curl ca-certificates dnsutils
  else
    warn "squid-openssl package was not found."
    warn "Installing normal squid, but HTTPS proxy listener may not work."
    apt install -y squid apache2-utils certbot openssl curl ca-certificates dnsutils
  fi

  log "Packages installed."
}

check_squid_openssl() {
  info "Checking Squid OpenSSL support..."

  if squid -v 2>/dev/null | grep -qi openssl; then
    log "Squid appears to have OpenSSL support."
  else
    warn "Could not confirm OpenSSL support from squid -v."
    warn "If https_port fails later, install squid-openssl or use a TLS wrapper."
  fi
}

check_dns() {
  local domain="$1"

  info "Checking DNS for $domain..."

  local resolved_v4=""
  local resolved_v6=""

  resolved_v4="$(dig +short A "$domain" | tail -n1 || true)"
  resolved_v6="$(dig +short AAAA "$domain" | tail -n1 || true)"

  if [[ -z "$resolved_v4" && -z "$resolved_v6" ]]; then
    warn "No A or AAAA record found for $domain."
    warn "Let's Encrypt will fail unless this hostname points to this server."
    read -rp "Continue anyway? [y/N]: " yn
    [[ "${yn,,}" == "y" ]] || die "Stopped."
  else
    if [[ -n "$resolved_v4" ]]; then
      log "$domain A record: $resolved_v4"
    fi

    if [[ -n "$resolved_v6" ]]; then
      log "$domain AAAA record: $resolved_v6"
    else
      warn "$domain has no AAAA record. That is fine if you only want IPv4."
    fi
  fi

  return 0
}

open_firewall() {
  local port="$1"

  info "Attempting to open firewall ports 80 and $port..."

  if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi active; then
    ufw allow 80/tcp || true
    ufw allow "$port/tcp" || true
    log "UFW rules added."
  else
    warn "UFW not active or not installed. Skipping UFW rules."
  fi

  if command -v iptables >/dev/null 2>&1; then
    iptables -C INPUT -p tcp --dport 80 -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport 80 -j ACCEPT
    iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport "$port" -j ACCEPT
    log "Temporary iptables rules added."
    warn "iptables rules may not persist after reboot unless your VPS image saves them."
  fi
}

stop_port_80_services_if_needed() {
  STOPPED_SERVICES=()

  if ss -tulpn | grep -qE '(:80[[:space:]]|:80$|:80,)'; then
    warn "Something appears to be using port 80."
    echo
    ss -tulpn | grep ':80' || true
    echo

    read -rp "Temporarily stop nginx/apache2/caddy if running so Certbot can use port 80? [Y/n]: " yn
    yn="${yn:-Y}"

    if [[ "${yn,,}" == "y" ]]; then
      for svc in nginx apache2 caddy; do
        if systemctl is-active --quiet "$svc" 2>/dev/null; then
          systemctl stop "$svc"
          STOPPED_SERVICES+=("systemd:$svc")
          warn "Stopped $svc temporarily."
        fi
      done
    fi
  fi
}

restart_stopped_services() {
  if [[ ${#STOPPED_SERVICES[@]} -gt 0 ]]; then
    info "Restarting services stopped for Certbot..."

    for item in "${STOPPED_SERVICES[@]}"; do
      kind="${item%%:*}"
      name="${item#*:}"

      if [[ "$kind" == "systemd" ]]; then
        systemctl start "$name" || warn "Could not restart $name."
      fi
    done
  fi
}

get_certificate() {
  local domain="$1"
  local email="$2"

  if [[ -f "/etc/letsencrypt/live/$domain/fullchain.pem" && -f "/etc/letsencrypt/live/$domain/privkey.pem" ]]; then
    log "Existing Let's Encrypt cert found for $domain."
    read -rp "Use existing certificate? [Y/n]: " yn
    yn="${yn:-Y}"

    if [[ "${yn,,}" == "y" ]]; then
      return 0
    fi
  fi

  stop_port_80_services_if_needed

  info "Requesting Let's Encrypt certificate for $domain..."

  if [[ -n "$email" ]]; then
    certbot certonly --standalone \
      -d "$domain" \
      --non-interactive \
      --agree-tos \
      --email "$email"
  else
    certbot certonly --standalone \
      -d "$domain" \
      --non-interactive \
      --agree-tos \
      --register-unsafely-without-email
  fi

  restart_stopped_services

  log "Certificate issued."
}

copy_certs() {
  local domain="$1"

  info "Copying certificate files into /etc/squid/certs..."

  mkdir -p /etc/squid/certs

  cp "/etc/letsencrypt/live/$domain/fullchain.pem" /etc/squid/certs/proxy-fullchain.pem
  cp "/etc/letsencrypt/live/$domain/privkey.pem" /etc/squid/certs/proxy-privkey.pem

  chown -R "$SQUID_USER:$SQUID_GROUP" /etc/squid/certs
  chmod 644 /etc/squid/certs/proxy-fullchain.pem
  chmod 600 /etc/squid/certs/proxy-privkey.pem

  log "Certificates copied."
}

create_auth() {
  local username="$1"
  local password="$2"

  info "Creating Squid username/password auth..."

  touch /etc/squid/passwd
  chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd
  chmod 640 /etc/squid/passwd

  printf '%s\n' "$password" | htpasswd -m -i /etc/squid/passwd "$username" >/dev/null

  chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd
  chmod 640 /etc/squid/passwd

  log "Proxy user created: $username"
}

test_auth_helper() {
  local username="$1"
  local password="$2"

  info "Testing Squid auth helper directly..."

  local result
  result="$(printf '%s %s\n' "$username" "$password" | /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd || true)"

  if echo "$result" | grep -q '^OK'; then
    log "Squid auth helper accepted the username/password."
  else
    warn "Squid auth helper did not accept the username/password."
    echo "Auth helper output:"
    echo "$result"
    die "Stopping because proxy auth would fail."
  fi
}

write_squid_config() {
  local domain="$1"
  local port="$2"
  local syntax="$3"

  local https_line

  if [[ "$syntax" == "new" ]]; then
    https_line="https_port $port tls-cert=/etc/squid/certs/proxy-fullchain.pem tls-key=/etc/squid/certs/proxy-privkey.pem"
  else
    https_line="https_port $port cert=/etc/squid/certs/proxy-fullchain.pem key=/etc/squid/certs/proxy-privkey.pem"
  fi

  info "Writing Squid config using syntax: $syntax"

  if [[ -f /etc/squid/squid.conf && ! -f /etc/squid/squid.conf.bak-before-https-proxy ]]; then
    cp /etc/squid/squid.conf /etc/squid/squid.conf.bak-before-https-proxy
    log "Backed up original config to /etc/squid/squid.conf.bak-before-https-proxy"
  fi

  cat > /etc/squid/squid.conf <<EOF
# ============================================================
# Squid HTTPS/TLS encrypted forward proxy
# Generated by setup-https-squid-proxy.sh
#
# Domain: $domain
# Port: $port
#
# IMPORTANT:
#   In FoxyProxy, use host "$domain", not the server IP.
# ============================================================

$https_line

auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
auth_param basic realm HTTPS Proxy
auth_param basic credentialsttl 2 hours

acl authenticated proxy_auth REQUIRED

acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl Safe_ports port 1025-65535
acl CONNECT method CONNECT

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow authenticated
http_access deny all

cache deny all
cache_mem 8 MB
maximum_object_size 0 KB

via off
forwarded_for delete
request_header_access X-Forwarded-For deny all
request_header_access Via deny all
request_header_access Cache-Control deny all

access_log /var/log/squid/access.log
cache_log /var/log/squid/cache.log

cache_effective_user $SQUID_USER
cache_effective_group $SQUID_GROUP

visible_hostname $domain
EOF
}

find_working_squid_syntax() {
  local domain="$1"
  local port="$2"

  write_squid_config "$domain" "$port" "new"

  if squid -k parse >/tmp/squid-parse.log 2>&1; then
    log "Squid config parsed successfully with tls-cert/tls-key syntax."
    return 0
  fi

  warn "tls-cert/tls-key syntax failed. Trying older cert/key syntax..."
  cat /tmp/squid-parse.log || true

  write_squid_config "$domain" "$port" "old"

  if squid -k parse >/tmp/squid-parse.log 2>&1; then
    log "Squid config parsed successfully with cert/key syntax."
    return 0
  fi

  cat /tmp/squid-parse.log || true
  die "Squid could not parse either HTTPS config style. Your Squid build may not support https_port/OpenSSL."
}

write_renewal_hook() {
  local domain="$1"

  info "Creating Certbot renewal hook..."

  mkdir -p /etc/letsencrypt/renewal-hooks/deploy

  cat > /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh <<EOF
#!/bin/bash
set -e

DOMAIN="$domain"

SRC_CERT="/etc/letsencrypt/live/\$DOMAIN/fullchain.pem"
SRC_KEY="/etc/letsencrypt/live/\$DOMAIN/privkey.pem"

DST_DIR="/etc/squid/certs"
DST_CERT="\$DST_DIR/proxy-fullchain.pem"
DST_KEY="\$DST_DIR/proxy-privkey.pem"

mkdir -p "\$DST_DIR"

cp "\$SRC_CERT" "\$DST_CERT"
cp "\$SRC_KEY" "\$DST_KEY"

chown $SQUID_USER:$SQUID_GROUP "\$DST_CERT" "\$DST_KEY"
chmod 644 "\$DST_CERT"
chmod 600 "\$DST_KEY"

systemctl reload squid || systemctl restart squid
EOF

  chmod +x /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh

  log "Renewal hook created at /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh"
}

restart_squid_cleanly() {
  info "Restarting Squid cleanly..."

  systemctl stop squid >/dev/null 2>&1 || true
  sleep 2

  pkill -9 squid >/dev/null 2>&1 || true
  sleep 1

  systemctl start squid

  if systemctl is-active --quiet squid; then
    systemctl enable squid >/dev/null 2>&1 || true
    log "Squid is running."
  else
    systemctl status squid --no-pager -l || true
    tail -100 /var/log/squid/cache.log || true
    die "Squid failed to start."
  fi
}

test_listener() {
  local port="$1"

  info "Checking if Squid is listening on port $port..."

  if ss -tulpn | grep -q ":$port"; then
    log "Something is listening on port $port:"
    ss -tulpn | grep ":$port" || true
  else
    warn "Port $port does not appear in ss output."
  fi
}

test_proxy() {
  local domain="$1"
  local port="$2"
  local username="$3"
  local password="$4"

  info "Testing proxy with curl..."

  set +e
  local result
  result="$(curl -4 -sS --max-time 30 \
    --proxy "https://$domain:$port" \
    --proxy-user "$username:$password" \
    https://chy.li/ip 2>&1)"
  local rc=$?
  set -e

  if [[ $rc -eq 0 ]]; then
    log "Proxy test succeeded. Outgoing IP:"
    echo "$result"
  else
    warn "Curl proxy test failed."
    echo "$result"
    echo
    warn "Check logs:"
    echo "  tail -f /var/log/squid/cache.log"
    echo "  tail -f /var/log/squid/access.log"
  fi
}

print_summary() {
  local domain="$1"
  local port="$2"
  local username="$3"

  echo
  echo "============================================================"
  echo " HTTPS Squid Proxy Setup Complete"
  echo "============================================================"
  echo
  echo "FoxyProxy settings:"
  echo
  echo "  Proxy Type: HTTPS"
  echo "  Host:       $domain"
  echo "  Port:       $port"
  echo "  Username:   $username"
  echo "  Password:   the password you entered"
  echo
  echo "IMPORTANT:"
  echo "  Use the hostname \"$domain\" in FoxyProxy."
  echo "  Do NOT use the server IP address."
  echo
  echo "Test command:"
  echo
  echo "  curl -v \\"
  echo "    --proxy \"https://$domain:$port\" \\"
  echo "    --proxy-user \"$username:YOUR_PASSWORD\" \\"
  echo "    https://chy.li/ip"
  echo
  echo "Useful logs:"
  echo
  echo "  tail -f /var/log/squid/cache.log"
  echo "  tail -f /var/log/squid/access.log"
  echo
  echo "============================================================"
}

main() {
  require_root

  echo
  echo "============================================================"
  echo " Interactive Squid HTTPS Proxy + Let's Encrypt Installer"
  echo "============================================================"
  echo

  DOMAIN="$(ask "Proxy domain, e.g. proxy.example.com")"
  validate_domain "$DOMAIN"

  PORT="$(ask "HTTPS proxy port" "8443")"
  validate_port "$PORT"

  USERNAME="$(ask "Proxy username" "proxyuser")"
  if [[ -z "$USERNAME" ]]; then
    die "Username cannot be empty."
  fi

  PASSWORD="$(ask_password_confirmed)"

  EMAIL="$(ask "Email for Let's Encrypt renewal notices, or leave blank" "")"

  echo
  echo "Summary:"
  echo "  Domain:   $DOMAIN"
  echo "  Port:     $PORT"
  echo "  Username: $USERNAME"
  if [[ -n "$EMAIL" ]]; then
    echo "  LE Email: $EMAIL"
  else
    echo "  LE Email: none"
  fi
  echo

  read -rp "Continue with install? [Y/n]: " confirm
  confirm="${confirm:-Y}"
  [[ "${confirm,,}" == "y" ]] || die "Cancelled."

  install_packages
  detect_proxy_user_group

  info "Detected Squid user/group: $SQUID_USER:$SQUID_GROUP"

  check_squid_openssl
  check_dns "$DOMAIN"
  open_firewall "$PORT"
  get_certificate "$DOMAIN" "$EMAIL"
  copy_certs "$DOMAIN"
  create_auth "$USERNAME" "$PASSWORD"
  test_auth_helper "$USERNAME" "$PASSWORD"
  find_working_squid_syntax "$DOMAIN" "$PORT"
  write_renewal_hook "$DOMAIN"
  restart_squid_cleanly
  test_listener "$PORT"
  test_proxy "$DOMAIN" "$PORT" "$USERNAME" "$PASSWORD"
  print_summary "$DOMAIN" "$PORT" "$USERNAME"
}

main "$@"
