moinsetup

moinsetup.py

11:271b22cfb2b8
2010-05-23 Paul Boddie Added SlideShow action resources installation upon installing a theme.
     1 #!/usr/bin/env python     2      3 from os.path import abspath, exists, extsep, join, normpath, split     4 from getpass import getpass     5 import os     6 import sys     7 import shutil     8 import re     9     10 # Regular expressions for editing MoinMoin scripts and configuration files.    11     12 def compile_definition(name):    13     return re.compile(r"^(\s*)#?(%s =).*$" % name, re.MULTILINE)    14     15 moin_cgi_prefix                 = re.compile("^#sys.path.insert\(0, 'PREFIX.*$", re.MULTILINE)    16 moin_cgi_wikiconfig             = re.compile("^#sys.path.insert\(0, '/path/to/wikiconfigdir.*$", re.MULTILINE)    17     18 wikiconfig_py_site_name         = compile_definition("site_?name")    19 wikiconfig_py_url_prefix_static = compile_definition("url_prefix_static")    20 wikiconfig_py_superuser         = compile_definition("superuser")    21 wikiconfig_py_acl_rights_before = compile_definition("acl_rights_before")    22 wikiconfig_py_page_front_page   = compile_definition("page_front_page")    23 wikiconfig_py_data_dir          = compile_definition("data_dir")    24 wikiconfig_py_data_underlay_dir = compile_definition("data_underlay_dir")    25 wikiconfig_py_theme_default     = compile_definition("theme_default")    26     27 # Templates for Apache site definitions.    28     29 apache_site = """    30 ScriptAlias %(url_path)s "%(web_app_dir)s/moin.cgi"    31 """    32     33 apache_site_extra_moin18 = """    34 Alias %(static_url_path)s "%(htdocs_dir)s/"    35 """    36     37 # Utility functions.    38     39 def readfile(filename):    40     f = open(filename)    41     try:    42         return f.read()    43     finally:    44         f.close()    45     46 def writefile(filename, s):    47     f = open(filename, "w")    48     try:    49         f.write(s)    50     finally:    51         f.close()    52     53 def status(message):    54     print message    55     56 def note(message):    57     print message    58     59 class Installation:    60     61     "A class for installing and initialising MoinMoin."    62     63     # NOTE: Need to detect Web server user.    64     65     web_user = "www-data"    66     web_group = "www-data"    67     68     # MoinMoin resources.    69     70     theme_master = "modernized"    71     extra_theme_css_files = ["SlideShow.css"]    72     73     def __init__(self, moin_distribution, prefix, web_app_dir, web_site_dir,    74         common_dir, url_path, superuser, site_name, front_page_name,    75         theme_default=None):    76     77         """    78         Initialise a Wiki installation using the following:    79     80           * moin_distribution - the directory containing a MoinMoin source    81                                 distribution    82           * prefix            - the installation prefix (equivalent to /usr)    83           * web_app_dir       - the directory where Web applications and scripts    84                                 reside (such as /home/www-user/cgi-bin)    85           * web_site_dir      - the directory where Web site definitions reside    86                                 (such as /etc/apache2/sites-available)    87           * common_dir        - the directory where the Wiki configuration,    88                                 resources and instance will reside (such as    89                                 /home/www-user/mywiki)    90           * url_path          - the URL path at which the Wiki will be made    91                                 available (such as / or /mywiki)    92           * superuser         - the name of the site's superuser (such as    93                                 "AdminUser")    94           * site_name         - the name of the site (such as "My Wiki")    95           * front_page_name   - the front page name for the site (such as    96                                 "FrontPage" or a specific name for the site)    97           * theme_default     - optional: the default theme (such as modern)    98         """    99    100         self.moin_distribution = moin_distribution   101         self.superuser = superuser   102         self.site_name = site_name   103         self.front_page_name = front_page_name   104         self.theme_default = theme_default   105    106         # NOTE: Support the detection of the Apache sites directory.   107    108         self.prefix, self.web_app_dir, self.web_site_dir, self.common_dir = \   109             map(abspath, (prefix, web_app_dir, web_site_dir, common_dir))   110    111         # Strip any trailing "/" from the URL path.   112    113         if url_path.endswith("/"):   114             self.url_path = url_path[:-1]   115         else:   116             self.url_path = url_path   117    118         # Define and create specific directories.   119    120         self.conf_dir = join(self.common_dir, "conf")   121         self.instance_dir = join(self.common_dir, "wikidata")   122    123         # Define useful directories.   124    125         self.prefix_site_packages = join(self.prefix, "lib", "python%s.%s" % sys.version_info[:2], "site-packages")   126    127         # Find the version.   128    129         self.moin_version = self.get_moin_version()   130    131         # 1.8: moin/share/moin/htdocs   132         # 1.9: moin/lib/python2.x/site-packages/MoinMoin/web/static/htdocs   133    134         if self.moin_version.startswith("1.9"):   135             self.htdocs_dir = join(self.prefix_site_packages, "MoinMoin", "web", "static", "htdocs")   136         else:   137             self.htdocs_dir = join(self.instance_dir, "share", "moin", "htdocs")   138    139     def get_moin_version(self):   140    141         "Inspect the MoinMoin package information, returning the version."   142    143         this_dir = os.getcwd()   144         os.chdir(self.moin_distribution)   145    146         try:   147             try:   148                 f = open("PKG-INFO")   149                 try:   150                     for line in f.xreadlines():   151                         columns = line.split()   152                         if columns[0] == "Version:":   153                             return columns[1]   154    155                     return None   156    157                 finally:   158                     f.close()   159    160             except IOError:   161                 f = os.popen("%s -c 'from MoinMoin.version import release; print release'" % sys.executable)   162                 try:   163                     return f.read()   164                 finally:   165                     f.close()   166         finally:   167             os.chdir(this_dir)   168    169     def ensure_directories(self):   170    171         "Make sure that all the directories are available."   172    173         for d in (self.conf_dir, self.instance_dir, self.web_app_dir, self.web_site_dir):   174             if not exists(d):   175                 os.makedirs(d)   176    177     # Main methods.   178    179     def setup(self):   180    181         "Set up the installation."   182    183         self.ensure_directories()   184         self.install_moin()   185         self.install_data()   186         self.configure_moin()   187         self.edit_moin_scripts()   188         self.add_superuser()   189         self.make_site_files()   190         self.make_post_install_script()   191    192     def install_moin(self):   193    194         "Enter the distribution directory and run the setup script."   195    196         # NOTE: Possibly check for an existing installation and skip repeated   197         # NOTE: installation attempts.   198    199         this_dir = os.getcwd()   200         os.chdir(self.moin_distribution)   201    202         log_filename = "install-%s.log" % split(self.common_dir)[-1]   203    204         status("Installing MoinMoin in %s..." % self.prefix)   205    206         os.system("python setup.py --quiet install --force --prefix='%s' --install-data='%s' --record='%s'" % (   207             self.prefix, self.instance_dir, log_filename))   208    209         os.chdir(this_dir)   210    211     def install_data(self):   212    213         "Install Wiki data."   214    215         # The default wikiconfig assumes data and underlay in the same directory.   216    217         status("Installing data and underlay in %s..." % self.conf_dir)   218    219         for d in ("data", "underlay"):   220             source = join(self.moin_distribution, "wiki", d)   221             source_tar = source + os.path.extsep + "tar"   222             d_tar = source + os.path.extsep + "tar"   223    224             if os.path.exists(source):   225                 shutil.copytree(source, join(self.conf_dir, d))   226             elif os.path.exists(source_tar):   227                 shutil.copy(source_tar, self.conf_dir)   228                 os.system("tar xf %s -C %s" % (d_tar, self.conf_dir))   229             else:   230                 status("Could not copy %s into installed Wiki." % d)   231    232     def configure_moin(self):   233    234         "Edit the Wiki configuration file."   235    236         # NOTE: Single Wiki only so far.   237    238         # Static URLs seem to be different in MoinMoin 1.9.x.   239         # For earlier versions, reserve URL space alongside the Wiki.   240         # NOTE: MoinMoin usually uses an apparently common URL space associated   241         # NOTE: with the version, but more specific locations are probably   242         # NOTE: acceptable if less efficient.   243    244         if self.moin_version.startswith("1.9"):   245             self.static_url_path = self.url_path   246             url_prefix_static_sub = r"\1\2 %r + url_prefix_static" % self.static_url_path   247         else:   248             self.static_url_path = self.url_path + "-static"   249             url_prefix_static_sub = r"\1\2 %r" % self.static_url_path   250    251         # Edit the Wiki configuration file.   252    253         wikiconfig_py = join(self.moin_distribution, "wiki", "config", "wikiconfig.py")   254    255         status("Editing configuration from %s..." % wikiconfig_py)   256    257         s = readfile(wikiconfig_py)   258         s = wikiconfig_py_url_prefix_static.sub(url_prefix_static_sub, s)   259         s = wikiconfig_py_superuser.sub(r"\1\2 %r" % [self.superuser], s)   260         s = wikiconfig_py_acl_rights_before.sub(r"\1\2 %r" % (u"%s:read,write,delete,revert,admin" % self.superuser), s)   261    262         if not self.moin_version.startswith("1.9"):   263             data_dir = join(self.conf_dir, "data")   264             data_underlay_dir = join(self.conf_dir, "underlay")   265    266             s = wikiconfig_py_data_dir.sub(r"\1\2 %r" % data_dir, s)   267             s = wikiconfig_py_data_underlay_dir.sub(r"\1\2 %r" % data_underlay_dir, s)   268    269         s = self._configure_moin(s)   270    271         writefile(join(self.conf_dir, "wikiconfig.py"), s)   272    273     def _configure_moin(self, s):   274    275         """   276         Configure Moin, taking the configuration file's contents as 's' and   277         returning the edited contents.   278         """   279    280         s = wikiconfig_py_site_name.sub(r"\1\2 %r" % self.site_name, s)   281         s = wikiconfig_py_page_front_page.sub(r"\1\2 %r" % self.front_page_name, s, count=1)   282    283         if self.theme_default is not None:   284             s = wikiconfig_py_theme_default.sub(r"\1\2 %r" % self.theme_default, s)   285    286         return s   287    288     def edit_moin_scripts(self):   289    290         "Edit the moin script and the CGI script."   291    292         moin_script = join(self.prefix, "bin", "moin")   293         prefix_site_packages = join(self.prefix, "lib", "python%s.%s" % sys.version_info[:2], "site-packages")   294    295         status("Editing moin script at %s..." % moin_script)   296    297         s = readfile(moin_script)   298         s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % prefix_site_packages)   299    300         writefile(moin_script, s)   301    302         # Edit and install CGI script.   303         # NOTE: CGI only so far.   304         # NOTE: Permissions should be checked.   305    306         moin_cgi = join(self.instance_dir, "share", "moin", "server", "moin.cgi")   307         moin_cgi_installed = join(self.web_app_dir, "moin.cgi")   308    309         status("Editing moin.cgi script from %s..." % moin_cgi)   310    311         s = readfile(moin_cgi)   312         s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % prefix_site_packages, s)   313         s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.conf_dir, s)   314    315         writefile(moin_cgi_installed, s)   316         os.system("chmod a+rx '%s'" % moin_cgi_installed)   317    318     def add_superuser(self):   319    320         "Add the superuser account."   321    322         moin_script = join(self.prefix, "bin", "moin")   323    324         print "Creating superuser", self.superuser, "using..."   325         email = raw_input("E-mail address: ")   326         password = getpass("Password: ")   327    328         path = os.environ.get("PYTHONPATH", "")   329    330         if path:   331             os.environ["PYTHONPATH"] = path + ":" + self.conf_dir   332         else:   333             os.environ["PYTHONPATH"] = self.conf_dir   334    335         os.system(moin_script + " account create --name='%s' --email='%s' --password='%s'" % (self.superuser, email, password))   336    337         if path:   338             os.environ["PYTHONPATH"] = path   339         else:   340             del os.environ["PYTHONPATH"]   341    342     def make_site_files(self):   343    344         "Make the Apache site files."   345    346         # NOTE: Using local namespace for substitution.   347    348         site_def = join(self.web_site_dir, self.site_name)   349         site_def_private = join(self.web_site_dir, "%s-private" % self.site_name)   350    351         status("Writing Apache site definitions to %s and %s..." % (site_def, site_def_private))   352    353         s = apache_site % self.__dict__   354    355         if not self.moin_version.startswith("1.9"):   356             s += apache_site_extra_moin18 % self.__dict__   357    358         writefile(site_def, s)   359    360     def make_post_install_script(self):   361    362         "Write a post-install script with additional actions."   363    364         this_user = os.environ["USER"]   365         postinst_script = "moinsetup-post.sh"   366    367         s = "#!/bin/sh\n"   368    369         for d in ("data", "underlay"):   370             s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, join(self.conf_dir, d))   371             s += "chmod -R g+w '%s'\n" % join(self.conf_dir, d)   372    373         if not self.moin_version.startswith("1.9"):   374             s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, self.htdocs_dir)   375    376         writefile(postinst_script, s)   377         os.chmod(postinst_script, 0755)   378         note("Run %s as root to set file ownership and permissions." % postinst_script)   379    380     # Accessory methods.   381    382     def reconfigure_moin(self):   383    384         "Edit the installed Wiki configuration file."   385    386         wikiconfig_py = join(self.conf_dir, "wikiconfig.py")   387    388         status("Editing configuration from %s..." % wikiconfig_py)   389    390         s = readfile(wikiconfig_py)   391         s = self._configure_moin(s)   392         writefile(wikiconfig_py, s)   393    394     def install_theme(self, theme_dir):   395    396         "Install Wiki theme provided in the given 'theme_dir'."   397    398         theme_dir = normpath(theme_dir)   399         theme_name = split(theme_dir)[-1]   400         theme_module = join(theme_dir, theme_name + extsep + "py")   401    402         data_dir = join(self.conf_dir, "data")   403         plugin_theme_dir = join(data_dir, "plugin", "theme")   404    405         # Copy the theme module.   406    407         status("Copying theme module to %s..." % plugin_theme_dir)   408    409         shutil.copy(theme_module, plugin_theme_dir)   410    411         # Copy the resources.   412    413         resources_dir = join(self.htdocs_dir, theme_name)   414    415         status("Copying theme resources to %s..." % resources_dir)   416    417         for d in ("css", "img"):   418             target_dir = join(resources_dir, d)   419             if exists(target_dir):   420                 status("Replacing %s..." % target_dir)   421                 shutil.rmtree(target_dir)   422             shutil.copytree(join(theme_dir, d), target_dir)   423    424         # Copy additional resources from other themes.   425    426         resources_source_dir = join(self.htdocs_dir, self.theme_master)   427         target_dir = join(resources_dir, "css")   428    429         status("Copying resources from %s..." % resources_source_dir)   430    431         for css_file in self.extra_theme_css_files:   432             css_file_path = join(resources_source_dir, "css", css_file)   433             if exists(css_file_path):   434                 shutil.copy(css_file_path, target_dir)   435    436 # Command line option syntax.   437    438 syntax_description = "<argument> ... [ --method=METHOD [ <method-argument> ... ] ]"   439    440 # Main program.   441    442 if __name__ == "__main__":   443     import sys, cmdsyntax   444    445     # Check the command syntax.   446    447     syntax = cmdsyntax.Syntax(syntax_description)   448     try:   449         matches = syntax.get_args(sys.argv[1:])   450         args = matches[0]   451    452         # Obtain as many arguments as needed for the configuration.   453    454         arguments = args["argument"]   455         method_arguments = args.get("method-argument", [])   456    457         # Attempt to initialise the configuration.   458    459         installation = Installation(*arguments)   460    461     except (IndexError, TypeError):   462         print "Syntax:"   463         print sys.argv[0], syntax_description   464         print   465         print "Arguments:"   466         print Installation.__init__.__doc__   467         sys.exit(1)   468    469     # Obtain and perform the method.   470    471     if args.has_key("method"):   472         method = getattr(installation, args["method"])   473     else:   474         method = installation.setup   475    476     method(*method_arguments)   477    478 # vim: tabstop=4 expandtab shiftwidth=4