1#!/usr/bin/ruby
2#
3# 05_postgresmigrator.rb
4#
5# Migration script for PostgreSQL
6# Supports migration from 10.7.x to the latest Server release
7# When source system is 10.6.x, initializes an empty set of databases.
8#
9# Author:: Apple Inc.
10# Documentation:: Apple Inc.
11# Copyright (c) 2011-2013 Apple Inc. All Rights Reserved.
12#
13# IMPORTANT NOTE: This file is licensed only for use on Apple-branded
14# computers and is subject to the terms and conditions of the Apple Software
15# License Agreement accompanying the package this file is a part of.
16# You may not port this file to another platform without Apple's written consent.
17# License:: All rights reserved.
18#
19# This script upgrades 10.7.x (PostgreSQL 9.0) data to the latest server version (PostgreSQL 9.2).
20# It also splits the original database into two new database clusters: One dedicated for customer use (in what was previously
21# the default database location), and another dedicated for use only by the shipping server services.
22#
23
24require 'fileutils'
25require 'logger'
26require 'osx/cocoa'
27include OSX
28require 'socket'
29
30$logFile = "/Library/Logs/ServerSetup.log"
31$logger = Logger.new($logFile)
32$logger.level = Logger::INFO
33$logger.info("*** PostgreSQL migration start ***")
34
35$serveradmin = "/Applications/Server.app/Contents/ServerRoot/usr/sbin/serveradmin"
36$serverctl = "/Applications/Server.app/Contents/ServerRoot/usr/sbin/serverctl"
37$postgresBinariesDir9_0 = "/Applications/Server.app/Contents/ServerRoot/usr/libexec/postgresql9.0"
38$newPostgresBinariesDir = "/Applications/Server.app/Contents/ServerRoot/usr/bin"
39$pgServiceDir = "/Library/Server/PostgreSQL For Server Services"
40$pgServiceDirCustomer =  "/Library/Server/PostgreSQL"
41$newPostgresDataDirServer = "/Library/Server/PostgreSQL For Server Services/Data"
42$newPostgresDataDirCustomer = "/Library/Server/PostgreSQL/Data"
43$customerSocketDir = "/private/var/pgsql_socket"
44$pgLogDir = "/Library/Logs/PostgreSQL"
45$serverSocketDir = "/Library/Server/PostgreSQL For Server Services/Socket"
46$migrationDir = "/Library/Server/PostgreSQL For Server Services/Migration"
47$serverDatabases =  ["caldav", "collab", "device_management"]	# databases to be forked away from customer data
48$serverRoles = ["caldav", "collab", "_devicemgr"]				# roles to be forked away from customer data
49$serverRolesSQL = "CREATE ROLE _devicemgr; ALTER ROLE _devicemgr WITH NOSUPERUSER INHERIT NOCREATEROLE CREATEDB LOGIN NOREPLICATION; CREATE ROLE caldav; ALTER ROLE caldav WITH NOSUPERUSER INHERIT NOCREATEROLE CREATEDB LOGIN NOREPLICATION; CREATE ROLE collab; ALTER ROLE collab WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN NOREPLICATION; CREATE ROLE webauth; ALTER ROLE webauth WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN NOREPLICATION;"
50
51$purge = "0"
52$sourceRoot = "/Previous System"
53$targetRoot = "/"
54$sourceType = "System"
55$sourceVersion = "10.7"
56$language = "en"
57$orig_wd = Dir.getwd
58
59def usage
60	usage_str =<<EOS
61usage: for example:\n
62#{File.basename($0)} --sourceRoot "/Previous System" --targetRoot / --purge 0 --language en --sourceVersion 10.7 --sourceType System
63
64In this implementation, --language and --sourceType are ignored
65EOS
66	$stderr.print(usage_str)
67end
68
69def exitWithError(message)
70	$logger.error(message)
71	$logger.info("*** PostgreSQL migration end ***")
72	$logger.close
73	Dir.chdir($orig_wd)
74	exit(2)
75end
76
77def exitWithMessage(message)
78	$logger.info(message)
79	$logger.info("*** PostgreSQL migration end ***")
80	$logger.close
81	Dir.chdir($orig_wd)
82	exit(0)
83end
84
85def runCommandOrExit(command)
86	ret = `#{command}`
87	if $? != 0
88		$logger.warn("command failed: #$?\nCommand: #{command}\nOutput: #{ret}")
89		exitWithError("Wiki and Profile Manager will not be available.")
90	end
91end
92
93def runCommand(command)
94	ret = `#{command}`
95	if $? != 0
96		$logger.warn("command failed: #$?\nCommand: #{command}\nOutput: #{ret}")
97		return 1
98	end
99	return 0
100end
101
102def startNewPostgres
103	runCommandOrExit("#{$serverctl} enable service=com.apple.postgres");
104	isRunning = 0
105	30.times do
106		statusDict = dictionaryFromServerAdmin("fullstatus postgres_server")
107		if statusDict["postgresIsResponding"]
108			$logger.info("Confirmed that postgres is responding after upgrade/migration")
109			isRunning = 1
110			break
111		end
112		sleep 1
113	end
114	if (! isRunning)
115		$logger.warn("Postgres is not responding after upgrade/migration: #{statusDict.inspect}")
116		exitWithError("Wiki and Profile Manager will not be available.")
117	end
118end
119
120def dictionaryFromServerAdmin(cmd)
121	tempFilePath = "/tmp/#{File.basename($0)}-#{$$}"
122	`/Applications/Server.app/Contents/ServerRoot/usr/sbin/serveradmin -x #{cmd} > #{tempFilePath}`
123	exitWithError("serveradmin #{cmd} failed") if $?.exitstatus != 0
124	dict = NSDictionary.dictionaryWithContentsOfFile(tempFilePath).to_ruby
125	FileUtils.rm_f(tempFilePath)
126	exitWithError("Could not obtain results from serveradmin #{cmd}") if dict.nil?
127	return dict
128end
129
130def settingsFromSourceLaunchd
131	settings = {}
132	plist = "#{$sourceRoot}/System/Library/LaunchDaemons/org.postgresql.postgres.plist"
133	
134	exitWithError("Required file missing from previous system: #{plist}. This is probably an invalid attempt to upgrade/migrate from a non-server system") if !File.exists?(plist)
135	dict = NSDictionary.dictionaryWithContentsOfFile(plist).to_ruby
136	exitWithError("Could not obtain settings from source plist #{plist}") if dict.nil?
137	args = dict["ProgramArguments"]
138	exitWithError("Could not obtain ProgramArguments from source plist #{plist}") if args.nil?
139	dataDirIndex = args.index("-D")
140	exitWithError("Could not obtain dataDir from source plist #{plist}") if dataDirIndex.nil?
141	dataDir = args[dataDirIndex + 1]
142	exitWithError("Could not obtain dataDir from source plist #{plist}") if dataDir.nil?
143	settings["dataDir"] = dataDir
144	args.each do |arg|
145		key,val = arg.split('=')
146		settings[key] = val unless val.nil?
147	end
148	overridesPlist = "#{$sourceRoot}/private/var/db/launchd.db/com.apple.launchd/overrides.plist"
149	overridesDict = NSDictionary.dictionaryWithContentsOfFile(overridesPlist).to_ruby
150	exitWithError("Could not obtain settings from source overrides plist #{overridesPlist}") if overridesDict.nil?
151	if overridesDict["org.postgresql.postgres"].nil?
152		settings["state"] = dict["Disabled"] ? "STOPPED" : "RUNNING"
153		else
154		settings["state"] = overridesDict["org.postgresql.postgres"]["Disabled"] ? "STOPPED" : "RUNNING"
155	end
156	return settings
157end
158
159def shutDownOrphans
160	# e. g. "20270 postgres: _devicemgr device_management [local] idle"
161	procs = `ps -U _postgres -o pid,comm`
162	return if procs.size == 0
163	orphanLines = procs.scan(/.*idle$/)
164	orphanLines.each do |line|
165		pid = line[/^\d+/].to_i
166		next if pid < 2
167		sig = line.sub(/^\d+/, "")
168		$logger.warn("Killing idle client process #{pid} with signature: #{sig}")
169		Process.kill("TERM", pid)
170	end
171end
172
173def megsAvailableOnVolume(volume)
174	fileManager = NSFileManager.defaultManager
175	path = NSString.alloc.initWithString(volume)
176	dict = NSDictionary.dictionaryWithDictionary(fileManager.attributesOfFileSystemForPath_error_(path, nil))
177	bytesAvailable = (dict["NSFileSystemFreeSize"].integerValue)
178	megsAvailable = bytesAvailable / 1024 / 1024
179	return megsAvailable
180end
181
182def forkDatabases(serverTargetDir)
183	# Preflight and leave customer database cluster running in order to extract server-specific data
184	# First disable any TCP sockets that are configured to prevent conflict with other postgres installations
185	tempFilePath = "/tmp/#{File.basename($0)}-#{$$}"
186	command = "echo \"postgres:listen_addresses=\\\"\\\"\" > #{tempFilePath}"
187	runCommandOrExit(command)
188	command = "#{$serveradmin} settings < #{tempFilePath}"
189	runCommandOrExit(command)
190	FileUtils.rm_f(tempFilePath)
191
192	$logger.info("Restarting customer-specific postgres with new settings, to check for successful initialization")
193	dictionaryFromServerAdmin("start postgres")
194	statusDict = dictionaryFromServerAdmin("fullstatus postgres")
195	if (! statusDict["postgresIsResponding"])
196		$logger.warn("Customer postgres database cluster is not responding after upgrade/migration: #{statusDict.inspect}")
197		exitWithError("Wiki and Profile Manager will not be available.")
198	end
199
200	$logger.info("Initializing the server-specific database cluster")
201	command = "sudo -u _postgres #{$newPostgresBinariesDir}/initdb --encoding UTF8 --locale=C -D \"#{serverTargetDir}\""
202	runCommandOrExit(command)
203
204	$logger.info("Restarting server-specific postgres with new settings, to check for successful initialization")
205	startNewPostgres
206
207	$logger.info("Creating Server roles")
208	command = "sudo -u _postgres #{$newPostgresBinariesDir}/psql postgres -h \"#{$serverSocketDir}\" -c \"#{$serverRolesSQL}\""
209	runCommandOrExit(command)
210
211	$logger.info("Moving Server databases to new database")
212	command = "sudo -u _postgres #{$newPostgresBinariesDir}/createdb collab -O collab -h \"#{$serverSocketDir}\""
213	runCommandOrExit(command)
214	command = "sudo -u _postgres #{$newPostgresBinariesDir}/createdb caldav -O caldav -h \"#{$serverSocketDir}\""
215	runCommandOrExit(command)
216	command = "sudo -u _postgres #{$newPostgresBinariesDir}/createdb device_management -O _devicemgr -h \"#{$serverSocketDir}\""
217	runCommandOrExit(command)
218	for database in $serverDatabases
219		command = "sudo -u _postgres #{$newPostgresBinariesDir}/pg_dump #{database} -h \"#{$customerSocketDir}\" | sudo -u _postgres #{$newPostgresBinariesDir}/psql -d #{database} -h \"#{$serverSocketDir}\""
220		runCommand(command)
221	end
222
223	$logger.info("Dropping Server databases from customer database cluster")
224	for database in $serverDatabases
225		command = "sudo -u _postgres #{$newPostgresBinariesDir}/dropdb -h \"#{$customerSocketDir}\" #{database}"
226		runCommand(command)
227	end
228
229	$logger.info("Dropping Server roles from customer database cluster")
230	for role in $serverRoles
231		command = "sudo -u _postgres #{$newPostgresBinariesDir}/dropuser -h \"#{$customerSocketDir}\" #{role}"
232		runCommand(command)
233	end
234end
235
236def initialize_for_clean_install
237	pgExtrasDir = "/Applications/Server.app/Contents/ServerRoot/System/Library/ServerSetup/CommonExtras/PostgreSQLExtras"
238
239	if File.exists?($newPostgresDataDirServer) || File.exists?($newPostgresDataDirCustomer)
240		exitWithError("Data directory already exists where there should be no directory.  Exiting.")
241	end
242	
243	$logger.info("Creating Data Directory for server database cluster")
244	FileUtils.mkdir($newPostgresDataDirServer)
245	FileUtils.chmod(0700, $newPostgresDataDirServer)
246	FileUtils.chown("_postgres", "_postgres", $newPostgresDataDirServer)
247	
248	$logger.info("Calling initdb for server database cluster")
249	command = "sudo -u _postgres #{$newPostgresBinariesDir}/initdb --encoding UTF8 -D \"#{$newPostgresDataDirServer}\""
250	runCommandOrExit(command)
251
252	if File.exists?(pgExtrasDir)
253		$logger.info("Executing PostgreSQLExtras")
254		d = Dir.new(pgExtrasDir)
255		if d.entries.count > 2	# allow for ".." and "."
256			startNewPostgres
257			d.sort{|a,b| a.downcase <=> b.downcase}.each do |executable|
258				next if executable == "." || executable == ".."
259				command = "#{pgExtrasDir}/#{executable}"
260				ret = runCommand(command)
261				if (ret != 0)
262					$logger.warn("Executable returned an error status: #{executable}")
263				end
264			end
265			# Leave it running
266		end
267	end
268
269	$logger.info("Creating Data Directory for customer database cluster")
270	FileUtils.mkdir($newPostgresDataDirCustomer)
271	FileUtils.chmod(0700, $newPostgresDataDirCustomer)
272	FileUtils.chown("_postgres", "_postgres", $newPostgresDataDirCustomer)
273	
274	$logger.info("Calling initdb for customer database cluster")
275	command = "sudo -u _postgres #{$newPostgresBinariesDir}/initdb --encoding UTF8 -D \"#{$newPostgresDataDirCustomer}\""
276	runCommandOrExit(command)
277end
278
279######################################  MAIN
280while arg = ARGV.shift
281	case arg
282		when /--purge/
283		$purge = ARGV.shift
284		when /--sourceRoot/
285		$sourceRoot = ARGV.shift
286		when /--targetRoot/
287		$targetRoot = ARGV.shift
288		when /--sourceType/
289		$sourceType = ARGV.shift
290		when /--sourceVersion/
291		$sourceVersion = ARGV.shift
292		when /--language/
293		$language = ARGV.shift
294		else
295		$stderr.print "Invalid arg: " + arg + "\n"
296		usage()
297		Process.exit(1)
298	end
299end
300
301$logger.info("#{$0} --purge " + $purge + " --sourceRoot " + $sourceRoot + " --targetRoot " + $targetRoot + " --sourceType " + $sourceType + " --sourceVersion " + $sourceVersion + " --language " + $language)
302exitWithMessage("PostgreSQL migration from #{$sourceVersion} is not supported.") if ($sourceVersion !~ /10.7/ && $sourceVersion !~ /10.6/)
303exitWithError("sourceRoot #{$sourceRoot} is not an existing directory") if !File.directory?($sourceRoot)
304oldServerPlistFile = $sourceRoot + "/System/Library/CoreServices/ServerVersion.plist"
305exitWithError("sourceRoot #{oldServerPlistFile} does not exist; this is an invalid attempt to upgrade/migrate from a non-server system") if !File.exists?(oldServerPlistFile)
306
307if File.identical?($sourceRoot, $targetRoot)
308	exitWithError("sourceRoot #{$sourceRoot} and targetRoot #{$targetRoot} are identical")
309end
310
311if !File.exists?($pgServiceDir)
312	$logger.info("Creating Service Directory for server database")
313	FileUtils.mkdir($pgServiceDir)
314	FileUtils.chmod(0755, $pgServiceDir)
315	FileUtils.chown("_postgres", "_postgres", $pgServiceDir)
316end
317
318if !File.exists?($pgServiceDirCustomer)
319	$logger.info("Creating Service Directory for customer database")
320	FileUtils.mkdir($pgServiceDirCustomer)
321	FileUtils.chmod(0755, $pgServiceDirCustomer)
322	FileUtils.chown("_postgres", "_postgres", $pgServiceDirCustomer)
323end
324
325if !File.exists?($serverSocketDir)
326	puts "Creating Socket Directory"
327	FileUtils.mkdir($serverSocketDir)
328	FileUtils.chmod(0755, $serverSocketDir)
329	FileUtils.chown("_postgres", "_postgres", $serverSocketDir)
330end
331
332if !File.exists?($customerSocketDir)
333	puts "Creating Socket Directory for customer database cluster"
334	FileUtils.mkdir($customerSocketDir)
335	FileUtils.chmod(0755, $customerSocketDir)
336	FileUtils.chown("_postgres", "_postgres", $customerSocketDir)
337end
338
339if !File.exists?($migrationDir)
340	puts "Creating Migration Directory"
341	FileUtils.mkdir($migrationDir)
342	FileUtils.chmod(0700, $migrationDir)
343	FileUtils.chown("_postgres", "_postgres", $migrationDir)
344end
345
346if !File.exists?($pgLogDir)
347	puts "Creating Log Directory"
348	FileUtils.mkdir($pgLogDir)
349	FileUtils.chmod(0755, $pgLogDir)
350	FileUtils.chown("_postgres", "_postgres", $pgLogDir)
351end
352
353command = "/Applications/Server.app/Contents/ServerRoot/usr/libexec/copy_postgresql_config_files.sh server"
354runCommandOrExit(command)
355command = "/Applications/Server.app/Contents/ServerRoot/usr/libexec/copy_postgresql_config_files.sh customer"
356runCommandOrExit(command)
357
358if ($sourceVersion =~ /10.6/)
359	# Just initialize; nothing to migrate
360	initialize_for_clean_install
361	exitWithMessage("Finished initializing postgres")
362end
363
364settingsDict = settingsFromSourceLaunchd
365oldDataDir = settingsDict["dataDir"]
366
367Dir.chdir($migrationDir)
368
369# Migration from PostgreSQL 9.0 to 9.2
370$logger.info("Migrating data from an earlier PostgreSQL version")
371
372statusDict = dictionaryFromServerAdmin("fullstatus postgres_server")
373exitWithError("Could not obtain postgres state") if statusDict["state"].nil?
374if statusDict["state"] == "RUNNING"
375	newState = dictionaryFromServerAdmin("stop postgres_server")
376	exitWithError("serveradmin stop failed") if $?.exitstatus != 0
377	exitWithError("Cannot confirm that postgres has stopped: #{newState}") if newState["state"] != "STOPPED"
378	$logger.info("Postgres is running; stopping to apply new settings")
379else
380	shutDownOrphans
381end
382
383statusDict = dictionaryFromServerAdmin("fullstatus postgres")
384exitWithError("Could not obtain postgres state") if statusDict["state"].nil?
385if statusDict["state"] == "RUNNING"
386	newState = dictionaryFromServerAdmin("stop postgres")
387	exitWithError("serveradmin stop failed") if $?.exitstatus != 0
388	exitWithError("Cannot confirm that postgres has stopped: #{newState}") if newState["state"] != "STOPPED"
389	$logger.info("Postgres is running; stopping to apply new settings")
390else
391	shutDownOrphans
392end
393
394if oldDataDir =~ /^\/Volumes\/.*/ && $targetRoot.eql?("/")
395	# Source and destination are the same (alternate volume)
396	exitWithError("dataDir not present at #{oldDataDir}") if !File.directory?(oldDataDir)
397	exitWithError("dataDir missing PG_VERSION file; concluding it is not a PostgreSQL data directory at #{oldDataDir}") if !File.exists?(oldDataDir + "/PG_VERSION")
398	exitWithError("dataDir missing configuration file; concluding it is not a PostgreSQL data directory at #{oldDataDir}") if !File.exists?(oldDataDir + "/postgresql.conf")
399
400	customerTargetDir = oldDataDir
401	oldDataDirString = NSString.alloc.initWithString(oldDataDir).stringByStandardizingPath
402	oldDataDirComponents = NSArray.alloc.init
403	oldDataDirComponents = oldDataDirString.componentsSeparatedByString("/")
404	dbVolume = "/#{oldDataDirComponents[1]}/#{oldDataDirComponents[2]}"
405	serverTargetDir = oldDataDirString.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent + "/PostgreSQL For Server Services/Data"
406	if (serverTargetDir.pathComponents.length < 4)
407		exitWithError("Target path for destination server database is too short, exiting (path: #{serverTargetDir})")
408	end
409
410	# If enough space is available on the alternate volume, move the original database to a new
411	#   directory on this volume and init a new database in place of the original.
412	#   Otherwise, try using the targetRoot to store the original database.
413	$logger.info("Moving out-of-date database aside to be upgraded")
414	newSourceDataDir = nil
415	megsAvailable = megsAvailableOnVolume(dbVolume)
416	if (megsAvailable == 0)
417		exitWithError("megsAvailable is 0 for volume #{dbVolume}")
418	end
419	dbSizeMegs = `du -m -s "#{customerTargetDir}" | awk '{print $1}'`.to_i
420	if ((dbSizeMegs * 2 + 1024) < megsAvailable)  # enough space for a copy of the database onto the same source volume, plus a bit extra.
421		newSourceDataDir = "#{dbVolume}/Library/Server/PostgreSQL/Data.before_PG9.1_upgrade"
422		newSourceDataDirDir = File.dirname(newSourceDataDir)
423		unless File.exists?(newSourceDataDirDir)
424			FileUtils.mkdir_p(newSourceDataDirDir)
425			FileUtils.chmod(0700, newSourceDataDirDir)
426			FileUtils.chown("_postgres", "_postgres", newSourceDataDirDir)
427		end
428		FileUtils.mv(customerTargetDir, newSourceDataDir)
429	else
430		megsAvailable = megsAvailableOnVolume($targetRoot)
431		if (megsAvailable == 0)
432			exitWithError("megsAvailable is 0 for volume #{$targetRoot}")
433		end
434		if ((dbSizeMegs + 1024) < megsAvailable)  # enough space for a copy of the database, plus a bit extra.
435			newSourceDataDir = "#{$targetRoot}/Library/Server/PostgreSQL/Data.before_PG9.1_upgrade"
436			newSourceDataDirDir = File.dirname(newSourceDataDir)
437			unless File.exists?(newSourceDataDirDir)
438				FileUtils.chmod(0700, newSourceDataDirDir)
439				FileUtils.chown("_postgres", "_postgres", newSourceDataDirDir)
440			end
441			FileUtils.cp_r(customerTargetDir, newSourceDataDir, :preserve => true)
442			FileUtils.rm_rf(customerTargetDir)
443		else
444			exitWithError("Not enough space free on data volume to migrate PostgreSQL database.")
445		end
446	end
447
448	unless File.exists?(customerTargetDir)
449		FileUtils.mkdir_p(customerTargetDir)
450		FileUtils.chmod(0700, customerTargetDir)
451		FileUtils.chown("_postgres", "_postgres", customerTargetDir)
452	end
453
454	$logger.info("Initializing the customer-specific database cluster")
455	command = "sudo -u _postgres #{$newPostgresBinariesDir}/initdb --encoding UTF8 --locale=C -D \"#{customerTargetDir}\""
456	runCommandOrExit(command)
457
458	# If the old data directory contains a .pid file due to Postgres not shutting down properly, get rid of the file so that we can attempt upgrade.
459	# There should be no chance that a postmaster is actually using the old data directory at this point.
460	if File.exists?(newSourceDataDir + "/postmaster.pid")
461		$logger.info("There is a .pid file in the source data dir.  Removing it to attempt upgrade.")
462		FileUtils.rm_f(newSourceDataDir + "/postmaster.pid")
463	end
464
465	$logger.info("Running pg_upgrade...")
466	firstServer = TCPServer.new('127.0.0.1', 0)
467	firstPort = firstServer.addr[1]
468	secondServer = TCPServer.new('127.0.0.1', 0)
469	secondPort = secondServer.addr[1]
470	firstServer.close
471	secondServer.close
472	command = "sudo -u _postgres #{$newPostgresBinariesDir}/pg_upgrade -b #{$postgresBinariesDir9_0} -B #{$newPostgresBinariesDir} -d \"#{newSourceDataDir}\" -D \"#{customerTargetDir}\" -p #{firstPort} -P #{secondPort}"
473	runCommandOrExit(command)
474
475	unless File.exists?(serverTargetDir)
476		FileUtils.mkdir_p(serverTargetDir)
477		FileUtils.chmod(0700, serverTargetDir)
478		FileUtils.chown("_postgres", "_postgres", serverTargetDir)
479	end
480	`/Applications/Server.app/Contents/ServerRoot/usr/sbin/serveradmin settings postgres_server:dataDir=\"#{serverTargetDir}\"`
481	`/Applications/Server.app/Contents/ServerRoot/usr/sbin/serveradmin settings postgres:dataDir=\"#{customerTargetDir}\"`
482	forkDatabases(serverTargetDir)
483
484	if $purge == "1"
485		if File.exists?("#{customerTargetDir}/PG_VERSION")
486			$logger.warn("purging #{newSourceDataDir}")
487			FileUtils.rm_rf(newSourceDataDir)
488		else
489			$logger.warn("Skipping purge because copy could not be confirmed")
490		end
491	end
492else  #  Source and destination on the same volume
493	sourceDir = NSString.alloc.initWithString($sourceRoot + oldDataDir).stringByStandardizingPath
494	serverTargetDir = NSString.alloc.initWithString($targetRoot + $newPostgresDataDirServer).stringByStandardizingPath
495	customerTargetDir = NSString.alloc.initWithString($targetRoot + $newPostgresDataDirCustomer).stringByStandardizingPath
496
497	exitWithError("dataDir not present at #{sourceDir}") if !File.directory?(sourceDir)
498	exitWithError("dataDir missing PG_VERSION file; concluding it is not a PostgreSQL data directory at #{sourceDir}") if !File.exists?(sourceDir + "/PG_VERSION")
499	exitWithError("dataDir missing configuration file; concluding it is not a PostgreSQL data directory at #{sourceDir}") if !File.exists?(sourceDir + "/postgresql.conf")
500
501	if  File.exists?("#{serverTargetDir}/PG_VERSION")
502		timestamp = Time.now.strftime("%Y-%m-%d %H:%M")
503		backupDir = "#{serverTargetDir}.#{timestamp}"
504		$logger.info("There appears to be an existing database in the target location.  Moving it aside to : #{backupDir}")
505		FileUtils.mv("#{serverTargetDir}", "#{backupDir}")
506	end
507
508	if  File.exists?("#{customerTargetDir}/PG_VERSION")
509		timestamp = Time.now.strftime("%Y-%m-%d %H:%M")
510		backupDir = "#{customerTargetDir}.#{timestamp}"
511		$logger.info("There appears to be an existing database in the target location.  Moving it aside to : #{backupDir}")
512		FileUtils.mv("#{customerTargetDir}", "#{backupDir}")
513	end
514
515	unless File.exists?(serverTargetDir)
516		FileUtils.mkdir_p(serverTargetDir)
517		FileUtils.chmod(0700, serverTargetDir)
518		FileUtils.chown("_postgres", "_postgres", serverTargetDir)
519	end
520
521	unless File.exists?(customerTargetDir)
522		FileUtils.mkdir_p(customerTargetDir)
523		FileUtils.chmod(0700, customerTargetDir)
524		FileUtils.chown("_postgres", "_postgres", customerTargetDir)
525	end
526
527	# If the old data directory contains a .pid file due to Postgres not shutting down properly, get rid of the file so that we can attempt upgrade.
528	# There should be no chance that a postmaster is actually using the old data directory at this point.
529	if File.exists?(sourceDir +  "/postmaster.pid")
530		$logger.info("There is a .pid file in the source data dir.  Removing it to attempt upgrade.")
531		FileUtils.rm_f(sourceDir + "/postmaster.pid")
532	end
533
534	# Because we are forking server-specific items into their own database cluster, we first
535	# pg_upgrade everything into the customer database cluster, then extract the server-specific items and
536	# move them into the server-specific cluster.
537
538	$logger.info("Initializing the server-specific database cluster")
539	command = "sudo -u _postgres #{$newPostgresBinariesDir}/initdb --encoding UTF8 --locale=C -D \"#{customerTargetDir}\""
540	runCommandOrExit(command)
541
542	$logger.info("Running pg_upgrade...")
543	firstServer = TCPServer.new('127.0.0.1', 0)
544	firstPort = firstServer.addr[1]
545	secondServer = TCPServer.new('127.0.0.1', 0)
546	secondPort = secondServer.addr[1]
547	firstServer.close
548	secondServer.close
549	command = "sudo -u _postgres #{$newPostgresBinariesDir}/pg_upgrade -b #{$postgresBinariesDir9_0} -B #{$newPostgresBinariesDir} -d \"#{sourceDir}\" -D \"#{customerTargetDir}\" -p #{firstPort} -P #{secondPort}"
550	runCommandOrExit(command)
551
552	`/Applications/Server.app/Contents/ServerRoot/usr/sbin/serveradmin settings postgres_server:dataDir=\"#{serverTargetDir}\"`
553	`/Applications/Server.app/Contents/ServerRoot/usr/sbin/serveradmin settings postgres:dataDir=\"#{customerTargetDir}\"`
554	forkDatabases(serverTargetDir)
555
556	if $purge == "1"
557		if File.exists?("#{customerTargetDir}/PG_VERSION") && File.exists?("#{serverTargetDir}/PG_VERSION")
558			$logger.warn("purging #{sourceDir}")
559			FileUtils.rm_rf(sourceDir)
560			else
561			$logger.warn("Skipping purge because copy could not be confirmed")
562		end
563	elsif File.exists?("#{sourceDir}/global/pg_control.old")
564		FileUtils.mv("#{sourceDir}/global/pg_control.old", "#{sourceDir}/global/pg_control")
565	end
566end
567
568$logger.info("Stopping the customer instance of PostgreSQL")
569dictionaryFromServerAdmin("stop postgres")
570
571$logger.info("Restoring default listen_addresses setting for customer instance of PostgreSQL")
572command = "#{$serveradmin} settings postgres:listen_addresses=\"127.0.0.1,::1\""
573runCommandOrExit(command)
574
575# Leave the new postgres instance running.  Services should not have to start it.
576
577statusDict = dictionaryFromServerAdmin("fullstatus postgres_server")
578$logger.info("Current postgres_server fullstatus: #{statusDict.inspect}")
579statusDict = dictionaryFromServerAdmin("fullstatus postgres")
580$logger.info("Current postgres fullstatus: #{statusDict.inspect}")
581exitWithMessage("Finished migrating data from an earlier PostgreSQL version.")
582