#!/usr/bin/env bash

# Rocky Linux 9.x + Apache + MariaDB + PHP + DVWA 설치 스크립트
# - MariaDB modular metadata 오류 대응
# - mysql_secure_installation expect 자동화 제거
# - DVWA 설정 파일을 안전하게 패치
#
# 사용법:
#   chmod +x asd_fixed.sh
#   ./asd_fixed.sh
#
# 선택 옵션:
#   DB_PW='원하는비밀번호' ./asd_fixed.sh
#   MARIADB_STREAM=11.8 ./asd_fixed.sh   # auto, 11.8, 10.11, none 중 선택 가능

set -Eeuo pipefail

DB_PW="${DB_PW:-asd123!@}"
MARIADB_STREAM="${MARIADB_STREAM:-auto}"
DVWA_DIR="/var/www/html/dvwa"
DVWA_REPO="https://github.com/digininja/DVWA.git"

if [[ ${EUID} -eq 0 ]]; then
  SUDO=()
else
  SUDO=(sudo)
fi

log()  { echo -e "\n[+] $*"; }
warn() { echo "[!] $*" >&2; }
die()  { echo "[ERROR] $*" >&2; exit 1; }
run()  { "${SUDO[@]}" "$@"; }

trap 'die "스크립트 실패: line ${LINENO}. 위쪽 오류 메시지를 확인하세요."' ERR

