moinsetup

Annotated moinsetup.py

5:92d47a4aa49c
2010-05-08 Paul Boddie Added support for installing from a Mercurial checkout (at least with 1.9.x era sources).
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