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 ensure_directories(self): 152 153 "Make sure that all the directories are available." 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 def setup(self): 160 161 "Set up the installation." 162 163 self.ensure_directories() 164 self.install_moin() 165 self.install_data() 166 self.configure_moin() 167 self.edit_moin_scripts() 168 self.add_superuser() 169 self.make_site_files() 170 self.make_post_install_script() 171 172 def install_moin(self): 173 174 "Enter the distribution directory and run the setup script." 175 176 # NOTE: Possibly check for an existing installation and skip repeated 177 # NOTE: installation attempts. 178 179 this_dir = os.getcwd() 180 os.chdir(self.moin_distribution) 181 182 log_filename = "install-%s.log" % split(self.common_dir)[-1] 183 184 status("Installing MoinMoin in %s..." % self.prefix) 185 186 os.system("python setup.py --quiet install --force --prefix='%s' --install-data='%s' --record='%s'" % ( 187 self.prefix, self.instance_dir, log_filename)) 188 189 os.chdir(this_dir) 190 191 def install_data(self): 192 193 "Install Wiki data." 194 195 # The default wikiconfig assumes data and underlay in the same directory. 196 197 status("Installing data and underlay in %s..." % self.conf_dir) 198 199 for d in ("data", "underlay"): 200 source = join(self.moin_distribution, "wiki", d) 201 source_tar = source + os.path.extsep + "tar" 202 d_tar = source + os.path.extsep + "tar" 203 204 if os.path.exists(source): 205 shutil.copytree(source, join(self.conf_dir, d)) 206 elif os.path.exists(source_tar): 207 shutil.copy(source_tar, self.conf_dir) 208 os.system("tar xf %s -C %s" % (d_tar, self.conf_dir)) 209 else: 210 status("Could not copy %s into installed Wiki." % d) 211 212 def configure_moin(self): 213 214 "Edit the Wiki configuration file." 215 216 # NOTE: Single Wiki only so far. 217 218 # Static URLs seem to be different in MoinMoin 1.9.x. 219 # For earlier versions, reserve URL space alongside the Wiki. 220 # NOTE: MoinMoin usually uses an apparently common URL space associated 221 # NOTE: with the version, but more specific locations are probably 222 # NOTE: acceptable if less efficient. 223 224 if self.moin_version.startswith("1.9"): 225 self.static_url_path = self.url_path 226 url_prefix_static_sub = r"\1\2 %r + url_prefix_static" % self.static_url_path 227 else: 228 self.static_url_path = self.url_path + "-static" 229 url_prefix_static_sub = r"\1\2 %r" % self.static_url_path 230 231 # Edit the Wiki configuration file. 232 233 wikiconfig_py = join(self.moin_distribution, "wiki", "config", "wikiconfig.py") 234 235 status("Editing configuration from %s..." % wikiconfig_py) 236 237 s = readfile(wikiconfig_py) 238 s = wikiconfig_py_site_name.sub(r"\1\2 %r" % self.site_name, s) 239 s = wikiconfig_py_url_prefix_static.sub(url_prefix_static_sub, s) 240 s = wikiconfig_py_superuser.sub(r"\1\2 %r" % [self.superuser], s) 241 s = wikiconfig_py_acl_rights_before.sub(r"\1\2 %r" % (u"%s:read,write,delete,revert,admin" % self.superuser), s) 242 s = wikiconfig_py_page_front_page.sub(r"\1\2 %r" % self.front_page_name, s, count=1) 243 244 if not self.moin_version.startswith("1.9"): 245 data_dir = join(self.conf_dir, "data") 246 data_underlay_dir = join(self.conf_dir, "underlay") 247 248 s = wikiconfig_py_data_dir.sub(r"\1\2 %r" % data_dir, s) 249 s = wikiconfig_py_data_underlay_dir.sub(r"\1\2 %r" % data_underlay_dir, s) 250 251 writefile(join(self.conf_dir, "wikiconfig.py"), s) 252 253 def edit_moin_scripts(self): 254 255 "Edit the moin script and the CGI script." 256 257 moin_script = join(self.prefix, "bin", "moin") 258 prefix_site_packages = join(self.prefix, "lib", "python%s.%s" % sys.version_info[:2], "site-packages") 259 260 status("Editing moin script at %s..." % moin_script) 261 262 s = readfile(moin_script) 263 s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % prefix_site_packages) 264 265 writefile(moin_script, s) 266 267 # Edit and install CGI script. 268 # NOTE: CGI only so far. 269 # NOTE: Permissions should be checked. 270 271 moin_cgi = join(self.instance_dir, "share", "moin", "server", "moin.cgi") 272 moin_cgi_installed = join(self.web_app_dir, "moin.cgi") 273 274 status("Editing moin.cgi script from %s..." % moin_cgi) 275 276 s = readfile(moin_cgi) 277 s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % prefix_site_packages, s) 278 s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.conf_dir, s) 279 280 writefile(moin_cgi_installed, s) 281 os.system("chmod a+rx '%s'" % moin_cgi_installed) 282 283 def add_superuser(self): 284 285 "Add the superuser account." 286 287 moin_script = join(self.prefix, "bin", "moin") 288 289 print "Creating superuser", self.superuser, "using..." 290 email = raw_input("E-mail address: ") 291 password = getpass("Password: ") 292 293 path = os.environ.get("PYTHONPATH", "") 294 295 if path: 296 os.environ["PYTHONPATH"] = path + ":" + self.conf_dir 297 else: 298 os.environ["PYTHONPATH"] = self.conf_dir 299 300 os.system(moin_script + " account create --name='%s' --email='%s' --password='%s'" % (self.superuser, email, password)) 301 302 if path: 303 os.environ["PYTHONPATH"] = path 304 else: 305 del os.environ["PYTHONPATH"] 306 307 def make_site_files(self): 308 309 "Make the Apache site files." 310 311 # NOTE: Using local namespace for substitution. 312 313 site_def = join(self.web_site_dir, self.site_name) 314 site_def_private = join(self.web_site_dir, "%s-private" % self.site_name) 315 316 status("Writing Apache site definitions to %s and %s..." % (site_def, site_def_private)) 317 318 s = apache_site % self.__dict__ 319 320 if not self.moin_version.startswith("1.9"): 321 s += apache_site_extra_moin18 % self.__dict__ 322 323 writefile(site_def, s) 324 325 def make_post_install_script(self): 326 327 "Write a post-install script with additional actions." 328 329 this_user = os.environ["USER"] 330 postinst_script = "moinsetup-post.sh" 331 332 s = "#!/bin/sh\n" 333 334 for d in ("data", "underlay"): 335 s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, join(self.conf_dir, d)) 336 s += "chmod -R g+w '%s'\n" % join(self.conf_dir, d) 337 338 if not self.moin_version.startswith("1.9"): 339 s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, self.htdocs_dir) 340 341 writefile(postinst_script, s) 342 os.chmod(postinst_script, 0755) 343 note("Run %s as root to set file ownership and permissions." % postinst_script) 344 345 # Command line option syntax. 346 347 syntax_description = "<argument> ... [ --method=METHOD ]" 348 349 # Main program. 350 351 if __name__ == "__main__": 352 import sys, cmdsyntax 353 354 # Check the command syntax. 355 356 syntax = cmdsyntax.Syntax(syntax_description) 357 try: 358 matches = syntax.get_args(sys.argv[1:]) 359 args = matches[0] 360 361 # Obtain as many arguments as needed for the configuration. 362 363 arguments = args["argument"] 364 365 # Attempt to initialise the configuration. 366 367 installation = Installation(*arguments) 368 369 except (IndexError, TypeError): 370 print "Syntax:" 371 print sys.argv[0], syntax_description 372 print 373 print "Arguments:" 374 print Installation.__init__.__doc__ 375 sys.exit(1) 376 377 # Obtain and perform the method. 378 379 if args.has_key("method"): 380 method = getattr(installation, args["method"]) 381 else: 382 method = installation.setup 383 384 method() 385 386 # vim: tabstop=4 expandtab shiftwidth=4