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