#!/usr/bin/env bash
# Roundcube webmail installer/updater for Limristem eMail.
#
# Runs as root via sudo from the panel. Three properties this script has to keep:
#   * the downloaded tarball is verified against Roundcube's release key before a
#     single file of it is extracted (see verify_release_signature);
#   * nothing is staged through a predictable path in /tmp — root writing to an
#     attacker-chosen name there is a local privilege escalation;
#   * config.inc.php holds the webmail DB password and des_key (the key Roundcube
#     encrypts the user's IMAP password with), so it is never world-readable.
set -euo pipefail

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=/dev/null
source "$SCRIPT_DIR/libenv.sh"

ENV_FILE=$(limristem_mail_resolve_main_env_file)
BASE_DIR=${LIMRISTEM_MAIL_BASE_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}
WEBMAIL_DIR=/var/www/roundcube
STATE_FILE=$BASE_DIR/config/webmail.json
WEBMAIL_DB=limristem_roundcube
WEBMAIL_DB_USER=roundcube
# 1.7.3 is a security release (SSRF filter bypasses, LDAP filter injection); do not
# move this back to 1.7.2 or earlier.
DEFAULT_ROUNDCUBE_VERSION="1.7.3"
# Roundcube Developers <devs@roundcube.net>. Primary key fingerprint, cross-checked
# against roundcube.net/download/pubkey.asc and keyserver.ubuntu.com; the same key has
# signed every release tarball from 1.6.0 onwards.
ROUNDCUBE_PRIMARY_FPR="F3E4C04BB3DB5D4215C45F7F5AB2BAA141C4F7D5"

WORKDIR=""
cleanup_workdir() {
  [[ -n "$WORKDIR" && -d "$WORKDIR" ]] && rm -rf "$WORKDIR"
  WORKDIR=""
}
trap cleanup_workdir EXIT

usage() {
  cat <<'EOF'
Usage:
  manage-webmail.sh status [--json]
  manage-webmail.sh install [--version <version>] [--reset-db] [--json]
  manage-webmail.sh upgrade [--json]
  manage-webmail.sh set-auto-update <yes|no> [--json]
  manage-webmail.sh remove [--json]

install/upgrade verify the release tarball against Roundcube's OpenPGP release key
before extracting it. Without a good signature nothing is installed.
EOF
}

load_env() {
  limristem_mail_load_env_file "$ENV_FILE"
}

require_root() {
  if [[ $EUID -ne 0 ]]; then
    echo "Run as root." >&2
    exit 1
  fi
}

fail() {
  echo "[Webmail] $*" >&2
  exit 1
}

note() {
  echo "[Webmail] $*" >&2
}

json_flag() {
  if [[ "${1:-}" == "--json" || "${2:-}" == "--json" || "${3:-}" == "--json" ]]; then
    printf 'yes\n'
  else
    printf 'no\n'
  fi
}

make_workdir() {
  cleanup_workdir
  # mktemp -d, never a fixed /tmp name: this script runs as root, and a predictable
  # path in a world-writable directory lets a local user pre-create it (or plant a
  # symlink) and have root write through it.
  WORKDIR=$(mktemp -d /tmp/limristem-webmail.XXXXXXXX)
  chmod 0700 "$WORKDIR"
}

is_valid_version() {
  [[ "${1:-}" =~ ^[0-9]{1,3}(\.[0-9]{1,4}){1,3}$ ]]
}

roundcube_key_path() {
  local candidate
  for candidate in \
    "$BASE_DIR/templates/roundcube/roundcube-release-key.asc" \
    "$SCRIPT_DIR/../templates/roundcube/roundcube-release-key.asc"
  do
    if [[ -f "$candidate" ]]; then
      printf '%s\n' "$candidate"
      return 0
    fi
  done
  return 1
}

