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