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:
@@ -9226,6 +9226,259 @@ EOF
|
||||
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
|
||||
# passdb's SQL query have to update
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user