File zm_database_init of Package ZoneMinder

#!/usr/bin/python
# -*- coding: utf-8 -*-

# zm_database_init version 1.0.2
#
# Author: Monex
# this script is under the same license as the package itself.
#
# Please submit bugfixes or comments to monex@liquid-co.de

import os.path
import re
import sys
import os
import getopt
import posix
import shutil
import getpass

########## important settings ################
VERSION = "1.25.0"# current version of zm

ZM_PATH ="/usr/share/zm" # path to zm shared dir

ZM_CONFIG = "/etc/zm.conf"
########## /important settings ###############

########## globals ###############
version_string = re.compile("ZM_VERSION=(.*)")
sql_string = re.compile("zm_update-(.*)\.sql")
zm_db_string = re.compile("ZM_PATH_BUILD=(.*)")

def updateVersionString(config, oldversion, toversion):
	""" replaces the version string in config file with new version string"""
	config = config.replace(oldversion, toversion, 1)
	writeConfig(config)
	
def checkZmPath(config):
	""" checks for the correct path in zm.conf for db upgrade"""
	path = zm_db_string.search(config)
	if path:
		if path.group(1) != ZM_PATH:
			print "found wrong ZM_BUILD Path in config file could not perform db upgrade"
			proceed = raw_input("should it be updated? [Y/n]: ")
			if proceed.lower() == "y" or proceed == "" or proceed.lower() == "yes":
				lines = config.split("\n")
				config = ""
				for line in lines:
					if zm_db_string.search(line):
						line = "ZM_PATH_BUILD=" + ZM_PATH
					config += line + "\n"
				writeConfig(config)
				print "ZM_PATH_BUILD successfully updated"
			else:
				print "WARNING: update may fail when ZM_PATH_BUILD not set to " + ZM_PATH
	else:
		print "config file wrong"
		sys.exit(1)
	return config

def checkMyCnf():
	""" checks for settings in ~/.my.cnf for client user root entry """
	mycnf = ""
	try:
		file = open(posix.environ["HOME"]+"/.my.cnf", "r")
		mycnf = file.read()
		file.close()
	except:
		return False
	
	clientEntriesReg = re.compile("\[client\]([\w\s\d=#]*)")
	clientEntries = clientEntriesReg.search(mycnf)
	if clientEntries:
		lines = clientEntries.group(1).split("\n")
		pwOk = False
		userOk = False
		
		for line in lines:
			commentPos = line.find("#")
			if commentPos >= 0:
				line = line[:commentPos]
				if line == "":
					continue
			
			userSearch = re.compile("user\s*=\s*([\S.]*)", re.I)
			passSearch = re.compile("password\s*=\s*([\S.]*)", re.I)
			user = userSearch.search(line)
			password = passSearch.search(line)
			
			if password: # search was successfull
				pwOk = True
			elif user:
				if user.group(1).lower() == "root": # user must be root
					userOk = True
			
			if userOk and pwOk:
				return True
	
	return False

def generateMyCnf(mysqlPassword):
	""" updates/generates a .my.cnf with user root an the given pw (client section) """
	mycnf = ""
	createBackupCopy = False # create a backup copy when my.cnf eyists
	
	try:
		file = open(posix.environ["HOME"]+"/.my.cnf", "r")
		mycnf = file.read()
		file.close()
		createBackupCopy = True # create backup when file exist
	except:
		pass
	
	clientEntriesReg = re.compile("\[client\]([\w\s\d=#]*)")
	clientEntries = clientEntriesReg.search(mycnf)
	if clientEntries:
		newMycnf = mycnf[:clientEntries.start()]
		newMycnf += "[client]\n"
		
		pwReplaced = False
		userReplaced = False
		lines = clientEntries.group(1).split("\n")
		for line in lines:
			lowerLine = line.lower()
			commentPos = lowerLine.find("#")
			if commentPos >= 0:
				lowerLine = lowerLine[:commentPos]
			
			if lowerLine.find("user") >= 0: # replace user with root user
				newMycnf += "user = root\n"
				userReplaced = True
			elif lowerLine.find("password") >= 0: #replace password with root password
				newMycnf += "password = "+ mysqlPassword +"\n"
				pwReplaced = True
			else:
				if len(line) > 0:
					newMycnf += line + "\n"
		
		if not userReplaced: # if entries for user and password do not exist create them
			newMycnf += "user = root\n"
		if not pwReplaced:
			newMycnf += "password = "+ mysqlPassword +"\n"
			
		newMycnf += "\n"
		newMycnf += mycnf[clientEntries.end():]
		mycnf = newMycnf
	else: # mycnf is completly missing generate new one
		mycnf = "[client]\n"
		mycnf += "user = root\n"
		mycnf += "password = "+ mysqlPassword +"\n"
		mycnf += "\n"
	
	if createBackupCopy:
		try:
			shutil.copy(posix.environ["HOME"]+"/.my.cnf", posix.environ["HOME"]+"/.my.cnf.backup")
			print "copied old .my.cnf to .my.cnf.backup"
		except:
			print "ERROR: could not create backup copy of " + posix.environ["HOME"]+"/.my.cnf"
			sys.exit(1)
	
	try:
		file = open(posix.environ["HOME"]+"/.my.cnf", "w")
		file.write(mycnf)
		file.close()
		os.chmod(posix.environ["HOME"]+"/.my.cnf", 0600) # chmod mycnf
		print "generated/updated ~/.my.cnf"
	except:
		print "ERROR: could not create/write the .my.cnf"
		sys.exit(1)

