Files
mailsystem/update_quota2_psql.sh

109 lines
3.3 KiB
Bash
Executable File

#!/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()