]> git.donarmstrong.com Git - dsa-puppet.git/blob - modules/porterbox/files/mail-big-homedirs
refactor somewhat
[dsa-puppet.git] / modules / porterbox / files / mail-big-homedirs
1 #!/usr/bin/python
2 ## vim:set et ts=2 sw=2 ai:
3 # Send email reminders to users having sizable homedirs.
4 ##
5 # Copyright (c) 2013 Philipp Kern <phil@philkern.de>
6 # Copyright (c) 2013 Peter Palfrader <peter@palfrader.org>
7 # Copyright (c) 2013 Luca Filipozzi <lfilipoz@debian.org>
8 #
9 # Permission is hereby granted, free of charge, to any person obtaining a copy
10 # of this software and associated documentation files (the "Software"), to deal
11 # in the Software without restriction, including without limitation the rights
12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 # copies of the Software, and to permit persons to whom the Software is
14 # furnished to do so, subject to the following conditions:
15 #
16 # The above copyright notice and this permission notice shall be included in
17 # all copies or substantial portions of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 # THE SOFTWARE.
26
27 from __future__ import print_function
28
29 from collections import defaultdict
30 import email
31 import email.mime.text
32 import glob
33 import logging
34 import os.path
35 import platform
36 import pwd
37 import random
38 import subprocess
39 import struct
40 import time
41 import StringIO
42
43 # avoid base64 encoding for utf-8
44 email.charset.add_charset('utf-8', email.charset.SHORTEST, email.charset.QP)
45
46 SENDMAIL = ['/usr/sbin/sendmail', '-t', '-oi']
47 #SENDMAIL = ['/bin/cat']
48
49 EXPLANATIONS = [
50 u"""\
51 {hostname}'s /home is growing close to full.  If you have anything in
52 there that you no longer need, please clean it up.""" # By Martin Zobel-Helas
53 ,u"""\
54 Can you please look at your $HOME on {hostname} and remove files which
55 you no longer need (such as old sources).""" # By Martin Michlmayr
56 ,u"""\
57 Thanks for your porting effort on {hostname}!
58
59 Please note that /home is running short of diskspace, so please remove
60 files that you do not need anymore.""" # By Bill Allombert
61   # Please add more from your archive!
62   ]
63
64 CRITERIA = [
65     { 'days':  5, 'size': 10240 },
66     { 'days': 10, 'size':  1024 },
67     { 'days': 30, 'size':   100 },
68     { 'days': 60, 'size':    60 },
69     { 'days': 90, 'size':    30 }
70   ]
71 EXCLUDED_USERNAMES = ['lost+found']
72 MAIL_FROM = 'debian-admin (via Cron) <bulk@admin.debian.org>'
73 MAIL_TO = '{username}@{hostname}.debian.org'
74 MAIL_CC = 'debian-admin (bulk sink) <bulk@admin.debian.org>'
75 MAIL_REPLYTO = 'debian-admin <dsa@debian.org>'
76 MAIL_SUBJECT = 'Please clean up ~{username} on {hostname}.debian.org'
77 MAIL_MESSAGE = u"""\
78 Hi {name}!
79
80 {explanation}
81
82 For your information, you last logged into {hostname} {days_ago} days
83 ago, and your home directory there is {homedir_size} MB in size.
84
85 If you currently do not use {hostname}, please keep ~{username} under
86 30 MB, if possible.
87
88 Please assist us in freeing up space by deleting schroots, also.
89
90 Thanks,
91
92 Debian System Administration Team via Cron
93
94 PS: replies not required.
95 """
96
97 class Error(Exception):
98   pass
99
100 class SendmailError(Error):
101   pass
102
103 class LastlogTimes(dict):
104   LASTLOG_STRUCT = '=L32s256s'
105   
106   def __init__(self):
107     record_size = struct.calcsize(self.LASTLOG_STRUCT)
108     with open('/var/log/lastlog', 'r') as fp:
109       uid = -1 # there is one record per uid in lastlog
110       for record in iter(lambda: fp.read(record_size), ''):
111         uid += 1 # so keep incrementing uid for each record read
112         lastlog_time, _, _ = list(struct.unpack(self.LASTLOG_STRUCT, record))
113         if lastlog_time == 0:
114           continue
115         try:
116           self[pwd.getpwuid(uid).pw_name] = lastlog_time
117         except KeyError:
118           logging.error('could not resolve username from uid')
119           continue
120
121 class HomedirSizes(dict):
122   def __init__(self):
123     for direntry in glob.glob('/home/*'):
124       username = os.path.basename(direntry)
125       if username in EXCLUDED_USERNAMES:
126         continue
127       try:
128         pwinfo = pwd.getpwnam(username)
129       except KeyError:
130         if os.path.isdir(direntry):
131           logging.warning('directory %s exists on %s but there is no %s user', direntry, platform.node(), username)
132         continue
133       command = ['/usr/bin/du', '-ms', pwinfo.pw_dir]
134       p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
135       (stdout, stderr) = p.communicate()
136       if p.returncode != 0: # ignore errors from du
137         logging.info('%s failed:', ' '.join(command))
138         logging.info(stderr)
139         continue
140       try:
141         self[username] = int(stdout.split('\t')[0])
142       except ValueError:
143         logging.error('could not convert size output from %s: %s', ' '.join(command), stdout)
144         continue
145
146 class HomedirReminder(object):
147   def __init__(self):
148     self.lastlog_times = LastlogTimes()
149     self.homedir_sizes = HomedirSizes()
150
151   def send_mail(self, **kwargs):
152     msg = email.mime.text.MIMEText(MAIL_MESSAGE.format(**kwargs), _charset='UTF-8')
153     msg['From'] = MAIL_FROM.format(**kwargs)
154     msg['To'] = MAIL_TO.format(**kwargs)
155     if MAIL_CC != "":
156       msg['Cc'] = MAIL_CC.format(**kwargs)
157     if MAIL_REPLYTO != "":
158       msg['Reply-To'] = MAIL_REPLYTO.format(**kwargs)
159     msg['Subject'] = MAIL_SUBJECT.format(**kwargs)
160     msg['Precedence'] = "bulk"
161     msg['Auto-Submitted'] = "auto-generated by mail-big-homedirs"
162     p = subprocess.Popen(SENDMAIL, stdin=subprocess.PIPE)
163     p.communicate(msg.as_string())
164     logging.debug(msg.as_string())
165     if p.returncode != 0:
166       raise SendmailError
167
168   def run(self):
169     current_time = time.time()
170     for username, homedir_size in self.homedir_sizes.iteritems():
171       try:
172         name = pwd.getpwnam(username).pw_gecos.decode('utf-8').split(',', 1)[0].split(' ', 1)[0]
173       except:
174         name = username
175       lastlog_time = self.lastlog_times[username]
176       days_ago = int( (current_time - lastlog_time) / 3600 / 24 )
177       if [x for x in CRITERIA if days_ago >= x['days'] and homedir_size >= x['size']]:
178         explanation = EXPLANATIONS[random.randint(0,len(EXPLANATIONS)-1)].format(hostname=platform.node())
179         self.send_mail(hostname=platform.node(), username=username, name=name, explanation=explanation, homedir_size=homedir_size, days_ago=days_ago)
180
181 if __name__ == '__main__':
182   logging.basicConfig()
183   # DEBUG for debugging, ERROR for production.
184   logging.getLogger().setLevel(logging.ERROR)
185   HomedirReminder().run()