def changeConfigValue(config, option, value):
	""" changes the given option in config file to the new given value """
	option_s = re.compile(option)
	if option[-1] != "=": # add '=' when missing
		option += "="
	
	lines = config.split("\n")
	config = ""
	for line in lines:
		if option_s.search(line):
			line = option + value
		config += line + "\n"
	
	return config
		
def addNewConfigOption(config, before_option, comment, new_option, value):
	""" adds a new option to the config file after the given option """
	option_s = re.compile(before_option)
	
	if new_option[-1] != "=": # add '=' when missing
		new_option += "="
	
	lines = config.split("\n")
	config = ""
	for line in lines:
		config += line + "\n"
		if option_s.search(line):
			if comment != "":
				config += comment + "\n"
			config += new_option + value + "\n"
	
	return config

def getConfigValue(config, option):
	""" gets the config value for the given option """
	if option[-1] != "=": # add '=' when missing
		option += "="
	
	option_s = re.compile(option+ "\s*(\S*)")
	lines = config.split("\n")
	config = ""
	for line in lines:
		found = option_s.search(line)
		if found:
			return found.group(1)

def writeConfig(config):
	""" writes the configuration to the config file"""
	file = open(ZM_CONFIG, "w")
	file.write(config)
	file.close()
	
def enter_mysql_root_pw():
	"""dialog for the mysql root password"""
	root_passwd = getpass.getpass("enter mysql root password: ")
	print # newline
	return root_passwd

