11 hours ago11 hr I encountered a recurring PHP startup warning on CWP servers:PHP Warning: PHP Startup: Unable to load dynamic library 'intl'The affected CWP PHP binary was:/usr/local/cwp/php71/bin/phpDespite the directory name, this was actually PHP 7.4.33:PHP 7.4.33 PHP API: 20190902The important diagnostic result was:/usr/local/cwp/php71/bin/php -n -r \ 'var_dump(PHP_VERSION, extension_loaded("intl"), phpversion("intl"));'Output:string(6) "7.4.33" bool(true) string(6) "7.4.33"This proved that intl was already compiled into the CWP PHP binary. However, CWP repeatedly recreated:/usr/local/cwp/php71/php.d/intl.iniwith:extension=intlPHP then tried to load a separate intl.so, although no compatible dynamic module was required.The source of the recurring configuration was:/scripts/update_roundcubeThe old updater also contained outdated PHP API paths, downloaded third-party intl.so binaries, installed separate ICU packages, and recreated intl.ini. Simply renaming or deleting intl.ini was therefore not permanent: a later CWP update could recreate it.Why upgrade Roundcube 1.5.15?Roundcube 1.5.15 was the final security release in the 1.5 branch. New security corrections are now released for the 1.6 LTS and 1.7 branches.Roundcube 1.6.20 contains later security fixes that are not present in 1.5.15, including fixes related to:IMAP command injection;stored and zero-click XSS;SSRF filter bypasses;e-mail header injection;remote-content blocking bypasses;cross-user address-book access;Sieve script injection;denial-of-service conditions.For existing CWP installations using the internal PHP 7.4 binary, Roundcube 1.6.20 is the appropriate target:Roundcube 1.6.20 LTS PHP requirement: PHP 7.3 or newer CWP PHP tested: PHP 7.4.33Roundcube 1.7 is not a drop-in replacement in this environment because it requires PHP 8.1 or newer and introduces additional web-root changes.Idempotent fleet upgrade scriptI created an idempotent migration script for CWP servers:roundcube-cwp-1.5.15-to-1.6.20.shIts behavior is:Current versionResult with --applyRoundcube 1.5.15Installs the protection and upgrades to 1.6.20Roundcube 1.6.20Verifies/reinstalls the protection and skips migrationAny other versionStops without performing an uncertain migrationThe script:verifies the installed Roundcube version;checks the CWP PHP version and required extensions;verifies whether intl is compiled into PHP;downloads the official Roundcube 1.6.20 complete package over HTTPS;verifies the official SHA-256 checksum;verifies the Roundcube PGP signature and full signing-key fingerprint;creates a complete Roundcube filesystem backup;creates a MariaDB/MySQL database backup;executes the official Roundcube installto.sh;ensures that both installto.sh and its child php bin/... commands use:/usr/local/cwp/php71/bin/phpstops and restarts cwpsrv during the migration;verifies the resulting Roundcube version;checks PHP syntax;preserves the previous /scripts/update_roundcube file for reference.It does not modify the verified Roundcube source package with sed. Instead, it starts installto.sh explicitly with CWP PHP and provides a temporary php PATH shim for any child migration commands.Preventing CWP from restoring the old updaterThe script replaces:/scripts/update_roundcubewith a harmless wrapper that logs the attempted invocation and exits successfully with status 0.This avoids disrupting the normal CWP nightly process while preventing the obsolete updater from:reinstalling an old Roundcube release;downloading incompatible intl.so files;reinstalling unnecessary ICU packages;recreating the faulty intl.ini configuration.A systemd path unit monitors:/scripts/update_roundcube /usr/local/cwp/php71/php.d/intl.iniIf a later CWP update overwrites either file, the guard:restores the safe /scripts/update_roundcube wrapper;removes the redundant dynamic intl loading when intl is proven to be compiled into the PHP binary.A systemd timer also performs a fallback check every five minutes.The solution intentionally does not use:chattr +iAn immutable file could cause a CWP update to fail when it attempts to replace the file.UsageCopy the script to the server and make it executable:chmod 750 roundcube-cwp-1.5.15-to-1.6.20.shRun the read-only preflight first:./roundcube-cwp-1.5.15-to-1.6.20.shThis performs checks without changing the server.If the preflight succeeds:./roundcube-cwp-1.5.15-to-1.6.20.sh --applyIf Roundcube 1.6.20 is already installed, the expected message is:Roundcube 1.6.20 is already installed. Migration skipped; guards verified.VerificationCheck the systemd protection:systemctl is-active \ cwp-roundcube-guard.path \ cwp-roundcube-guard.timerExpected output:active activeVerify that the protected updater is identical to the safe copy:cmp \ /usr/local/libexec/cwp-roundcube-guard/update_roundcube.disabled \ /scripts/update_roundcube echo $?Expected exit status:0Verify PHP and intl:/usr/local/cwp/php71/bin/php -v /usr/local/cwp/php71/bin/php -m \ | grep -i '^intl$'The PHP command should no longer emit an intl startup warning, and the module list should contain:intlCheck all relevant services:systemctl is-active \ cwpsrv \ cwp-roundcube-guard.path \ cwp-roundcube-guard.timerBackups and logsBackups of replaced legacy scripts:/root/roundcube-upgrade-backups/legacy-script-backups/Full filesystem and database backups:/root/roundcube-upgrade-backups/YYYYMMDD-HHMMSS/Migration log:/var/log/cwp-roundcube-1.6.20-upgrade.logGuard activity:journalctl \ -u cwp-roundcube-guard.service \ --since today \ --no-pagerFunctional tests after upgradingI recommend testing:direct login through webmail.example.com;CWP administrator e-mail auto-login;opening plain-text and HTML messages;sending a test message;sending and downloading an attachment;address-book access;logout and a new login.On the first upgraded production server, all of the following worked:Roundcube 1.5.15 → 1.6.20 migration;direct webmail access;CWP administrator auto-login;database migration;PHP 7.4 compatibility;sending and receiving mail;removal of the recurring intl warning.A temporary proxy error appeared during the first auto-login attempt immediately after the update, but it disappeared after the services and session state settled. It was not a persistent compatibility problem.What if the server still has Roundcube 1.5.8?Roundcube 1.5.8 is older and should not remain exposed as a production webmail installation.The current fleet script intentionally refuses to modify unknown source versions. It accepts only:1.5.15 1.6.20This is a safety measure. Do not simply remove the version check on a production server.For a server still running 1.5.8, use this process:Create a complete backup of:the Roundcube installation directory;the Roundcube database;config/config.inc.php;installed plugins and custom CWP integration files.Download the official Roundcube 1.5.15 complete release package.Verify its PGP signature before extracting it.Upgrade 1.5.8 to 1.5.15 with the official bin/installto.sh, ensuring that the CWP PHP binary is used:/usr/local/cwp/php71/bin/phpVerify:direct webmail login;CWP auto-login;IMAP access;SMTP sending;attachments;address book;logout and login.Run the fleet script in preflight mode:./roundcube-cwp-1.5.15-to-1.6.20.shIf the preflight succeeds, perform the protected 1.5.15 → 1.6.20 migration:./roundcube-cwp-1.5.15-to-1.6.20.sh --applyThis two-stage approach is deliberately conservative. It provides a tested checkpoint at 1.5.15 and avoids turning an old, possibly customized CWP Roundcube installation into an unverified direct migration.Do not use an old updater that downloads PHP modules or RPM packages from unrelated repositories. Roundcube source updates and PHP-extension repairs should remain separate operations, and downloaded release artifacts should always be authenticated before installation.And here is the script, save as `roundcube-cwp-1.5.15-to-1.6.20.sh` Controlled CWP Roundcube migration: 1.5.15 -> 1.6.20 LTS#!/usr/bin/env bash set -Eeuo pipefail umask 077 # Controlled CWP Roundcube migration: 1.5.15 -> 1.6.20 LTS # # This script deliberately does not install PHP extensions, ICU packages, or # third-party binaries. Run without --apply for a read-only preflight. readonly SOURCE_VERSION="1.5.15" readonly TARGET_VERSION="1.6.20" readonly TARGET_SHA256="46919c70c85609b57038903c4ecc4e1d7fa4e14c535fe5b14192d778dcd4a1ee" readonly RELEASE_BASE="https://github.com/roundcube/roundcubemail/releases/download/${TARGET_VERSION}" readonly ARCHIVE_NAME="roundcubemail-${TARGET_VERSION}-complete.tar.gz" readonly SIGNATURE_NAME="${ARCHIVE_NAME}.asc" readonly RELEASE_URL="${RELEASE_BASE}/${ARCHIVE_NAME}" readonly SIGNATURE_URL="${RELEASE_BASE}/${SIGNATURE_NAME}" readonly PUBKEY_URL="https://roundcube.net/download/pubkey.asc" readonly EXPECTED_KEY_FINGERPRINT="F3E4C04BB3DB5D4215C45F7F5AB2BAA141C4F7D5" readonly TARGET_DIR="/usr/local/cwpsrv/var/services/roundcube" readonly PHP_BIN="/usr/local/cwp/php71/bin/php" readonly RESTART_SCRIPT="/scripts/restart_cwpsrv" readonly LOG_FILE="/var/log/cwp-roundcube-1.6.20-upgrade.log" readonly BACKUP_ROOT="/root/roundcube-upgrade-backups" readonly CONFIG_FILE="${TARGET_DIR}/config/config.inc.php" readonly VERSION_FILE="${TARGET_DIR}/program/include/iniset.php" readonly INTL_INI="/usr/local/cwp/php71/php.d/intl.ini" readonly LEGACY_UPDATE_SCRIPT="/scripts/update_roundcube" readonly GUARD_DIR="/usr/local/libexec/cwp-roundcube-guard" readonly GUARD_SCRIPT="/usr/local/sbin/cwp-roundcube-guard" readonly GUARD_SERVICE="/etc/systemd/system/cwp-roundcube-guard.service" readonly GUARD_PATH="/etc/systemd/system/cwp-roundcube-guard.path" readonly GUARD_TIMER="/etc/systemd/system/cwp-roundcube-guard.timer" APPLY=0 KEEP_WORKDIR=0 WORK_DIR="" BACKUP_DIR="" CWPSRV_WAS_ACTIVE=0 usage() { cat <<'EOF' Usage: roundcube-cwp-1.5.15-to-1.6.20.sh roundcube-cwp-1.5.15-to-1.6.20.sh --apply roundcube-cwp-1.5.15-to-1.6.20.sh --apply --keep-workdir Without --apply, only preflight checks are performed. EOF } log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S%z')" "$*" | tee -a "$LOG_FILE" } die() { log "ERROR: $*" exit 1 } cleanup() { local exit_code=$? if (( CWPSRV_WAS_ACTIVE == 1 )) && ! systemctl is-active --quiet cwpsrv; then log "Starting cwpsrv after interrupted or failed migration." "$RESTART_SCRIPT" >>"$LOG_FILE" 2>&1 || true fi if [[ -n "$WORK_DIR" && -d "$WORK_DIR" && $KEEP_WORKDIR -eq 0 ]]; then rm -rf -- "$WORK_DIR" elif [[ -n "$WORK_DIR" && -d "$WORK_DIR" ]]; then log "Working directory retained: $WORK_DIR" fi exit "$exit_code" } install_cwp_guards() { local legacy_backup_dir legacy_backup_dir="${BACKUP_ROOT}/legacy-script-backups" mkdir -p "$GUARD_DIR" "$legacy_backup_dir" if [[ -f "$LEGACY_UPDATE_SCRIPT" ]] && \ ! grep -q 'Managed by cwp-roundcube-guard' "$LEGACY_UPDATE_SCRIPT"; then cp -a "$LEGACY_UPDATE_SCRIPT" \ "${legacy_backup_dir}/update_roundcube.$(date '+%Y%m%d-%H%M%S')" fi cat >"${GUARD_DIR}/update_roundcube.disabled" <<'EOF' #!/bin/sh # Managed by cwp-roundcube-guard. The legacy CWP updater is intentionally disabled. logger -t cwp-roundcube-guard \ "Blocked obsolete /scripts/update_roundcube invocation; managed Roundcube updater is required." exit 0 EOF chmod 0755 "${GUARD_DIR}/update_roundcube.disabled" cat >"$GUARD_SCRIPT" <<'EOF' #!/usr/bin/env bash set -Eeuo pipefail readonly PHP_BIN="/usr/local/cwp/php71/bin/php" readonly INTL_INI="/usr/local/cwp/php71/php.d/intl.ini" readonly LEGACY_SCRIPT="/scripts/update_roundcube" readonly SAFE_SCRIPT="/usr/local/libexec/cwp-roundcube-guard/update_roundcube.disabled" readonly LOG_TAG="cwp-roundcube-guard" changed=0 if [[ -f "$SAFE_SCRIPT" ]] && ! cmp --silent "$SAFE_SCRIPT" "$LEGACY_SCRIPT" 2>/dev/null; then install -o root -g root -m 0755 "$SAFE_SCRIPT" "$LEGACY_SCRIPT" logger -t "$LOG_TAG" "Restored the safe /scripts/update_roundcube wrapper." changed=1 fi if [[ -x "$PHP_BIN" ]] && \ "$PHP_BIN" -n -r 'exit(extension_loaded("intl") ? 0 : 1);'; then if [[ -f "$INTL_INI" ]] && \ grep -Eq '^[[:space:]]*extension[[:space:]]*=[[:space:]]*"?intl(\.so)?"?[[:space:]]*$' "$INTL_INI"; then printf '%s\n' \ '; Disabled automatically: intl is compiled into the CWP PHP binary.' \ >"$INTL_INI" chmod 0644 "$INTL_INI" logger -t "$LOG_TAG" "Disabled redundant dynamic intl loading." changed=1 fi fi exit 0 EOF chmod 0750 "$GUARD_SCRIPT" cat >"$GUARD_SERVICE" <<EOF [Unit] Description=Protect managed CWP Roundcube and PHP intl configuration After=local-fs.target [Service] Type=oneshot ExecStart=$GUARD_SCRIPT EOF cat >"$GUARD_PATH" <<EOF [Unit] Description=Watch CWP Roundcube updater and PHP intl configuration [Path] PathChanged=$LEGACY_UPDATE_SCRIPT PathChanged=$INTL_INI Unit=cwp-roundcube-guard.service [Install] WantedBy=multi-user.target EOF cat >"$GUARD_TIMER" <<'EOF' [Unit] Description=Periodic fallback check for CWP Roundcube guard [Timer] OnBootSec=2min OnUnitActiveSec=5min AccuracySec=30s Persistent=true Unit=cwp-roundcube-guard.service [Install] WantedBy=timers.target EOF systemctl daemon-reload systemctl enable --now cwp-roundcube-guard.path cwp-roundcube-guard.timer \ >>"$LOG_FILE" 2>&1 systemctl start cwp-roundcube-guard.service cmp --silent "${GUARD_DIR}/update_roundcube.disabled" "$LEGACY_UPDATE_SCRIPT" || \ die "Failed to replace the legacy Roundcube updater." systemctl is-active --quiet cwp-roundcube-guard.path || \ die "Roundcube guard path unit is not active." systemctl is-active --quiet cwp-roundcube-guard.timer || \ die "Roundcube guard timer is not active." log "Legacy Roundcube updater disabled; persistent CWP/intl guard installed." } trap cleanup EXIT trap 'die "Failure at line $LINENO."' ERR for argument in "$@"; do case "$argument" in --apply) APPLY=1 ;; --keep-workdir) KEEP_WORKDIR=1 ;; -h|--help) usage; exit 0 ;; *) usage; die "Unknown argument: $argument" ;; esac done [[ $EUID -eq 0 ]] || die "This script must be run as root." for command_name in base64 curl gpg gzip sha256sum tar awk grep sed stat systemctl; do command -v "$command_name" >/dev/null 2>&1 || die "Required command not found: $command_name" done [[ -x "$PHP_BIN" ]] || die "CWP PHP binary not found: $PHP_BIN" [[ -d "$TARGET_DIR" ]] || die "Roundcube directory not found: $TARGET_DIR" [[ -f "$CONFIG_FILE" ]] || die "Roundcube configuration not found: $CONFIG_FILE" [[ -f "$VERSION_FILE" ]] || die "Roundcube version file not found: $VERSION_FILE" [[ -x "$RESTART_SCRIPT" ]] || die "CWP restart script not found: $RESTART_SCRIPT" INSTALLED_VERSION="$({ grep "define('RCMAIL_VERSION'" "$VERSION_FILE" || true } | awk -F"'" 'NR == 1 {print $4}')" if [[ "$INSTALLED_VERSION" != "$SOURCE_VERSION" && \ "$INSTALLED_VERSION" != "$TARGET_VERSION" ]]; then die "Expected Roundcube ${SOURCE_VERSION} or ${TARGET_VERSION}, found '${INSTALLED_VERSION:-unknown}'." fi PHP_VERSION="$($PHP_BIN -n -r 'echo PHP_VERSION;')" PHP_VERSION_ID="$($PHP_BIN -n -r 'echo PHP_VERSION_ID;')" (( PHP_VERSION_ID >= 70300 )) || die "Roundcube 1.6 requires PHP 7.3 or newer; found $PHP_VERSION." if "$PHP_BIN" -n -r 'exit(extension_loaded("intl") ? 0 : 1);'; then INTL_MODE="compiled-in" else INTL_MODE="configured extension" fi for extension_name in dom fileinfo intl json mbstring openssl pcre pdo session sockets; do "$PHP_BIN" -r 'exit(extension_loaded($argv[1]) ? 0 : 1);' "$extension_name" 2>/dev/null || \ die "Required PHP extension is not loaded: $extension_name" done log "Preflight: Roundcube $INSTALLED_VERSION, PHP $PHP_VERSION, intl mode: $INTL_MODE." if [[ "$INTL_MODE" == "compiled-in" && -f "$INTL_INI" ]] && \ grep -Eq '^[[:space:]]*extension[[:space:]]*=[[:space:]]*"?intl(\.so)?"?[[:space:]]*$' "$INTL_INI"; then if (( APPLY == 1 )); then cp -a "$INTL_INI" "${INTL_INI}.before-roundcube-1.6.20" printf '%s\n' '; Disabled: intl is compiled into the CWP PHP binary.' >"$INTL_INI" log "Disabled redundant dynamic intl loading." else log "Preflight warning: $INTL_INI contains redundant dynamic intl loading." fi fi if (( APPLY == 0 )); then if [[ "$INSTALLED_VERSION" == "$TARGET_VERSION" ]]; then log "Roundcube ${TARGET_VERSION} is already installed; migration would be skipped." fi log "Preflight completed. No changes were made. Re-run with --apply to install guards and migrate if needed." exit 0 fi mkdir -p "$BACKUP_ROOT" install_cwp_guards if [[ "$INSTALLED_VERSION" == "$TARGET_VERSION" ]]; then log "Roundcube ${TARGET_VERSION} is already installed. Migration skipped; guards verified." exit 0 fi WORK_DIR="$(mktemp -d /usr/local/src/roundcube-1.6.20-upgrade.XXXXXX)" readonly WORK_DIR BACKUP_DIR="${BACKUP_ROOT}/$(date '+%Y%m%d-%H%M%S')" readonly BACKUP_DIR mkdir -p "$BACKUP_DIR" log "Downloading the official Roundcube ${TARGET_VERSION} complete package." curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 \ "$RELEASE_URL" --output "${WORK_DIR}/${ARCHIVE_NAME}" curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 \ "$SIGNATURE_URL" --output "${WORK_DIR}/${SIGNATURE_NAME}" curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 \ "$PUBKEY_URL" --output "${WORK_DIR}/roundcube-pubkey.asc" printf '%s %s\n' "$TARGET_SHA256" "${WORK_DIR}/${ARCHIVE_NAME}" | sha256sum --check --status || \ die "SHA-256 verification failed." GNUPGHOME="${WORK_DIR}/gnupg" export GNUPGHOME mkdir -m 700 "$GNUPGHOME" ACTUAL_FINGERPRINT="$( gpg --batch --quiet --with-colons --import-options show-only --import \ "${WORK_DIR}/roundcube-pubkey.asc" 2>/dev/null | awk -F: '$1 == "fpr" {print $10; exit}' )" [[ "$ACTUAL_FINGERPRINT" == "$EXPECTED_KEY_FINGERPRINT" ]] || \ die "Unexpected Roundcube signing-key fingerprint: $ACTUAL_FINGERPRINT" gpg --batch --quiet --import "${WORK_DIR}/roundcube-pubkey.asc" >>"$LOG_FILE" 2>&1 gpg --batch --verify "${WORK_DIR}/${SIGNATURE_NAME}" \ "${WORK_DIR}/${ARCHIVE_NAME}" >>"$LOG_FILE" 2>&1 || die "PGP signature verification failed." log "Release checksum and PGP signature verified." log "Creating filesystem backup: $BACKUP_DIR" tar --xattrs --acls --selinux -czpf "${BACKUP_DIR}/roundcube-files.tar.gz" \ -C "$(dirname "$TARGET_DIR")" "$(basename "$TARGET_DIR")" cp -a "$0" "${BACKUP_DIR}/migration-script.sh" cp -a "$CONFIG_FILE" "${BACKUP_DIR}/config.inc.php" # Try to create a database backup without printing credentials. Roundcube DSNs # normally use mysql:// or mysqli://. Unsupported/custom DSNs abort the upgrade. DB_ENV_FILE="${WORK_DIR}/read-db-config.php" cat >"$DB_ENV_FILE" <<'PHP' <?php $config = []; require $argv[1]; $dsn = $config['db_dsnw'] ?? ''; $parts = parse_url($dsn); if (!is_array($parts) || !isset($parts['scheme'], $parts['host'], $parts['user'], $parts['path'])) { exit(2); } $scheme = strtolower($parts['scheme']); if (!in_array($scheme, ['mysql', 'mysqli'], true)) { exit(3); } $values = [ rawurldecode($parts['host']), (string) ($parts['port'] ?? 3306), rawurldecode($parts['user']), rawurldecode((string) ($parts['pass'] ?? '')), rawurldecode(ltrim($parts['path'], '/')), ]; foreach ($values as $value) { echo base64_encode($value), "\n"; } PHP mapfile -t DB_FIELDS < <("$PHP_BIN" -n "$DB_ENV_FILE" "$CONFIG_FILE") || \ die "Could not parse a supported MySQL/MariaDB db_dsnw from Roundcube configuration." [[ ${#DB_FIELDS[@]} -eq 5 ]] || die "Incomplete Roundcube database configuration." decode_field() { printf '%s' "$1" | base64 --decode; } DB_HOST="$(decode_field "${DB_FIELDS[0]}")" DB_PORT="$(decode_field "${DB_FIELDS[1]}")" DB_USER="$(decode_field "${DB_FIELDS[2]}")" DB_PASS="$(decode_field "${DB_FIELDS[3]}")" DB_NAME="$(decode_field "${DB_FIELDS[4]}")" DUMP_BIN="$(command -v mariadb-dump || command -v mysqldump || true)" [[ -n "$DUMP_BIN" ]] || die "Neither mariadb-dump nor mysqldump is installed." DB_DEFAULTS="${WORK_DIR}/db-client.cnf" { printf '%s\n' '[client]' printf 'host=%s\n' "$DB_HOST" printf 'port=%s\n' "$DB_PORT" printf 'user=%s\n' "$DB_USER" printf 'password=%s\n' "$DB_PASS" } >"$DB_DEFAULTS" chmod 600 "$DB_DEFAULTS" "$DUMP_BIN" --defaults-extra-file="$DB_DEFAULTS" --single-transaction \ --skip-lock-tables --hex-blob --databases "$DB_NAME" | gzip -9 >"${BACKUP_DIR}/roundcube-database.sql.gz" [[ -s "${BACKUP_DIR}/roundcube-database.sql.gz" ]] || die "Database backup is empty." unset DB_PASS DB_FIELDS rm -f "$DB_DEFAULTS" log "Filesystem and database backups completed." mkdir -p "${WORK_DIR}/source" tar -xzf "${WORK_DIR}/${ARCHIVE_NAME}" -C "${WORK_DIR}/source" --strip-components=1 [[ -f "${WORK_DIR}/source/bin/installto.sh" ]] || die "installto.sh missing from verified archive." # installto.sh is started explicitly with CWP PHP below. It can also invoke # child commands in the form `php bin/...`; provide a temporary PATH shim so # those processes use the same CWP PHP binary without modifying signed source. mkdir -p "${WORK_DIR}/cwp-php-bin" ln -s "$PHP_BIN" "${WORK_DIR}/cwp-php-bin/php" if systemctl is-active --quiet cwpsrv; then CWPSRV_WAS_ACTIVE=1 log "Stopping cwpsrv for the migration." systemctl stop cwpsrv fi log "Migrating Roundcube files and database." PATH="${WORK_DIR}/cwp-php-bin:${PATH}" \ "$PHP_BIN" "${WORK_DIR}/source/bin/installto.sh" -y "$TARGET_DIR" \ >>"$LOG_FILE" 2>&1 NEW_VERSION="$({ grep "define('RCMAIL_VERSION'" "$VERSION_FILE" || true } | awk -F"'" 'NR == 1 {print $4}')" [[ "$NEW_VERSION" == "$TARGET_VERSION" ]] || die "Migration finished with unexpected version: $NEW_VERSION" "$PHP_BIN" -l "$TARGET_DIR/index.php" >>"$LOG_FILE" 2>&1 "$PHP_BIN" -l "$CONFIG_FILE" >>"$LOG_FILE" 2>&1 log "Restarting cwpsrv." "$RESTART_SCRIPT" >>"$LOG_FILE" 2>&1 systemctl is-active --quiet cwpsrv || die "cwpsrv did not become active." CWPSRV_WAS_ACTIVE=0 log "Roundcube ${SOURCE_VERSION} -> ${TARGET_VERSION} migration completed." log "Backup directory: $BACKUP_DIR" log "Verify webmail login, message display, sending, attachments, address book, and enabled plugins."
10 hours ago10 hr Author Additional procedure for servers still running Roundcube 1.5.8The fleet migration script intentionally accepts only Roundcube 1.5.15 and 1.6.20. If it detects 1.5.8, it stops without changing the installation.For an older 1.5.8 installation, use a controlled two-stage upgrade:1.5.8 → 1.5.15 → 1.6.20This provides a tested intermediate checkpoint and avoids bypassing the script’s version-safety gate.1. Confirm the installed versiongrep "define('RCMAIL_VERSION'" \ /usr/local/cwpsrv/var/services/roundcube/program/include/iniset.phpExpected result for an affected server:define('RCMAIL_VERSION', '1.5.8');2. Create backupsDo not proceed without both a filesystem and database backup.Create a filesystem backup:BACKUP_DIR="/root/roundcube-1.5.8-backup-$(date +%Y%m%d-%H%M%S)" mkdir -p "$BACKUP_DIR" tar --xattrs --acls --selinux -czpf \ "$BACKUP_DIR/roundcube-files.tar.gz" \ -C /usr/local/cwpsrv/var/services \ roundcubeAlso create a database dump using the Roundcube database credentials stored in:/usr/local/cwpsrv/var/services/roundcube/config/config.inc.phpVerify that both backup files exist and are non-empty before continuing.3. Download the official Roundcube 1.5.15 packageUse a temporary working directory:WORK_DIR="$(mktemp -d /usr/local/src/roundcube-1.5.15.XXXXXX)" cd "$WORK_DIR"Download the official complete package and its signature:curl --fail --location --proto '=https' --tlsv1.2 \ --output roundcubemail-1.5.15-complete.tar.gz \ https://github.com/roundcube/roundcubemail/releases/download/1.5.15/roundcubemail-1.5.15-complete.tar.gz curl --fail --location --proto '=https' --tlsv1.2 \ --output roundcubemail-1.5.15-complete.tar.gz.asc \ https://github.com/roundcube/roundcubemail/releases/download/1.5.15/roundcubemail-1.5.15-complete.tar.gz.asc curl --fail --location --proto '=https' --tlsv1.2 \ --output roundcube-pubkey.asc \ https://roundcube.net/download/pubkey.asc4. Verify the signing key and packageUse an isolated GPG directory:export GNUPGHOME="$WORK_DIR/gnupg" mkdir -m 700 "$GNUPGHOME"Display the downloaded key fingerprint:gpg --batch \ --with-fingerprint \ --keyid-format long \ --import-options show-only \ --import roundcube-pubkey.ascThe expected primary key fingerprint is:F3E4 C04B B3DB 5D42 15C4 5F7F 5AB2 BAA1 41C4 F7D5If the fingerprint differs, stop immediately.Import the verified key and check the release signature:gpg --batch --import roundcube-pubkey.asc gpg --batch --verify \ roundcubemail-1.5.15-complete.tar.gz.asc \ roundcubemail-1.5.15-complete.tar.gzProceed only after a valid signature result.5. Extract Roundcube 1.5.15mkdir "$WORK_DIR/source" tar -xzf roundcubemail-1.5.15-complete.tar.gz \ -C "$WORK_DIR/source" \ --strip-components=1Confirm that the official installer exists:test -f "$WORK_DIR/source/bin/installto.sh"6. Ensure all migration commands use CWP PHPThe CWP PHP binary is:/usr/local/cwp/php71/bin/phpDespite the directory name, verify its real version:/usr/local/cwp/php71/bin/php -vCreate a temporary php PATH shim:mkdir "$WORK_DIR/cwp-php-bin" ln -s /usr/local/cwp/php71/bin/php \ "$WORK_DIR/cwp-php-bin/php"This is important because installto.sh may start additional commands using:php bin/...The shim ensures that these child commands also use the CWP PHP binary instead of the system default PHP.7. Perform the 1.5.8 → 1.5.15 upgradeStop the CWP web service during the file and database migration:systemctl stop cwpsrvRun the official installer:PATH="$WORK_DIR/cwp-php-bin:$PATH" \ /usr/local/cwp/php71/bin/php \ "$WORK_DIR/source/bin/installto.sh" \ -y \ /usr/local/cwpsrv/var/services/roundcubeRestart CWP:/scripts/restart_cwpsrvConfirm the service state:systemctl is-active cwpsrvExpected result:active8. Verify the intermediate versiongrep "define('RCMAIL_VERSION'" \ /usr/local/cwpsrv/var/services/roundcube/program/include/iniset.phpExpected result:define('RCMAIL_VERSION', '1.5.15');Test at least:direct webmail login;CWP administrator auto-login;opening an existing message;sending a test message;attachment upload and download;address-book access;logout and a new login.Do not continue to 1.6.20 until the 1.5.15 installation works correctly.9. Run the protected 1.5.15 → 1.6.20 migrationFirst run the fleet script without modifications:./roundcube-cwp-1.5.15-to-1.6.20.shIf the preflight succeeds:./roundcube-cwp-1.5.15-to-1.6.20.sh --applyThe second migration installs Roundcube 1.6.20 and also:disables the obsolete /scripts/update_roundcube;protects it against later CWP overwrites;removes the recurring redundant extension=intl configuration;installs the systemd path and timer guards;creates another complete filesystem and database backup.10. Final verificationgrep "define('RCMAIL_VERSION'" \ /usr/local/cwpsrv/var/services/roundcube/program/include/iniset.php /usr/local/cwp/php71/bin/php -v /usr/local/cwp/php71/bin/php -m \ | grep -i '^intl$' systemctl is-active \ cwpsrv \ cwp-roundcube-guard.path \ cwp-roundcube-guard.timerExpected Roundcube version:1.6.20Expected service results:active active activeOnce the final functional tests have passed, remove the temporary 1.5.15 working directory:rm -rf -- "$WORK_DIR"Keep both sets of backups until the upgraded server has completed at least one normal operational and nightly CWP update cycle.
Create an account or sign in to comment