#!/usr/bin/env bash
# ============================================================================
# Sakura — one-line installer (P2, Takahashi 2026-10-24)
#
#   curl -fsSL https://sakura.moklav.co.jp/install.sh | bash
#
# or with options:
#   bash install.sh --panel-host panel.example.com --email admin@example.com
#   bash install.sh --source /path/to/sakura-panel.tar.gz
#
# Installs the FULL stack on a clean Debian 12 / Ubuntu 22.04-24.04 server:
# nginx + PHP-FPM, MariaDB, Postfix + Dovecot mail, its own PowerDNS,
# Let's Encrypt, and the Sakura itself (Node 20 + PM2).
# Everything is the panel's own — no external panel or service is required.
# Re-running on an installed server performs an upgrade (data is kept).
# ============================================================================
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive

PANEL_HOST=""
ADMIN_EMAIL=""
SOURCE="${SAKURA_SOURCE:-}"
LOG=/var/log/sakura-install.log
APP_DIR=/opt/webpanel
CREDS_FILE=/root/sakura-credentials.txt

while [ $# -gt 0 ]; do
  case "$1" in
    --panel-host) PANEL_HOST="$2"; shift 2 ;;
    --email) ADMIN_EMAIL="$2"; shift 2 ;;
    --source) SOURCE="$2"; shift 2 ;;
    *) echo "Unknown option: $1" >&2; exit 1 ;;
  esac
done

mkdir -p "$(dirname "$LOG")"
exec > >(tee -a "$LOG") 2>&1
step() { echo; echo "== $* =="; }

[ "$(id -u)" = "0" ] || { echo "ERROR: run as root."; exit 1; }
. /etc/os-release
case "$ID:$VERSION_ID" in
  debian:12|ubuntu:22.04|ubuntu:24.04) echo "OS: $PRETTY_NAME ✓" ;;
  *) echo "WARNING: untested OS ($PRETTY_NAME). Sakura targets Debian 12 / Ubuntu 22.04+." ;;
esac

SERVER_IP="$(curl -fsS -4 https://ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')"
[ -n "$PANEL_HOST" ] || PANEL_HOST="$(hostname -f 2>/dev/null || echo "$SERVER_IP")"
case "$PANEL_HOST" in *.*) : ;; *) PANEL_HOST="$SERVER_IP" ;; esac
MAIL_HOST="mail.$PANEL_HOST"
[ -n "$ADMIN_EMAIL" ] || ADMIN_EMAIL="admin@$PANEL_HOST"
echo "Panel host: $PANEL_HOST   Mail host: $MAIL_HOST   Server IP: $SERVER_IP"

FRESH=1
[ -f "$APP_DIR/data/webpanel.db" ] && FRESH=0
[ "$FRESH" = "0" ] && echo "Existing installation detected — upgrade mode (data kept)."

# Preflight: the full stack (4 PHP versions, ClamAV, PostgreSQL, MariaDB) needs room.
AVAIL_GB="$(df -BG / | awk 'NR==2 {gsub("G","",$4); print $4}')"
if [ "${AVAIL_GB:-0}" -lt 6 ] && [ ! -f "$APP_DIR/data/webpanel.db" ]; then
  echo "ERROR: less than 6GB free on /. Sakura needs a disk of at least 8GB."; exit 1
fi

step "Base packages"
apt-get update -qq
apt-get install -y -qq curl git rsync ca-certificates gnupg unzip tar sqlite3 openssl \
  build-essential python3 rsync dnsutils > /dev/null

step "Node.js 20"
if ! node -v 2>/dev/null | grep -qE '^v(18|20|22)\.'; then
  curl -fsSL https://deb.nodesource.com/setup_20.x | bash - > /dev/null 2>&1
  apt-get install -y -qq nodejs > /dev/null
fi
node -v; npm -v