fetch_latest_roundcube_version() {
  local candidate
  candidate=$(python3 - <<'PY' 2>/dev/null || true
import json
import urllib.request

try:
    request = urllib.request.Request(
        "https://api.github.com/repos/roundcube/roundcubemail/releases/latest",
        headers={"User-Agent": "limristem-mail"},
    )
    with urllib.request.urlopen(request, timeout=10) as response:
        tag = str(json.loads(response.read()).get("tag_name", "")).lstrip("v").strip()
    if tag:
        print(tag)
except Exception:
    pass
PY
  )
  if is_valid_version "$candidate"; then
    printf '%s\n' "$candidate"
  else
    printf '%s\n' "$DEFAULT_ROUNDCUBE_VERSION"
  fi
}

detect_php_fpm_sock() {
  local sock attempt
  # The socket appears a moment after "systemctl enable --now php*-fpm" returns.
  for attempt in 1 2 3 4 5 6 7 8 9 10; do
    for sock in /run/php/php*-fpm.sock /var/run/php/php*-fpm.sock /run/php/php-fpm.sock; do
      if [[ -S "$sock" ]]; then
        printf '%s\n' "$sock"
        return 0
      fi
    done
    sleep 1
  done
  return 1
}

detect_installed_version() {
  local composer_json=$WEBMAIL_DIR/composer.json
  local source=""
  if [[ -f "$composer_json" ]]; then
    source=$composer_json
  elif [[ -f "$STATE_FILE" ]]; then
    source=$STATE_FILE
  fi
  if [[ -z "$source" ]]; then
    printf '%s\n' "$DEFAULT_ROUNDCUBE_VERSION"
    return 0
  fi
  python3 - "$source" "$DEFAULT_ROUNDCUBE_VERSION" <<'PY' 2>/dev/null || printf '%s\n' "$DEFAULT_ROUNDCUBE_VERSION"
import json
import sys

try:
    with open(sys.argv[1], encoding="utf-8") as handle:
        print(json.load(handle).get("version", sys.argv[2]))
except Exception:
    print(sys.argv[2])
PY
}

get_status_json() {
  local installed="no" active="no" ver="" auto_update
  auto_update=${LIMRISTEM_MAIL_WEBMAIL_AUTO_UPDATE:-no}
  if [[ -f "$WEBMAIL_DIR/index.php" ]]; then
    installed="yes"
    ver=$(detect_installed_version)
    if systemctl is-active --quiet nginx && (systemctl is-active --quiet "php*-fpm" || systemctl is-active --quiet php8.4-fpm || systemctl is-active --quiet php8.2-fpm || systemctl is-active --quiet php-fpm); then
      active="yes"
    fi
  fi

  python3 - "$installed" "$active" "$ver" "$auto_update" <<'PY'
import json
import sys

installed = sys.argv[1] == "yes"
active = sys.argv[2] == "yes"
version = sys.argv[3]
auto_update = sys.argv[4]

print(json.dumps({
    "installed": installed,
    "active": active,
    "version": version if installed else None,
    "auto_update": auto_update,
    "url_mail": "/mail",
    "url_webmail": "/webmail",
    "webmail_dir": "/var/www/roundcube",
}))
PY
}

install_php_stack() {
  note "Installo PHP-FPM e i pacchetti richiesti dal webmail..."
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq || true

  # php-fpm FIRST, on its own. Several php-* packages depend on the "php" metapackage,
  # whose dependency is "libapache2-mod-php | php-fpm | php-cgi"; apt picks the first
  # alternative unless one of the others is already installed. Installing php-fpm up
  # front is what stops Apache from being dragged in — and Apache binds :80, which
  # breaks nginx and makes the Let's Encrypt standalone challenge fail.
  apt-get install -y -qq --no-install-recommends php-fpm \
    || fail "Installazione di php-fpm non riuscita."
  # No "|| true" here either: a half-installed PHP stack produces a webmail that
  # reports success and then serves a blank page.
  apt-get install -y -qq --no-install-recommends php-cli php-mysql php-xml php-mbstring \
    php-intl php-zip php-gd php-curl php-imagick curl tar gnupg \
    || fail "Installazione dello stack PHP non riuscita."
  systemctl enable --now php*-fpm 2>/dev/null || true
  neutralize_apache_on_port_80
}