def run_stuff(db_host = "localhost"):
	file = open(ZM_CONFIG, "r")
	config = file.read()
	file.close()
	version = version_string.search(config)
	mysql_root_password = ""
	myCnfStatus = checkMyCnf()
	if version == None:
		print "ERROR: could not find out version from config file"
		sys.exit(1)

	if VERSION == version.group(1):
		print "new install create db"
		proceed = raw_input("run mysql command to create db as user root? [Y/n]: ")
		if proceed.lower() == "y" or proceed == "" or proceed.lower() == "yes":
			if not myCnfStatus:
				mysql_root_password = enter_mysql_root_pw()
				generateMyCnf(mysql_root_password)
				myCnfStatus = checkMyCnf()
			done = os.system("mysql mysql < "+ZM_PATH+"/db/zm_create.sql -h " + db_host)
			if done != 0:
				print "SQL ERROR: exiting"
				sys.exit(1)
		
		proceed = raw_input("create user zm_admin for zoneminder? [Y/n]: ")
		if proceed.lower() == "y" or proceed == "" or proceed.lower() == "yes":
			passwd = getpass.getpass("Enter new passwd for user zm_admin: ")
			if passwd != "":
				print # newline
				t_pass = getpass.getpass("retype password: ")
				if (t_pass != passwd):
					print "ERROR: password do not match. exiting"
					sys.exit(1)
				print # add one newline
				if not myCnfStatus:
					mysql_root_password = enter_mysql_root_pw()
					generateMyCnf(mysql_root_password)
				done = os.system("""echo "GRANT USAGE ON * . * TO 'zm_admin'@'"""+ db_host +"""' IDENTIFIED BY '"""+ passwd + """' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 ; GRANT SELECT , INSERT , UPDATE , DELETE ON zm . * TO 'zm_admin'@'"""+ db_host +"""';" | mysql mysql -h """ + db_host)
				if done != 0:
					print "error on mysql. exiting"
					sys.exit(1)
			else:
				print "ERROR: empty passphrase. exiting"
				sys.exit(1)
				
			config_update = raw_input("should the config file updated with the new passwd? [Y/n]: ")
			if config_update == "Y" or config_update == "y" or config_update == "":
				config = changeConfigValue(config, "ZM_DB_PASS=", passwd)
			
			if db_host != "localhost":
				config_update = raw_input("should the config file updated with new db host? [Y/n]: ")
				if config_update == "Y" or config_update == "y" or config_update == "":
					config = changeConfigValue(config, "ZM_DB_HOST=", db_host)
			
			writeConfig(config)
	else:
		print "when update fails or you are not upgrading from"
		print "previous rpm version please ensure that the ZM_PATH_BUILD is set to"
		print ZM_PATH + " to find the database update skripts"
		print
		print "when done upgrade using zmupdate.pl before then answer this with n"
		proceed = raw_input("Do you want to perform db update? [Y/n]: ")
		if proceed.lower() == "y" or proceed == "" or proceed.lower() == "yes":
			config = checkZmPath(config)
			print "updating config file version string"
			updateVersionString(config, version.group(1), VERSION)
			success = True # for tracking all commandos
			if not myCnfStatus:
				mysql_root_password = enter_mysql_root_pw()
				generateMyCnf(mysql_root_password)
			
			zmDbUser = getConfigValue(config, "ZM_DB_USER")
			zmDb = getConfigValue(config, "ZM_DB_NAME")
			db_host = getConfigValue(config, "ZM_DB_HOST")
			print "Giving all priviliges to "+ zmDbUser +" on database "+ zmDb
			done = os.system("""echo "GRANT ALL PRIVILEGES ON """+ zmDb +""". * TO '"""+ zmDbUser +"""'@'"""+ db_host +"""';" | mysql mysql -h """ + db_host)
			if done != 0:
				success = False
			
			print
			done = os.system("zmupdate.pl -version="+version.group(1))
			if done != 0:
				success = False
			
			print "Revoking 'all priviliges' from "+ zmDbUser +" and giving back normal rights"
			done = os.system("""echo "REVOKE ALL PRIVILEGES ON """+ zmDb +""" . * FROM '"""+ zmDbUser +"""'@'"""+ db_host +"""'; GRANT SELECT , INSERT , UPDATE , DELETE ON """+ zmDb +""" . * TO '"""+ zmDbUser +"""'@'"""+ db_host +"""';" | mysql mysql -h """ + db_host)
			if done != 0:
				success = False
			
			if not success:
				print "ERROR: upgrade fails"
				# undo config update
				print "undo config file update version string"
				updateVersionString(config, VERSION, version.group(1))
				sys.exit(1)
		else:
			print "updating config file version string"
			updateVersionString(config, version.group(1), VERSION)
		print "check documentation for new config file settings"
	
	if os.path.isfile(ZM_PATH + "/lock"):
		print "removing lock file"
		os.remove(ZM_PATH + "/lock")
	print "done"

def printUsage():
	""" prints out the usage """
	print "Usage: zm_database_init [OPTION]...\n"
	print "-h, --host=name\tConnect to mysql host (default localhost)"
	print "    --help\tprints out this help"
	

def main():
	if posix.getuid() != 0:
		print "ERROR: you must run zm_database_init as root"
		print "exiting ..."
		sys.exit(1)
	try:
		database_host = "localhost" # set localhost as default value
		
		try:
			opts, args = getopt.getopt(sys.argv[1:], "h:", ["host=", "help"])
		except getopt.GetoptError:
		# print help information and exit:
			#printUsage()
			print "usage"
			sys.exit(2)
		for o, a in opts:
			if o == "--help":
				printUsage()
				sys.exit()
			if o in ("-h", "--host"):
				database_host = a
			
		
		print "INFO: when db is correctly installed and you just reinstalled rpm, then answer all questions with 'n'"
		
		# check for lockfile
		if os.path.isfile(ZM_PATH + "/lock"):
			run_stuff(database_host)
		else:
			proceed = raw_input("no lockfile found, proceed anyway? [y/N]: ")
			if proceed.lower() == "y" or proceed.lower() == "yes":
				run_stuff(database_host)
	except KeyboardInterrupt:
		print "Interrupted exiting"
		sys.exit(0)


if __name__ == "__main__":
	main()
openSUSE Build Service is sponsored by