step "Stack packages (nginx, MariaDB, mail, PowerDNS, PHP, certbot)"
echo "postfix postfix/main_mailer_type select Internet Site" | debconf-set-selections
echo "postfix postfix/mailname string $MAIL_HOST" | debconf-set-selections
apt-get install -y -qq nginx mariadb-server \
  postfix opendkim opendkim-tools redis-server dovecot-imapd dovecot-lmtpd dovecot-sieve dovecot-managesieved \
  pdns-server pdns-backend-sqlite3 certbot \
  > /dev/null
# Multi-version PHP (PHP Selector) via sury.org: 8.1 / 8.2 / 8.3 / 8.4 side by side
apt-get install -y -qq apt-transport-https lsb-release ca-certificates curl gnupg2 > /dev/null 2>&1 || true
curl -fsSL https://packages.sury.org/php/apt.gpg -o /etc/apt/trusted.gpg.d/php-sury.gpg 2>/dev/null || true
echo "deb https://packages.sury.org/php/ $(lsb_release -sc 2>/dev/null || echo bookworm) main" > /etc/apt/sources.list.d/php-sury.list
apt-get update -qq > /dev/null 2>&1 || true
for v in 8.1 8.2 8.3 8.4; do
  apt-get install -y -qq php$v-fpm php$v-cli php$v-mysql php$v-xml php$v-mbstring php$v-curl php$v-zip php$v-gd php$v-intl php$v-bcmath php$v-opcache php$v-sqlite3 php$v-redis > /dev/null 2>&1 || echo "WARN: PHP $v install failed (selector will offer the rest)"
  systemctl enable php$v-fpm > /dev/null 2>&1 || true
done
# Bundled web apps: phpMyAdmin + Roundcube webmail (served from the panel vhost)
echo "phpmyadmin phpmyadmin/dbconfig-install boolean false" | debconf-set-selections
echo "roundcube-core roundcube/dbconfig-install boolean false" | debconf-set-selections
apt-get install -y -qq phpmyadmin roundcube roundcube-sqlite3 sqlite3 > /dev/null 2>&1 || echo "WARN: phpMyAdmin/Roundcube install failed"
apt-get install -y -qq clamav > /dev/null 2>&1 || echo "WARN: ClamAV install failed"
apt-get install -y -qq postgresql > /dev/null 2>&1 || echo "WARN: PostgreSQL install failed"
(freshclam > /dev/null 2>&1 &)
if [ -d /usr/share/phpmyadmin ]; then
  BLOW="$(head -c 48 /dev/urandom | base64 | tr -d '\n' | head -c 32)"
  printf "<?php\n\$cfg['blowfish_secret'] = '%s';\n\$i = 0;\n\$i++;\n\$cfg['Servers'][\$i]['auth_type'] = 'cookie';\n\$cfg['Servers'][\$i]['host'] = 'localhost';\n\$cfg['Servers'][\$i]['compress'] = false;\n\$cfg['Servers'][\$i]['AllowNoPassword'] = false;\n\$cfg['UploadDir'] = '';\n\$cfg['SaveDir'] = '';\n" "$BLOW" > /etc/phpmyadmin/config.inc.php
  chmod 640 /etc/phpmyadmin/config.inc.php; chown root:www-data /etc/phpmyadmin/config.inc.php 2>/dev/null || true
fi
if [ -d /usr/share/roundcube ]; then
  mkdir -p /var/lib/roundcube
  DESK="$(head -c 32 /dev/urandom | base64 | tr -d '\n' | head -c 24)"
  printf "<?php\n\$config['db_dsnw'] = 'sqlite:////var/lib/roundcube/roundcube.sqlite?mode=0646';\n\$config['default_host'] = 'localhost';\n\$config['default_port'] = 143;\n\$config['smtp_server'] = 'localhost';\n\$config['smtp_port'] = 587;\n\$config['smtp_user'] = '%%u';\n\$config['smtp_pass'] = '%%p';\n\$config['product_name'] = 'Sakura Webmail';\n\$config['des_key'] = '%s';\n\$config['plugins'] = ['archive', 'zipdownload', 'managesieve'];\n\$config['language'] = 'ja_JP';\n\$config['enable_installer'] = false;\n" "$DESK" > /etc/roundcube/config.inc.php
  chmod 640 /etc/roundcube/config.inc.php; chown root:www-data /etc/roundcube/config.inc.php 2>/dev/null || true
  [ -s /var/lib/roundcube/roundcube.sqlite ] || sqlite3 /var/lib/roundcube/roundcube.sqlite < /usr/share/roundcube/SQL/sqlite.initial.sql 2>/dev/null || true
  chown -R www-data:www-data /var/lib/roundcube 2>/dev/null || true