sql_escape() {
  local s=${1//\\/\\\\}
  s=${s//\'/\'\'}
  printf '%s' "$s"
}

choose_mariadb_stream() {
  local module_list

  if [[ "$MARIADB_STREAM" != "auto" ]]; then
    [[ "$MARIADB_STREAM" == "none" ]] && return 0
    printf '%s' "$MARIADB_STREAM"
    return 0
  fi

  module_list="$(run dnf -q --refresh module list mariadb 2>/dev/null || true)"

  if grep -Eq '^[[:space:]]*mariadb[[:space:]]+11\.8([[:space:]]|$)' <<<"$module_list"; then
    printf '11.8'
  elif grep -Eq '^[[:space:]]*mariadb[[:space:]]+10\.11([[:space:]]|$)' <<<"$module_list"; then
    printf '10.11'
  else
    return 0
  fi
}

find_nonmodular_mariadb_server() {
  # 모듈 메타데이터가 깨진 저장소에서는 11.8/10.11 module 패키지 대신
  # non-module mariadb-server 10.5 계열을 정확한 NEVRA로 지정해 설치한다.
  run dnf -q repoquery --available --qf '%{nevra}' mariadb-server 2>/dev/null \
    | grep -v 'module+' \
    | sort -V \
    | tail -n 1 || true
}

install_packages() {
  local stream="$1"
  local nonmodular_server=""

  local base_pkgs=(
    httpd
    php
    php-cli
    php-fpm
    php-mysqlnd
    php-gd
    php-mbstring
    php-xml
    python3
    git
    nano
    firewalld
    policycoreutils
    libsemanage-utils
  )

  if [[ -n "$stream" ]]; then
    log "MariaDB 모듈 스트림 mariadb:${stream} 활성화"
    run dnf -y module reset mariadb || true
    run dnf -y module enable "mariadb:${stream}" || true
    run dnf -y module switch-to "mariadb:${stream}" || true
  else
    warn "mariadb 모듈 메타데이터를 찾지 못했습니다. non-module mariadb-server 패키지로 우회 시도합니다."
  fi

  log "Apache / MariaDB / PHP / DVWA 필요 패키지 설치"
  if run dnf -y --refresh install "${base_pkgs[@]}" mariadb-server; then
    return 0
  fi

  warn "일반 mariadb-server 설치가 실패했습니다. DNF 캐시 정리 후 non-module MariaDB로 재시도합니다."
  run dnf clean all || true
  run dnf -y module reset mariadb || true
  run dnf -y module disable mariadb || true

  nonmodular_server="$(find_nonmodular_mariadb_server)"
  if [[ -z "$nonmodular_server" ]]; then
    die "mariadb-server의 정상 모듈 메타데이터도, non-module 패키지도 찾지 못했습니다. /etc/yum.repos.d/의 AppStream 저장소 또는 로컬 미러 repodata를 확인하세요."
  fi

  log "non-module MariaDB 패키지로 설치: ${nonmodular_server}"
  run dnf -y --refresh install "${base_pkgs[@]}" "$nonmodular_server"
}

mysql_root() {
  local mysql_bin="$1"
  shift

  if run "$mysql_bin" -uroot --protocol=socket -e 'SELECT 1' >/dev/null 2>&1; then
    run "$mysql_bin" -uroot --protocol=socket "$@"
  elif run "$mysql_bin" -uroot -p"$DB_PW" -e 'SELECT 1' >/dev/null 2>&1; then
    run "$mysql_bin" -uroot -p"$DB_PW" "$@"
  else
    die "MariaDB root 접속 실패. fresh install이면 'sudo mariadb'가 되어야 합니다. 기존 DB가 있으면 root 인증 방식을 확인하세요."
  fi
}

log "DNF 캐시/메타데이터 정리"
run dnf clean packages || true
run dnf clean metadata || true
run dnf makecache --refresh
run dnf -y --refresh install dnf-plugins-core || true

STREAM="$(choose_mariadb_stream)"
install_packages "$STREAM"

log "서비스 활성화 및 시작"
run systemctl enable --now firewalld || true
run systemctl enable --now mariadb
run systemctl enable --now php-fpm || true
run systemctl enable --now httpd

MYSQL_BIN="$(command -v mariadb || command -v mysql || true)"
if [[ -z "$MYSQL_BIN" && -x /usr/bin/mariadb ]]; then
  MYSQL_BIN="/usr/bin/mariadb"
elif [[ -z "$MYSQL_BIN" && -x /usr/bin/mysql ]]; then
  MYSQL_BIN="/usr/bin/mysql"
fi
[[ -n "$MYSQL_BIN" ]] || die "mariadb/mysql 클라이언트를 찾지 못했습니다."

DB_PW_SQL="$(sql_escape "$DB_PW")"

log "DVWA 데이터베이스 및 계정 생성"
mysql_root "$MYSQL_BIN" <<SQL
DROP DATABASE IF EXISTS test;
CREATE DATABASE IF NOT EXISTS dvwa CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP USER IF EXISTS 'dvwa_user'@'localhost';
CREATE USER 'dvwa_user'@'localhost' IDENTIFIED BY '${DB_PW_SQL}';
GRANT ALL PRIVILEGES ON dvwa.* TO 'dvwa_user'@'localhost';
FLUSH PRIVILEGES;
SQL

log "DVWA 소스 설치"
run mkdir -p /var/www/html

if [[ -d "$DVWA_DIR" && ! -d "$DVWA_DIR/.git" ]]; then
  backup_dir="${DVWA_DIR}.bak.$(date +%Y%m%d%H%M%S)"
  warn "기존 ${DVWA_DIR} 디렉터리가 git 저장소가 아니므로 ${backup_dir} 로 백업합니다."
  run mv "$DVWA_DIR" "$backup_dir"
fi

if [[ -d "$DVWA_DIR/.git" ]]; then
  log "기존 DVWA 저장소 업데이트"
  run git config --global --add safe.directory "$DVWA_DIR" || true
  run git -C "$DVWA_DIR" fetch --all --tags
  run git -C "$DVWA_DIR" reset --hard origin/master || run git -C "$DVWA_DIR" reset --hard origin/main
else
  run git clone "$DVWA_REPO" "$DVWA_DIR"
fi

log "DVWA config.inc.php 생성 및 DB 접속 정보 반영"
run cp "$DVWA_DIR/config/config.inc.php.dist" "$DVWA_DIR/config/config.inc.php"

run env DB_PW="$DB_PW" DVWA_DIR="$DVWA_DIR" python3 - <<'PY'
from pathlib import Path
import os
import re

cfg = Path(os.environ["DVWA_DIR"]) / "config" / "config.inc.php"
s = cfg.read_text(encoding="utf-8")

def php_single(value: str) -> str:
    return value.replace("\\", "\\\\").replace("'", "\\'")

values = {
    "db_server": "127.0.0.1",
    "db_database": "dvwa",
    "db_user": "dvwa_user",
    "db_password": os.environ["DB_PW"],
    "db_port": "3306",
}

for key, value in values.items():
    pattern = r"\$_DVWA\[\s*'" + re.escape(key) + r"'\s*\]\s*=\s*'[^']*';"
    line = "$_DVWA[ '{}' ] = '{}';".format(key, php_single(value))
    s, count = re.subn(pattern, lambda _m, line=line: line, s)
    if count == 0:
        if "?>" in s:
            s = s.replace("?>", line + "\n?>", 1)
        else:
            s += "\n" + line + "\n"

cfg.write_text(s, encoding="utf-8")
PY

log "DVWA용 PHP 설정 적용"
run tee /etc/php.d/99-dvwa-lab.ini >/dev/null <<'EOF_PHP'
; DVWA lab only. Do not use on a public production server.
allow_url_fopen = On
allow_url_include = On
display_errors = On
EOF_PHP

log "DVWA 권한 및 SELinux 컨텍스트 설정"
run mkdir -p "$DVWA_DIR/external/phpids/0.6/lib/IDS/tmp" "$DVWA_DIR/hackable/uploads"
run touch "$DVWA_DIR/external/phpids/0.6/lib/IDS/tmp/phpids_log.txt"
run chown -R apache:apache "$DVWA_DIR"
run find "$DVWA_DIR" -type d -exec chmod 755 {} \;
run find "$DVWA_DIR" -type f -exec chmod 644 {} \;
run chmod 775 "$DVWA_DIR/config" "$DVWA_DIR/hackable/uploads" "$DVWA_DIR/external/phpids/0.6/lib/IDS/tmp" || true
run chmod 664 "$DVWA_DIR/external/phpids/0.6/lib/IDS/tmp/phpids_log.txt" || true

if command -v getenforce >/dev/null 2>&1 && [[ "$(getenforce)" != "Disabled" ]]; then
  run setsebool -P httpd_can_network_connect 1 || true
  run setsebool -P httpd_unified 1 || true
  run restorecon -R "$DVWA_DIR" || true
fi

log "방화벽 HTTP 오픈 및 웹 서비스 재시작"
if command -v firewall-cmd >/dev/null 2>&1 && run firewall-cmd --state >/dev/null 2>&1; then
  run firewall-cmd --permanent --add-service=http
  run firewall-cmd --reload
else
  warn "firewalld가 실행 중이 아니어서 방화벽 설정은 건너뜁니다."
fi

run systemctl restart mariadb
run systemctl restart php-fpm || true
run systemctl restart httpd

cat <<EOF_DONE
==================================================================
[완료] DVWA 설치 스크립트가 끝났습니다.
==================================================================
▶ DVWA DB 이름: dvwa
▶ DVWA DB 사용자: dvwa_user
▶ DVWA DB 비밀번호: ${DB_PW}
▶ MariaDB root 접속: sudo mariadb
==================================================================
브라우저에서 다음 주소로 접속하세요.
  http://[로키_아이피]/dvwa/setup.php

접속 후 맨 아래 [Create / Reset Database] 버튼을 누르면 마무리됩니다.
주의: DVWA는 취약점 실습용입니다. 공인 IP에 그대로 노출하지 마세요.
==================================================================
EOF_DONE