neutralize_apache_on_port_80() {
  # Belt and braces: if Apache slipped in anyway (a recommends chain, or it was already
  # on the box), it will hold :80 and keep nginx from starting. This host serves the
  # panel and the webmail through nginx, so Apache is stopped and disabled — not purged,
  # so the operator can undo it if it was there on purpose.
  dpkg -l apache2 2>/dev/null | grep -q "^ii" || return 0
  if ! ss -tlnp 2>/dev/null | grep -q '"apache2"'; then
    return 0
  fi
  note "AVVISO: apache2 occupa la porta 80 e impedisce a nginx di partire: lo fermo e disabilito."
  systemctl disable --now apache2 >/dev/null 2>&1 || true
  systemctl start nginx >/dev/null 2>&1 || true
}

# ---------------------------------------------------------------------------
# Download + signature verification
# ---------------------------------------------------------------------------

download_release() {
  local version=$1
  local base="https://github.com/roundcube/roundcubemail/releases/download/${version}"
  local tarball="$WORKDIR/roundcubemail.tar.gz"

  note "Scarico Roundcube Webmail v${version}..."
  curl -fsSL --proto '=https' --tlsv1.2 -o "$tarball" \
    "${base}/roundcubemail-${version}-complete.tar.gz" \
    || fail "Download di Roundcube v${version} non riuscito."
  curl -fsSL --proto '=https' --tlsv1.2 -o "${tarball}.asc" \
    "${base}/roundcubemail-${version}-complete.tar.gz.asc" \
    || fail "Firma OpenPGP di Roundcube v${version} non disponibile: installazione interrotta."
  printf '%s\n' "$tarball"
}

verify_release_signature() {
  local tarball=$1
  local key_file gnupg_home status_file

  command -v gpg >/dev/null 2>&1 || fail "gpg non disponibile: impossibile verificare la firma del pacchetto Roundcube."
  key_file=$(roundcube_key_path) \
    || fail "Chiave di firma Roundcube non trovata (templates/roundcube/roundcube-release-key.asc)."

  gnupg_home="$WORKDIR/gnupg"
  mkdir -p "$gnupg_home"
  chmod 0700 "$gnupg_home"
  status_file="$WORKDIR/gpg-status"

  GNUPGHOME="$gnupg_home" gpg --batch --quiet --import "$key_file" 2>/dev/null \
    || fail "Import della chiave di firma Roundcube non riuscito."

  GNUPGHOME="$gnupg_home" gpg --batch --status-file "$status_file" \
    --verify "${tarball}.asc" "$tarball" >/dev/null 2>&1 || true

  # VALIDSIG reports the signing subkey first and the primary key fingerprint last;
  # pin the primary so a rotated subkey keeps working but a foreign key does not.
  awk -v fpr="$ROUNDCUBE_PRIMARY_FPR" \
    '$2 == "VALIDSIG" && $NF == fpr { ok = 1 } END { exit !ok }' "$status_file" \
    || fail "Firma del pacchetto Roundcube non valida o non emessa dalla chiave attesa ($ROUNDCUBE_PRIMARY_FPR): installazione interrotta."

  note "Firma OpenPGP verificata (Roundcube Developers, $ROUNDCUBE_PRIMARY_FPR)."
}

extract_release() {
  local tarball=$1
  local extract_dir="$WORKDIR/extract"
  local extracted
  mkdir -p "$extract_dir"
  # --no-same-owner: the archive is authenticated but still third-party; files land
  # under root and get their ownership assigned explicitly by apply_permissions.
  tar -xzf "$tarball" -C "$extract_dir" --no-same-owner \
    || fail "Estrazione del pacchetto Roundcube non riuscita."
  extracted=$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d -name 'roundcubemail-*' | head -n 1)
  [[ -n "$extracted" ]] || fail "Il pacchetto Roundcube non contiene la directory attesa."
  printf '%s\n' "$extracted"
}

# ---------------------------------------------------------------------------
# Database
# ---------------------------------------------------------------------------

mysql_root() {
  # SQL on stdin, never in argv: "mysql -e ...IDENTIFIED BY '<pass>'" leaks the
  # password to every local user through /proc/<pid>/cmdline.
  mysql --batch "$@"
}