fi
npm install -g pm2 > /dev/null 2>&1 || npm install -g pm2 || { echo "ERROR: pm2 install failed."; exit 1; }
PHP_FPM_SERVICE="$(systemctl list-unit-files --no-legend 2>/dev/null | awk '/php.*-fpm\.service/ {print $1; exit}' || true)"
echo "PHP-FPM service: ${PHP_FPM_SERVICE:-unknown}"

step "Panel source"
SRC_DIR=""
if [ -z "$SOURCE" ]; then
  SOURCE="https://sakura.moklav.co.jp/sakura-panel.tar.gz"
fi
TMPD="$(mktemp -d)"
if [ -d "$SOURCE" ]; then
  SRC_DIR="$SOURCE"
elif [ -f "$SOURCE" ]; then
  tar -xzf "$SOURCE" -C "$TMPD"
  SRC_DIR="$(find "$TMPD" -maxdepth 2 -name package.json -printf '%h\n' | head -1)"
else
  echo "Downloading $SOURCE"
  curl -fsSL "$SOURCE" -o "$TMPD/src.tar.gz" || { echo "ERROR: cannot download the panel source. Pass --source <dir|tarball|url>."; exit 1; }
  tar -xzf "$TMPD/src.tar.gz" -C "$TMPD"
  SRC_DIR="$(find "$TMPD" -maxdepth 2 -name package.json -printf '%h\n' | head -1)"
fi
[ -n "$SRC_DIR" ] && [ -f "$SRC_DIR/package.json" ] || { echo "ERROR: panel source not found (no package.json)."; exit 1; }
mkdir -p "$APP_DIR"
rsync -a --exclude data --exclude node_modules --exclude pdns.json --exclude dbadmin.json \
  --exclude sakura.json --exclude tests "$SRC_DIR"/ "$APP_DIR"/
mkdir -p "$APP_DIR/data/www" "$APP_DIR/data/home" "$APP_DIR/data/apps" \
  "$APP_DIR/data/backups" "$APP_DIR/data/db-files" "$APP_DIR/data/ssl"
cd "$APP_DIR"
npm install --omit=dev --no-audit --no-fund 2>&1 | tail -1

ADMIN_PW=""; DBADMIN_PW=""; PDNS_KEY=""
if [ "$FRESH" = "1" ]; then
  ADMIN_PW="$(openssl rand -hex 9)"
  DBADMIN_PW="$(openssl rand -hex 18)"
  PDNS_KEY="$(openssl rand -hex 20)"
  umask 077
  cat > "$CREDS_FILE" <<EOF
Sakura credentials (generated at install — keep this file safe)
panel-admin: admin / $ADMIN_PW
mariadb-sakura-admin: $DBADMIN_PW
powerdns-api-key: $PDNS_KEY
EOF
  chmod 600 "$CREDS_FILE"
fi

step "MariaDB"
systemctl enable --now mariadb > /dev/null 2>&1 || systemctl enable --now mysql > /dev/null 2>&1 || true
if [ "$FRESH" = "1" ]; then
  mysql -u root <<SQL
CREATE USER IF NOT EXISTS 'sakura'@'localhost' IDENTIFIED BY '$DBADMIN_PW';
ALTER USER 'sakura'@'localhost' IDENTIFIED BY '$DBADMIN_PW';
GRANT ALL PRIVILEGES ON *.* TO 'sakura'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
SQL
  printf '{"user":"sakura","password":"%s","host":"localhost"}' "$DBADMIN_PW" > "$APP_DIR/dbadmin.json"
  chmod 600 "$APP_DIR/dbadmin.json"
