3
# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
"""Perform all necessary upgrades.
21
Usage: %(PROGRAM)s [options]
25
Force running the upgrade procedures. Normally, if the version number
26
of the installed Mailman matches the current version number (or a
27
`downgrade' is detected), nothing will be done.
30
Print this text and exit.
32
Use this script to help you update to the latest release of Mailman from
33
some previous version. It knows about versions back to 1.0b4 (?).
46
from Mailman import mm_cfg
47
from Mailman import Utils
48
from Mailman import MailList
49
from Mailman.LockFile import TimeOutError
50
from Mailman.i18n import _
51
from Mailman.Queue.Switchboard import Switchboard
52
from Mailman.OldStyleMemberships import OldStyleMemberships
53
from Mailman.MemberAdaptor import BYBOUNCE, ENABLED
58
LMVFILE = os.path.join(mm_cfg.DATA_DIR, 'last_mailman_version')
64
# Returns a tuple of (lastversion, thisversion). If the last version
65
# could not be determined, lastversion will be FRESH or NOTFRESH,
66
# depending on whether this installation appears to be fresh or not. The
67
# determining factor is whether there are files in the $var_prefix/logs
68
# subdir or not. The version numbers are HEX_VERSIONs.
70
# See if we stored the last updated version
72
thisversion = mm_cfg.HEX_VERSION
77
lastversion = int(data, 16)
78
except (IOError, ValueError):
81
# try to figure out if this is a fresh install
82
if lastversion is None:
85
if os.listdir(mm_cfg.LOG_DIR):
86
lastversion = NOTFRESH
89
return (lastversion, thisversion)
94
return os.path.join(mm_cfg.PREFIX, relpath)
96
def make_varabs(relpath):
97
return os.path.join(mm_cfg.VAR_PREFIX, relpath)
100
def move_language_templates(mlist):
101
listname = mlist.internal_name()
102
print _('Fixing language templates: %(listname)s')
103
# Mailman 2.1 has a new cascading search for its templates, defined and
104
# described in Utils.py:maketext(). Putting templates in the top level
105
# templates/ subdir or the lists/<listname> subdir is deprecated and no
108
# What this means is that most templates can live in the global templates/
109
# subdirectory, and only needs to be copied into the list-, vhost-, or
110
# site-specific language directories when needed.
112
# Also, by default all standard (i.e. English) templates must now live in
113
# the templates/en directory. This update cleans up all the templates,
114
# deleting more-specific duplicates (as calculated by md5 checksums) in
115
# favor of more-global locations.
117
# First, get rid of any lists/<list> template or lists/<list>/en template
118
# that is identical to the global templates/* default.
119
for gtemplate in os.listdir(os.path.join(mm_cfg.TEMPLATE_DIR, 'en')):
120
# BAW: get rid of old templates, e.g. admlogin.txt and
123
fp = open(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate))
125
if e.errno <> errno.ENOENT: raise
129
gcksum = md5.new(fp.read()).digest()
131
# Match against the lists/<list>/* template
133
fp = open(os.path.join(mlist.fullpath(), gtemplate))
135
if e.errno <> errno.ENOENT: raise
137
tcksum = md5.new(fp.read()).digest()
140
os.unlink(os.path.join(mlist.fullpath(), gtemplate))
141
# Match against the lists/<list>/*.prev template
143
fp = open(os.path.join(mlist.fullpath(), gtemplate + '.prev'))
145
if e.errno <> errno.ENOENT: raise
147
tcksum = md5.new(fp.read()).digest()
150
os.unlink(os.path.join(mlist.fullpath(), gtemplate + '.prev'))
151
# Match against the lists/<list>/en/* templates
153
fp = open(os.path.join(mlist.fullpath(), 'en', gtemplate))
155
if e.errno <> errno.ENOENT: raise
157
tcksum = md5.new(fp.read()).digest()
160
os.unlink(os.path.join(mlist.fullpath(), 'en', gtemplate))
161
# Match against the templates/* template
163
fp = open(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate))
165
if e.errno <> errno.ENOENT: raise
167
tcksum = md5.new(fp.read()).digest()
170
os.unlink(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate))
171
# Match against the templates/*.prev template
173
fp = open(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate + '.prev'))
175
if e.errno <> errno.ENOENT: raise
177
tcksum = md5.new(fp.read()).digest()
180
os.unlink(os.path.join(mm_cfg.TEMPLATE_DIR,
181
gtemplate + '.prev'))
185
def dolist(listname):
187
mlist = MailList.MailList(listname, lock=0)
191
print >> sys.stderr, _('WARNING: could not acquire lock for list: '
195
# Sanity check the invariant that every BYBOUNCE disabled member must have
196
# bounce information. Some earlier betas broke this. BAW: we're
197
# submerging below the MemberAdaptor interface, so skip this if we're not
198
# using OldStyleMemberships.
199
if isinstance(mlist._memberadaptor, OldStyleMemberships):
201
for addr, (reason, when) in mlist.delivery_status.items():
202
if reason == BYBOUNCE and not mlist.bounce_info.has_key(addr):
203
noinfo[addr] = reason, when
204
# What to do about these folks with a BYBOUNCE delivery status and no
205
# bounce info? This number should be very small, and I think it's
206
# fine to simple re-enable them and let the bounce machinery
207
# re-disable them if necessary.
211
'Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info')
212
for addr in noinfo.keys():
213
mlist.setDeliveryStatus(addr, ENABLED)
215
# Update the held requests database
216
print _("""Updating the held requests database.""")
217
mlist._UpdateRecords()
219
mbox_dir = make_varabs('archives/private/%s.mbox' % (listname))
220
mbox_file = make_varabs('archives/private/%s.mbox/%s' % (listname,
223
o_pub_mbox_file = make_varabs('archives/public/%s' % (listname))
224
o_pri_mbox_file = make_varabs('archives/private/%s' % (listname))
226
html_dir = o_pri_mbox_file
227
o_html_dir = makeabs('public_html/archives/%s' % (listname))
229
# make the mbox directory if it's not there.
231
if not os.path.exists(mbox_dir):
233
os.mkdir(mbox_dir, 02775)
236
# this shouldn't happen, but hey, just in case
237
if not os.path.isdir(mbox_dir):
239
For some reason, %(mbox_dir)s exists as a file. This won't work with
240
b6, so I'm renaming it to %(mbox_dir)s.tmp and proceeding.""")
241
os.rename(mbox_dir, "%s.tmp" % (mbox_dir))
243
os.mkdir(mbox_dir, 02775)
246
# Move any existing mboxes around, but watch out for both a public and a
247
# private one existing
248
if os.path.isfile(o_pri_mbox_file) and os.path.isfile(o_pub_mbox_file):
249
if mlist.archive_private:
252
%(listname)s has both public and private mbox archives. Since this list
253
currently uses private archiving, I'm installing the private mbox archive
254
-- %(o_pri_mbox_file)s -- as the active archive, and renaming
257
%(o_pub_mbox_file)s.preb6
259
You can integrate that into the archives if you want by using the 'arch'
261
""") % (mlist._internal_name, o_pri_mbox_file, o_pub_mbox_file,
263
os.rename(o_pub_mbox_file, "%s.preb6" % (o_pub_mbox_file))
266
%s has both public and private mbox archives. Since this list
267
currently uses public archiving, I'm installing the public mbox file
268
archive file (%s) as the active one, and renaming
273
You can integrate that into the archives if you want by using the 'arch'
275
""") % (mlist._internal_name, o_pub_mbox_file, o_pri_mbox_file,
277
os.rename(o_pri_mbox_file, "%s.preb6" % (o_pri_mbox_file))
279
# move private archive mbox there if it's around
280
# and take into account all sorts of absurdities
282
print _('- updating old private mbox file')
283
if os.path.exists(o_pri_mbox_file):
284
if os.path.isfile(o_pri_mbox_file):
285
os.rename(o_pri_mbox_file, mbox_file)
286
elif not os.path.isdir(o_pri_mbox_file):
287
newname = "%s.mm_install-dunno_what_this_was_but_its_in_the_way" \
289
os.rename(o_pri_mbox_file, newname)
291
unknown file in the way, moving
298
looks like you have a really recent CVS installation...
299
you're either one brave soul, or you already ran me""")
303
# move public archive mbox there if it's around
304
# and take into account all sorts of absurdities.
306
print _('- updating old public mbox file')
307
if os.path.exists(o_pub_mbox_file):
308
if os.path.isfile(o_pub_mbox_file):
309
os.rename(o_pub_mbox_file, mbox_file)
310
elif not os.path.isdir(o_pub_mbox_file):
311
newname = "%s.mm_install-dunno_what_this_was_but_its_in_the_way" \
313
os.rename(o_pub_mbox_file, newname)
315
unknown file in the way, moving
321
looks like you have a really recent CVS installation...
322
you're either one brave soul, or you already ran me""")
325
# move the html archives there
327
if os.path.isdir(o_html_dir):
328
os.rename(o_html_dir, html_dir)
330
# chmod the html archives
332
os.chmod(html_dir, 02775)
333
# BAW: Is this still necessary?!
336
# check to see if pre-b4 list-specific templates are around
337
# and move them to the new place if there's not already
340
tmpl_dir = os.path.join(mm_cfg.PREFIX, "templates")
341
list_dir = os.path.join(mm_cfg.PREFIX, "lists")
342
b4_tmpl_dir = os.path.join(tmpl_dir, mlist._internal_name)
343
new_tmpl_dir = os.path.join(list_dir, mlist._internal_name)
344
if os.path.exists(b4_tmpl_dir):
346
- This list looks like it might have <= b4 list templates around""")
347
for f in os.listdir(b4_tmpl_dir):
348
o_tmpl = os.path.join(b4_tmpl_dir, f)
349
n_tmpl = os.path.join(new_tmpl_dir, f)
350
if not os.path.exists(n_tmpl):
351
os.rename(o_tmpl, n_tmpl)
352
print _('- moved %(o_tmpl)s to %(n_tmpl)s')
355
- both %(o_tmpl)s and %(n_tmpl)s exist, leaving untouched""")
357
# Move all the templates to the en language subdirectory as required for
360
move_language_templates(mlist)
361
# Avoid eating filehandles with the list lockfiles
367
def archive_path_fixer(unused_arg, dir, files):
368
# Passed to os.path.walk to fix the perms on old html archives.
370
abs = os.path.join(dir, f)
371
if os.path.isdir(abs):
376
elif os.path.isfile(abs):
379
def remove_old_sources(module):
380
# Also removes old directories.
381
src = '%s/%s' % (mm_cfg.PREFIX, module)
383
if os.path.isdir(src):
384
print _('removing directory %(src)s and everything underneath')
386
elif os.path.exists(src):
387
print _('removing %(src)s')
390
except os.error, rest:
391
print _("Warning: couldn't remove %(src)s -- %(rest)s")
392
if module.endswith('.py') and os.path.exists(pyc):
395
except os.error, rest:
396
print _("couldn't remove old file %(pyc)s -- %(rest)s")
400
print _('updating old qfiles')
401
prefix = `time.time()` + '+'
402
# Be sure the qfiles/in directory exists (we don't really need the
403
# switchboard object, but it's convenient for creating the directory).
404
sb = Switchboard(mm_cfg.INQUEUE_DIR)
405
for file in os.listdir(mm_cfg.QUEUE_DIR):
406
# Updating means just mokving the .db and .msg files to qfiles/in where
407
# it should be dequeued, converted, and processed normally.
408
if file.endswith('.msg'):
409
oldmsgfile = os.path.join(mm_cfg.QUEUE_DIR, file)
410
newmsgfile = os.path.join(mm_cfg.INQUEUE_DIR, prefix + file)
411
os.rename(oldmsgfile, newmsgfile)
412
elif file.endswith('.db'):
413
olddbfile = os.path.join(mm_cfg.QUEUE_DIR, file)
414
newdbfile = os.path.join(mm_cfg.INQUEUE_DIR, prefix + file)
415
os.rename(olddbfile, newdbfile)
421
# get rid of old stuff
422
print _('getting rid of old source files')
423
for mod in ('Mailman/Archiver.py', 'Mailman/HyperArch.py',
424
'Mailman/HyperDatabase.py', 'Mailman/pipermail.py',
425
'Mailman/smtplib.py', 'Mailman/Cookie.py',
426
'bin/update_to_10b6', 'scripts/mailcmd',
427
'scripts/mailowner', 'mail/wrapper', 'Mailman/pythonlib',
428
'cgi-bin/archives', 'Mailman/MailCommandHandler'):
429
remove_old_sources(mod)
430
listnames = Utils.list_names()
432
print _('no lists == nothing to do, exiting')
435
# for people with web archiving, make sure the directories
436
# in the archiving are set with proper perms for b6.
438
if os.path.isdir("%s/public_html/archives" % mm_cfg.PREFIX):
440
fixing all the perms on your old html archives to work with b6
441
If your archives are big, this could take a minute or two...""")
442
os.path.walk("%s/public_html/archives" % mm_cfg.PREFIX,
443
archive_path_fixer, "")
445
for listname in listnames:
446
print _('Updating mailing list: %(listname)s')
447
errors = errors + dolist(listname)
449
print _('Updating Usenet watermarks')
450
wmfile = os.path.join(mm_cfg.DATA_DIR, 'gate_watermarks')
454
print _('- nothing to update here')
458
for listname in d.keys():
459
if listname not in listnames:
460
# this list no longer exists
462
mlist = MailList.MailList(listname, lock=0)
466
print >> sys.stderr, _(
467
'WARNING: could not acquire lock for list: %(listname)s')
470
# Pre 1.0b7 stored 0 in the gate_watermarks file to indicate
471
# that no gating had been done yet. Without coercing this to
472
# None, the list could now suddenly get flooded.
473
mlist.usenet_watermark = d[listname] or None
477
print _('- usenet watermarks updated and gate_watermarks removed')
479
# In Mailman 2.1, the pending database format and file name has changed.
481
oldpendingfile = os.path.join(mm_cfg.DATA_DIR, 'pending_subscriptions.db')
483
fp = open(oldpendingfile)
485
if e.errno <> errno.ENOENT: raise
487
print _('Updating old pending_subscriptions.db database')
488
from Mailman import Pending
489
db = marshal.load(fp)
492
os.unlink(oldpendingfile)
494
# In Mailman 2.1, the qfiles directory has a different structure and a
499
# This warning was necessary for the upgrade from 1.0b9 to 1.0b10.
500
# There's no good way of figuring this out for releases prior to 2.0beta2
503
if lastversion == NOTFRESH:
506
NOTE NOTE NOTE NOTE NOTE
508
You are upgrading an existing Mailman installation, but I can't tell what
509
version you were previously running.
511
If you are upgrading from Mailman 1.0b9 or earlier you will need to
512
manually update your mailing lists. For each mailing list you need to
513
copy the file templates/options.html lists/<listname>/options.html.
515
However, if you have edited this file via the Web interface, you will have
516
to merge your changes into this file, otherwise you will lose your
519
NOTE NOTE NOTE NOTE NOTE
526
def usage(code, msg=''):
531
print >> fd, _(__doc__) % globals()
533
print >> sys.stderr, msg
538
if __name__ == '__main__':
540
opts, args = getopt.getopt(sys.argv[1:], 'hf',
542
except getopt.error, msg:
546
usage(1, 'Unexpected arguments: %s' % args)
549
for opt, arg in opts:
550
if opt in ('-h', '--help'):
552
elif opt in ('-f', '--force'):
555
# calculate the versions
556
lastversion, thisversion = calcversions()
557
hexlversion = hex(lastversion)
558
hextversion = hex(thisversion)
559
if lastversion == thisversion and not force:
561
print _('No updates are necessary.')
563
if lastversion > thisversion and not force:
565
Downgrade detected, from version %(hexlversion)s to version %(hextversion)s
566
This is probably not safe.
569
print _('Upgrading from version %(hexlversion)s to %(hextversion)s')
572
# Record the version we just upgraded to
573
fp = open(LMVFILE, 'w')
574
fp.write(hex(mm_cfg.HEX_VERSION) + '\n')
577
lockdir = mm_cfg.LOCK_DIR
582
The locks for some lists could not be acquired. This means that either
583
Mailman was still active when you upgraded, or there were stale locks in the
584
%(lockdir)s directory.
586
You must put Mailman into a quiescent state and remove all stale locks, then
587
re-run "make update" manually. See the INSTALL and UPGRADE files for details.