3
# MigrationExtra script to maintain the enabled/disabled state of the
6
# This script examines the launchd preferences from the previous system
7
# (also taking into account the overrides.plist) and then invokes serveradmin
8
# to start/stop calendar server.
10
# The only argument this script currently cares about is --sourceRoot, which
11
# should point to the root of the previous system.
13
# Copyright (c) 2005-2009 Apple Inc. All Rights Reserved.
15
# IMPORTANT NOTE: This file is licensed only for use on Apple-labeled
16
# computers and is subject to the terms and conditions of the Apple
17
# Software License Agreement accompanying the package this file is a
18
# part of. You may not port this file to another platform without
19
# Apple's written consent.
21
from __future__ import with_statement
31
LAUNCHD_KEY = "org.calendarserver.calendarserver"
32
LOG = "/Library/Logs/Migration/calendarmigrator.log"
33
SERVICE_NAME = "calendar"
34
LAUNCHD_OVERRIDES = "var/db/launchd.db/com.apple.launchd/overrides.plist"
35
LAUNCHD_PREFS_DIR = "System/Library/LaunchDaemons"
36
CALDAVD_CONFIG_DIR = "private/etc/caldavd"
40
optionParser = optparse.OptionParser()
42
optionParser.add_option('--purge', choices=('0', '1'),
44
help='remove old files after migration (IGNORED)')
46
optionParser.add_option('--sourceRoot', type='string',
48
help='path to the root of the system to migrate')
50
optionParser.add_option('--sourceType', type='string',
51
metavar='[System|TimeMachine]',
52
help='migration source type (IGNORED)')
54
optionParser.add_option('--sourceVersion', type='string',
56
help='version number of previous system (IGNORED)')
58
optionParser.add_option('--targetRoot', type='string',
60
help='path to the root of the new system',
63
optionParser.add_option('--language', choices=('en', 'fr', 'de', 'ja'),
64
metavar='[en|fr|de|ja]',
65
help='language identifier (IGNORED)')
67
(options, args) = optionParser.parse_args()
69
if options.sourceRoot:
71
if os.path.exists(options.sourceRoot):
72
migrateConfiguration(options)
73
migrateRunState(options)
76
log("ERROR: --sourceRoot must be specified")
80
def migrateRunState(options):
82
Try to determine whether server was running in previous system, then
83
modify the launchd settings in the new system.
87
disabled = isServiceDisabled(options.sourceRoot, LAUNCHD_KEY)
88
log("Service '%s' was previously %s" %
89
(LAUNCHD_KEY, "disabled" if disabled else "enabled"))
90
except ServiceStateError, e:
91
log("Couldn't determine previous state of service '%s': %s" %
95
setServiceStateDisabled(options.targetRoot, LAUNCHD_KEY, disabled)
98
def migrateConfiguration(options):
100
Copy files/directories/symlinks from previous system's /etc/caldavd
102
Skips anything ending in ".default".
103
Regular files overwrite copies in new system.
104
Directories and symlinks only copied over if they don't overwrite anything.
107
oldConfigDir = os.path.join(options.sourceRoot, CALDAVD_CONFIG_DIR)
108
if not os.path.exists(oldConfigDir):
109
log("Old configuration directory does not exist: %s" % (oldConfigDir,))
112
newConfigDir = os.path.join(options.targetRoot, CALDAVD_CONFIG_DIR)
113
if not os.path.exists(newConfigDir):
114
log("New configuration directory does not exist: %s" % (newConfigDir,))
117
log("Copying configuration files from %s to %s" % (oldConfigDir, newConfigDir))
119
for name in os.listdir(oldConfigDir):
121
if not name.endswith(".default"):
123
oldPath = os.path.join(oldConfigDir, name)
124
newPath = os.path.join(newConfigDir, name)
126
if os.path.islink(oldPath) and not os.path.exists(newPath):
127
# Recreate the symlink if it won't overwrite an existing file
128
link = os.readlink(oldPath)
129
log("Symlinking %s to %s" % (newPath, link))
130
os.symlink(link, newPath)
132
elif os.path.isfile(oldPath):
134
if name == "caldavd.plist":
135
# Migrate certain settings from the old plist to new:
136
log("Parsing %s" % (oldPath,))
137
oldPlist = plistlib.readPlist(oldPath)
138
if os.path.exists(newPath):
139
log("Parsing %s" % (newPath,))
140
newPlist = plistlib.readPlist(newPath)
141
log("Removing %s" % (newPath,))
145
log("Processing %s" % (oldPath,))
146
mergePlist(oldPlist, newPlist)
147
log("Writing %s" % (newPath,))
148
plistlib.writePlist(newPlist, newPath)
151
# Copy the file over, overwriting copy in newConfigDir
152
log("Copying file %s to %s" % (oldPath, newConfigDir))
153
shutil.copy2(oldPath, newConfigDir)
156
elif os.path.isdir(oldPath) and not os.path.exists(newPath):
157
# Copy the dir over, but only if new one doesn't exist
158
log("Copying directory %s to %s" % (oldPath, newPath))
159
shutil.copytree(oldPath, newPath, symlinks=True)
161
def mergePlist(oldPlist, newPlist):
163
# The following CalendarServer v1.x keys are ignored:
164
# EnableNotifications, Verbose
166
# These keys are copied verbatim:
168
"AccessLogFile", "AdminPrincipals", "BindAddresses", "BindHTTPPorts",
169
"BindSSLPorts", "ControlSocket", "DocumentRoot", "EnableDropBox",
170
"EnableProxyPrincipals", "EnableSACLs", "ErrorLogFile", "GroupName",
171
"HTTPPort", "MaximumAttachmentSize", "MultiProcess", "PIDFile",
172
"ProcessType", "ResponseCompression", "RotateAccessLog",
173
"SSLAuthorityChain", "SSLCertificate", "SSLPort", "SSLPrivateKey",
174
"ServerHostName", "ServerStatsFile", "SudoersFile", "UserName",
178
newPlist[key] = oldPlist[key]
180
# "Wiki" is a new authentication in v2.x; copy all "Authentication" sub-keys # over, and "Wiki" will be picked up from the new plist:
181
if "Authentication" in oldPlist:
182
for key in oldPlist["Authentication"]:
183
newPlist["Authentication"][key] = oldPlist["Authentication"][key]
185
# Strip out any unknown params from the DirectoryService:
186
if "DirectoryService" in oldPlist:
187
newPlist["DirectoryService"] = oldPlist["DirectoryService"]
188
for key in newPlist["DirectoryService"]["params"].keys():
190
"node", "restrictEnabledRecords", "restrictToGroup",
191
"cacheTimeout", "xmlFile"
193
del newPlist["DirectoryService"]["params"][key]
195
# Place DataRoot as a sibling of DocumentRoot:
196
parent = os.path.dirname(newPlist["DocumentRoot"].rstrip("/"))
197
newPlist["DataRoot"] = os.path.join(parent, "Data")
200
def isServiceDisabled(source, service):
202
Returns whether or not a service is disabled
204
@param source: System root to examine
205
@param service: launchd key representing service
206
@return: True if service is disabled, False if enabled
209
overridesPath = os.path.join(source, LAUNCHD_OVERRIDES)
210
if os.path.isfile(overridesPath):
211
overrides = plistlib.readPlist(overridesPath)
213
return overrides[service]['Disabled']
215
# Key is not in the overrides.plist, continue on
218
prefsPath = os.path.join(source, LAUNCHD_PREFS_DIR, "%s.plist" % service)
219
if os.path.isfile(prefsPath):
220
prefs = plistlib.readPlist(prefsPath)
222
return prefs['Disabled']
226
raise ServiceStateError("Neither %s nor %s exist" %
227
(overridesPath, prefsPath))
230
def setServiceStateDisabled(target, service, disabled):
232
Modifies launchd settings for a service
234
@param target: System root
235
@param service: launchd key representing service
236
@param disabled: boolean
239
overridesPath = os.path.join(target, LAUNCHD_OVERRIDES)
240
if os.path.isfile(overridesPath):
241
overrides = plistlib.readPlist(overridesPath)
242
if not overrides.has_key(service):
243
overrides[service] = { }
244
overrides[service]['Disabled'] = disabled
245
plistlib.writePlist(overrides, overridesPath)
248
class ServiceStateError(Exception):
250
Could not determine service state
256
with open(LOG, 'a') as output:
257
timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
258
output.write("%s %s\n" % (timestamp, msg))
260
# Could not write to log
264
if __name__ == '__main__':