fi

step "PowerDNS (the panel's own DNS server)"
if [ "$FRESH" = "1" ]; then
  mkdir -p /var/lib/powerdns
  if [ ! -f /var/lib/powerdns/pdns.sqlite3 ]; then
    sqlite3 /var/lib/powerdns/pdns.sqlite3 < /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql 2>/dev/null \
      || zcat /usr/share/doc/pdns-backend-sqlite3/schema.sqlite3.sql.gz 2>/dev/null | sqlite3 /var/lib/powerdns/pdns.sqlite3 \
      || true
  fi
  chown -R pdns:pdns /var/lib/powerdns 2>/dev/null || true
  cat > /etc/powerdns/pdns.conf <<EOF
launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
local-address=0.0.0.0, ::
api=yes
api-key=$PDNS_KEY
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081
webserver-allow-from=127.0.0.1
master=yes
EOF
  printf '{"url":"http://127.0.0.1:8081","apiKey":"%s","serverIp":"%s","mailHost":"%s"}' \
    "$PDNS_KEY" "$SERVER_IP" "$MAIL_HOST" > "$APP_DIR/pdns.json"
  chmod 600 "$APP_DIR/pdns.json"
fi
# Free port 53: systemd-resolved's stub listener blocks the authoritative server.
# (Checked via the service, not ss output — the stub shows as 127.0.0.53%lo:53.)
if systemctl is-active --quiet systemd-resolved; then
  echo "Disabling the systemd-resolved stub listener so PowerDNS can bind port 53…"
  if grep -q '127\.0\.0\.53' /etc/resolv.conf 2>/dev/null; then
    rm -f /etc/resolv.conf
    printf 'nameserver 1.1.1.1\nnameserver 8.8.8.8\n' > /etc/resolv.conf
  fi
  mkdir -p /etc/systemd/resolved.conf.d
  printf '[Resolve]\nDNSStubListener=no\n' > /etc/systemd/resolved.conf.d/sakura-nostub.conf
  # systemd 252 quirk: the drop-in alone is not always honored for this knob;
  # set it in the main file too (found on the tpl-debian-12 demo rebuild).
  sed -i 's/^#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf 2>/dev/null || true
  grep -q '^DNSStubListener=no' /etc/systemd/resolved.conf 2>/dev/null || printf '\n[Resolve]\nDNSStubListener=no\n' >> /etc/systemd/resolved.conf
  systemctl restart systemd-resolved 2>/dev/null || true
  # The stub release is racy on fresh boots — wait until port 53 is really free.
  for i in $(seq 1 20); do
    ss -lntup 2>/dev/null | grep -qE '127[.]0[.]0[.]5[34]' || break
    sleep 1
  done
fi
systemctl enable pdns > /dev/null 2>&1 || true
if ! systemctl restart pdns; then
  sleep 3
  if ! systemctl restart pdns; then
    echo "ERROR: PowerDNS failed to start — last log lines:"; journalctl -u pdns --no-pager -n 15 | tail -10; exit 1
  fi
fi

step "Panel TLS certificate"
CERT=""; KEYF=""
if getent hosts "$PANEL_HOST" | grep -q "$SERVER_IP"; then
  echo "Panel host resolves here — requesting a Let's Encrypt certificate…"
  systemctl stop nginx 2>/dev/null || true
  if certbot certonly --standalone -d "$PANEL_HOST" --non-interactive --agree-tos --email "$ADMIN_EMAIL" > /dev/null 2>&1; then
    CERT="/etc/letsencrypt/live/$PANEL_HOST/fullchain.pem"
    KEYF="/etc/letsencrypt/live/$PANEL_HOST/privkey.pem"
    systemctl enable --now certbot.timer > /dev/null 2>&1 || true
  fi
  systemctl start nginx 2>/dev/null || true
