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