From c7f3c746ccfd74480064dbe73fbc754548c30927 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 14 Jan 2008 12:43:26 -0500 Subject: Backup system state in ipa-server-install This patch adds a sysrestore module which allows ipa-server-install code to backup any system state so that it can be restored again with e.g. ipa-server-install --uninstall. The idea is that any files ipa-server-install modifies gets backed up to /var/cache/ipa/sysrestore/ while any "meta" state, like whether a service is enabled with chkconfig, is saved to /var/cache/ipa/sysrestore.state. Signed-off-by: Mark McLoughlin --- Makefile | 8 +- ipa-server/Makefile.am | 7 + ipa-server/ipa-install/ipa-server-install | 3 + ipa-server/ipa-server.spec | 5 +- ipa-server/ipa-server.spec.in | 4 +- ipa-server/ipaserver/Makefile.am | 1 + ipa-server/ipaserver/bindinstance.py | 10 +- ipa-server/ipaserver/certs.py | 13 ++ ipa-server/ipaserver/dsinstance.py | 13 +- ipa-server/ipaserver/httpinstance.py | 26 ++- ipa-server/ipaserver/krbinstance.py | 21 ++- ipa-server/ipaserver/ntpinstance.py | 15 +- ipa-server/ipaserver/service.py | 4 + ipa-server/ipaserver/sysrestore.py | 253 ++++++++++++++++++++++++++++++ ipa-server/ipaserver/webguiinstance.py | 12 +- 15 files changed, 375 insertions(+), 20 deletions(-) create mode 100644 ipa-server/ipaserver/sysrestore.py diff --git a/Makefile b/Makefile index ff4c3697..f8b61081 100644 --- a/Makefile +++ b/Makefile @@ -57,12 +57,12 @@ all: bootstrap-autogen done bootstrap-autogen: - cd ipa-server; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR); fi - cd ipa-client; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR); fi + cd ipa-server; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); fi + cd ipa-client; if [ ! -e Makefile ]; then ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); fi autogen: - cd ipa-server; ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR) - cd ipa-client; ./autogen.sh --prefix=/usr --sysconfdir=/etc --libdir=$(LIBDIR) + cd ipa-server; ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR) + cd ipa-client; ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR) configure: cd ipa-server; ./configure --prefix=/usr --sysconfdir=/etc diff --git a/ipa-server/Makefile.am b/ipa-server/Makefile.am index a145ac03..b04a4cf9 100644 --- a/ipa-server/Makefile.am +++ b/ipa-server/Makefile.am @@ -13,6 +13,13 @@ SUBDIRS = \ xmlrpc-server \ $(NULL) +install-exec-local: + mkdir -p $(DESTDIR)$(localstatedir)/cache/ipa/sysrestore + +uninstall-local: + -rmdir $(DESTDIR)$(localstatedir)/cache/ipa/sysrestore + -rmdir $(DESTDIR)$(localstatedir)/cache/ipa + EXTRA_DIST = \ ipa-server.spec \ COPYING \ diff --git a/ipa-server/ipa-install/ipa-server-install b/ipa-server/ipa-install/ipa-server-install index 5f0879d7..bc4d8e27 100644 --- a/ipa-server/ipa-install/ipa-server-install +++ b/ipa-server/ipa-install/ipa-server-install @@ -49,6 +49,7 @@ import ipaserver.ntpinstance import ipaserver.webguiinstance from ipaserver import service +from ipaserver import sysrestore from ipaserver.installutils import * from ipa.ipautil import * @@ -167,6 +168,7 @@ def read_ip_address(host_name): continue print "Adding ["+ip+" "+host_name+"] to your /etc/hosts file" + sysrestore.backup_file("/etc/hosts") hosts_fd = open('/etc/hosts', 'r+') hosts_fd.seek(0, 2) hosts_fd.write(ip+'\t'+host_name+' '+host_name[:host_name.find('.')]+'\n') @@ -420,6 +422,7 @@ def main(): ds.change_admin_password(admin_password) # Create the config file + sysrestore.backup_file("/etc/ipa/ipa.conf") fd = open("/etc/ipa/ipa.conf", "w") fd.write("[defaults]\n") fd.write("server=" + host_name + "\n") diff --git a/ipa-server/ipa-server.spec b/ipa-server/ipa-server.spec index cbd0de54..98de382c 100755 --- a/ipa-server/ipa-server.spec +++ b/ipa-server/ipa-server.spec @@ -48,7 +48,7 @@ Ipa is a server for identity, policy, and audit. %prep %setup -q -./configure --prefix=%{buildroot}/usr --libdir=%{buildroot}/%{_libdir} --sysconfdir=%{buildroot}/etc +./configure --prefix=%{buildroot}/usr --libdir=%{buildroot}/%{_libdir} --sysconfdir=%{buildroot}/etc --localstatedir=%{buildroot}/var %build @@ -93,6 +93,7 @@ fi %{_sbindir}/ipa-server-install %{_sbindir}/ipa-replica-install %{_sbindir}/ipa-replica-prepare +%{_sbindir}/ipa-replica-manage %{_sbindir}/ipa-server-certinstall %{_sbindir}/ipa_kpasswd %{_sbindir}/ipa-webgui @@ -106,6 +107,8 @@ fi %attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so %attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so +%dir %{_localstatedir}/cache/ipa +%dir %{_localstatedir}/cache/ipa/sysrestore %changelog * Fri Dec 21 2007 Karl MacMillan - 0.6.0-1 diff --git a/ipa-server/ipa-server.spec.in b/ipa-server/ipa-server.spec.in index 52641548..24b02c9e 100644 --- a/ipa-server/ipa-server.spec.in +++ b/ipa-server/ipa-server.spec.in @@ -48,7 +48,7 @@ Ipa is a server for identity, policy, and audit. %prep %setup -q -./configure --prefix=%{buildroot}/usr --libdir=%{buildroot}/%{_libdir} --sysconfdir=%{buildroot}/etc +./configure --prefix=%{buildroot}/usr --libdir=%{buildroot}/%{_libdir} --sysconfdir=%{buildroot}/etc --localstatedir=%{buildroot}/var %build @@ -107,6 +107,8 @@ fi %attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so %attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so +%dir %{_localstatedir}/cache/ipa +%dir %{_localstatedir}/cache/ipa/sysrestore %changelog * Fri Dec 21 2007 Karl MacMillan - 0.6.0-1 diff --git a/ipa-server/ipaserver/Makefile.am b/ipa-server/ipaserver/Makefile.am index b1d00a80..99b74b50 100644 --- a/ipa-server/ipaserver/Makefile.am +++ b/ipa-server/ipaserver/Makefile.am @@ -14,6 +14,7 @@ app_PYTHON = \ installutils.py \ replication.py \ certs.py \ + sysrestore.py \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-server/ipaserver/bindinstance.py b/ipa-server/ipaserver/bindinstance.py index cc99eacf..770663c6 100644 --- a/ipa-server/ipaserver/bindinstance.py +++ b/ipa-server/ipaserver/bindinstance.py @@ -25,6 +25,7 @@ import os import socket import service +import sysrestore from ipa import ipautil class BindInstance(service.Service): @@ -72,6 +73,7 @@ class BindInstance(service.Service): self.__setup_named_conf() try: + self.backup_state("running", self.is_running()) self.start() except: print "named service failed to start" @@ -84,14 +86,15 @@ class BindInstance(service.Service): REALM=self.realm) def __setup_zone(self): + self.backup_state("domain", self.domain) zone_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict) + sysrestore.backup_file('/var/named/'+self.domain+'.zone.db') zone_fd = open('/var/named/'+self.domain+'.zone.db', 'w') zone_fd.write(zone_txt) zone_fd.close() def __setup_named_conf(self): - if os.path.exists('/etc/named.conf'): - shutil.copy2('/etc/named.conf', '/etc/named.conf.ipabkp') + sysrestore.backup_file('/etc/named.conf') named_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.named.conf.template", self.sub_dict) named_fd = open('/etc/named.conf', 'w') named_fd.seek(0) @@ -99,8 +102,7 @@ class BindInstance(service.Service): named_fd.write(named_txt) named_fd.close() - if os.path.exists('/etc/resolve.conf'): - shutil.copy2('/etc/resolve.conf', '/etc/resolv.conf.ipabkp') + sysrestore.backup_file('/etc/resolve.conf') resolve_txt = "search "+self.domain+"\nnameserver "+self.ip_address+"\n" resolve_fd = open('/etc/resolve.conf', 'w') resolve_fd.seek(0) diff --git a/ipa-server/ipaserver/certs.py b/ipa-server/ipaserver/certs.py index d8bc35f2..b39cf224 100644 --- a/ipa-server/ipaserver/certs.py +++ b/ipa-server/ipaserver/certs.py @@ -318,4 +318,17 @@ class CertDB(object): self.trust_root_cert(nickname) self.create_pin_file() self.export_ca_cert() + + def backup_files(self): + sysrestore.backup_file(self.noise_fname) + sysrestore.backup_file(self.passwd_fname) + sysrestore.backup_file(self.certdb_fname) + sysrestore.backup_file(self.keydb_fname) + sysrestore.backup_file(self.secmod_fname) + sysrestore.backup_file(self.cacert_fname) + sysrestore.backup_file(self.pk12_fname) + sysrestore.backup_file(self.pin_fname) + sysrestore.backup_file(self.certreq_fname) + sysrestore.backup_file(self.certder_fname) + diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py index 0de891fc..78a84759 100644 --- a/ipa-server/ipaserver/dsinstance.py +++ b/ipa-server/ipaserver/dsinstance.py @@ -154,10 +154,14 @@ class DsInstance(service.Service): self.step("initializing group membership", self.__init_memberof) - self.step("configuring directory to start on boot", self.chkconfig_on) + self.step("configuring directory to start on boot", self.__enable) self.start_creation("Configuring directory server:") + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + def __setup_sub_dict(self): server_root = find_server_root() self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid, @@ -166,10 +170,12 @@ class DsInstance(service.Service): SERVER_ROOT=server_root, DOMAIN=self.domain) def __create_ds_user(self): + user_exists = True try: pwd.getpwnam(self.ds_user) logging.debug("ds user %s exists" % self.ds_user) except KeyError: + user_exists = False logging.debug("adding ds user %s" % self.ds_user) args = ["/usr/sbin/useradd", "-c", "DS System User", "-d", "/var/lib/dirsrv", "-M", "-r", "-s", "/sbin/nologin", self.ds_user] try: @@ -178,7 +184,12 @@ class DsInstance(service.Service): except ipautil.CalledProcessError, e: logging.critical("failed to add user %s" % e) + self.backup_state("user", self.ds_user) + self.backup_state("user_exists", user_exists) + def __create_instance(self): + self.backup_state("running", self.is_running()) + self.backup_state("serverid", self.serverid) inf_txt = ipautil.template_str(INF_TEMPLATE, self.sub_dict) logging.debug(inf_txt) inf_fd = ipautil.write_tmp_file(inf_txt) diff --git a/ipa-server/ipaserver/httpinstance.py b/ipa-server/ipaserver/httpinstance.py index beca3e83..76e314df 100644 --- a/ipa-server/ipaserver/httpinstance.py +++ b/ipa-server/ipaserver/httpinstance.py @@ -29,6 +29,7 @@ import sys import shutil import service +import sysrestore import certs import dsinstance import installutils @@ -63,11 +64,19 @@ class HTTPInstance(service.Service): self.step("Setting up ssl", self.__setup_ssl) self.step("Setting up browser autoconfig", self.__setup_autoconfig) self.step("configuring SELinux for httpd", self.__selinux_config) - self.step("restarting httpd", self.restart) - self.step("configuring httpd to start on boot", self.chkconfig_on) + self.step("restarting httpd", self.__start) + self.step("configuring httpd to start on boot", self.__enable) self.start_creation("Configuring the web interface") + def __start(self): + self.backup_state("running", self.is_running()) + self.restart() + + def __enable(self): + self.backup_state("enabled", self.is_running()) + self.chkconfig_on() + def __selinux_config(self): selinux=0 try: @@ -79,6 +88,14 @@ class HTTPInstance(service.Service): pass if selinux: + try: + # returns e.g. "httpd_can_network_connect --> off" + (stdout, stderr) = ipautils.run(["/usr/sbin/getsebool", + "httpd_can_network_connect"]) + self.backup_state("httpd_can_network_connect", stdout.split()[2]) + except: + pass + # Allow apache to connect to the turbogears web gui # This can still fail even if selinux is enabled try: @@ -96,6 +113,7 @@ class HTTPInstance(service.Service): def __configure_http(self): http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", self.sub_dict) + sysrestore.backup_file("/etc/httpd/conf.d/ipa.conf") http_fd = open("/etc/httpd/conf.d/ipa.conf", "w") http_fd.write(http_txt) http_fd.close() @@ -103,9 +121,11 @@ class HTTPInstance(service.Service): def __disable_mod_ssl(self): if os.path.exists(SSL_CONF): - os.rename(SSL_CONF, "%s.moved_by_ipa" % SSL_CONF) + sysrestore.backup_file(SSL_CONF) + os.unlink(SSL_CONF) def __set_mod_nss_port(self): + sysrestore.backup_file(NSS_CONF) if installutils.update_file(NSS_CONF, '8443', '443') != 0: print "Updating %s failed." % NSS_CONF diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py index db7004e4..10dab364 100644 --- a/ipa-server/ipaserver/krbinstance.py +++ b/ipa-server/ipaserver/krbinstance.py @@ -32,6 +32,7 @@ import socket import shutil import service +import sysrestore import installutils from ipa import ipautil from ipa import ipaerror @@ -107,6 +108,7 @@ class KrbInstance(service.Service): logging.critical("Could not connect to DS") raise e + self.backup_state("running", self.is_running()) try: self.stop() except: @@ -115,7 +117,7 @@ class KrbInstance(service.Service): def __common_post_setup(self): self.step("starting the KDC", self.__start_instance) - self.step("configuring KDC to start on boot", self.chkconfig_on) + self.step("configuring KDC to start on boot", self.__enable) self.step("enabling and starting ipa-kpasswd", self.__enable_kpasswd) def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password): @@ -155,6 +157,7 @@ class KrbInstance(service.Service): self.start_creation("Configuring Kerberos KDC") def __copy_ldap_passwd(self, filename): + sysrestore.backup_file("/var/kerberos/krb5kdc/ldappwd") shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd") os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) @@ -163,11 +166,16 @@ class KrbInstance(service.Service): hexpwd = '' for x in self.kdc_password: hexpwd += (hex(ord(x))[2:]) + sysrestore.backup_file("/var/kerberos/krb5kdc/ldappwd") pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w") pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n") pwd_fd.close() os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + def __start_instance(self): try: self.start() @@ -175,6 +183,8 @@ class KrbInstance(service.Service): logging.critical("krb5kdc service failed to start") def __enable_kpasswd(self): + sysrestore.backup_state("ipa-kpasswd", "enabled", service.is_enabled("ipa-kpasswd")) + sysrestore.backup_state("ipa-kpasswd", "running", service.is_running("ipa-kpasswd")) service.chkconfig_on("ipa-kpasswd") service.start("ipa-kpasswd") @@ -265,6 +275,7 @@ class KrbInstance(service.Service): def __template_file(self, path): template = os.path.join(ipautil.SHARE_DIR, os.path.basename(path) + ".template") conf = ipautil.template_file(template, self.sub_dict) + sysrestore.backup_file(path) fd = open(path, "w+") fd.write(conf) fd.close() @@ -337,8 +348,11 @@ class KrbInstance(service.Service): def __create_ds_keytab(self): ldap_principal = "ldap/" + self.fqdn + "@" + self.realm installutils.kadmin_addprinc(ldap_principal) + + sysrestore.backup_file("/etc/dirsrv/ds.keytab") installutils.create_keytab("/etc/dirsrv/ds.keytab", ldap_principal) + sysrestore.backup_file("/etc/sysconfig/dirsrv") update_key_val_in_file("/etc/sysconfig/dirsrv", "export KRB5_KTNAME", "/etc/dirsrv/ds.keytab") pent = pwd.getpwnam(self.ds_user) os.chown("/etc/dirsrv/ds.keytab", pent.pw_uid, pent.pw_gid) @@ -346,6 +360,8 @@ class KrbInstance(service.Service): def __create_host_keytab(self): host_principal = "host/" + self.fqdn + "@" + self.realm installutils.kadmin_addprinc(host_principal) + + sysrestore.backup_file("/etc/krb5.keytab") installutils.create_keytab("/etc/krb5.keytab", host_principal) # Make sure access is strictly reserved to root only for now @@ -354,8 +370,11 @@ class KrbInstance(service.Service): def __export_kadmin_changepw_keytab(self): installutils.kadmin_modprinc("kadmin/changepw", "+requires_preauth") + + sysrestore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") installutils.create_keytab("/var/kerberos/krb5kdc/kpasswd.keytab", "kadmin/changepw") + sysrestore.backup_file("/etc/sysconfig/ipa-kpasswd") update_key_val_in_file("/etc/sysconfig/ipa-kpasswd", "export KRB5_KTNAME", "/var/kerberos/krb5kdc/kpasswd.keytab") pent = pwd.getpwnam(self.ds_user) os.chown("/var/kerberos/krb5kdc/kpasswd.keytab", pent.pw_uid, pent.pw_gid) diff --git a/ipa-server/ipaserver/ntpinstance.py b/ipa-server/ipaserver/ntpinstance.py index b321ec07..c40b12b0 100644 --- a/ipa-server/ipaserver/ntpinstance.py +++ b/ipa-server/ipaserver/ntpinstance.py @@ -20,6 +20,7 @@ import shutil import service +import sysrestore from ipa import ipautil class NTPInstance(service.Service): @@ -45,19 +46,27 @@ class NTPInstance(service.Service): ntp_conf = ipautil.template_file(ipautil.SHARE_DIR + "ntp.conf.server.template", sub_dict) - shutil.copy("/etc/ntp.conf", "/etc/ntp.conf.ipasave") + sysrestore.backup_file("/etc/ntp.conf") fd = open("/etc/ntp.conf", "w") fd.write(ntp_conf) fd.close() + def __start(self): + self.backup_state("running", self.is_running()) + self.start() + + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + def create_instance(self): self.step("writing configuration", self.__write_config) # we might consider setting the date manually using ntpd -qg in case # the current time is very far off. - self.step("starting ntpd", self.start) - self.step("configuring ntpd to start on boot", self.chkconfig_on) + self.step("starting ntpd", self.__start) + self.step("configuring ntpd to start on boot", self.__enable) self.start_creation("Configuring ntpd") diff --git a/ipa-server/ipaserver/service.py b/ipa-server/ipaserver/service.py index f6df6e5b..0ea3f661 100644 --- a/ipa-server/ipaserver/service.py +++ b/ipa-server/ipaserver/service.py @@ -18,6 +18,7 @@ # import logging, sys +import sysrestore from ipa import ipautil @@ -100,6 +101,9 @@ class Service: def is_enabled(self): return is_enabled(self.service_name) + def backup_state(self, key, value): + sysrestore.backup_state(self.service_name, key, value) + def print_msg(self, message): print_msg(message, self.output_fd) diff --git a/ipa-server/ipaserver/sysrestore.py b/ipa-server/ipaserver/sysrestore.py new file mode 100644 index 00000000..4716aa0d --- /dev/null +++ b/ipa-server/ipaserver/sysrestore.py @@ -0,0 +1,253 @@ +# Authors: Mark McLoughlin +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 or later +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# +# This module provides a very simple API which allows +# ipa-server-install --uninstall to restore certain +# parts of the system configuration to the way it was +# before ipa-server-install was first run +# + +import os +import os.path +import errno +import shutil +import logging +import ConfigParser + +from ipa import ipautil + +SYSRESTORE_CACHE_PATH = "/var/cache/ipa/sysrestore" +SYSRESTORE_STATEFILE_PATH = "/var/cache/ipa/sysrestore.state" + +def _mktree(basedir, reldir): + """Create the tree of directories specified by @reldir + under the directory @base. + + Caveats: + - @basedir must exist + - @reldir must not be absolute + - @reldir must refer to a directory + """ + (parentdir, subdir) = os.path.split(reldir) + if parentdir: + _mktree(basedir, parentdir) + + absdir = os.path.join(basedir, reldir) + try: + logging.debug("Creating directory '%s'", absdir) + os.mkdir(absdir) + except OSError, err: + if err.errno != errno.EEXIST: + raise err + +def _rmtree(basedir, reldir): + """Delete a tree of directories specified by @reldir + under the directory @base, excluding the @base itself. + Only empty directories will be deleted. + + Caveats: + - @reldir must not be absolute + - @reldir must refer to a directory + """ + absdir = os.path.join(basedir, reldir) + try: + logging.debug("Deleting directory '%s'", absdir) + os.rmdir(absdir) + except OSError, err: + if err.errno == errno.ENOTEMPTY: + logging.debug("Directory '%s' not empty", absdir) + return + else: + raise err + + (parentdir, subdir) = os.path.split(reldir) + if parentdir: + _rmtree(basedir, parentdir) + +def backup_file(path): + """Create a copy of the file at @path - so long as a copy + does not already exist - which will be restored to its + original location by restore_files(). + """ + logging.debug("Backing up system configuration file '%s'", path) + + if not os.path.isabs(path): + raise ValueError("Absolute path required") + + if not os.path.isfile(path): + logging.debug(" -> Not backing up - '%s' doesn't exist", path) + return + + relpath = path[1:] + + backup_path = os.path.join(SYSRESTORE_CACHE_PATH, relpath) + if os.path.exists(backup_path): + logging.debug(" -> Not backing up - already have a copy of '%s'", path) + return + + (reldir, file) = os.path.split(relpath) + if reldir: + _mktree(SYSRESTORE_CACHE_PATH, reldir) + + shutil.copy2(path, backup_path) + +def restore_file(path): + """Restore the copy of a file at @path to its original + location and delete the copy. + + Returns #True if the file was restored, #False if there + was no backup file to restore + """ + logging.debug("Restoring system configuration file '%s'", path) + + if not os.path.isabs(path): + raise ValueError("Absolute path required") + + relpath = path[1:] + + backup_path = os.path.join(SYSRESTORE_CACHE_PATH, relpath) + if not os.path.exists(backup_path): + logging.debug(" -> Not restoring - '%s' doesn't exist", backup_path) + return False + + shutil.move(backup_path, path) + + ipautil.run(["/sbin/restorecon", path]) + + (reldir, file) = os.path.split(relpath) + if reldir: + _rmtree(SYSRESTORE_CACHE_PATH, reldir) + + return True + +class _StateFile: + """A metadata file for recording system state which can + be backed up and later restored. The format is something + like: + + [httpd] + running=True + enabled=False + """ + + def __init__(self, path = SYSRESTORE_STATEFILE_PATH): + """Create a _StateFile object, loading from @path. + + The dictionary @modules, a member of the returned object, + is where the state can be modified. @modules is indexed + using a module name to return another dictionary containing + key/value pairs with the saved state of that module. + + The keys in these latter dictionaries are arbitrary strings + and the values may either be strings or booleans. + """ + self._path = path + + self.modules = {} + + self._load() + + def _load(self): + """Load the modules from the file @_path. @modules will + be an empty dictionary if the file doesn't exist. + """ + logging.debug("Loading StateFile from '%s'", self._path) + + self.modules = {} + + p = ConfigParser.SafeConfigParser() + p.read(self._path) + + for module in p.sections(): + self.modules[module] = {} + for (key, value) in p.items(module): + if value == str(True): + value = True + elif value == str(False): + value = False + self.modules[module][key] = value + + def save(self): + """Save the modules to @_path. If @modules is an empty + dict, then @_path should be removed. + """ + logging.debug("Saving StateFile to '%s'", self._path) + + for module in self.modules.keys(): + if len(self.modules[module]) == 0: + del self.modules[module] + + if len(self.modules) == 0: + logging.debug(" -> no modules, removing file") + if os.path.exists(self._path): + os.remove(self._path) + return + + p = ConfigParser.SafeConfigParser() + + for module in self.modules.keys(): + p.add_section(module) + for (key, value) in self.modules[module].items(): + p.set(module, key, str(value)) + + f = file(self._path, "w") + p.write(f) + f.close() + +def backup_state(module, key, value): + """Backup an item of system state from @module, identified + by the string @key and with the value @value. @value may be + a string or boolean. + """ + if not (isinstance(value, str) or isinstance(value, bool)): + raise ValueError("Only strings or booleans supported") + + state = _StateFile() + + if not state.modules.has_key(module): + state.modules[module] = {} + + if not state.modules.has_key(key): + state.modules[module][key] = value + + state.save() + +def restore_state(module, key): + """Return the value of an item of system state from @module, + identified by the string @key, and remove it from the backed + up system state. + + If the item doesn't exist, #None will be returned, otherwise + the original string or boolean value is returned. + """ + state = _StateFile() + + if not state.modules.has_key(module): + return None + + if not state.modules[module].has_key(key): + return None + + value = state.modules[module][key] + del state.modules[module][key] + + state.save() + + return value diff --git a/ipa-server/ipaserver/webguiinstance.py b/ipa-server/ipaserver/webguiinstance.py index 28543558..f3900245 100644 --- a/ipa-server/ipaserver/webguiinstance.py +++ b/ipa-server/ipaserver/webguiinstance.py @@ -24,6 +24,14 @@ class WebGuiInstance(service.Service): service.Service.__init__(self, "ipa-webgui") def create_instance(self): - self.step("starting ipa-webgui", self.restart) - self.step("configuring ipa-webgui to start on boot", self.chkconfig_on) + self.step("starting ipa-webgui", self.__start) + self.step("configuring ipa-webgui to start on boot", self.__enable) self.start_creation("Configuring ipa-webgui") + + def __start(self): + self.backup_state("running", self.is_running()) + self.restart() + + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() -- cgit