Compare commits

...

14 Commits

Author SHA1 Message Date
chris b73fa92d43 install_postfix_base.sh,install_postfix_advanced.sh: add libsasl2-modules-db to the reqired debian packages. 2026-07-17 14:58:44 +02:00
chris bb8cd0c2cc install_roundcube.sh: change cronjob for cleaning up db. 2026-07-11 02:00:24 +02:00
chris 2d288c6705 Merge branch 'master' of https://git.oopen.de/install/mailsystem 2026-07-04 00:34:59 +02:00
chris a96c92524e upgrade_roundcube.sh: the script is no longer paused as usual (for example, to disable plugins). 2026-07-04 00:34:31 +02:00
chris dd105ad5d9 Add rebuild_roundcube_config.php for regenerating Roundcube config 2026-07-03 23:12:53 +02:00
chris 7fb1ac4511 Rename ol dovecot install/upgrade script. 2026-07-03 18:45:14 +02:00
chris cb3079a7ae migrate_markasjunk2_to_markasjunk.sh: migrate from extern deprecated markasjunk plugin to the core one. 2026-07-03 17:44:08 +02:00
chris 5a7e79d8c4 upgrade_roundcube.sh: Allow upgrading to the even installed version. 2026-07-03 17:42:59 +02:00
chris d60cb62d8f upgrade_roundcube.sh: some fixes.. 2026-07-03 17:08:56 +02:00
chris 5b9e632fe4 upgrade_roundcube.sh: make upügrade script ready for newer composer and newer roundcube installation. 2026-07-03 12:26:59 +02:00
chris 7be12b675c Add skript 'update_quota2_mariadb.sh'. 2026-07-02 03:19:09 +02:00
chris d385627251 Merge branch 'master' of https://git.oopen.de/install/mailsystem 2026-07-02 02:23:59 +02:00
chris 8df8b715c7 Add script 'update_quota2_psql.sh'. 2026-07-02 01:20:25 +02:00
chris b90c692af1 install_postfixadmin.sh: create composer.lock bevor installing/updating postfixadmin. 2026-07-02 01:19:38 +02:00
10 changed files with 1077 additions and 25 deletions
+1 -1
View File
@@ -623,7 +623,7 @@ fi
# - Install Postfix from debian packages system # - Install Postfix from debian packages system
# - # -
echononl " Install Postfix from debian packages system" echononl " Install Postfix from debian packages system"
_needed_packages="postfix postfix-pgsql postfix-mysql postfix-pcre libsasl2-modules bsd-mailx haveged" _needed_packages="postfix postfix-pgsql postfix-mysql postfix-pcre libsasl2-modules libsasl2-modules-db bsd-mailx haveged"
if [[ "$SASL_AUTH_ENABLED" = "yes" ]]; then if [[ "$SASL_AUTH_ENABLED" = "yes" ]]; then
_needed_packages="$_needed_packages sasl2-bin" _needed_packages="$_needed_packages sasl2-bin"
fi fi
+1 -1
View File
@@ -575,7 +575,7 @@ fi
# - Install Postfix from debian packages system # - Install Postfix from debian packages system
# - # -
echononl " Install Postfix from debian packages system" echononl " Install Postfix from debian packages system"
_needed_packages="postfix postfix-pcre libsasl2-modules bsd-mailx haveged" _needed_packages="postfix postfix-pcre libsasl2-modules libsasl2-modules-db bsd-mailx haveged"
for _pkg in $_needed_packages ; do for _pkg in $_needed_packages ; do
if `dpkg -l | grep $_pkg | grep -e "^i" > /dev/null 2>&1` ; then if `dpkg -l | grep $_pkg | grep -e "^i" > /dev/null 2>&1` ; then
continue continue
+95 -2
View File
@@ -1182,14 +1182,89 @@ else
echo_ok echo_ok
fi fi
echononl "\tMake file composer.json writable.."
chown ${HTTP_USER} ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION}/composer.json > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
echononl "\tcontinue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/nno]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Script terminated by user input.."
fi
echononl "\tCreate composer.lock file.."
if [[ ${PF_ADMIN_MAJOR_VERSION} -gt 3 ]] ; then
if $PHP_DEBIAN_INSTALLATION ; then
su ${HTTP_USER} -s /bin/bash -c "cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \
php /usr/local/bin/composer config --json \
policy.advisories.ignore-id '[\"PKSA-qv5y-crcz-9nxw\",\"PKSA-kbc7-dq62-pt7d\"]'" \
> $log_file 2>&1
else
su ${HTTP_USER} -s /bin/bash -c "cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \
/usr/local/php-${php_latest_ver}/bin/php /usr/local/bin/composer config --json \
policy.advisories.ignore-id '[\"PKSA-qv5y-crcz-9nxw\",\"PKSA-kbc7-dq62-pt7d\"]'" \
> $log_file 2>&1
fi
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
echo ""
echo -e "\tCommand was: "
if $PHP_DEBIAN_INSTALLATION ; then
cat <<EOF
su ${HTTP_USER} -s /bin/bash -c "cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \
php /usr/local/bin/composer config --json \
policy.advisories.ignore-id '[\"PKSA-qv5y-crcz-9nxw\",\"PKSA-kbc7-dq62-pt7d\"]'" \
> $log_file 2>&1
EOF
else
cat <<EOF
su ${HTTP_USER} -s /bin/bash -c "cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \
/usr/local/php-${php_latest_ver}/bin/php /usr/local/bin/composer config --json \
policy.advisories.ignore-id '[\"PKSA-qv5y-crcz-9nxw\",\"PKSA-kbc7-dq62-pt7d\"]'" \
> $log_file 2>&1
EOF
fi
echo ""
error "$(cat $log_file)"
echononl "\tcontinue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/nno]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Script terminated by user input.."
fi
fi
echononl "\tInstall PHP dependencies ( composer install --no-dev ... ).." echononl "\tInstall PHP dependencies ( composer install --no-dev ... ).."
if [[ ${PF_ADMIN_MAJOR_VERSION} -gt 3 ]] ; then if [[ ${PF_ADMIN_MAJOR_VERSION} -gt 3 ]] ; then
if $PHP_DEBIAN_INSTALLATION ; then if $PHP_DEBIAN_INSTALLATION ; then
su ${HTTP_USER} -c"cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} su ${HTTP_USER} -c"cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \
php /usr/local/bin/composer install --prefer-dist --no-interaction --no-dev" -s /bin/bash \ php /usr/local/bin/composer install --prefer-dist --no-interaction --no-dev" -s /bin/bash \
> $log_file 2>&1 > $log_file 2>&1
else else
su ${HTTP_USER} -c"cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} su ${HTTP_USER} -c"cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \
/usr/local/php-${php_latest_ver}/bin/php /usr/local/bin/composer --prefer-dist \ /usr/local/php-${php_latest_ver}/bin/php /usr/local/bin/composer --prefer-dist \
--no-interaction install --no-dev" -s /bin/bash > $log_file 2>&1 --no-interaction install --no-dev" -s /bin/bash > $log_file 2>&1
fi fi
@@ -1197,6 +1272,24 @@ if [[ ${PF_ADMIN_MAJOR_VERSION} -gt 3 ]] ; then
echo_ok echo_ok
else else
echo_failed echo_failed
echo ""
echo -e "\tCommand was: "
if $PHP_DEBIAN_INSTALLATION ; then
cat <<EOF
su ${HTTP_USER} -c"cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \\
php /usr/local/bin/composer --prefer-dist --no-interaction install --no-dev" -s /bin/bash \\
> $log_file 2>&1
EOF
else
cat <<EOF
su ${HTTP_USER} -c"cd ${WEBSITE_BASEDIR}/postfixadmin-${PF_ADMIN_VERSION} && \\
/usr/local/php-${php_latest_ver}/bin/php /usr/local/bin/composer --prefer-dist \\
--no-interaction install --no-dev" -s /bin/bash > $log_file 2>&1
EOF
fi
echo ""
error "$(cat $log_file)" error "$(cat $log_file)"
echononl "\tcontinue anyway [yes/no]: " echononl "\tcontinue anyway [yes/no]: "
+2 -2
View File
@@ -1046,11 +1046,11 @@ fi
# - # -
echononl "\tInstall a cronjob for cleaning up database" echononl "\tInstall a cronjob for cleaning up database"
crontab -l > /tmp/tmp_crontab crontab -l > /tmp/tmp_crontab
if ! grep -q -E "${WEBSITE_BASEDIR}/htdocs/bin/cleandb.sh" /tmp/tmp_crontab 2> /dev/null ; then if ! grep -q -E "/root/bin/admin-stuff/roundcube-cleandb.sh" /tmp/tmp_crontab 2> /dev/null ; then
echo "" >> /tmp/tmp_crontab echo "" >> /tmp/tmp_crontab
echo "# - Keep roundcube database slick and clean" >> /tmp/tmp_crontab echo "# - Keep roundcube database slick and clean" >> /tmp/tmp_crontab
echo "# -" >> /tmp/tmp_crontab echo "# -" >> /tmp/tmp_crontab
echo "37 3 * * * su - www-data -c \"${WEBSITE_BASEDIR}/htdocs/bin/cleandb.sh > /dev/null\" -s /bin/bash" >> /tmp/tmp_crontab echo "37 3 * * * /root/bin/admin-stuff/roundcube-cleandb.sh -w ${WEBSITE_BASEDIR}" >> /tmp/tmp_crontab
crontab /tmp/tmp_crontab > $log_file 2>&1 crontab /tmp/tmp_crontab > $log_file 2>&1
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
echo_ok echo_ok
+160
View File
@@ -0,0 +1,160 @@
#!/bin/bash
# =============================================================================
# migrate_markasjunk2_to_markasjunk.sh
#
# Migrates the external roundcube-markasjunk2 plugin to the built-in
# markasjunk plugin included in Roundcube 1.7.x+
#
# Usage: migrate_markasjunk2_to_markasjunk.sh <roundcube-install-dir>
#
# Example:
# migrate_markasjunk2_to_markasjunk.sh /var/www/webmail/roundcubemail-1.7.1
# =============================================================================
RC_DIR="${1}"
# -----------------------------------------------------------------------------
# Colors / output helpers
# -----------------------------------------------------------------------------
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[1;33m'
cyan='\033[0;36m'
reset='\033[0m'
info() { echo -e "${cyan}[INFO]${reset} $*"; }
ok() { echo -e "${green}[OK]${reset} $*"; }
skip() { echo -e "\033[0;37m[SKIP] $*${reset}"; }
warn() { echo -e "${yellow}[WARN]${reset} $*"; }
error() { echo -e "${red}[ERROR]${reset} $*"; }
#fatal() { echo -e "${red}[FATAL]${reset} $*"; exit 1; }
fatal() { echo -e "\n ${red}[FATAL]${reset} $*\n"; exit 1; }
# -----------------------------------------------------------------------------
# Sanity checks
# -----------------------------------------------------------------------------
[[ -z "$RC_DIR" ]] && fatal "Usage: $0 <roundcube-install-dir>"
[[ -d "$RC_DIR" ]] || fatal "Directory not found: $RC_DIR"
RC_CONFIG="${RC_DIR}/config/config.inc.php"
PLUGIN_DIR_NEW="${RC_DIR}/plugins/markasjunk"
PLUGIN_CONFIG_NEW="${PLUGIN_DIR_NEW}/config.inc.php"
[[ -f "$RC_CONFIG" ]] || fatal "Roundcube config not found: $RC_CONFIG"
[[ -d "$PLUGIN_DIR_NEW" ]] || fatal "Built-in markasjunk plugin not found: ${PLUGIN_DIR_NEW}"
echo ""
echo "================================================================="
echo " Migrate markasjunk2 -> markasjunk"
echo " Roundcube: ${RC_DIR}"
echo "================================================================="
echo ""
# -----------------------------------------------------------------------------
# Find markasjunk2 plugin directory (may have different names)
# -----------------------------------------------------------------------------
PLUGIN_DIR_OLD=""
for candidate in \
"${RC_DIR}/plugins/markasjunk2" \
"${RC_DIR}/plugins/roundcube-markasjunk2-master" \
"${RC_DIR}/plugins/roundcube-markasjunk2" ; do
if [[ -d "$candidate" ]]; then
PLUGIN_DIR_OLD="$candidate"
break
fi
done
PLUGIN_CONFIG_OLD=""
if [[ -n "$PLUGIN_DIR_OLD" ]]; then
if [[ -f "${PLUGIN_DIR_OLD}/config.inc.php" ]]; then
PLUGIN_CONFIG_OLD="${PLUGIN_DIR_OLD}/config.inc.php"
elif [[ -f "${PLUGIN_DIR_OLD}/config.inc.php.dist" ]]; then
PLUGIN_CONFIG_OLD="${PLUGIN_DIR_OLD}/config.inc.php.dist"
warn "No config.inc.php found in markasjunk2 plugin, using .dist file"
fi
fi
# -----------------------------------------------------------------------------
# Step 1: Replace plugin name in Roundcube config
# -----------------------------------------------------------------------------
info "Step 1: Replacing 'markasjunk2' with 'markasjunk' in config.inc.php.."
if grep -q "'markasjunk2'" "$RC_CONFIG" ; then
sed -i "s/'markasjunk2'/'markasjunk'/g" "$RC_CONFIG"
if [[ $? -eq 0 ]]; then
ok "Plugin name updated in ${RC_CONFIG}"
else
fatal "Failed to update plugin name in ${RC_CONFIG}"
fi
elif grep -q "'markasjunk'" "$RC_CONFIG" ; then
skip "Plugin 'markasjunk' already active in config"
else
warn "Neither 'markasjunk2' nor 'markasjunk' found in config please check manually"
fi
# -----------------------------------------------------------------------------
# Step 2: Migrate plugin config (markasjunk2_ -> markasjunk_)
# -----------------------------------------------------------------------------
info "Step 2: Migrating plugin config.."
if [[ -f "$PLUGIN_CONFIG_NEW" ]]; then
skip "markasjunk config already exists: ${PLUGIN_CONFIG_NEW}"
elif [[ -n "$PLUGIN_CONFIG_OLD" ]]; then
info "Source config: ${PLUGIN_CONFIG_OLD}"
# Copy and rename all markasjunk2_ keys to markasjunk_
sed "s/markasjunk2_/markasjunk_/g" "$PLUGIN_CONFIG_OLD" > "$PLUGIN_CONFIG_NEW"
if [[ $? -eq 0 ]]; then
ok "Config migrated to ${PLUGIN_CONFIG_NEW}"
info "Key changes applied: markasjunk2_* -> markasjunk_*"
else
fatal "Failed to write ${PLUGIN_CONFIG_NEW}"
fi
else
warn "No markasjunk2 config found using built-in defaults for markasjunk"
info "Defaults are fine if your markasjunk2 config was unmodified"
fi
# -----------------------------------------------------------------------------
# Step 3: Optionally remove old markasjunk2 plugin directory
# -----------------------------------------------------------------------------
if [[ -n "$PLUGIN_DIR_OLD" ]]; then
echo ""
echo -n "Remove old markasjunk2 plugin directory '${PLUGIN_DIR_OLD}'? [yes/no]: "
read REMOVE
REMOVE="$(echo "$REMOVE" | tr '[:upper:]' '[:lower:]')"
while [[ "$REMOVE" != "yes" ]] && [[ "$REMOVE" != "no" ]] ; do
echo -n "Wrong entry! - repeat [yes/no]: "
read REMOVE
done
if [[ "$REMOVE" = "yes" ]]; then
rm -rf "$PLUGIN_DIR_OLD"
if [[ $? -eq 0 ]]; then
ok "Removed: ${PLUGIN_DIR_OLD}"
else
error "Failed to remove: ${PLUGIN_DIR_OLD}"
fi
else
info "Keeping old plugin directory: ${PLUGIN_DIR_OLD}"
fi
else
skip "No markasjunk2 plugin directory found nothing to remove"
fi
# -----------------------------------------------------------------------------
# Summary
# -----------------------------------------------------------------------------
echo ""
echo "================================================================="
echo " Summary"
echo "================================================================="
grep -n "markasjunk" "$RC_CONFIG" | while read line ; do
info "config.inc.php line: $line"
done
[[ -f "$PLUGIN_CONFIG_NEW" ]] && ok "Plugin config: ${PLUGIN_CONFIG_NEW}" \
|| warn "No plugin config using built-in defaults"
echo ""
ok "Migration complete."
echo ""
+367
View File
@@ -0,0 +1,367 @@
<?php
/**
* rebuild_roundcube_config.php
*
* Regenerates a Roundcube config/config.inc.php file:
* - parameter order and comments follow config/defaults.inc.php
* - only parameters whose effective value differs from the default are kept
* - parameters that now equal the default, or that no longer exist in this
* Roundcube version, are written to a "*.removed-params.txt" file together
* with the reason and their last known value
* - parameters listed in FORCE_KEEP_PARAMS are kept even if their value
* matches the default; this does NOT resurrect a parameter that no
* longer exists in Roundcube at all (defaults.inc.php / core source)
*
* Usage:
* php rebuild_roundcube_config.php <path-to-roundcube-config-dir> [--apply]
*
* Without --apply (dry run, default):
* writes config.inc.php.new and config.inc.php.removed-params.<timestamp>.txt
* next to the existing files, does NOT touch config.inc.php itself.
*
* With --apply:
* additionally backs up the existing config.inc.php to
* config.inc.php.bak.<timestamp> and replaces it with the regenerated file.
*/
// ---------------------------------------------------------------------------
// Parameters that must always end up in the regenerated config.inc.php, even
// if their value equals the default (they are still kept explicit/visible
// per site policy). Add further keys here as needed. Note: this only
// suppresses the "equals default" removal - a parameter that no longer
// exists in Roundcube at all is still dropped, see UNDOCUMENTED_CORE_PARAMS
// below for parameters that need to be recognized as still valid.
// ---------------------------------------------------------------------------
const FORCE_KEEP_PARAMS = [
'imap_host',
'imap_auth_type',
'smtp_host',
'smtp_port',
'smtp_user',
'smtp_pass',
];
// ---------------------------------------------------------------------------
// Parameters that exist in Roundcube 1.7.1 core (verified against program/
// source via `->config->get('key', ...)` calls) but are NOT documented in
// the shipped config/defaults.inc.php. Keys found here are kept (not treated
// as "removed"), using this one-line note as their comment since
// defaults.inc.php has none. Verified 2026-07-03 against roundcubemail-1.7.1.
// ---------------------------------------------------------------------------
const UNDOCUMENTED_CORE_PARAMS = [
'additional_logo_types' => 'Additional file extensions accepted as skin logo/favicon images.',
'dark_mode_support' => 'Tell the skin/UI that dark mode is supported (exposed to the client as env var).',
'devel_mode' => 'Enable developer/debug mode (verbose errors, disables some internal caching).',
'editor_css_location' => 'Path to a custom CSS file loaded into the HTML message editor (TinyMCE).',
'embed_css_location' => 'Path to a custom CSS file loaded for embedded/quoted HTML messages.',
'enable_caching' => 'Enable IMAP/message caching in the database.',
'lock_special_folders' => "Prevent users from unsubscribing/renaming special folders (Inbox, Sent, ...).",
'media_browser_css_location' => 'Path to a custom CSS file for the TinyMCE media browser.',
'namespace_fixed' => 'Treat the configured IMAP namespace as fixed instead of auto-detecting it.',
'pagesize' => 'Legacy fallback for addressbook_pagesize (see mail_pagesize/addressbook_pagesize).',
'performance_stats' => 'Log performance/timing statistics (like devel_mode).',
'shared_cache_ttl' => 'TTL for the shared memcache/redis object cache.',
'spellcheck_atd_key' => 'API key for the After-the-Deadline spell checking service.',
'storage_driver' => "Storage backend driver; currently only 'imap' is implemented by core.",
'tnef_decode' => 'Enable/disable decoding of winmail.dat (TNEF) attachments.',
];
function fail(string $msg): void
{
fwrite(STDERR, "ERROR: $msg\n");
exit(1);
}
function read_lines(string $file): array
{
$lines = file($file);
if ($lines === false) {
fail("Cannot read $file");
}
return $lines;
}
/**
* Parse a Roundcube config source file into an ordered list of parameter
* blocks: key => ['comment' => <preceding comment/blank lines verbatim>,
* 'value_src' => <raw PHP source of the right-hand side>]
*
* Returns [orderedKeys (first-seen order), paramsByKey (last occurrence wins,
* matching PHP's own "last assignment wins" semantics for duplicate keys)]
*/
function parse_config_source(string $file): array
{
$lines = read_lines($file);
$n = count($lines);
$order = [];
$params = [];
$commentBuf = [];
$i = 0;
while ($i < $n) {
$line = $lines[$i];
// $config = []; / $config = array(); is the file's own initializer,
// not a parameter - discard any accumulated header/license comments.
if (preg_match('/^\$config\s*=\s*(\[\]|array\(\))\s*;/', trim($line))) {
$commentBuf = [];
$i++;
continue;
}
if (preg_match("/^\\\$config\\['([a-zA-Z0-9_]+)'\\]\\s*=/", $line, $m)) {
$key = $m[1];
$stmtLines = [$line];
$depth = substr_count($line, '(') - substr_count($line, ')')
+ substr_count($line, '[') - substr_count($line, ']');
$j = $i;
while (!(preg_match('/;\s*$/', rtrim($line)) && $depth <= 0)) {
$j++;
if ($j >= $n) {
fail("Unterminated statement for \$config['$key'] in $file at line " . ($i + 1));
}
$line = $lines[$j];
$stmtLines[] = $line;
$depth += substr_count($line, '(') - substr_count($line, ')')
+ substr_count($line, '[') - substr_count($line, ']');
}
$raw = implode('', $stmtLines);
if (!preg_match("/^\\\$config\\['" . preg_quote($key, '/') . "'\\]\\s*=\\s*(.*);\\s*$/s", $raw, $vm)) {
fail("Could not extract value for \$config['$key'] in $file");
}
$comment = rtrim(implode('', $commentBuf));
$params[$key] = [
'comment' => $comment,
'value_src' => rtrim($vm[1]),
];
if (!in_array($key, $order, true)) {
$order[] = $key;
}
$commentBuf = [];
$i = $j + 1;
continue;
}
$commentBuf[] = $line;
$i++;
}
return [$order, $params];
}
/**
* Evaluate a raw PHP expression (right-hand side of a $config[...] = ...;
* assignment) in isolation. These expressions in defaults.inc.php /
* config.inc.php are self-contained literals (strings, bools, numbers,
* arrays) with no external references, so isolated eval() is safe here.
*/
function eval_value_src(string $key, string $src, string $originFile)
{
try {
return eval("return $src;");
} catch (\Throwable $e) {
fail("Failed to evaluate \$config['$key'] from $originFile: " . $e->getMessage());
}
}
function values_equal($a, $b): bool
{
return var_export($a, true) === var_export($b, true);
}
function format_value(string $key, $val): string
{
// Prefer var_export for the (rare) fallback path where we don't have
// the original source text available.
$s = var_export($val, true);
return $s;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
$configDir = isset($argv[1]) ? rtrim($argv[1], '/') : null;
$apply = in_array('--apply', $argv, true);
if (!$configDir || !is_dir($configDir)) {
fail("Usage: php rebuild_roundcube_config.php <path-to-roundcube-config-dir> [--apply]");
}
$defaultsFile = "$configDir/defaults.inc.php";
$localFile = "$configDir/config.inc.php";
if (!is_file($defaultsFile)) {
fail("Not found: $defaultsFile");
}
if (!is_file($localFile)) {
fail("Not found: $localFile");
}
// defaults.inc.php references RCUBE_INSTALL_PATH (e.g. for log_dir/temp_dir
// defaults). Define it exactly as Roundcube's own bootstrap does: the
// installation root (parent of config/) with a trailing slash.
if (!defined('RCUBE_INSTALL_PATH')) {
define('RCUBE_INSTALL_PATH', dirname($configDir) . '/');
}
[$defaultsOrder, $defaultsParams] = parse_config_source($defaultsFile);
[$localOrder, $localParams] = parse_config_source($localFile);
// Detect duplicate keys in the local file, and report which value wins
// (informational - matches actual PHP "last wins" behaviour).
$rawLocalLines = read_lines($localFile);
$assignCounts = [];
foreach ($rawLocalLines as $line) {
if (preg_match("/^\\\$config\\['([a-zA-Z0-9_]+)'\\]\\s*=/", $line, $m)) {
$assignCounts[$m[1]] = ($assignCounts[$m[1]] ?? 0) + 1;
}
}
$duplicates = array_filter($assignCounts, fn($c) => $c > 1);
$keep = []; // key => value_src (raw source text to emit)
$removed = []; // key => reason string
foreach ($localOrder as $key) {
$localSrc = $localParams[$key]['value_src'];
$localVal = eval_value_src($key, $localSrc, $localFile);
$forceKeep = in_array($key, FORCE_KEEP_PARAMS, true);
if (array_key_exists($key, $defaultsParams)) {
$defaultVal = eval_value_src($key, $defaultsParams[$key]['value_src'], $defaultsFile);
if (!$forceKeep && values_equal($localVal, $defaultVal)) {
$removed[$key] = [
'reason' => 'entspricht dem Default-Wert',
'value' => format_value($key, $localVal),
];
continue;
}
$keep[$key] = $localSrc;
} elseif (array_key_exists($key, UNDOCUMENTED_CORE_PARAMS)) {
// Known to still be a valid core parameter, just undocumented.
$keep[$key] = $localSrc;
} else {
// Parameter no longer exists at all (neither in defaults.inc.php nor
// in UNDOCUMENTED_CORE_PARAMS) - FORCE_KEEP_PARAMS cannot resurrect
// it, there is nothing left to keep it in sync with.
$removed[$key] = [
'reason' => 'Parameter existiert in dieser Roundcube-Version nicht (mehr) / ist unbekannt',
'value' => format_value($key, $localVal),
];
}
}
// Force-keep parameters that were not set in config.inc.php at all: fall
// back to the default value so the parameter still shows up explicitly.
// Only applies to parameters that still exist in defaults.inc.php.
foreach (FORCE_KEEP_PARAMS as $key) {
if (array_key_exists($key, $keep) || array_key_exists($key, $localParams)) {
continue;
}
if (array_key_exists($key, $defaultsParams)) {
$keep[$key] = $defaultsParams[$key]['value_src'];
}
}
// ---------------------------------------------------------------------------
// Build new config.inc.php content
// ---------------------------------------------------------------------------
$timestamp = date('Y-m-d-Hi');
$totalKept = count($keep);
$out = [];
$out[] = '<?php';
$out[] = '';
$out[] = '/* Local configuration for Roundcube Webmail';
$out[] = ' *';
$out[] = " * Regenerated by rebuild_roundcube_config.php on " . date('Y-m-d H:i');
$out[] = ' * Parameter order and comments follow config/defaults.inc.php.';
$out[] = ' * Parameters dropped during regeneration are listed in the';
$out[] = ' * accompanying *.removed-params.*.txt file.';
$out[] = ' */';
$out[] = '';
$out[] = '$config = [];';
$out[] = '';
foreach ($defaultsOrder as $key) {
if (!array_key_exists($key, $keep)) {
continue;
}
$comment = $defaultsParams[$key]['comment'];
if ($comment !== '') {
$out[] = $comment;
}
$out[] = "\$config['$key'] = " . $keep[$key] . ';';
$out[] = '';
unset($keep[$key]);
}
// Remaining kept keys are the "undocumented but valid core parameter" ones -
// defaults.inc.php has no position/comment for them.
if (!empty($keep)) {
$out[] = '// ----------------------------------------------------------------';
$out[] = '// Parameters valid in this Roundcube core version but not documented';
$out[] = '// in config/defaults.inc.php (see rebuild_roundcube_config.php).';
$out[] = '// ----------------------------------------------------------------';
$out[] = '';
foreach ($keep as $key => $src) {
$out[] = '// ' . (UNDOCUMENTED_CORE_PARAMS[$key] ?? '');
$out[] = "\$config['$key'] = " . $src . ';';
$out[] = '';
}
}
$newContent = implode("\n", $out) . "\n";
// ---------------------------------------------------------------------------
// Build removed-params report
// ---------------------------------------------------------------------------
$report = [];
$report[] = "# Parameter removed from config.inc.php on regeneration ($timestamp)";
$report[] = "# Format: key = last effective value // reason";
$report[] = "";
foreach ($removed as $key => $info) {
$report[] = "$key = {$info['value']} // {$info['reason']}";
}
$reportContent = implode("\n", $report) . "\n";
// ---------------------------------------------------------------------------
// Write output / apply
// ---------------------------------------------------------------------------
$newFile = "$localFile.new";
$reportFile = "$configDir/config.inc.php.removed-params.$timestamp.txt";
file_put_contents($newFile, $newContent);
file_put_contents($reportFile, $reportContent);
echo "Wrote: $newFile\n";
echo "Wrote: $reportFile\n";
echo "\n";
echo "Kept parameters : $totalKept\n";
echo "Removed parameters: " . count($removed) . "\n";
if (!empty($duplicates)) {
echo "\nWARNING: the following keys were assigned more than once in config.inc.php\n";
echo "(PHP uses the LAST assignment - already reflected above, listed here for review):\n";
foreach ($duplicates as $key => $count) {
echo " - $key ($count assignments)\n";
}
}
if ($apply) {
$backupFile = "$localFile.bak.$timestamp";
if (!copy($localFile, $backupFile)) {
fail("Could not create backup $backupFile");
}
if (!rename($newFile, $localFile)) {
fail("Could not move $newFile to $localFile");
}
echo "\nApplied: backed up original to $backupFile and replaced $localFile\n";
} else {
echo "\nDry run only. Review $newFile and $reportFile, then re-run with --apply.\n";
}
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Batch-update quota2 table for all active mailboxes from doveadm quota get."""
import subprocess
import sys
DOVEADM = '/usr/local/dovecot/bin/doveadm'
MYSQL = 'mysql'
DB_USER = 'postfix'
DB_PASS = 'T3CJnFMJNX9wmhNs'
DB_SOCKET = '/run/mysqld/mysqld.sock'
DB_NAME = 'postfix'
def mysql_query(sql):
return subprocess.run(
[MYSQL, '-u', DB_USER, '-p' + DB_PASS, '--socket', DB_SOCKET,
DB_NAME, '-sNe', sql],
capture_output=True, text=True)
def get_users(limit=None):
query = 'SELECT username FROM mailbox WHERE active = 1 ORDER BY username;'
if limit:
query = 'SELECT username FROM mailbox WHERE active = 1 ORDER BY username LIMIT %d;' % limit
r = mysql_query(query)
return [u.strip() for u in r.stdout.strip().split('\n') if u.strip()]
def get_quota(user):
result = subprocess.run(
[DOVEADM, 'quota', 'get', '-u', user],
capture_output=True, text=True
)
bytes_val = None
messages_val = None
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) < 4:
continue
if parts[2] == 'STORAGE' and parts[1] != 'Type':
try:
bytes_val = int(parts[3]) * 1024
except ValueError:
bytes_val = 0
elif parts[2] == 'MESSAGE' and parts[1] != 'Type':
try:
messages_val = int(parts[3])
except ValueError:
messages_val = 0
return bytes_val, messages_val
def upsert_quota2(user, bytes_val, messages_val):
sql = ("REPLACE INTO quota2 (username, bytes, messages) VALUES ('"
+ user.replace("'", "\'") + "', "
+ str(bytes_val) + ", " + str(messages_val) + ");")
mysql_query(sql)
def main():
dry_run = '--dry-run' in sys.argv
limit = None
if '--limit' in sys.argv:
idx = sys.argv.index('--limit')
try:
limit = int(sys.argv[idx + 1])
except (IndexError, ValueError):
print('Usage: update_quota2.sh [--dry-run] [--limit N]')
sys.exit(1)
users = get_users(limit)
total = len(users)
print('Processing %d mailboxes...' % total)
ok = 0
skip = 0
for i, user in enumerate(users, 1):
bytes_val, messages_val = get_quota(user)
if bytes_val is None or messages_val is None:
print(' [%d/%d] SKIP %s (no quota data)' % (i, total, user))
skip += 1
continue
if dry_run:
print(' [%d/%d] DRY %s: %d bytes, %d messages' % (i, total, user, bytes_val, messages_val))
else:
upsert_quota2(user, bytes_val, messages_val)
if i % 50 == 0 or i == total:
print(' [%d/%d] done' % (i, total))
ok += 1
print('Done: %d updated, %d skipped.' % (ok, skip))
if __name__ == '__main__':
main()
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
Batch-update the quota2 table for all active mailboxes.
Uses 'doveadm quota get' (must run as root) to read current usage and
writes it to quota2 via INSERT ... ON CONFLICT DO UPDATE.
Usage:
update_quota2.sh [--dry-run] [--limit N]
Options:
--dry-run Print what would be done without touching the database.
--limit N Process only the first N mailboxes (useful for testing).
"""
import subprocess
import sys
DOVEADM = '/usr/local/dovecot/bin/doveadm'
PSQL = 'psql'
DB_USER = 'postfix'
DB_NAME = 'postfix'
def get_users(limit=None):
query = 'SELECT username FROM mailbox WHERE active = true ORDER BY username;'
if limit:
query = ('SELECT username FROM mailbox WHERE active = true '
'ORDER BY username LIMIT %d;' % limit)
result = subprocess.run(
[PSQL, '-U', DB_USER, DB_NAME, '-tAF', '|', '-c', query],
capture_output=True, text=True
)
return [u.strip() for u in result.stdout.strip().split('\n') if u.strip()]
def get_quota(user):
"""Returns (bytes, messages) or (None, None) if not available."""
result = subprocess.run(
[DOVEADM, 'quota', 'get', '-u', user],
capture_output=True, text=True
)
bytes_val = messages_val = None
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) < 4:
continue
if parts[2] == 'STORAGE' and parts[1] != 'Type':
try:
bytes_val = int(parts[3]) * 1024 # KiB → bytes
except ValueError:
bytes_val = 0
elif parts[2] == 'MESSAGE' and parts[1] != 'Type':
try:
messages_val = int(parts[3])
except ValueError:
messages_val = 0
return bytes_val, messages_val
def upsert_quota2(user, bytes_val, messages_val):
sql = (
"INSERT INTO quota2 (username, bytes, messages) VALUES ('"
+ user.replace("'", "''")
+ "', " + str(bytes_val) + ", " + str(messages_val) + ") "
"ON CONFLICT (username) DO UPDATE "
"SET bytes = EXCLUDED.bytes, messages = EXCLUDED.messages;"
)
subprocess.run([PSQL, '-U', DB_USER, DB_NAME, '-c', sql],
capture_output=True)
def main():
dry_run = '--dry-run' in sys.argv
limit = None
if '--limit' in sys.argv:
idx = sys.argv.index('--limit')
try:
limit = int(sys.argv[idx + 1])
except (IndexError, ValueError):
print('Usage: update_quota2.sh [--dry-run] [--limit N]')
sys.exit(1)
users = get_users(limit)
total = len(users)
print('Processing %d mailboxes...' % total)
updated = skipped = 0
for i, user in enumerate(users, 1):
bytes_val, messages_val = get_quota(user)
if bytes_val is None or messages_val is None:
print(' [%d/%d] SKIP %s (no quota data)' % (i, total, user))
skipped += 1
continue
if dry_run:
print(' [%d/%d] DRY %s: %d bytes, %d messages'
% (i, total, user, bytes_val, messages_val))
else:
upsert_quota2(user, bytes_val, messages_val)
if i % 50 == 0 or i == total:
print(' [%d/%d] done' % (i, total))
updated += 1
print('Done: %d updated, %d skipped.' % (updated, skipped))
if __name__ == '__main__':
main()
+234 -6
View File
@@ -4,6 +4,7 @@ clear
echo -e "\n \033[32mStart script for upgrading Roundcube Webmailer..\033[m" echo -e "\n \033[32mStart script for upgrading Roundcube Webmailer..\033[m"
CUR_IFS="$IFS" CUR_IFS="$IFS"
export COMPOSER_ALLOW_SUPERUSER=1
## ----------------------------------------------------------------- ## -----------------------------------------------------------------
## ---------------------------------------------------------------- ## ----------------------------------------------------------------
@@ -295,6 +296,12 @@ DEFAULT_WEBSITE_BASEDIR="/var/www/${WEBSITE_NAME}"
[[ -n "$WEBSITE_BASEDIR" ]] || WEBSITE_BASEDIR=$DEFAULT_WEBSITE_BASEDIR [[ -n "$WEBSITE_BASEDIR" ]] || WEBSITE_BASEDIR=$DEFAULT_WEBSITE_BASEDIR
CUR_INSTALL_DIR="$(realpath "${WEBSITE_BASEDIR}/htdocs")" CUR_INSTALL_DIR="$(realpath "${WEBSITE_BASEDIR}/htdocs")"
# For Roundcube 1.7.x the symlink points to roundcubemail-x.y.z/public_html/
# We need the parent roundcubemail directory, not public_html
if [[ "$(basename "$CUR_INSTALL_DIR")" = "public_html" ]]; then
CUR_INSTALL_DIR="$(dirname "$CUR_INSTALL_DIR")"
fi
if [[ ! -d "$CUR_INSTALL_DIR" ]] ; then if [[ ! -d "$CUR_INSTALL_DIR" ]] ; then
fatal "No current installation of roundcube found!" fatal "No current installation of roundcube found!"
fi fi
@@ -429,6 +436,9 @@ do
echo -e "\n\t\033[33m\033[1mA version number is required!\033[m\n" echo -e "\n\t\033[33m\033[1mA version number is required!\033[m\n"
fi fi
done done
# Hauptversionsnummer extrahieren (1.6 oder 1.7)
RC_MAIN_VERSION=$(echo "$ROUNDCUBE_VERSION" | cut -d. -f1-2)
echo "" echo ""
echo -e "\033[32m--\033[m" echo -e "\033[32m--\033[m"
echo "" echo ""
@@ -521,6 +531,7 @@ echo ""
echo -e "\033[1;32mSettings for upgrading \033[1;37mRoundcube Webmail\033[m" echo -e "\033[1;32mSettings for upgrading \033[1;37mRoundcube Webmail\033[m"
echo "" echo ""
echo -e "\tRoundcube Version....................: $ROUNDCUBE_VERSION" echo -e "\tRoundcube Version....................: $ROUNDCUBE_VERSION"
echo -e "\tRoundcube Main Version...............: $RC_MAIN_VERSION"
echo "" echo ""
echo -e "\tName of the Website..................: $WEBSITE_NAME" echo -e "\tName of the Website..................: $WEBSITE_NAME"
echo "" echo ""
@@ -891,6 +902,7 @@ fi
echononl "\tRename the composer.json-dist file into composer.json" echononl "\tRename the composer.json-dist file into composer.json"
if [ -f "composer.json-dist" ] ; then
cp -a "composer.json-dist" "composer.json" > $log_file 2>&1 cp -a "composer.json-dist" "composer.json" > $log_file 2>&1
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
echo_ok echo_ok
@@ -898,6 +910,9 @@ else
echo_failed echo_failed
error "$(cat $log_file)" error "$(cat $log_file)"
fi fi
else
echo_skipped
fi
echononl "\tInstall composer to ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}" echononl "\tInstall composer to ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}"
php ${script_dir}/composer-setup.php --install-dir=${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION} > $log_file 2>&1 php ${script_dir}/composer-setup.php --install-dir=${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION} > $log_file 2>&1
@@ -940,12 +955,51 @@ else
fi fi
echo "" # Root-Warning unterdrücken
echo "" export COMPOSER_ALLOW_SUPERUSER=1
echo "You might want to deactivate some plugins in the configuration file, e.g. the plugin 'carddav'.." export COMPOSER_ROOT_VERSION=${ROUNDCUBE_VERSION}
echo ""
echononl "continue [yes/no]: "
echononl " Deactivate spellcheck_engine (pspell not available in PHP 8.4+).."
if grep -q "spellcheck_engine.*pspell" "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/config/config.inc.php" 2>/dev/null ; then
sed -i "s/\\\$config\['spellcheck_engine'\].*=.*'pspell'.*/\$config['spellcheck_engine'] = '';/" \
"${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/config/config.inc.php" > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
echo_skipped
fi
echononl " Replace deprecated markasjunk2 plugin with built-in markasjunk.."
if grep -q "'markasjunk2'" "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/config/config.inc.php" 2>/dev/null ; then
sed -i "s/'markasjunk2'/'markasjunk'/" \
"${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/config/config.inc.php" > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
echo_skipped
fi
echononl " Copy composer.json from source archive to installation directory.."
cp "${script_dir}/roundcubemail-${ROUNDCUBE_VERSION}/composer.json" \
"${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/composer.json" > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
echononl "continue anyway [yes/no]: "
read OK read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')" OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
@@ -953,8 +1007,60 @@ while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
read OK read OK
done done
[[ $OK = "yes" ]] || fatal "Abbruch durch User" [[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
echononl " Remove old composer.lock (force fresh dependency resolution).."
if [[ -f "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/composer.lock" ]]; then
rm "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/composer.lock" > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
echo_skipped
fi
echononl " Allow Plugins .."
php composer.phar --no-interaction config allow-plugins.roundcube/plugin-installer true > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "command was:\n\t # cd "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}"\n\t # php composer.phar --no-interaction config allow-plugins.roundcube/plugin-installer truen\n$(cat ${log_file})"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
echononl " Deactivate Security-Advisory-Block (for Dev-Deps).."
php composer.phar --no-interaction config policy.advisories.block false > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "command was:\n\t # cd "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}"\n\t # php composer.phar --no-interaction config policy.advisories.block false\n\n$(cat ${log_file})"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
echononl " Update dependencies by running 'php composer.phar update --no-dev'" echononl " Update dependencies by running 'php composer.phar update --no-dev'"
php composer.phar --no-interaction update --no-dev > ${_log_dir}/update_roundcube-${ROUNDCUBE_VERSION}-dependencies.${backup_date}.log 2>&1 php composer.phar --no-interaction update --no-dev > ${_log_dir}/update_roundcube-${ROUNDCUBE_VERSION}-dependencies.${backup_date}.log 2>&1
@@ -974,6 +1080,125 @@ else
[[ $OK = "yes" ]] || fatal "Abbruch durch User" [[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi fi
echononl " Reactivate Security-Advisory-Block .."
php composer.phar --no-interaction config policy.advisories.block true
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "command was:\n\t # cd "${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}"\n\t #php composer.phar --no-interaction config policy.advisories.block true\ni\n$(cat ${log_file})"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
echononl " Regenerate Composer autoloader (remove stale files).."
_autoload_dir="${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/vendor/composer"
rm -f "${_autoload_dir}/autoload_real.php" \
"${_autoload_dir}/ClassLoader.php" \
"${_autoload_dir}/InstalledVersions.php" \
"${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/vendor/autoload.php"
php composer.phar --no-interaction dump-autoload > $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
echononl " Run database schema migration (bin/updatedb.sh).."
php ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/bin/updatedb.sh \
--package=roundcube \
--dir=${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/SQL \
> $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
# Check if failure is due to a partially applied migration ("already exists")
if grep -q "already exists\|existiert bereits" $log_file ; then
warn "DB migration had 'already exists' errors - attempting to skip failed migration and retry.."
# Extract the failed migration ID from the error output
_failed_migration=$(grep -oE "\([0-9]{10}\)" $log_file | head -1 | tr -d '()')
if [[ -n "$_failed_migration" ]]; then
info "Setting DB schema version to '${_failed_migration}' and retrying.."
if [[ "$DB_TYPE" = "pgsql" ]]; then
su - postgres -c "${psql_command} -q -d ${DB_NAME} -c \
\"UPDATE system SET value='${_failed_migration}' WHERE name='roundcube-version';\"" > $log_file 2>&1
elif [[ "$DB_TYPE" = "mysql" ]]; then
${mysql_command} ${MYSQL_CREDENTIALS} ${DB_NAME} -e \
"UPDATE system SET value='${_failed_migration}' WHERE name='roundcube-version';" > $log_file 2>&1
fi
# Retry migration
php ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/bin/updatedb.sh \
--package=roundcube \
--dir=${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/SQL \
>> $log_file 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "Retry also failed:\n$(cat $log_file)"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
else
echo_failed
error "Could not extract failed migration ID.\ncommand was:\n\t# php ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/bin/updatedb.sh --package=roundcube --dir=${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/SQL\n\n$(cat $log_file)"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
else
echo_failed
error "command was:\n\t# php ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/bin/updatedb.sh --package=roundcube --dir=${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/SQL\n\n$(cat $log_file)"
echononl "continue anyway [yes/no]: "
read OK
OK="$(echo "$OK" | tr '[:upper:]' '[:lower:]')"
while [[ "$OK" != "yes" ]] && [[ "$OK" != "no" ]] ; do
echononl "Wrong entry! - repeat [yes/no]: "
read OK
done
[[ $OK = "yes" ]] || fatal "Abbruch durch User"
fi
fi
echononl " Index build-in addressbook" echononl " Index build-in addressbook"
${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/bin/indexcontacts.sh > $log_file 2>&1 ${WEBSITE_BASEDIR}/roundcubemail-${ROUNDCUBE_VERSION}/bin/indexcontacts.sh > $log_file 2>&1
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
@@ -1006,7 +1231,11 @@ elif [[ -f "${WEBSITE_BASEDIR}/htdocs" ]]; then
fi fi
fi fi
if [ "$RC_MAIN_VERSION" == "1.7" ]; then
ln -s "roundcubemail-${ROUNDCUBE_VERSION}/public_html/" "${WEBSITE_BASEDIR}/htdocs" >> $log_file 2>&1
else
ln -s "roundcubemail-${ROUNDCUBE_VERSION}" "${WEBSITE_BASEDIR}/htdocs" >> $log_file 2>&1 ln -s "roundcubemail-${ROUNDCUBE_VERSION}" "${WEBSITE_BASEDIR}/htdocs" >> $log_file 2>&1
fi
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
_failed=true _failed=true
fi fi
@@ -1106,4 +1335,3 @@ fi
echo "" echo ""
clean_up 0 clean_up 0