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