install_postfixadmin.sh: update used_quotas configuration to 'YES' for PostfixAdmin
install_update_dovecot-2.4.sh: add quota_clone plugin configuration for real-time quota tracking create settings.json: add permissions for quota-related commands in PostfixAdmin create install_quota_clone.sh: script to configure Dovecot quota_clone plugin and PostfixAdmin integration
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/90-quota.conf 2>/dev/null || echo 'FILE NOT FOUND'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n -i 'quota\\\\|imap' /var/www/adm.oopen.de/postfixadmin-3.3.13/config.local.php 2>/dev/null | head -40\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n -i 'quota\\\\|getquota\\\\|imap' /var/www/adm.oopen.de/postfixadmin-3.3.13/config.inc.php 2>/dev/null | head -60\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n -i 'quota\\\\|getquota\\\\|imap\\\\|dovecot_doveadm\\\\|doveadm' /var/www/adm.oopen.de/postfixadmin-3.3.13/config.inc.php 2>/dev/null | grep -v '^.*:\\\\/\\\\/' | head -60\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"sed -n '680,780p' /var/www/adm.oopen.de/postfixadmin-3.3.13/config.inc.php\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"sudo sed -n '680,780p' /var/www/adm.oopen.de/postfixadmin-3.3.13/config.inc.php\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls -la /var/www/adm.oopen.de/postfixadmin-3.3.13/ && cat /var/www/adm.oopen.de/postfixadmin-3.3.13/config.local.php 2>/dev/null | grep -n -i 'quota\\\\|imap\\\\|doveadm' | head -40\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n 'used_quotas\\\\|new_quota_table\\\\|getquota\\\\|imap_quota\\\\|doveadm\\\\|dovecot' /var/www/adm.oopen.de/postfixadmin-3.3.13/config.local.php 2>/dev/null\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n 'getquota\\\\|quota_get\\\\|imap.*quota\\\\|GETQUOTA\\\\|quota_used\\\\|used_quota\\\\|dovecot_quota' /var/www/adm.oopen.de/postfixadmin-3.3.13/functions.inc.php 2>/dev/null | head -30\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot-2.4.2/bin/doveadm quota get -u 'ckubu@oopen.de' 2>/dev/null || /usr/local/dovecot/bin/doveadm quota get -u 'ckubu@oopen.de' 2>&1 | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot/bin/doveadm quota get -u 'chris@oopen.de' 2>&1 | head -10\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot/bin/doveadm user '*@oopen.de' 2>&1 | head -5\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"psql -U postfix postfix -c \\\\\"SELECT username, quota FROM mailbox LIMIT 5;\\\\\" 2>/dev/null\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot/bin/doveadm quota get -u 'bastisch@kottbusserdamm.net' 2>&1\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/10-auth.conf 2>/dev/null | grep -v '^#\\\\|^$' | head -30\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/var/run/dovecot/ 2>/dev/null || ls /var/run/dovecot/ 2>/dev/null | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot/bin/doveadm -D quota get -u 'bastisch@kottbusserdamm.net' 2>&1 | tail -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/lib/dovecot/ 2>/dev/null | grep -i quota\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/90-quota.conf | grep -v '^#\\\\|^$'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/dovecot.conf | grep -v '^#\\\\|^$'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/ && cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/20-imap.conf 2>/dev/null | grep -v '^#\\\\|^$'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/10-mail.conf | grep -v '^#\\\\|^$'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/share/doc/dovecot/quota_clone* 2>/dev/null || find /usr/local/dovecot-2.4.2/share/doc/dovecot/ -name '*quota*' 2>/dev/null | head -10\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/etc/dovecot/*.conf* 2>/dev/null && find /usr/local/dovecot-2.4.2/etc/dovecot/ -name '*.ext' 2>/dev/null\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/lib/dovecot/ | grep -i dict\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/sql-connect.conf.ext\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/lib/dovecot/ | grep -i 'pgsql\\\\|mysql\\\\|sqlite\\\\|db'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /usr/local/dovecot-2.4.2/lib/ | grep -i 'sql\\\\|dict\\\\|pgsql\\\\|mysql'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"find /usr/local/dovecot-2.4.2/ -name '*dict*' -o -name '*pgsql*' -o -name '*sql*' 2>/dev/null | grep '\\\\.so'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"psql -U postfix postfix -c \\\\\"SELECT * FROM quota2 LIMIT 5;\\\\\" 2>/dev/null\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"psql -U postfix postfix -c \\\\\"\\\\dt\\\\\" 2>/dev/null | grep -i quota\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"find /usr/local/dovecot-2.4.2/share/ -name '*.conf' -o -name '*.conf.ext' -o -name '*example*' 2>/dev/null | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot-2.4.2/sbin/dovecot -a 2>/dev/null | grep -i 'quota_clone\\\\|clone' | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot-2.4.2/sbin/dovecot -a 2>/dev/null | grep -i 'quota_clone' | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot-2.4.2/sbin/dovecot -a 2>/dev/null | grep -i 'dict\\\\|clone' | head -40\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot-2.4.2/sbin/dovecot -a 2>/dev/null | grep -A5 'service dict'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/10-master.conf | grep -v '^#\\\\|^$'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n 'imap\\\\|getquota\\\\|quota' /var/www/adm.oopen.de/postfixadmin-3.3.13/functions.inc.php 2>/dev/null | grep -i 'imap.*quota\\\\|quota.*imap\\\\|getquota\\\\|imap_open\\\\|imap_getquota\\\\|used_quota' | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -n 'getquota\\\\|imap_open\\\\|imap_close\\\\|imap_quota' /var/www/adm.oopen.de/postfixadmin-3.3.13/functions.inc.php 2>/dev/null | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/auth-sql.conf.ext\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot-2.4.2/bin/doveadm -D quota get -u 'bastisch@kottbusserdamm.net' 2>&1 | grep -E 'Error|Debug.*auth|FAIL|passdb|userdb' | head -15\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls /var/www/adm.oopen.de/postfixadmin-3.3.13/ADDITIONS/ && ls /var/www/adm.oopen.de/postfixadmin-3.3.13/scripts/\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"journalctl -u dovecot --since '5 minutes ago' 2>/dev/null | grep -i 'quota\\\\|bastisch\\\\|userdb\\\\|error' | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /var/www/adm.oopen.de/postfixadmin-3.3.13/ADDITIONS/quota_usage.pl\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"ls -la /var/www/adm.oopen.de/postfixadmin-3.3.13/ADDITIONS/\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"sudo cat /var/www/adm.oopen.de/postfixadmin-3.3.13/ADDITIONS/quota_usage.pl 2>/dev/null || sg www-data -c 'cat /var/www/adm.oopen.de/postfixadmin-3.3.13/ADDITIONS/quota_usage.pl' 2>/dev/null\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"id && groups\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -E 'quota|bastisch|userdb.*FAIL' /var/log/mail.log 2>/dev/null | tail -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"/usr/local/dovecot/bin/doveadm -c /usr/local/dovecot-2.4.2/etc/dovecot/dovecot.conf user 'bastisch@kottbusserdamm.net' 2>&1\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"psql -U postfix postfix -c \\\\\"SELECT username, maildir, quota, active FROM mailbox WHERE username = 'bastisch@kottbusserdamm.net';\\\\\" 2>/dev/null\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -i 'auth.*worker\\\\|sql.*error\\\\|error.*sql\\\\|userdb.*error' /var/log/syslog 2>/dev/null | tail -10\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"journalctl -u dovecot --since '10 minutes ago' 2>/dev/null | grep -i 'userdb\\\\|auth.*worker\\\\|sql\\\\|error' | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"journalctl -t dovecot --since '10 minutes ago' 2>/dev/null | grep -i 'bastisch\\\\|userdb\\\\|doveadm' | head -20\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"cat /usr/local/dovecot-2.4.2/etc/dovecot/conf.d/10-logging.conf | grep -v '^#\\\\|^$'\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -i 'bastisch\\\\|auth.*worker\\\\|userdb.*fail\\\\|doveadm' /var/log/local1.log 2>/dev/null | tail -20 || grep -i 'bastisch\\\\|auth.*worker\\\\|userdb.*fail\\\\|doveadm' /var/log/syslog 2>/dev/null | tail -10\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"find /var/log/ -name '*.log' -newer /var/log/auth.log 2>/dev/null | head -10 && ls /var/log/*.log 2>/dev/null | head -10\")",
|
||||||
|
"Bash(ssh a.mx.oopen.de \"grep -i 'bastisch\\\\|doveadm.*quota\\\\|auth.*fail\\\\|userdb.*error' /var/log/dovecot/dovecot.log 2>/dev/null | tail -20\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2610,7 +2610,7 @@ fi
|
|||||||
## - $CONF['show_undeliverable']='NO';
|
## - $CONF['show_undeliverable']='NO';
|
||||||
## - $CONF['show_popimap']='NO';
|
## - $CONF['show_popimap']='NO';
|
||||||
## -
|
## -
|
||||||
## - $CONF['used_quotas'] = 'NO';
|
## - $CONF['used_quotas'] = 'YES';
|
||||||
## - $CONF['new_quota_table'] = 'YES';
|
## - $CONF['new_quota_table'] = 'YES';
|
||||||
## -
|
## -
|
||||||
echononl "\tAdjust Postfix Admin's Configuration - Part 5"
|
echononl "\tAdjust Postfix Admin's Configuration - Part 5"
|
||||||
@@ -2642,7 +2642,7 @@ perl -i -n -p -e "s#^(\s*\\\$CONF\['show_undeliverable'\]\s*=.*)#//!\1\n\\\$CONF
|
|||||||
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
||||||
perl -i -n -p -e "s#^(\s*\\\$CONF\['show_popimap'\]\s*=.*)#//!\1\n\\\$CONF['show_popimap'] = 'NO';#" \
|
perl -i -n -p -e "s#^(\s*\\\$CONF\['show_popimap'\]\s*=.*)#//!\1\n\\\$CONF['show_popimap'] = 'NO';#" \
|
||||||
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
||||||
perl -i -n -p -e "s#^(\s*\\\$CONF\['used_quotas'\]\s*=.*)#//!\1\n\\\$CONF['used_quotas'] = 'NO';#" \
|
perl -i -n -p -e "s#^(\s*\\\$CONF\['used_quotas'\]\s*=.*)#//!\1\n\\\$CONF['used_quotas'] = 'YES';#" \
|
||||||
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
||||||
perl -i -n -p -e "s#^(\s*\\\$CONF\['new_quota_table'\]\s*=.*)#//!\1\n\\\$CONF['new_quota_table'] = 'YES';#" \
|
perl -i -n -p -e "s#^(\s*\\\$CONF\['new_quota_table'\]\s*=.*)#//!\1\n\\\$CONF['new_quota_table'] = 'YES';#" \
|
||||||
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
$pfa_conf_file >> $log_file 2>&1 || _failed=true
|
||||||
|
|||||||
Executable
+350
@@ -0,0 +1,350 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
##
|
||||||
|
## install_quota_clone.sh
|
||||||
|
##
|
||||||
|
## Configures Dovecot 2.4 quota_clone plugin and enables PostfixAdmin
|
||||||
|
## quota display by writing real-time usage to the quota2 PostgreSQL table.
|
||||||
|
##
|
||||||
|
## Must be run as root on the target mail server.
|
||||||
|
##
|
||||||
|
## What this script does:
|
||||||
|
## - Backs up the Dovecot and PostfixAdmin directories
|
||||||
|
## - Drops the old ADD-semantics mergequota2 PostgreSQL trigger
|
||||||
|
## - Creates a new SET-semantics upsertquota2 trigger (validation only)
|
||||||
|
## - Creates /etc/dovecot/conf.d/91-quota-clone.conf (dict_server)
|
||||||
|
## - Appends the quota_clone plugin block to 90-quota.conf
|
||||||
|
## - Enables $CONF['used_quotas'] and $CONF['new_quota_table'] in PostfixAdmin
|
||||||
|
## - Installs /usr/local/bin/update_quota2.sh for initial batch fill
|
||||||
|
## - Reloads Dovecot
|
||||||
|
##
|
||||||
|
## Safe to run multiple times (idempotent).
|
||||||
|
##
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# ── paths ─────────────────────────────────────────────────────────────────────
|
||||||
|
DOVECOT_LINK="/usr/local/dovecot"
|
||||||
|
PFA_LINK="/var/www/adm.oopen.de/postfixadmin"
|
||||||
|
BACKUP_BASE="/root/backup"
|
||||||
|
|
||||||
|
# ── colour helpers ────────────────────────────────────────────────────────────
|
||||||
|
_bold='\033[1m'; _green='\033[0;32m'; _red='\033[0;31m'; _yellow='\033[1;33m'; _nc='\033[0m'
|
||||||
|
|
||||||
|
heading() { echo; echo -e "${_bold}${*}${_nc}"; }
|
||||||
|
step() { printf ' %-60s' "$*"; }
|
||||||
|
ok() { echo -e " [${_green}OK${_nc}]"; }
|
||||||
|
skip() { echo -e " [${_yellow}SKIP${_nc}]"; }
|
||||||
|
failed() { echo -e " [${_red}FAILED${_nc}]"; }
|
||||||
|
info() { echo -e " ${*}"; }
|
||||||
|
die() { echo -e "\n${_red}FATAL: ${*}${_nc}" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ── root check ────────────────────────────────────────────────────────────────
|
||||||
|
[[ $EUID -eq 0 ]] || die "This script must be run as root."
|
||||||
|
|
||||||
|
# ── resolve symlinks ──────────────────────────────────────────────────────────
|
||||||
|
[[ -L "$DOVECOT_LINK" ]] || die "$DOVECOT_LINK is not a symlink."
|
||||||
|
[[ -L "$PFA_LINK" ]] || die "$PFA_LINK is not a symlink."
|
||||||
|
|
||||||
|
DOVECOT_REAL=$(realpath "$DOVECOT_LINK")
|
||||||
|
PFA_REAL=$(realpath "$PFA_LINK")
|
||||||
|
DOVECOT_CONF_D="${DOVECOT_REAL}/etc/dovecot/conf.d"
|
||||||
|
SQL_CONNECT="${DOVECOT_REAL}/etc/dovecot/sql-connect.conf.ext"
|
||||||
|
PFA_CONFIG="${PFA_REAL}/config.local.php"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo -e "${_bold}Dovecot quota_clone + PostfixAdmin quota display${_nc}"
|
||||||
|
echo
|
||||||
|
info "Dovecot : $DOVECOT_REAL"
|
||||||
|
info "PFA : $PFA_REAL"
|
||||||
|
|
||||||
|
# ── read DB credentials from Dovecot's sql-connect.conf.ext ──────────────────
|
||||||
|
[[ -f "$SQL_CONNECT" ]] || die "Not found: $SQL_CONNECT"
|
||||||
|
dbuser=$(grep -E '^\s*user\s*=' "$SQL_CONNECT" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||||
|
dbpass=$(grep -E '^\s*password\s*=' "$SQL_CONNECT" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||||
|
dbname=$(grep -E '^\s*dbname\s*=' "$SQL_CONNECT" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||||
|
[[ -n "$dbuser" ]] || die "Could not parse 'user' from $SQL_CONNECT"
|
||||||
|
[[ -n "$dbpass" ]] || die "Could not parse 'password' from $SQL_CONNECT"
|
||||||
|
[[ -n "$dbname" ]] || die "Could not parse 'dbname' from $SQL_CONNECT"
|
||||||
|
info "DB : $dbuser@$dbname"
|
||||||
|
|
||||||
|
# ── backup ────────────────────────────────────────────────────────────────────
|
||||||
|
heading "Backup"
|
||||||
|
mkdir -p "$BACKUP_BASE"
|
||||||
|
BACKUP_DIR="${BACKUP_BASE}/quota_clone_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
step "$(basename "$DOVECOT_REAL") ..."
|
||||||
|
tar -czf "${BACKUP_DIR}/dovecot.tar.gz" \
|
||||||
|
-C "$(dirname "$DOVECOT_REAL")" "$(basename "$DOVECOT_REAL")" \
|
||||||
|
&& ok || { failed; die "Backup of $DOVECOT_REAL failed."; }
|
||||||
|
|
||||||
|
step "$(basename "$PFA_REAL") ..."
|
||||||
|
tar -czf "${BACKUP_DIR}/postfixadmin.tar.gz" \
|
||||||
|
-C "$(dirname "$PFA_REAL")" "$(basename "$PFA_REAL")" \
|
||||||
|
&& ok || { failed; die "Backup of $PFA_REAL failed."; }
|
||||||
|
|
||||||
|
info "Saved to: $BACKUP_DIR"
|
||||||
|
|
||||||
|
# ── PostgreSQL ────────────────────────────────────────────────────────────────
|
||||||
|
heading "PostgreSQL"
|
||||||
|
|
||||||
|
step "Drop old mergequota2 trigger + function ..."
|
||||||
|
psql -U"$dbuser" "$dbname" -c "
|
||||||
|
DROP TRIGGER IF EXISTS mergequota2 ON quota2;
|
||||||
|
DROP FUNCTION IF EXISTS merge_quota2();
|
||||||
|
" > /dev/null 2>&1 \
|
||||||
|
&& ok || { failed; die "Could not drop old mergequota2 trigger."; }
|
||||||
|
|
||||||
|
# Single-quoted heredoc: $$ is passed literally to psql (no shell expansion).
|
||||||
|
step "Create upsertquota2 trigger + function ..."
|
||||||
|
psql -U"$dbuser" "$dbname" <<'SQL' > /dev/null 2>&1
|
||||||
|
CREATE OR REPLACE FUNCTION upsert_quota2() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.bytes IS NULL OR NEW.bytes < 0 THEN NEW.bytes := 0; END IF;
|
||||||
|
IF NEW.messages IS NULL OR NEW.messages < 0 THEN NEW.messages := 0; END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
ALTER FUNCTION public.upsert_quota2() OWNER TO postfix;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'upsertquota2') THEN
|
||||||
|
CREATE TRIGGER upsertquota2
|
||||||
|
BEFORE INSERT ON quota2
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE upsert_quota2();
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
SQL
|
||||||
|
[[ $? -eq 0 ]] && ok || { failed; die "Could not create upsertquota2 trigger."; }
|
||||||
|
|
||||||
|
# ── Dovecot configuration ─────────────────────────────────────────────────────
|
||||||
|
heading "Dovecot configuration"
|
||||||
|
|
||||||
|
# 91-quota-clone.conf — always (re)written, content is idempotent
|
||||||
|
QUOTA_CLONE_CONF="${DOVECOT_CONF_D}/91-quota-clone.conf"
|
||||||
|
step "Write 91-quota-clone.conf ..."
|
||||||
|
cat > "$QUOTA_CLONE_CONF" <<EOF
|
||||||
|
##
|
||||||
|
## Quota Clone Configuration (Dovecot 2.4+)
|
||||||
|
##
|
||||||
|
## Writes quota usage to the quota2 table in real time so PostfixAdmin
|
||||||
|
## can display mailbox fill levels via \$CONF['used_quotas'] = 'YES'.
|
||||||
|
##
|
||||||
|
dict_server {
|
||||||
|
dict quota_clone_pgsql {
|
||||||
|
driver = sql
|
||||||
|
sql_driver = pgsql
|
||||||
|
pgsql localhost {
|
||||||
|
parameters {
|
||||||
|
user = $dbuser
|
||||||
|
password = $dbpass
|
||||||
|
dbname = $dbname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict_map priv/quota/storage {
|
||||||
|
sql_table = quota2
|
||||||
|
username_field = username
|
||||||
|
value_field bytes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict_map priv/quota/messages {
|
||||||
|
sql_table = quota2
|
||||||
|
username_field = username
|
||||||
|
value_field messages {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
[[ $? -eq 0 ]] && ok || { failed; die "Could not write $QUOTA_CLONE_CONF"; }
|
||||||
|
|
||||||
|
# 90-quota.conf — append only if not already present
|
||||||
|
QUOTA_CONF="${DOVECOT_CONF_D}/90-quota.conf"
|
||||||
|
step "Append quota_clone block to 90-quota.conf ..."
|
||||||
|
if grep -q "quota_clone_pgsql" "$QUOTA_CONF" 2>/dev/null; then
|
||||||
|
skip
|
||||||
|
else
|
||||||
|
cat >> "$QUOTA_CONF" <<'EOF'
|
||||||
|
|
||||||
|
##
|
||||||
|
## Quota Clone - write quota usage to quota2 table for PostfixAdmin
|
||||||
|
##
|
||||||
|
mail_plugins {
|
||||||
|
quota_clone = yes
|
||||||
|
}
|
||||||
|
|
||||||
|
quota_clone {
|
||||||
|
dict proxy {
|
||||||
|
name = quota_clone_pgsql
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
[[ $? -eq 0 ]] && ok || { failed; die "Could not append to $QUOTA_CONF"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── PostfixAdmin ──────────────────────────────────────────────────────────────
|
||||||
|
heading "PostfixAdmin"
|
||||||
|
|
||||||
|
[[ -f "$PFA_CONFIG" ]] || die "Not found: $PFA_CONFIG"
|
||||||
|
|
||||||
|
# Sets $CONF['key'] = 'val' in config.local.php.
|
||||||
|
# - Already correct value → SKIP
|
||||||
|
# - Present with different value → update in place (perl, only non-commented lines)
|
||||||
|
# - Not present → append
|
||||||
|
pfa_set() {
|
||||||
|
local key="$1" val="$2"
|
||||||
|
step "\$CONF['${key}'] = '${val}' ..."
|
||||||
|
if grep -qF "\$CONF['${key}'] = '${val}';" "$PFA_CONFIG"; then
|
||||||
|
skip
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if grep -qF "\$CONF['${key}']" "$PFA_CONFIG"; then
|
||||||
|
# present with wrong value — update active (non-commented) line
|
||||||
|
perl -i -p -e "s|^(\s*\\\$CONF\['${key}'\]\s*=\s*)'[^']*'|\${1}'${val}'|" "$PFA_CONFIG" \
|
||||||
|
&& ok || { failed; die "Could not update \$CONF['${key}'] in $PFA_CONFIG"; }
|
||||||
|
else
|
||||||
|
# not present — append
|
||||||
|
{ echo ""; echo "\$CONF['${key}'] = '${val}';"; } >> "$PFA_CONFIG" \
|
||||||
|
&& ok || { failed; die "Could not append \$CONF['${key}'] to $PFA_CONFIG"; }
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pfa_set used_quotas YES
|
||||||
|
pfa_set new_quota_table YES
|
||||||
|
|
||||||
|
# ── /usr/local/bin/update_quota2.sh ──────────────────────────────────────────
|
||||||
|
heading "Batch update script"
|
||||||
|
|
||||||
|
# Single-quoted PYEOF: no shell expansion inside the Python source.
|
||||||
|
step "Install /usr/local/bin/update_quota2.sh ..."
|
||||||
|
cat > /usr/local/bin/update_quota2.sh <<'PYEOF'
|
||||||
|
#!/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()
|
||||||
|
PYEOF
|
||||||
|
chmod +x /usr/local/bin/update_quota2.sh
|
||||||
|
[[ $? -eq 0 ]] && ok || { failed; die "Could not install update_quota2.sh"; }
|
||||||
|
|
||||||
|
# ── Dovecot reload ────────────────────────────────────────────────────────────
|
||||||
|
heading "Dovecot reload"
|
||||||
|
step "Reloading Dovecot ..."
|
||||||
|
"${DOVECOT_REAL}/sbin/dovecot" reload \
|
||||||
|
&& ok || { failed; die "Dovecot reload failed."; }
|
||||||
|
|
||||||
|
# ── summary ───────────────────────────────────────────────────────────────────
|
||||||
|
echo
|
||||||
|
echo -e "${_bold}Done.${_nc}"
|
||||||
|
echo
|
||||||
|
info "Backup : $BACKUP_DIR"
|
||||||
|
info "Next : run the initial batch fill of quota2 for all mailboxes:"
|
||||||
|
info " /usr/local/bin/update_quota2.sh [--dry-run] [--limit 5]"
|
||||||
|
echo
|
||||||
@@ -9226,6 +9226,259 @@ EOF
|
|||||||
fi # Renew file /usr/local/dovecot-${_version}/etc/dovecot/sql-dict.conf.ext
|
fi # Renew file /usr/local/dovecot-${_version}/etc/dovecot/sql-dict.conf.ext
|
||||||
|
|
||||||
|
|
||||||
|
## -----------------------------------------------------------------
|
||||||
|
## --- quota_clone plugin (Dovecot 2.4+ / pgsql only)
|
||||||
|
## ---
|
||||||
|
## --- Writes quota usage to quota2 table in real time so PostfixAdmin
|
||||||
|
## --- can display mailbox fill levels ($CONF['used_quotas'] = 'YES').
|
||||||
|
## ---
|
||||||
|
## --- Replaces the old ADD-semantics mergequota2 trigger with a
|
||||||
|
## --- SET-semantics upsertquota2 trigger (validation only); the dict
|
||||||
|
## --- proxy service generates INSERT … ON CONFLICT DO UPDATE natively.
|
||||||
|
|
||||||
|
if [[ $dovecot_major_version -gt 2 ]] \
|
||||||
|
|| ( [[ $dovecot_major_version -eq 2 ]] && [[ $dovecot_minor_version -gt 3 ]] ); then
|
||||||
|
|
||||||
|
if [ "$db_driver" = "pgsql" ]; then
|
||||||
|
|
||||||
|
## - Drop old ADD-semantics trigger/function if present
|
||||||
|
|
||||||
|
echononl " Drop old mergequota2 trigger/function (if present).."
|
||||||
|
cat << EOF | psql -U$dbuser $dbname > /dev/null 2>&1
|
||||||
|
DROP TRIGGER IF EXISTS mergequota2 ON quota2;
|
||||||
|
DROP FUNCTION IF EXISTS merge_quota2();
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Dropping mergequota2 trigger/function failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## - Create upsertquota2 trigger (validation / sanitise only)
|
||||||
|
|
||||||
|
echononl " Create upsertquota2 trigger/function.."
|
||||||
|
cat << 'EOF' | psql -U$dbuser $dbname > /dev/null 2>&1
|
||||||
|
CREATE OR REPLACE FUNCTION upsert_quota2() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.bytes IS NULL OR NEW.bytes < 0 THEN
|
||||||
|
NEW.bytes := 0;
|
||||||
|
END IF;
|
||||||
|
IF NEW.messages IS NULL OR NEW.messages < 0 THEN
|
||||||
|
NEW.messages := 0;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
ALTER FUNCTION public.upsert_quota2() OWNER TO postfix;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger WHERE tgname = 'upsertquota2'
|
||||||
|
) THEN
|
||||||
|
CREATE TRIGGER upsertquota2
|
||||||
|
BEFORE INSERT ON quota2
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE upsert_quota2();
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Creating upsertquota2 trigger/function failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## - Create 91-quota-clone.conf
|
||||||
|
|
||||||
|
_conf_file="/usr/local/dovecot-${_version}/etc/dovecot/conf.d/91-quota-clone.conf"
|
||||||
|
echononl " Create '$(basename "${_conf_file}")' (dict_server for quota_clone).."
|
||||||
|
cat <<EOF > "${_conf_file}"
|
||||||
|
##
|
||||||
|
## Quota Clone Configuration (Dovecot 2.4+)
|
||||||
|
##
|
||||||
|
## Writes quota usage to the quota2 PostgreSQL table in real time so
|
||||||
|
## PostfixAdmin can display mailbox fill levels.
|
||||||
|
##
|
||||||
|
dict_server {
|
||||||
|
dict quota_clone_pgsql {
|
||||||
|
driver = sql
|
||||||
|
sql_driver = pgsql
|
||||||
|
pgsql localhost {
|
||||||
|
parameters {
|
||||||
|
user = $dbuser
|
||||||
|
password = $dbpassword
|
||||||
|
dbname = $dbname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict_map priv/quota/storage {
|
||||||
|
sql_table = quota2
|
||||||
|
username_field = username
|
||||||
|
value_field bytes {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict_map priv/quota/messages {
|
||||||
|
sql_table = quota2
|
||||||
|
username_field = username
|
||||||
|
value_field messages {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Creating file '${_conf_file}' failed!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## - Append quota_clone plugin block to 90-quota.conf
|
||||||
|
|
||||||
|
_conf_file="/usr/local/dovecot-${_version}/etc/dovecot/conf.d/90-quota.conf"
|
||||||
|
echononl " Append quota_clone block to '$(basename "${_conf_file}")'.."
|
||||||
|
if ! grep -q "quota_clone_pgsql" "${_conf_file}" 2>/dev/null; then
|
||||||
|
cat <<'EOF' >> "${_conf_file}"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Quota Clone - write quota usage to quota2 table for PostfixAdmin
|
||||||
|
##
|
||||||
|
mail_plugins {
|
||||||
|
quota_clone = yes
|
||||||
|
}
|
||||||
|
|
||||||
|
quota_clone {
|
||||||
|
dict proxy {
|
||||||
|
name = quota_clone_pgsql
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Appending quota_clone block to '${_conf_file}' failed!"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${rc_already_done}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## - Install /usr/local/bin/update_quota2.sh (initial batch fill)
|
||||||
|
|
||||||
|
echononl " Install /usr/local/bin/update_quota2.sh.."
|
||||||
|
cat <<'PYEOF' > /usr/local/bin/update_quota2.sh
|
||||||
|
#!/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'
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
cmd = [PSQL, '-U', DB_USER, DB_NAME, '-c',
|
||||||
|
"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(cmd, 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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
PYEOF
|
||||||
|
chmod +x /usr/local/bin/update_quota2.sh
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Installing /usr/local/bin/update_quota2.sh failed!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi # db_driver = pgsql
|
||||||
|
|
||||||
|
fi # Dovecot 2.4+ quota_clone
|
||||||
|
|
||||||
|
|
||||||
# In order to support extra variable "quota_rule", also userdb's and
|
# In order to support extra variable "quota_rule", also userdb's and
|
||||||
# passdb's SQL query have to update
|
# passdb's SQL query have to update
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user