fi
if [ -z "$CERT" ]; then
  echo "Using a self-signed certificate (add DNS for $PANEL_HOST and re-run, or issue one later)."
  mkdir -p /etc/ssl/sakura
  SAN="DNS:$PANEL_HOST"
  [ "$PANEL_HOST" = "$SERVER_IP" ] && SAN="IP:$SERVER_IP"
  openssl req -x509 -newkey rsa:2048 -nodes -days 825 \
    -keyout /etc/ssl/sakura/panel.key -out /etc/ssl/sakura/panel.crt \
    -subj "/CN=$PANEL_HOST" -addext "subjectAltName=$SAN" > /dev/null 2>&1
  CERT="/etc/ssl/sakura/panel.crt"; KEYF="/etc/ssl/sakura/panel.key"
fi
# Mail certificate (self-signed for the mail host in v1; upgradeable any time)
mkdir -p /etc/ssl/sakura
if [ ! -f /etc/ssl/sakura/mail.crt ]; then
  openssl req -x509 -newkey rsa:2048 -nodes -days 825 \
    -keyout /etc/ssl/sakura/mail.key -out /etc/ssl/sakura/mail.crt \
    -subj "/CN=$MAIL_HOST" -addext "subjectAltName=DNS:$MAIL_HOST" > /dev/null 2>&1
fi

step "nginx panel proxy (8443)"
cat > /etc/nginx/sites-available/sakura-panel.conf <<EOF
# Sakura TLS entry — the Node app binds loopback only.
server {
    listen 8443 ssl;
    listen [::]:8443 ssl;
    server_name $PANEL_HOST;
    ssl_certificate $CERT;
    ssl_certificate_key $KEYF;
    ssl_protocols TLSv1.2 TLSv1.3;
    client_max_body_size 64m;
    location /phpmyadmin/ {
        alias /usr/share/phpmyadmin/;
        index index.php;
        location ~ [.]php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME \$request_filename;
            fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        }
    }
    location /webmail/ {
        alias /var/lib/roundcube/public_html/;
        index index.php;
        location ~ [.]php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME \$request_filename;
            fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        }
    }
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Panel-Request 1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
EOF
ln -sf /etc/nginx/sites-available/sakura-panel.conf /etc/nginx/sites-enabled/sakura-panel.conf
# Firewall boot persistence (rules are managed from the panel, W4)
cat > /etc/systemd/system/sakura-firewall.service <<'FWUNIT'
[Unit]
Description=Sakura firewall rules
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/nft -f /etc/sakura-firewall.nft
RemainAfterExit=yes
ConditionPathExists=/etc/sakura-firewall.nft
[Install]
WantedBy=multi-user.target
FWUNIT
systemctl daemon-reload 2>/dev/null || true
systemctl enable sakura-firewall.service > /dev/null 2>&1 || true
nginx -t && systemctl enable --now nginx > /dev/null 2>&1 && systemctl reload nginx

step "Mail stack (Postfix + Dovecot)"
if [ "$FRESH" = "1" ]; then
  groupadd -g 5000 vmail 2>/dev/null || true
  useradd -u 5000 -g vmail -d /var/vmail -M -s /usr/sbin/nologin vmail 2>/dev/null || true
  mkdir -p /var/vmail && chown vmail:vmail /var/vmail && chmod 750 /var/vmail
  postconf -e "myhostname=$MAIL_HOST" "mydestination=localhost" "mynetworks=127.0.0.0/8 [::1]/128" \
    "virtual_mailbox_base=/var/vmail" "virtual_mailbox_domains=hash:/etc/postfix/sakura-vdomains" \
    "virtual_mailbox_maps=hash:/etc/postfix/sakura-vmailbox" "virtual_alias_maps=hash:/etc/postfix/sakura-valias" \
    "virtual_uid_maps=static:5000" "virtual_gid_maps=static:5000" \
    "virtual_transport=lmtp:unix:private/dovecot-lmtp" \
    "smtpd_sasl_type=dovecot" "smtpd_sasl_path=private/auth" "smtpd_sasl_auth_enable=yes" \
    "smtpd_recipient_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination" \
    "smtpd_tls_cert_file=/etc/ssl/sakura/mail.crt" "smtpd_tls_key_file=/etc/ssl/sakura/mail.key" \
    "smtpd_tls_security_level=may" "message_size_limit=26214400"
  postconf -M "submission/inet=submission" && postconf -P "submission/inet/smtpd_tls_security_level=encrypt" \
    && postconf -P "submission/inet/smtpd_sasl_auth_enable=yes"
  postconf -M "smtps/inet=smtps" && postconf -P "smtps/inet/smtpd_tls_wrappermode=yes" \
    && postconf -P "smtps/inet/smtpd_sasl_auth_enable=yes"
  cat > /etc/dovecot/local.conf <<'DOVE'
