Merge branch 'master' of https://git.oopen.de/install/mailsystem
This commit is contained in:
+320
-130
@@ -3,13 +3,15 @@
|
|||||||
## install_quota_clone.sh
|
## install_quota_clone.sh
|
||||||
##
|
##
|
||||||
## Configures Dovecot 2.4 quota_clone plugin and enables PostfixAdmin
|
## Configures Dovecot 2.4 quota_clone plugin and enables PostfixAdmin
|
||||||
## quota display by writing real-time usage to the quota2 PostgreSQL table.
|
## quota display by writing real-time usage to the quota2 table.
|
||||||
##
|
##
|
||||||
|
## Supports PostgreSQL and MySQL/MariaDB backends.
|
||||||
## Must be run as root on the target mail server.
|
## Must be run as root on the target mail server.
|
||||||
##
|
##
|
||||||
## What this script does:
|
## What this script does:
|
||||||
## - Backs up the Dovecot and PostfixAdmin directories
|
## - Backs up the Dovecot and PostfixAdmin directories
|
||||||
## - Drops the old ADD-semantics mergequota2 PostgreSQL trigger
|
## - Creates the quota2 table if it does not exist (MySQL only)
|
||||||
|
## - Drops the old ADD-semantics mergequota2 trigger (if present)
|
||||||
## - Creates a new SET-semantics upsertquota2 trigger (validation only)
|
## - Creates a new SET-semantics upsertquota2 trigger (validation only)
|
||||||
## - Creates /etc/dovecot/conf.d/91-quota-clone.conf (dict_server)
|
## - Creates /etc/dovecot/conf.d/91-quota-clone.conf (dict_server)
|
||||||
## - Appends the quota_clone plugin block to 90-quota.conf
|
## - Appends the quota_clone plugin block to 90-quota.conf
|
||||||
@@ -52,7 +54,6 @@ SQL_CONNECT="${DOVECOT_REAL}/etc/dovecot/sql-connect.conf.ext"
|
|||||||
info "Dovecot : $DOVECOT_REAL"
|
info "Dovecot : $DOVECOT_REAL"
|
||||||
|
|
||||||
# ── PostfixAdmin path (interactive) ──────────────────────────────────────────
|
# ── PostfixAdmin path (interactive) ──────────────────────────────────────────
|
||||||
# Auto-detect: look for a 'postfixadmin' symlink under /var/www/*/htdocs/
|
|
||||||
_pfa_default=""
|
_pfa_default=""
|
||||||
mapfile -t _pfa_candidates < <(find /var/www -maxdepth 3 -name "postfixadmin" -type l 2>/dev/null | sort)
|
mapfile -t _pfa_candidates < <(find /var/www -maxdepth 3 -name "postfixadmin" -type l 2>/dev/null | sort)
|
||||||
if [[ ${#_pfa_candidates[@]} -eq 1 ]]; then
|
if [[ ${#_pfa_candidates[@]} -eq 1 ]]; then
|
||||||
@@ -90,15 +91,50 @@ else
|
|||||||
fi
|
fi
|
||||||
info "Config : $PFA_CONFIG"
|
info "Config : $PFA_CONFIG"
|
||||||
|
|
||||||
# ── read DB credentials from Dovecot's sql-connect.conf.ext ──────────────────
|
# ── DB credentials from sql-connect.conf.ext ─────────────────────────────────
|
||||||
[[ -f "$SQL_CONNECT" ]] || die "Not found: $SQL_CONNECT"
|
[[ -f "$SQL_CONNECT" ]] || die "Not found: $SQL_CONNECT"
|
||||||
|
|
||||||
|
sql_driver=$(grep -E '^\s*sql_driver\s*=' "$SQL_CONNECT" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||||
dbuser=$(grep -E '^\s*user\s*=' "$SQL_CONNECT" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
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:]')
|
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:]')
|
dbname=$(grep -E '^\s*dbname\s*=' "$SQL_CONNECT" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||||
|
|
||||||
|
[[ -n "$sql_driver" ]] || die "Could not parse 'sql_driver' from $SQL_CONNECT"
|
||||||
[[ -n "$dbuser" ]] || die "Could not parse 'user' from $SQL_CONNECT"
|
[[ -n "$dbuser" ]] || die "Could not parse 'user' from $SQL_CONNECT"
|
||||||
[[ -n "$dbpass" ]] || die "Could not parse 'password' from $SQL_CONNECT"
|
[[ -n "$dbpass" ]] || die "Could not parse 'password' from $SQL_CONNECT"
|
||||||
[[ -n "$dbname" ]] || die "Could not parse 'dbname' from $SQL_CONNECT"
|
[[ -n "$dbname" ]] || die "Could not parse 'dbname' from $SQL_CONNECT"
|
||||||
info "DB : $dbuser@$dbname"
|
|
||||||
|
case "$sql_driver" in
|
||||||
|
pgsql)
|
||||||
|
info "DB : PostgreSQL $dbuser@$dbname"
|
||||||
|
dbsocket=""
|
||||||
|
;;
|
||||||
|
mysql)
|
||||||
|
# socket path: line looks like mysql /run/mysqld/mysqld.sock {
|
||||||
|
dbsocket=$(grep -E '^\s*mysql\s+/' "$SQL_CONNECT" | head -1 | awk '{print $2}')
|
||||||
|
[[ -n "$dbsocket" ]] || die "Could not parse MySQL socket path from $SQL_CONNECT"
|
||||||
|
info "DB : MariaDB/MySQL $dbuser@$dbname (socket: $dbsocket)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "Unsupported sql_driver '$sql_driver' in $SQL_CONNECT (expected: pgsql or mysql)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── DB helper: run SQL (DDL: CREATE TABLE, triggers) ─────────────────────────
|
||||||
|
# pgsql: postfix user via psql.
|
||||||
|
# mysql: OS root → MariaDB root via unix_socket auth (Debian default);
|
||||||
|
# falls back to postfix user credentials if that fails.
|
||||||
|
db_exec() {
|
||||||
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
|
psql -U"$dbuser" "$dbname" "$@"
|
||||||
|
else
|
||||||
|
if mysql --socket="$dbsocket" "$dbname" "$@" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
mysql -u"$dbuser" -p"$dbpass" --socket="$dbsocket" "$dbname" "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ── backup ────────────────────────────────────────────────────────────────────
|
# ── backup ────────────────────────────────────────────────────────────────────
|
||||||
heading "Backup"
|
heading "Backup"
|
||||||
@@ -107,30 +143,70 @@ BACKUP_DIR="${BACKUP_BASE}/quota_clone_$(date +%Y%m%d_%H%M%S)"
|
|||||||
mkdir -p "$BACKUP_DIR"
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
step "$(basename "$DOVECOT_REAL") ..."
|
step "$(basename "$DOVECOT_REAL") ..."
|
||||||
tar -czf "${BACKUP_DIR}/dovecot.tar.gz" \
|
if tar -czf "${BACKUP_DIR}/dovecot.tar.gz" \
|
||||||
-C "$(dirname "$DOVECOT_REAL")" "$(basename "$DOVECOT_REAL")" \
|
-C "$(dirname "$DOVECOT_REAL")" "$(basename "$DOVECOT_REAL")"; then
|
||||||
&& ok || { failed; die "Backup of $DOVECOT_REAL failed."; }
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Backup of $DOVECOT_REAL failed."
|
||||||
|
fi
|
||||||
|
|
||||||
step "$(basename "$PFA_REAL") ..."
|
_pfa_backup_src=$(dirname "$PFA_REAL")
|
||||||
tar -czf "${BACKUP_DIR}/postfixadmin.tar.gz" \
|
_pfa_backup_name=$(basename "$PFA_REAL")
|
||||||
-C "$(dirname "$PFA_REAL")" "$(basename "$PFA_REAL")" \
|
step "${_pfa_backup_name} ..."
|
||||||
&& ok || { failed; die "Backup of $PFA_REAL failed."; }
|
if tar -czf "${BACKUP_DIR}/postfixadmin.tar.gz" \
|
||||||
|
-C "$_pfa_backup_src" "$_pfa_backup_name"; then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Backup of $PFA_REAL failed."
|
||||||
|
fi
|
||||||
|
|
||||||
info "Saved to: $BACKUP_DIR"
|
info "Saved to: $BACKUP_DIR"
|
||||||
|
|
||||||
# ── PostgreSQL ────────────────────────────────────────────────────────────────
|
# ── Database ──────────────────────────────────────────────────────────────────
|
||||||
heading "PostgreSQL"
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
|
heading "PostgreSQL"
|
||||||
|
else
|
||||||
|
heading "MariaDB / MySQL"
|
||||||
|
fi
|
||||||
|
|
||||||
step "Drop old mergequota2 trigger + function ..."
|
if [[ "$sql_driver" == "mysql" ]]; then
|
||||||
psql -U"$dbuser" "$dbname" -c "
|
step "Create quota2 table if not exists ..."
|
||||||
|
if db_exec -e "
|
||||||
|
CREATE TABLE IF NOT EXISTS quota2 (
|
||||||
|
username varchar(100) NOT NULL,
|
||||||
|
bytes bigint NOT NULL DEFAULT 0,
|
||||||
|
messages int NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (username)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
" > /dev/null 2>&1; then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not create quota2 table."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
step "Drop old mergequota2 trigger ..."
|
||||||
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
|
if db_exec -c "
|
||||||
DROP TRIGGER IF EXISTS mergequota2 ON quota2;
|
DROP TRIGGER IF EXISTS mergequota2 ON quota2;
|
||||||
DROP FUNCTION IF EXISTS merge_quota2();
|
DROP FUNCTION IF EXISTS merge_quota2();
|
||||||
" > /dev/null 2>&1 \
|
" > /dev/null 2>&1; then
|
||||||
&& ok || { failed; die "Could not drop old mergequota2 trigger."; }
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not drop old mergequota2 trigger."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if db_exec -e "DROP TRIGGER IF EXISTS mergequota2;" > /dev/null 2>&1; then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not drop old mergequota2 trigger."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Single-quoted heredoc: $$ is passed literally to psql (no shell expansion).
|
step "Create upsertquota2 trigger ..."
|
||||||
step "Create upsertquota2 trigger + function ..."
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
psql -U"$dbuser" "$dbname" <<'SQL' > /dev/null 2>&1
|
# Single-quoted heredoc: $$ reaches psql literally (no shell expansion).
|
||||||
|
if db_exec <<'SQL' > /dev/null 2>&1
|
||||||
CREATE OR REPLACE FUNCTION upsert_quota2() RETURNS trigger
|
CREATE OR REPLACE FUNCTION upsert_quota2() RETURNS trigger
|
||||||
LANGUAGE plpgsql AS $$
|
LANGUAGE plpgsql AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -152,20 +228,42 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
SQL
|
SQL
|
||||||
[[ $? -eq 0 ]] && ok || { failed; die "Could not create upsertquota2 trigger."; }
|
then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not create upsertquota2 trigger."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# DELIMITER lets the MySQL CLI handle ; inside BEGIN...END in batch mode.
|
||||||
|
if db_exec <<'SQL' > /dev/null 2>&1
|
||||||
|
DROP TRIGGER IF EXISTS upsertquota2;
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER upsertquota2
|
||||||
|
BEFORE INSERT ON quota2
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF NEW.bytes IS NULL OR NEW.bytes < 0 THEN SET NEW.bytes = 0; END IF;
|
||||||
|
IF NEW.messages IS NULL OR NEW.messages < 0 THEN SET NEW.messages = 0; END IF;
|
||||||
|
END//
|
||||||
|
DELIMITER ;
|
||||||
|
SQL
|
||||||
|
then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not create upsertquota2 trigger."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Dovecot configuration ─────────────────────────────────────────────────────
|
# ── Dovecot configuration ─────────────────────────────────────────────────────
|
||||||
heading "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"
|
QUOTA_CLONE_CONF="${DOVECOT_CONF_D}/91-quota-clone.conf"
|
||||||
step "Write 91-quota-clone.conf ..."
|
|
||||||
cat > "$QUOTA_CLONE_CONF" <<EOF
|
_write_quota_clone_conf() {
|
||||||
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
|
cat > "$QUOTA_CLONE_CONF" <<EOF
|
||||||
##
|
##
|
||||||
## Quota Clone Configuration (Dovecot 2.4+)
|
## Quota Clone Configuration (Dovecot 2.4+ / PostgreSQL)
|
||||||
##
|
|
||||||
## 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_server {
|
||||||
dict quota_clone_pgsql {
|
dict quota_clone_pgsql {
|
||||||
@@ -193,15 +291,54 @@ dict_server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
[[ $? -eq 0 ]] && ok || { failed; die "Could not write $QUOTA_CLONE_CONF"; }
|
else
|
||||||
|
cat > "$QUOTA_CLONE_CONF" <<EOF
|
||||||
|
##
|
||||||
|
## Quota Clone Configuration (Dovecot 2.4+ / MariaDB / MySQL)
|
||||||
|
##
|
||||||
|
dict_server {
|
||||||
|
dict quota_clone_mysql {
|
||||||
|
driver = sql
|
||||||
|
sql_driver = mysql
|
||||||
|
mysql $dbsocket {
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
step "Write 91-quota-clone.conf ..."
|
||||||
|
if _write_quota_clone_conf; then ok; else failed; die "Could not write $QUOTA_CLONE_CONF"; fi
|
||||||
|
|
||||||
|
# dict name differs by driver — used both as grep marker and in quota_clone block
|
||||||
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
|
_dict_name="quota_clone_pgsql"
|
||||||
|
else
|
||||||
|
_dict_name="quota_clone_mysql"
|
||||||
|
fi
|
||||||
|
|
||||||
# 90-quota.conf — append only if not already present
|
|
||||||
QUOTA_CONF="${DOVECOT_CONF_D}/90-quota.conf"
|
QUOTA_CONF="${DOVECOT_CONF_D}/90-quota.conf"
|
||||||
step "Append quota_clone block to 90-quota.conf ..."
|
step "Append quota_clone block to 90-quota.conf ..."
|
||||||
if grep -q "quota_clone_pgsql" "$QUOTA_CONF" 2>/dev/null; then
|
if grep -q "$_dict_name" "$QUOTA_CONF" 2>/dev/null; then
|
||||||
skip
|
skip
|
||||||
else
|
else
|
||||||
cat >> "$QUOTA_CONF" <<'EOF'
|
if cat >> "$QUOTA_CONF" <<EOF
|
||||||
|
|
||||||
##
|
##
|
||||||
## Quota Clone - write quota usage to quota2 table for PostfixAdmin
|
## Quota Clone - write quota usage to quota2 table for PostfixAdmin
|
||||||
@@ -212,20 +349,20 @@ mail_plugins {
|
|||||||
|
|
||||||
quota_clone {
|
quota_clone {
|
||||||
dict proxy {
|
dict proxy {
|
||||||
name = quota_clone_pgsql
|
name = $_dict_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
[[ $? -eq 0 ]] && ok || { failed; die "Could not append to $QUOTA_CONF"; }
|
then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not append to $QUOTA_CONF"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── PostfixAdmin ──────────────────────────────────────────────────────────────
|
# ── PostfixAdmin ──────────────────────────────────────────────────────────────
|
||||||
heading "PostfixAdmin"
|
heading "PostfixAdmin"
|
||||||
|
|
||||||
# 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() {
|
pfa_set() {
|
||||||
local key="$1" val="$2"
|
local key="$1" val="$2"
|
||||||
step "\$CONF['${key}'] = '${val}' ..."
|
step "\$CONF['${key}'] = '${val}' ..."
|
||||||
@@ -234,13 +371,17 @@ pfa_set() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if grep -qF "\$CONF['${key}']" "$PFA_CONFIG"; then
|
if grep -qF "\$CONF['${key}']" "$PFA_CONFIG"; then
|
||||||
# present with wrong value — update active (non-commented) line
|
if perl -i -p -e "s|^(\s*\\\$CONF\['${key}'\]\s*=\s*)'[^']*'|\${1}'${val}'|" "$PFA_CONFIG"; then
|
||||||
perl -i -p -e "s|^(\s*\\\$CONF\['${key}'\]\s*=\s*)'[^']*'|\${1}'${val}'|" "$PFA_CONFIG" \
|
ok
|
||||||
&& ok || { failed; die "Could not update \$CONF['${key}'] in $PFA_CONFIG"; }
|
|
||||||
else
|
else
|
||||||
# not present — append
|
failed; die "Could not update \$CONF['${key}'] in $PFA_CONFIG"
|
||||||
{ echo ""; echo "\$CONF['${key}'] = '${val}';"; } >> "$PFA_CONFIG" \
|
fi
|
||||||
&& ok || { failed; die "Could not append \$CONF['${key}'] to $PFA_CONFIG"; }
|
else
|
||||||
|
if { echo ""; echo "\$CONF['${key}'] = '${val}';"; } >> "$PFA_CONFIG"; then
|
||||||
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Could not append \$CONF['${key}'] to $PFA_CONFIG"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,126 +391,175 @@ pfa_set new_quota_table YES
|
|||||||
# ── /usr/local/bin/update_quota2.sh ──────────────────────────────────────────
|
# ── /usr/local/bin/update_quota2.sh ──────────────────────────────────────────
|
||||||
heading "Batch update script"
|
heading "Batch update script"
|
||||||
|
|
||||||
# Single-quoted PYEOF: no shell expansion inside the Python source.
|
|
||||||
step "Install /usr/local/bin/update_quota2.sh ..."
|
step "Install /usr/local/bin/update_quota2.sh ..."
|
||||||
|
|
||||||
|
if [[ "$sql_driver" == "pgsql" ]]; then
|
||||||
|
|
||||||
cat > /usr/local/bin/update_quota2.sh <<'PYEOF'
|
cat > /usr/local/bin/update_quota2.sh <<'PYEOF'
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Batch-update the quota2 table for all active mailboxes.
|
Batch-update the quota2 table for all active mailboxes (PostgreSQL).
|
||||||
|
|
||||||
Uses 'doveadm quota get' (must run as root) to read current usage and
|
Uses 'doveadm quota get' (must run as root) and writes to quota2 via
|
||||||
writes it to quota2 via INSERT ... ON CONFLICT DO UPDATE.
|
INSERT ... ON CONFLICT DO UPDATE.
|
||||||
|
|
||||||
Usage:
|
Usage: update_quota2.sh [--dry-run] [--limit N]
|
||||||
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, sys
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
DOVEADM = '/usr/local/dovecot/bin/doveadm'
|
DOVEADM = '/usr/local/dovecot/bin/doveadm'
|
||||||
PSQL = 'psql'
|
PSQL = 'psql'
|
||||||
DB_USER = 'postfix'
|
DB_USER = 'postfix'
|
||||||
DB_NAME = 'postfix'
|
DB_NAME = 'postfix'
|
||||||
|
|
||||||
|
|
||||||
def get_users(limit=None):
|
def get_users(limit=None):
|
||||||
query = 'SELECT username FROM mailbox WHERE active = true ORDER BY username;'
|
q = 'SELECT username FROM mailbox WHERE active = true ORDER BY username;'
|
||||||
if limit:
|
if limit:
|
||||||
query = ('SELECT username FROM mailbox WHERE active = true '
|
q = 'SELECT username FROM mailbox WHERE active = true ORDER BY username LIMIT %d;' % limit
|
||||||
'ORDER BY username LIMIT %d;' % limit)
|
r = subprocess.run([PSQL, '-U', DB_USER, DB_NAME, '-tAF', '|', '-c', q],
|
||||||
result = subprocess.run(
|
capture_output=True, text=True)
|
||||||
[PSQL, '-U', DB_USER, DB_NAME, '-tAF', '|', '-c', query],
|
return [u.strip() for u in r.stdout.strip().split('\n') if u.strip()]
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
return [u.strip() for u in result.stdout.strip().split('\n') if u.strip()]
|
|
||||||
|
|
||||||
|
|
||||||
def get_quota(user):
|
def get_quota(user):
|
||||||
"""Returns (bytes, messages) or (None, None) if not available."""
|
r = subprocess.run([DOVEADM, 'quota', 'get', '-u', user],
|
||||||
result = subprocess.run(
|
capture_output=True, text=True)
|
||||||
[DOVEADM, 'quota', 'get', '-u', user],
|
bv = mv = None
|
||||||
capture_output=True, text=True
|
for line in r.stdout.splitlines():
|
||||||
)
|
p = line.split()
|
||||||
bytes_val = messages_val = None
|
if len(p) < 4: continue
|
||||||
for line in result.stdout.splitlines():
|
if p[2] == 'STORAGE' and p[1] != 'Type':
|
||||||
parts = line.split()
|
try: bv = int(p[3]) * 1024
|
||||||
if len(parts) < 4:
|
except ValueError: bv = 0
|
||||||
continue
|
elif p[2] == 'MESSAGE' and p[1] != 'Type':
|
||||||
if parts[2] == 'STORAGE' and parts[1] != 'Type':
|
try: mv = int(p[3])
|
||||||
try:
|
except ValueError: mv = 0
|
||||||
bytes_val = int(parts[3]) * 1024 # KiB → bytes
|
return bv, mv
|
||||||
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(user, bv, mv):
|
||||||
def upsert_quota2(user, bytes_val, messages_val):
|
sql = ("INSERT INTO quota2 (username,bytes,messages) VALUES ('"
|
||||||
sql = (
|
+ user.replace("'","''") + "'," + str(bv) + "," + str(mv) + ") "
|
||||||
"INSERT INTO quota2 (username, bytes, messages) VALUES ('"
|
|
||||||
+ user.replace("'", "''")
|
|
||||||
+ "', " + str(bytes_val) + ", " + str(messages_val) + ") "
|
|
||||||
"ON CONFLICT (username) DO UPDATE "
|
"ON CONFLICT (username) DO UPDATE "
|
||||||
"SET bytes = EXCLUDED.bytes, messages = EXCLUDED.messages;"
|
"SET bytes=EXCLUDED.bytes, messages=EXCLUDED.messages;")
|
||||||
)
|
subprocess.run([PSQL, '-U', DB_USER, DB_NAME, '-c', sql], capture_output=True)
|
||||||
subprocess.run([PSQL, '-U', DB_USER, DB_NAME, '-c', sql],
|
|
||||||
capture_output=True)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
dry_run = '--dry-run' in sys.argv
|
dry = '--dry-run' in sys.argv
|
||||||
limit = None
|
limit = None
|
||||||
if '--limit' in sys.argv:
|
if '--limit' in sys.argv:
|
||||||
idx = sys.argv.index('--limit')
|
try: limit = int(sys.argv[sys.argv.index('--limit') + 1])
|
||||||
try:
|
|
||||||
limit = int(sys.argv[idx + 1])
|
|
||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
print('Usage: update_quota2.sh [--dry-run] [--limit N]')
|
print('Usage: update_quota2.sh [--dry-run] [--limit N]'); sys.exit(1)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
users = get_users(limit)
|
users = get_users(limit)
|
||||||
total = len(users)
|
total = len(users)
|
||||||
print('Processing %d mailboxes...' % total)
|
print('Processing %d mailboxes...' % total)
|
||||||
|
ok = skip = 0
|
||||||
updated = skipped = 0
|
|
||||||
for i, user in enumerate(users, 1):
|
for i, user in enumerate(users, 1):
|
||||||
bytes_val, messages_val = get_quota(user)
|
bv, mv = get_quota(user)
|
||||||
if bytes_val is None or messages_val is None:
|
if bv is None or mv is None:
|
||||||
print(' [%d/%d] SKIP %s (no quota data)' % (i, total, user))
|
print(' [%d/%d] SKIP %s' % (i, total, user)); skip += 1; continue
|
||||||
skipped += 1
|
if dry:
|
||||||
continue
|
print(' [%d/%d] DRY %s: %d bytes, %d msgs' % (i, total, user, bv, mv))
|
||||||
if dry_run:
|
|
||||||
print(' [%d/%d] DRY %s: %d bytes, %d messages'
|
|
||||||
% (i, total, user, bytes_val, messages_val))
|
|
||||||
else:
|
else:
|
||||||
upsert_quota2(user, bytes_val, messages_val)
|
upsert(user, bv, mv)
|
||||||
if i % 50 == 0 or i == total:
|
if i % 50 == 0 or i == total: print(' [%d/%d] done' % (i, total))
|
||||||
print(' [%d/%d] done' % (i, total))
|
ok += 1
|
||||||
updated += 1
|
print('Done: %d updated, %d skipped.' % (ok, skip))
|
||||||
|
|
||||||
print('Done: %d updated, %d skipped.' % (updated, skipped))
|
if __name__ == '__main__': main()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
PYEOF
|
PYEOF
|
||||||
chmod +x /usr/local/bin/update_quota2.sh
|
|
||||||
[[ $? -eq 0 ]] && ok || { failed; die "Could not install update_quota2.sh"; }
|
else # mysql — shell expands DB_USER, DB_PASS, DB_SOCKET, DB_NAME into the script
|
||||||
|
|
||||||
|
cat > /usr/local/bin/update_quota2.sh <<PYEOF
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Batch-update the quota2 table for all active mailboxes (MariaDB/MySQL).
|
||||||
|
|
||||||
|
Uses 'doveadm quota get' (must run as root) and writes to quota2 via
|
||||||
|
REPLACE INTO.
|
||||||
|
|
||||||
|
Usage: update_quota2.sh [--dry-run] [--limit N]
|
||||||
|
"""
|
||||||
|
import subprocess, sys
|
||||||
|
|
||||||
|
DOVEADM = '/usr/local/dovecot/bin/doveadm'
|
||||||
|
MYSQL = 'mysql'
|
||||||
|
DB_USER = '$dbuser'
|
||||||
|
DB_PASS = '$dbpass'
|
||||||
|
DB_SOCKET = '$dbsocket'
|
||||||
|
DB_NAME = '$dbname'
|
||||||
|
|
||||||
|
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):
|
||||||
|
q = 'SELECT username FROM mailbox WHERE active = 1 ORDER BY username;'
|
||||||
|
if limit:
|
||||||
|
q = 'SELECT username FROM mailbox WHERE active = 1 ORDER BY username LIMIT %d;' % limit
|
||||||
|
r = mysql_query(q)
|
||||||
|
return [u.strip() for u in r.stdout.strip().split('\n') if u.strip()]
|
||||||
|
|
||||||
|
def get_quota(user):
|
||||||
|
r = subprocess.run([DOVEADM, 'quota', 'get', '-u', user],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
bv = mv = None
|
||||||
|
for line in r.stdout.splitlines():
|
||||||
|
p = line.split()
|
||||||
|
if len(p) < 4: continue
|
||||||
|
if p[2] == 'STORAGE' and p[1] != 'Type':
|
||||||
|
try: bv = int(p[3]) * 1024
|
||||||
|
except ValueError: bv = 0
|
||||||
|
elif p[2] == 'MESSAGE' and p[1] != 'Type':
|
||||||
|
try: mv = int(p[3])
|
||||||
|
except ValueError: mv = 0
|
||||||
|
return bv, mv
|
||||||
|
|
||||||
|
def upsert(user, bv, mv):
|
||||||
|
sql = ("REPLACE INTO quota2 (username, bytes, messages) VALUES ('"
|
||||||
|
+ user.replace("'", "\\'") + "', " + str(bv) + ", " + str(mv) + ");")
|
||||||
|
mysql_query(sql)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
dry = '--dry-run' in sys.argv
|
||||||
|
limit = None
|
||||||
|
if '--limit' in sys.argv:
|
||||||
|
try: limit = int(sys.argv[sys.argv.index('--limit') + 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 = skip = 0
|
||||||
|
for i, user in enumerate(users, 1):
|
||||||
|
bv, mv = get_quota(user)
|
||||||
|
if bv is None or mv is None:
|
||||||
|
print(' [%d/%d] SKIP %s' % (i, total, user)); skip += 1; continue
|
||||||
|
if dry:
|
||||||
|
print(' [%d/%d] DRY %s: %d bytes, %d msgs' % (i, total, user, bv, mv))
|
||||||
|
else:
|
||||||
|
upsert(user, bv, mv)
|
||||||
|
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
|
||||||
|
|
||||||
|
fi # sql_driver
|
||||||
|
|
||||||
|
if chmod +x /usr/local/bin/update_quota2.sh; then ok; else failed; die "Could not install update_quota2.sh"; fi
|
||||||
|
|
||||||
# ── Dovecot reload ────────────────────────────────────────────────────────────
|
# ── Dovecot reload ────────────────────────────────────────────────────────────
|
||||||
heading "Dovecot reload"
|
heading "Dovecot reload"
|
||||||
step "Reloading Dovecot ..."
|
step "Reloading Dovecot ..."
|
||||||
"${DOVECOT_REAL}/sbin/dovecot" reload \
|
if "${DOVECOT_REAL}/sbin/dovecot" reload; then
|
||||||
&& ok || { failed; die "Dovecot reload failed."; }
|
ok
|
||||||
|
else
|
||||||
|
failed; die "Dovecot reload failed."
|
||||||
|
fi
|
||||||
|
|
||||||
# ── summary ───────────────────────────────────────────────────────────────────
|
# ── summary ───────────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -9227,14 +9227,15 @@ fi # Renew file /usr/local/dovecot-${_version}/etc/dovecot/sql-dict.conf.ext
|
|||||||
|
|
||||||
|
|
||||||
## -----------------------------------------------------------------
|
## -----------------------------------------------------------------
|
||||||
## --- quota_clone plugin (Dovecot 2.4+ / pgsql only)
|
## --- quota_clone plugin (Dovecot 2.4+ / pgsql + mysql)
|
||||||
## ---
|
## ---
|
||||||
## --- Writes quota usage to quota2 table in real time so PostfixAdmin
|
## --- Writes quota usage to quota2 table in real time so PostfixAdmin
|
||||||
## --- can display mailbox fill levels ($CONF['used_quotas'] = 'YES').
|
## --- can display mailbox fill levels ($CONF['used_quotas'] = 'YES').
|
||||||
## ---
|
## ---
|
||||||
## --- Replaces the old ADD-semantics mergequota2 trigger with a
|
## --- Replaces the old ADD-semantics mergequota2 trigger with a
|
||||||
## --- SET-semantics upsertquota2 trigger (validation only); the dict
|
## --- SET-semantics upsertquota2 trigger (validation only); Dovecot's
|
||||||
## --- proxy service generates INSERT … ON CONFLICT DO UPDATE natively.
|
## --- dict service generates native UPSERT queries (pgsql: ON CONFLICT
|
||||||
|
## --- DO UPDATE; mysql: ON DUPLICATE KEY UPDATE).
|
||||||
|
|
||||||
if [[ $dovecot_major_version -gt 2 ]] \
|
if [[ $dovecot_major_version -gt 2 ]] \
|
||||||
|| ( [[ $dovecot_major_version -eq 2 ]] && [[ $dovecot_minor_version -gt 3 ]] ); then
|
|| ( [[ $dovecot_major_version -eq 2 ]] && [[ $dovecot_minor_version -gt 3 ]] ); then
|
||||||
@@ -9474,7 +9475,245 @@ PYEOF
|
|||||||
error "Installing /usr/local/bin/update_quota2.sh failed!"
|
error "Installing /usr/local/bin/update_quota2.sh failed!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi # db_driver = pgsql
|
elif [ "$db_driver" = "mysql" ]; then
|
||||||
|
|
||||||
|
## - Create quota2 table if it does not yet exist
|
||||||
|
|
||||||
|
echononl " Create quota2 table if not exists.."
|
||||||
|
cat << EOF | mysql -u$dbuser -p$dbpassword $dbname > /dev/null 2>&1
|
||||||
|
CREATE TABLE IF NOT EXISTS quota2 (
|
||||||
|
username varchar(100) NOT NULL,
|
||||||
|
bytes bigint NOT NULL DEFAULT 0,
|
||||||
|
messages integer NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (username)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Creating quota2 table failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## - Drop old ADD-semantics trigger if present
|
||||||
|
|
||||||
|
echononl " Drop old mergequota2 trigger (if present).."
|
||||||
|
cat << EOF | mysql -u$dbuser -p$dbpassword $dbname > /dev/null 2>&1
|
||||||
|
DROP TRIGGER IF EXISTS mergequota2;
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Dropping mergequota2 trigger failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## - Create upsertquota2 trigger (DELIMITER needed in MySQL batch mode)
|
||||||
|
|
||||||
|
echononl " Create upsertquota2 trigger.."
|
||||||
|
cat << 'EOF' | mysql -u$dbuser -p$dbpassword $dbname > /dev/null 2>&1
|
||||||
|
DROP TRIGGER IF EXISTS upsertquota2;
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER upsertquota2
|
||||||
|
BEFORE INSERT ON quota2
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF NEW.bytes IS NULL OR NEW.bytes < 0 THEN SET NEW.bytes = 0; END IF;
|
||||||
|
IF NEW.messages IS NULL OR NEW.messages < 0 THEN SET NEW.messages = 0; END IF;
|
||||||
|
END//
|
||||||
|
DELIMITER ;
|
||||||
|
EOF
|
||||||
|
if [ "$?" = 0 ]; then
|
||||||
|
echo -e "$rc_done"
|
||||||
|
else
|
||||||
|
echo -e "$rc_failed"
|
||||||
|
error "Creating upsertquota2 trigger 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+ / MariaDB / MySQL)
|
||||||
|
##
|
||||||
|
## Writes quota usage to the quota2 table in real time so
|
||||||
|
## PostfixAdmin can display mailbox fill levels.
|
||||||
|
##
|
||||||
|
dict_server {
|
||||||
|
dict quota_clone_mysql {
|
||||||
|
driver = sql
|
||||||
|
sql_driver = mysql
|
||||||
|
mysql $dbhost {
|
||||||
|
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_mysql" "${_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_mysql
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
## - Variables $dbuser, $dbpassword, $dbhost, $dbname are expanded
|
||||||
|
## - into the Python script by the unquoted heredoc.
|
||||||
|
|
||||||
|
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'
|
||||||
|
MYSQL = 'mysql'
|
||||||
|
DB_USER = '$dbuser'
|
||||||
|
DB_PASS = '$dbpassword'
|
||||||
|
DB_SOCKET = '$dbhost'
|
||||||
|
DB_NAME = '$dbname'
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
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
|
||||||
|
|
||||||
fi # Dovecot 2.4+ quota_clone
|
fi # Dovecot 2.4+ quota_clone
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user