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