protocols = imap lmtp sieve
mail_location = maildir:/var/vmail/%d/%n/Maildir
passdb {
  driver = passwd-file
  args = scheme=BLF-CRYPT /etc/dovecot/sakura-users
}
userdb {
  driver = passwd-file
  args = /etc/dovecot/sakura-users
}
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}
protocol lmtp {
  mail_plugins = $mail_plugins sieve
}
plugin {
  sieve = file:~/sieve;active=~/.dovecot.sieve
}
ssl = yes
ssl_cert = </etc/ssl/sakura/mail.crt
ssl_key = </etc/ssl/sakura/mail.key
ssl_min_protocol = TLSv1.2
DOVE
  touch /etc/postfix/sakura-vdomains /etc/postfix/sakura-vmailbox /etc/postfix/sakura-valias /etc/dovecot/sakura-users
  postmap /etc/postfix/sakura-vmailbox /etc/postfix/sakura-valias || true
  chown vmail:vmail /etc/dovecot/sakura-users; chmod 640 /etc/dovecot/sakura-users
fi
systemctl enable --now postfix dovecot > /dev/null 2>&1 || true
systemctl restart postfix dovecot || true

step "Panel configuration + admin user"
printf '{"acmeEmail":"%s"}' "$ADMIN_EMAIL" > "$APP_DIR/sakura.json"
chmod 600 "$APP_DIR/sakura.json"
if [ "$FRESH" = "1" ]; then
  (cd "$APP_DIR" && SAKURA_PRODUCTION=1 SAKURA_ADMIN_PASSWORD="$ADMIN_PW" node src/seed.js)
fi
[ -n "${PHP_FPM_SERVICE:-}" ] && systemctl enable --now "$PHP_FPM_SERVICE" > /dev/null 2>&1 || true

step "Start the panel (PM2)"
cd "$APP_DIR"
pm2 delete webpanel > /dev/null 2>&1 || true
pm2 start src/server.js --name webpanel > /dev/null
pm2 save > /dev/null
STARTUP_CMD="$(pm2 startup systemd -u root --hp /root 2>/dev/null | grep -o 'env PATH=[^ ]* pm2 startup systemd[^"]*' | head -1 || true)"
[ -n "$STARTUP_CMD" ] && eval "$STARTUP_CMD" > /dev/null 2>&1 || true
pm2 save > /dev/null
sleep 2
HEALTH="$(curl -s -m 8 http://127.0.0.1:3000/api/health || true)"
echo "Health: $HEALTH"

step "DONE"
cat <<EOF

  Sakura is installed — a full hosting stack of its own:
    Web: nginx + PHP-FPM    Mail: Postfix + Dovecot    DNS: PowerDNS (own)
    DB:  MariaDB            SSL:  Let's Encrypt / self-signed

  Panel:   https://$PANEL_HOST:8443
  Docs:    https://$PANEL_HOST:8443/api-docs
  Login:   admin / (see $CREDS_FILE)
EOF
if [ "$FRESH" = "1" ]; then
  echo "  Password: $ADMIN_PW"
fi
cat <<EOF

  Open these ports in your firewall: 22, 25, 80, 443, 465, 587, 993, 8443, 53 (tcp+udp).
  Install log: $LOG
EOF
