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 | f = open("PKG-INFO") |
paul@2 | 133 | try: |
paul@2 | 134 | for line in f.xreadlines(): |
paul@2 | 135 | columns = line.split() |
paul@2 | 136 | if columns[0] == "Version:": |
paul@2 | 137 | return columns[1] |
paul@2 | 138 | |
paul@2 | 139 | return None |
paul@2 | 140 | |
paul@2 | 141 | finally: |
paul@2 | 142 | f.close() |
paul@2 | 143 | os.chdir(this_dir) |
paul@2 | 144 | |
paul@2 | 145 | def setup(self): |
paul@2 | 146 | |
paul@2 | 147 | "Set up the installation." |
paul@2 | 148 | |
paul@2 | 149 | for d in (self.conf_dir, self.instance_dir, self.web_app_dir, self.web_site_dir): |
paul@2 | 150 | if not exists(d): |
paul@2 | 151 | os.makedirs(d) |
paul@2 | 152 | |
paul@2 | 153 | self.install_moin() |
paul@2 | 154 | self.install_data() |
paul@2 | 155 | self.configure_moin() |
paul@2 | 156 | self.edit_moin_scripts() |
paul@4 | 157 | self.add_superuser() |
paul@2 | 158 | self.make_site_files() |
paul@2 | 159 | self.make_post_install_script() |
paul@2 | 160 | |
paul@2 | 161 | def install_moin(self): |
paul@2 | 162 | |
paul@2 | 163 | "Enter the distribution directory and run the setup script." |
paul@2 | 164 | |
paul@2 | 165 | # NOTE: Possibly check for an existing installation and skip repeated |
paul@2 | 166 | # NOTE: installation attempts. |
paul@2 | 167 | |
paul@2 | 168 | this_dir = os.getcwd() |
paul@2 | 169 | os.chdir(self.moin_distribution) |
paul@2 | 170 | |
paul@2 | 171 | log_filename = "install-%s.log" % split(self.common_dir)[-1] |
paul@2 | 172 | |
paul@2 | 173 | status("Installing MoinMoin in %s..." % self.prefix) |
paul@2 | 174 | |
paul@2 | 175 | os.system("python setup.py --quiet install --force --prefix='%s' --install-data='%s' --record='%s'" % ( |
paul@2 | 176 | self.prefix, self.instance_dir, log_filename)) |
paul@2 | 177 | |
paul@2 | 178 | os.chdir(this_dir) |
paul@2 | 179 | |
paul@2 | 180 | def install_data(self): |
paul@2 | 181 | |
paul@2 | 182 | "Install Wiki data." |
paul@2 | 183 | |
paul@2 | 184 | # The default wikiconfig assumes data and underlay in the same directory. |
paul@2 | 185 | |
paul@2 | 186 | status("Installing data and underlay in %s..." % self.conf_dir) |
paul@2 | 187 | |
paul@2 | 188 | for d in ("data", "underlay"): |
paul@2 | 189 | shutil.copytree(join(self.moin_distribution, "wiki", d), join(self.conf_dir, d)) |
paul@2 | 190 | |
paul@2 | 191 | def configure_moin(self): |
paul@2 | 192 | |
paul@2 | 193 | "Edit the Wiki configuration file." |
paul@2 | 194 | |
paul@2 | 195 | # NOTE: Single Wiki only so far. |
paul@2 | 196 | |
paul@2 | 197 | # Static URLs seem to be different in MoinMoin 1.9.x. |
paul@2 | 198 | # For earlier versions, reserve URL space alongside the Wiki. |
paul@2 | 199 | # NOTE: MoinMoin usually uses an apparently common URL space associated |
paul@2 | 200 | # NOTE: with the version, but more specific locations are probably |
paul@2 | 201 | # NOTE: acceptable if less efficient. |
paul@2 | 202 | |
paul@2 | 203 | if self.moin_version.startswith("1.9"): |
paul@2 | 204 | self.static_url_path = self.url_path |
paul@2 | 205 | url_prefix_static_sub = r"\1\2 %r + url_prefix_static" % self.static_url_path |
paul@2 | 206 | else: |
paul@2 | 207 | self.static_url_path = self.url_path + "-static" |
paul@2 | 208 | url_prefix_static_sub = r"\1\2 %r" % self.static_url_path |
paul@2 | 209 | |
paul@2 | 210 | # Edit the Wiki configuration file. |
paul@2 | 211 | |
paul@2 | 212 | wikiconfig_py = join(self.moin_distribution, "wiki", "config", "wikiconfig.py") |
paul@2 | 213 | |
paul@2 | 214 | status("Editing configuration from %s..." % wikiconfig_py) |
paul@2 | 215 | |
paul@2 | 216 | s = readfile(wikiconfig_py) |
paul@2 | 217 | s = wikiconfig_py_site_name.sub(r"\1\2 %r" % self.site_name, s) |
paul@2 | 218 | s = wikiconfig_py_url_prefix_static.sub(url_prefix_static_sub, s) |
paul@2 | 219 | s = wikiconfig_py_superuser.sub(r"\1\2 %r" % [self.superuser], s) |
paul@2 | 220 | s = wikiconfig_py_acl_rights_before.sub(r"\1\2 %r" % (u"%s:read,write,delete,revert,admin" % self.superuser), s) |
paul@2 | 221 | s = wikiconfig_py_page_front_page.sub(r"\1\2 %r" % self.front_page_name, s, count=1) |
paul@2 | 222 | |
paul@2 | 223 | if not self.moin_version.startswith("1.9"): |
paul@2 | 224 | data_dir = join(self.conf_dir, "data") |
paul@2 | 225 | data_underlay_dir = join(self.conf_dir, "underlay") |
paul@2 | 226 | |
paul@2 | 227 | s = wikiconfig_py_data_dir.sub(r"\1\2 %r" % data_dir, s) |
paul@2 | 228 | s = wikiconfig_py_data_underlay_dir.sub(r"\1\2 %r" % data_underlay_dir, s) |
paul@2 | 229 | |
paul@2 | 230 | writefile(join(self.conf_dir, "wikiconfig.py"), s) |
paul@2 | 231 | |
paul@2 | 232 | def edit_moin_scripts(self): |
paul@2 | 233 | |
paul@2 | 234 | "Edit the moin script and the CGI script." |
paul@2 | 235 | |
paul@2 | 236 | moin_script = join(self.prefix, "bin", "moin") |
paul@2 | 237 | prefix_site_packages = join(self.prefix, "lib", "python%s.%s" % sys.version_info[:2], "site-packages") |
paul@2 | 238 | |
paul@2 | 239 | status("Editing moin script at %s..." % moin_script) |
paul@2 | 240 | |
paul@2 | 241 | s = readfile(moin_script) |
paul@2 | 242 | s = s.replace("#import sys", "import sys\nsys.path.insert(0, %r)" % prefix_site_packages) |
paul@2 | 243 | |
paul@2 | 244 | writefile(moin_script, s) |
paul@2 | 245 | |
paul@2 | 246 | # Edit and install CGI script. |
paul@2 | 247 | # NOTE: CGI only so far. |
paul@2 | 248 | # NOTE: Permissions should be checked. |
paul@2 | 249 | |
paul@2 | 250 | moin_cgi = join(self.instance_dir, "share", "moin", "server", "moin.cgi") |
paul@2 | 251 | moin_cgi_installed = join(self.web_app_dir, "moin.cgi") |
paul@2 | 252 | |
paul@2 | 253 | status("Editing moin.cgi script from %s..." % moin_cgi) |
paul@2 | 254 | |
paul@2 | 255 | s = readfile(moin_cgi) |
paul@2 | 256 | s = moin_cgi_prefix.sub("sys.path.insert(0, %r)" % prefix_site_packages, s) |
paul@2 | 257 | s = moin_cgi_wikiconfig.sub("sys.path.insert(0, %r)" % self.conf_dir, s) |
paul@2 | 258 | |
paul@2 | 259 | writefile(moin_cgi_installed, s) |
paul@2 | 260 | os.system("chmod a+rx '%s'" % moin_cgi_installed) |
paul@2 | 261 | |
paul@4 | 262 | def add_superuser(self): |
paul@4 | 263 | |
paul@4 | 264 | "Add the superuser account." |
paul@4 | 265 | |
paul@4 | 266 | moin_script = join(self.prefix, "bin", "moin") |
paul@4 | 267 | |
paul@4 | 268 | print "Creating superuser", self.superuser, "using..." |
paul@4 | 269 | email = raw_input("E-mail address: ") |
paul@4 | 270 | password = getpass("Password: ") |
paul@4 | 271 | |
paul@4 | 272 | path = os.environ.get("PYTHONPATH", "") |
paul@4 | 273 | |
paul@4 | 274 | if path: |
paul@4 | 275 | os.environ["PYTHONPATH"] = path + ":" + self.conf_dir |
paul@4 | 276 | else: |
paul@4 | 277 | os.environ["PYTHONPATH"] = self.conf_dir |
paul@4 | 278 | |
paul@4 | 279 | os.system(moin_script + " account create --name='%s' --email='%s' --password='%s'" % (self.superuser, email, password)) |
paul@4 | 280 | |
paul@4 | 281 | if path: |
paul@4 | 282 | os.environ["PYTHONPATH"] = path |
paul@4 | 283 | else: |
paul@4 | 284 | del os.environ["PYTHONPATH"] |
paul@4 | 285 | |
paul@2 | 286 | def make_site_files(self): |
paul@2 | 287 | |
paul@2 | 288 | "Make the Apache site files." |
paul@2 | 289 | |
paul@2 | 290 | # NOTE: Using local namespace for substitution. |
paul@2 | 291 | |
paul@2 | 292 | site_def = join(self.web_site_dir, self.site_name) |
paul@2 | 293 | site_def_private = join(self.web_site_dir, "%s-private" % self.site_name) |
paul@2 | 294 | |
paul@2 | 295 | status("Writing Apache site definitions to %s and %s..." % (site_def, site_def_private)) |
paul@2 | 296 | |
paul@2 | 297 | s = apache_site % self.__dict__ |
paul@2 | 298 | |
paul@2 | 299 | if not self.moin_version.startswith("1.9"): |
paul@2 | 300 | s += apache_site_extra_moin18 % self.__dict__ |
paul@2 | 301 | |
paul@2 | 302 | writefile(site_def, s) |
paul@2 | 303 | |
paul@2 | 304 | def make_post_install_script(self): |
paul@2 | 305 | |
paul@2 | 306 | "Write a post-install script with additional actions." |
paul@2 | 307 | |
paul@2 | 308 | this_user = os.environ["USER"] |
paul@2 | 309 | postinst_script = "moinsetup-post.sh" |
paul@2 | 310 | |
paul@4 | 311 | s = "#!/bin/sh\n" |
paul@2 | 312 | |
paul@2 | 313 | for d in ("data", "underlay"): |
paul@2 | 314 | s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, join(self.conf_dir, d)) |
paul@2 | 315 | s += "chmod -R g+w '%s'\n" % join(self.conf_dir, d) |
paul@2 | 316 | |
paul@2 | 317 | if not self.moin_version.startswith("1.9"): |
paul@2 | 318 | s += "chown -R %s.%s '%s'\n" % (this_user, self.web_group, self.htdocs_dir) |
paul@2 | 319 | |
paul@2 | 320 | writefile(postinst_script, s) |
paul@4 | 321 | os.chmod(postinst_script, 0755) |
paul@2 | 322 | note("Run %s as root to set file ownership and permissions." % postinst_script) |
paul@1 | 323 | |
paul@1 | 324 | # Main program. |
paul@0 | 325 | |
paul@0 | 326 | if __name__ == "__main__": |
paul@1 | 327 | |
paul@1 | 328 | # Obtain as many arguments as needed for the setup function. |
paul@1 | 329 | |
paul@0 | 330 | try: |
paul@2 | 331 | n = 9 # number of Installation initialiser arguments |
paul@0 | 332 | args = sys.argv[1:n+1] |
paul@0 | 333 | args[n-1] |
paul@0 | 334 | except (IndexError, ValueError): |
paul@2 | 335 | print Installation.__init__.__doc__ |
paul@0 | 336 | sys.exit(1) |
paul@0 | 337 | |
paul@2 | 338 | installation = Installation(*args) |
paul@2 | 339 | installation.setup() |
paul@0 | 340 | |
paul@0 | 341 | # vim: tabstop=4 expandtab shiftwidth=4 |