webmail_db_password() {
  local db_pass
  db_pass=$(limristem_mail_get_env_value "$ENV_FILE" "LIMRISTEM_MAIL_ROUNDCUBE_DB_PASS" || true)
  if [[ -z "$db_pass" ]]; then
    db_pass=$(openssl rand -hex 16)
    limristem_mail_upsert_env_value "$ENV_FILE" "LIMRISTEM_MAIL_ROUNDCUBE_DB_PASS" "$db_pass"
  fi
  # Hex only — the value is interpolated into SQL and into a DSN below.
  [[ "$db_pass" =~ ^[0-9a-f]{16,64}$ ]] || fail "LIMRISTEM_MAIL_ROUNDCUBE_DB_PASS non è un valore esadecimale valido."
  printf '%s\n' "$db_pass"
}

webmail_db_has_schema() {
  local count
  # Statement on stdin only: with -e mysql executes the argument and ignores stdin.
  count=$(mysql_root -N <<SQL 2>/dev/null || true
SELECT COUNT(*) FROM information_schema.tables
 WHERE table_schema = '${WEBMAIL_DB}' AND table_name = 'users';
SQL
  )
  [[ "${count//[[:space:]]/}" == "1" ]]
}

ensure_mariadb_webmail_db() {
  local db_pass=$1
  local host
  local mail_db=${LIMRISTEM_MAIL_DB_NAME:-limristem-mail}
  mysql_root <<SQL || fail "Creazione del database del webmail non riuscita."
CREATE DATABASE IF NOT EXISTS \`${WEBMAIL_DB}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
SQL
  for host in localhost 127.0.0.1; do
    mysql_root <<SQL || fail "Creazione dell'utente del database del webmail non riuscita."
CREATE USER IF NOT EXISTS '${WEBMAIL_DB_USER}'@'${host}' IDENTIFIED BY '${db_pass}';
ALTER USER '${WEBMAIL_DB_USER}'@'${host}' IDENTIFIED BY '${db_pass}';
GRANT ALL PRIVILEGES ON \`${WEBMAIL_DB}\`.* TO '${WEBMAIL_DB_USER}'@'${host}';
GRANT SELECT ON \`${mail_db}\`.domains TO '${WEBMAIL_DB_USER}'@'${host}';
GRANT SELECT, UPDATE (password_hash) ON \`${mail_db}\`.accounts TO '${WEBMAIL_DB_USER}'@'${host}';
SQL
  done
  mysql_root <<'SQL' || true
FLUSH PRIVILEGES;
SQL
}

reset_webmail_db() {
  note "--reset-db: ricreo $WEBMAIL_DB da zero (i dati esistenti vengono persi)."
  mysql_root <<SQL || fail "Reset del database del webmail non riuscito."
DROP DATABASE IF EXISTS \`${WEBMAIL_DB}\`;
SQL
}

import_webmail_schema() {
  local source_dir=$1
  local schema="$source_dir/SQL/mysql.initial.sql"
  [[ -f "$schema" ]] || return 0

  if webmail_db_has_schema; then
    # Reinstalling must not wipe address books, identities and settings; the previous
    # version dropped the database unconditionally here.
    note "Schema del webmail già presente: lo conservo (usa --reset-db per ricrearlo)."
    return 0
  fi

  note "Importo lo schema iniziale di Roundcube..."
  mysql_root "$WEBMAIL_DB" < "$schema" || fail "Import dello schema del webmail non riuscito."
}

# ---------------------------------------------------------------------------
# Configuration + permissions
# ---------------------------------------------------------------------------

write_webmail_config() {
  local db_pass=$1
  local des_key
  des_key=$(openssl rand -hex 12)

  cat > "$WEBMAIL_DIR/config/config.inc.php" <<EOF
<?php
\$config = array();
\$config['db_dsnw'] = 'mysqli://${WEBMAIL_DB_USER}:${db_pass}@localhost/${WEBMAIL_DB}';
\$config['imap_host'] = 'ssl://127.0.0.1:993';
\$config['smtp_host'] = 'tls://127.0.0.1:587';
\$config['smtp_user'] = '%u';
\$config['smtp_pass'] = '%p';
// Loopback to the local Dovecot/Postfix, which present the server certificate for the
// public hostname; verifying it against "127.0.0.1" would always fail.
\$config['imap_conn_options'] = array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true,
    ),
);
\$config['smtp_conn_options'] = array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true,
    ),
);
\$config['support_url'] = '';
\$config['product_name'] = 'Limristem eMail Webmail';
\$config['des_key'] = '${des_key}';
\$config['plugins'] = array('archive', 'zipdownload', 'password', 'managesieve', 'markasjunk');
\$config['skin'] = 'elastic';
EOF

  mkdir -p "$WEBMAIL_DIR/plugins/managesieve"
  cat > "$WEBMAIL_DIR/plugins/managesieve/config.inc.php" <<'EOF'
<?php
$config['managesieve_port'] = 4190;
$config['managesieve_host'] = '127.0.0.1';
$config['managesieve_auth_type'] = 'LOGIN';
$config['managesieve_conn_options'] = array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true,
    ),
);
EOF

  local mail_db=${LIMRISTEM_MAIL_DB_NAME:-limristem-mail}
  mkdir -p "$WEBMAIL_DIR/plugins/password"
  cat > "$WEBMAIL_DIR/plugins/password/config.inc.php" <<EOF
<?php
\$config['password_driver'] = 'sql';
\$config['password_confirm_current'] = true;
\$config['password_minimum_length'] = 8;
\$config['password_require_nonalpha'] = false;
\$config['password_log'] = false;
\$config['password_db_dsn'] = 'mysqli://${WEBMAIL_DB_USER}:${db_pass}@localhost/${mail_db}';
\$config['password_query'] = "UPDATE \`${mail_db}\`.accounts a JOIN \`${mail_db}\`.domains d ON a.domain_id = d.id SET a.password_hash = %P WHERE (a.username = %u OR (a.local_part = %l AND d.name = %d)) AND a.is_active = 1 AND d.is_active = 1";
\$config['password_algorithm'] = 'argon2id';
EOF
}

apply_permissions() {
  local web_user=www-data
  id "$web_user" >/dev/null 2>&1 || web_user=root

  # Nothing world-readable: config.inc.php carries the webmail DB password and des_key,
  # the key Roundcube encrypts the session's IMAP password with. The previous
  # "chmod -R 755" published both to every local account on the host.
  chown -R "root:${web_user}" "$WEBMAIL_DIR"
  find "$WEBMAIL_DIR" -type d -exec chmod 0750 {} +
  find "$WEBMAIL_DIR" -type f -exec chmod 0640 {} +

  # Roundcube writes here at runtime.
  local writable
  for writable in temp logs; do
    mkdir -p "$WEBMAIL_DIR/$writable"
    chown -R "${web_user}:${web_user}" "$WEBMAIL_DIR/$writable"
    chmod 0770 "$WEBMAIL_DIR/$writable"
  done

  # bin/ helpers stay executable for the upgrade path.
  if [[ -d "$WEBMAIL_DIR/bin" ]]; then
    find "$WEBMAIL_DIR/bin" -type f -name '*.sh' -exec chmod 0750 {} +
  fi
}

configure_nginx_webmail() {
  local sock_path snippet
  snippet=$LIMRISTEM_MAIL_WEBMAIL_NGINX_SNIPPET
  if ! sock_path=$(detect_php_fpm_sock); then
    fail "Nessun socket PHP-FPM trovato: il webmail non può essere servito."
  fi

  note "Scrivo le location del webmail in $snippet..."
  mkdir -p "$(dirname "$snippet")"
  cat > "$snippet" <<EOF
# Roundcube webmail locations — generated by manage-webmail.sh.
# Included from the API site config; never edit that file to add webmail routes,
# it is re-rendered from template on every TLS apply.
location ^~ /webmail/ {
    alias /var/www/roundcube/public_html/;
    index index.php;
    try_files \$uri \$uri/ /webmail/index.php?\$args;

    location ~ ^/webmail/(.+\.php)(/.*)?\$ {
        alias /var/www/roundcube/public_html/\$1;
        include fastcgi_params;
        fastcgi_pass unix:${sock_path};
        fastcgi_param SCRIPT_FILENAME /var/www/roundcube/public_html/\$1;
        fastcgi_param PATH_INFO \$2;
        fastcgi_param SCRIPT_NAME /webmail/\$1;
    }
}

location = /webmail {
    return 301 /webmail/;
}

location /mail {
    return 301 /webmail/;
}
EOF
  chmod 0644 "$snippet"

  if nginx -t >/dev/null 2>&1; then
    systemctl reload nginx >/dev/null 2>&1 || true
  else
    note "AVVISO: nginx -t fallito dopo la scrittura di $snippet; controllo manuale necessario."
  fi
}

clear_nginx_webmail() {
  local snippet=$LIMRISTEM_MAIL_WEBMAIL_NGINX_SNIPPET
  [[ -f "$snippet" ]] || return 0
  {
    printf '# Roundcube webmail locations — generated by manage-webmail.sh.\n'
    printf '# Intentionally empty while no webmail is installed.\n'
  } > "$snippet"
  chmod 0644 "$snippet"
  nginx -t >/dev/null 2>&1 && systemctl reload nginx >/dev/null 2>&1 || true
}

write_state_file() {
  local version=$1
  mkdir -p "$(dirname "$STATE_FILE")"
  python3 - "$version" "$STATE_FILE" <<'PY'
import json
import sys
import time

version, path = sys.argv[1], sys.argv[2]
payload = {
    "installed": True,
    "version": version,
    "updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
with open(path, "w", encoding="utf-8") as handle:
    handle.write(json.dumps(payload, indent=2))
PY
  chown root:limristem-mail "$STATE_FILE" 2>/dev/null || true
  chmod 0640 "$STATE_FILE" 2>/dev/null || true
}

enable_managesieve_plugin() {
  local conf=/etc/dovecot/conf.d/20-managesieve.conf
  local changed=no
  if [[ ! -f "$conf" ]]; then
    if [[ -f "$BASE_DIR/templates/dovecot/conf.d/20-managesieve.conf" ]]; then
      mkdir -p /etc/dovecot/conf.d
      cp "$BASE_DIR/templates/dovecot/conf.d/20-managesieve.conf" "$conf"
      chmod 0644 "$conf"
      changed=yes
    else
      return 0
    fi
  fi
  # Dovecot 2.4 requires block syntax: mail_plugins { sieve = yes }.
  # Never activate a legacy 2.3 'mail_plugins =' setting which prevents Dovecot from starting.
  if grep -qE '^[#[:space:]]*mail_plugins[[:space:]]*=' "$conf" 2>/dev/null; then
    sed -i -E 's/^[#[:space:]]*mail_plugins[[:space:]]*=.*$/mail_plugins {\n  sieve = yes\n}/' "$conf"
    changed=yes
  elif ! grep -q 'mail_plugins' "$conf" 2>/dev/null; then
    printf '\nmail_plugins {\n  sieve = yes\n}\n' >> "$conf"
    changed=yes
  fi
  # Ensure service managesieve-login is active on port 4190
  if ! grep -q 'service managesieve-login' "$conf" 2>/dev/null; then
    cat >> "$conf" <<'EOF'

service managesieve-login {
  inet_listener sieve {
    port = 4190
  }
}
EOF
    changed=yes
  fi
  if [[ "$changed" == "yes" ]]; then
    systemctl restart dovecot >/dev/null 2>&1 || true
  fi
}

# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------

install_roundcube() {
  local target_ver=${1:-}
  local reset_db=${2:-no}
  local tarball source_dir db_pass

  if [[ -z "$target_ver" ]]; then
    target_ver=$(fetch_latest_roundcube_version)
  fi
  is_valid_version "$target_ver" || fail "Versione Roundcube non valida: '$target_ver'"

  install_php_stack
  make_workdir

  tarball=$(download_release "$target_ver")
  verify_release_signature "$tarball"
  source_dir=$(extract_release "$tarball")

  db_pass=$(webmail_db_password)
  # Order matters: DROP DATABASE also removes the database-level grants, so the user is
  # (re)created after the reset rather than before it.
  [[ "$reset_db" == "yes" ]] && reset_webmail_db
  ensure_mariadb_webmail_db "$db_pass"
  import_webmail_schema "$source_dir"

  note "Installo in $WEBMAIL_DIR..."
  mkdir -p "$(dirname "$WEBMAIL_DIR")"
  rm -rf "$WEBMAIL_DIR"
  mv "$source_dir" "$WEBMAIL_DIR"
  rm -rf "$WEBMAIL_DIR/installer"

  write_webmail_config "$db_pass"
  enable_managesieve_plugin
  apply_permissions
  configure_nginx_webmail
  write_state_file "$target_ver"
  cleanup_workdir

  note "Roundcube Webmail v${target_ver} installato."
}

upgrade_roundcube() {
  local current_ver latest_ver tarball source_dir
  current_ver=$(detect_installed_version)
  latest_ver=$(fetch_latest_roundcube_version)

  if [[ ! -f "$WEBMAIL_DIR/index.php" ]]; then
    note "Webmail non installato: eseguo un'installazione completa."
    install_roundcube "$latest_ver" no
    return 0
  fi
  if [[ "$current_ver" == "$latest_ver" ]]; then
    note "Roundcube è già alla versione $current_ver."
    return 0
  fi

  note "Aggiorno Roundcube da v${current_ver} a v${latest_ver}..."
  make_workdir
  tarball=$(download_release "$latest_ver")
  verify_release_signature "$tarball"
  source_dir=$(extract_release "$tarball")

  if [[ -x "$source_dir/bin/installto.sh" ]]; then
    "$source_dir/bin/installto.sh" -y "$WEBMAIL_DIR" \
      || fail "installto.sh ha restituito un errore: aggiornamento interrotto."
    if [[ -x "$WEBMAIL_DIR/bin/updatedb.sh" ]]; then
      "$WEBMAIL_DIR/bin/updatedb.sh" --dir "$WEBMAIL_DIR/SQL" --package roundcube || true
    fi
    apply_permissions
    write_state_file "$latest_ver"
    cleanup_workdir
    systemctl reload nginx >/dev/null 2>&1 || true
    systemctl restart php*-fpm >/dev/null 2>&1 || true
    note "Roundcube Webmail aggiornato a v${latest_ver}."
    return 0
  fi

  cleanup_workdir
  install_roundcube "$latest_ver" no
}

set_auto_update() {
  local val=${1:-no}
  case "${val,,}" in
    yes|true|1|on) val=yes ;;
    no|false|0|off) val=no ;;
    *) fail "set-auto-update accetta solo yes o no." ;;
  esac
  limristem_mail_upsert_env_value "$ENV_FILE" "LIMRISTEM_MAIL_WEBMAIL_AUTO_UPDATE" "$val"
  note "Aggiornamento automatico del webmail impostato su $val."
}

remove_webmail() {
  rm -rf "$WEBMAIL_DIR"
  rm -f "$STATE_FILE"
  clear_nginx_webmail
  note "File del webmail Roundcube rimossi."
}

require_root
load_env

# install/upgrade/remove mutate /var/www, /etc/nginx and the package database, so they
# must escape the app's read-only mount namespace when invoked from the panel/API.
case "${1:-}" in
  install|upgrade|set-auto-update|remove)
    limristem_mail_escape_write_namespace LIMRISTEM_MAIL_WEBMAIL_WORKER limristem-mail-webmail "$@"
    ;;
esac

command=${1:-status}
case "$command" in
  status)
    get_status_json
    ;;
  install)
    shift
    ver=""
    reset_db=no
    while [[ $# -gt 0 ]]; do
      case "$1" in
        --version)
          ver=${2:-}
          [[ -n "$ver" ]] || fail "--version richiede un valore."
          shift 2
          ;;
        --reset-db)
          reset_db=yes
          shift
          ;;
        --json)
          shift
          ;;
        *)
          fail "Opzione sconosciuta per install: $1"
          ;;
      esac
    done
    install_roundcube "$ver" "$reset_db"
    get_status_json
    ;;
  upgrade)
    upgrade_roundcube
    get_status_json
    ;;
  set-auto-update)
    set_auto_update "${2:-no}"
    get_status_json
    ;;
  remove)
    remove_webmail
    get_status_json
    ;;
  -h|--help|help)
    usage
    ;;
  *)
    usage >&2
    exit 1
    ;;
esac
