~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: mattgiuca
  • Date: 2008-05-11 05:11:09 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:754
listusers.py: Added options processing (now accepts -h and -n).
    If -n is given, just prints out a list of usernames, rather than
    all the user details.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
# Date:   12/12/2007
22
22
 
23
23
# This is a command-line application, for use by the administrator.
24
 
# This program is a frontend for the modules in the setup packages that 
25
 
# configure, build and install IVLE in three separate steps.
 
24
# This program configures, builds and installs IVLE in three separate steps.
26
25
# It is called with at least one argument, which specifies which operation to
27
26
# take.
28
27
 
 
28
# setup.py listmake (for developer use only)
 
29
# Recurses through the source tree and builds a list of all files which should
 
30
# be copied upon installation. This should be run by the developer before
 
31
# cutting a distribution, and the listfile it generates should be included in
 
32
# the distribution, avoiding the administrator having to run it.
 
33
 
 
34
# setup.py config [args]
 
35
# Configures IVLE with machine-specific details, most notably, various paths.
 
36
# Either prompts the administrator for these details or accepts them as
 
37
# command-line args.
 
38
# Creates lib/conf/conf.py and trampoline/conf.h.
 
39
 
 
40
# setup.py build
 
41
# Compiles all files and sets up a jail template in the source directory.
 
42
# Details:
 
43
# Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
 
44
# Creates jail/.
 
45
# Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
 
46
# Copies console/ to a location within the jail.
 
47
# Copies OS programs and files to corresponding locations within the jail
 
48
#   (eg. python and Python libs, ld.so, etc).
 
49
# Generates .pyc files for all the IVLE .py files.
 
50
 
 
51
# setup.py install [--nojail] [--dry|n]
 
52
# (Requires root)
 
53
# Create target install directory ($target).
 
54
# Create $target/bin.
 
55
# Copy trampoline/trampoline to $target/bin.
 
56
# chown and chmod the installed trampoline.
 
57
# Copy www/ to $target.
 
58
# Copy jail/ to jails template directory (unless --nojail specified).
 
59
 
 
60
import os
 
61
import stat
 
62
import shutil
29
63
import sys
30
 
import setup.configure
31
 
import setup.build
32
 
import setup.install
33
 
import setup.listmake
34
 
 
 
64
import getopt
 
65
import string
 
66
import errno
 
67
import mimetypes
 
68
import compileall
 
69
import getopt
 
70
import hashlib
 
71
import uuid
 
72
import pysvn
 
73
 
 
74
# Import modules from the website is tricky since they're in the www
 
75
# directory.
 
76
sys.path.append(os.path.join(os.getcwd(), 'lib'))
 
77
import conf
 
78
import common.makeuser
 
79
 
 
80
# Determine which Python version (2.4 or 2.5, for example) we are running,
 
81
# and use that as the filename to the Python directory.
 
82
# Just get the first 3 characters of sys.version.
 
83
PYTHON_VERSION = sys.version[0:3]
 
84
 
 
85
# Operating system files to copy over into the jail.
 
86
# These will be copied from the given place on the OS file system into the
 
87
# same place within the jail.
 
88
JAIL_FILES = [
 
89
    '/lib/ld-linux.so.2',
 
90
    '/lib/tls/i686/cmov/libc.so.6',
 
91
    '/lib/tls/i686/cmov/libdl.so.2',
 
92
    '/lib/tls/i686/cmov/libm.so.6',
 
93
    '/lib/tls/i686/cmov/libpthread.so.0',
 
94
    '/lib/tls/i686/cmov/libutil.so.1',
 
95
    '/etc/ld.so.conf',
 
96
    '/etc/ld.so.cache',
 
97
    # These 2 files do not exist in Ubuntu
 
98
    #'/etc/ld.so.preload',
 
99
    #'/etc/ld.so.nohwcap',
 
100
    # UNIX commands
 
101
    '/usr/bin/strace',
 
102
    '/bin/ls',
 
103
    '/bin/echo',
 
104
    # Needed by python
 
105
    '/usr/bin/python%s' % PYTHON_VERSION,
 
106
    # Needed by fileservice
 
107
    '/lib/libcom_err.so.2',
 
108
    '/lib/libcrypt.so.1',
 
109
    '/lib/libkeyutils.so.1',
 
110
    '/lib/libresolv.so.2',
 
111
    '/lib/librt.so.1',
 
112
    '/lib/libuuid.so.1',
 
113
    '/usr/lib/libapr-1.so.0',
 
114
    '/usr/lib/libaprutil-1.so.0',
 
115
    '/usr/lib/libdb-4.4.so',
 
116
    '/usr/lib/libexpat.so.1',
 
117
    '/usr/lib/libgcrypt.so.11',
 
118
    '/usr/lib/libgnutls.so.13',
 
119
    '/usr/lib/libgpg-error.so.0',
 
120
    '/usr/lib/libgssapi_krb5.so.2',
 
121
    '/usr/lib/libk5crypto.so.3',
 
122
    '/usr/lib/libkrb5.so.3',
 
123
    '/usr/lib/libkrb5support.so.0',
 
124
    '/usr/lib/liblber.so.2',
 
125
    '/usr/lib/libldap_r.so.2',
 
126
    '/usr/lib/libneon.so.26',
 
127
    '/usr/lib/libpq.so.5',
 
128
    '/usr/lib/libsasl2.so.2',
 
129
    '/usr/lib/libsqlite3.so.0',
 
130
    '/usr/lib/libsvn_client-1.so.1',
 
131
    '/usr/lib/libsvn_delta-1.so.1',
 
132
    '/usr/lib/libsvn_diff-1.so.1',
 
133
    '/usr/lib/libsvn_fs-1.so.1',
 
134
    '/usr/lib/libsvn_fs_base-1.so.1',
 
135
    '/usr/lib/libsvn_fs_fs-1.so.1',
 
136
    '/usr/lib/libsvn_ra-1.so.1',
 
137
    '/usr/lib/libsvn_ra_dav-1.so.1',
 
138
    '/usr/lib/libsvn_ra_local-1.so.1',
 
139
    '/usr/lib/libsvn_ra_svn-1.so.1',
 
140
    '/usr/lib/libsvn_repos-1.so.1',
 
141
    '/usr/lib/libsvn_subr-1.so.1',
 
142
    '/usr/lib/libsvn_wc-1.so.1',
 
143
    '/usr/lib/libtasn1.so.3',
 
144
    '/usr/lib/libxml2.so.2',
 
145
    # Needed by matplotlib
 
146
    '/usr/lib/i686/cmov/libssl.so.0.9.8',
 
147
    '/usr/lib/i686/cmov/libcrypto.so.0.9.8',
 
148
    '/lib/tls/i686/cmov/libnsl.so.1',
 
149
    '/usr/lib/libz.so.1',
 
150
    '/usr/lib/atlas/liblapack.so.3',
 
151
    '/usr/lib/atlas/libblas.so.3',
 
152
    '/usr/lib/libg2c.so.0',
 
153
    '/usr/lib/libstdc++.so.6',
 
154
    '/usr/lib/libfreetype.so.6',
 
155
    '/usr/lib/libpng12.so.0',
 
156
    '/usr/lib/libBLT.2.4.so.8.4',
 
157
    '/usr/lib/libtk8.4.so.0',
 
158
    '/usr/lib/libtcl8.4.so.0',
 
159
    '/usr/lib/tcl8.4/init.tcl',
 
160
    '/usr/lib/libX11.so.6',
 
161
    '/usr/lib/libXau.so.6',
 
162
    '/usr/lib/libXdmcp.so.6',
 
163
    '/lib/libgcc_s.so.1',
 
164
    '/etc/matplotlibrc',
 
165
    # Needed for resolv
 
166
    '/lib/libnss_dns.so.2',
 
167
    '/lib/libnss_mdns4_minimal.so.2',
 
168
    '/etc/hosts',
 
169
    '/etc/resolv.conf',
 
170
    #'/etc/hosts.conf',
 
171
    #'/etc/hostname',
 
172
    '/etc/nsswitch.conf',
 
173
    '/lib/libnss_files.so.2',
 
174
    # Needed for PIL
 
175
    '/usr/lib/libjpeg.so.62',
 
176
    # Needed by lxml
 
177
    '/usr/lib/libxslt.so.1',
 
178
    '/usr/lib/libexslt.so.0',
 
179
    # Needed by elementtree
 
180
    '/usr/lib/libtidy-0.99.so.0',
 
181
]
 
182
# Symlinks to make within the jail. Src mapped to dst.
 
183
JAIL_LINKS = {
 
184
    'python%s' % PYTHON_VERSION: 'jail/usr/bin/python',
 
185
}
 
186
# Trees to copy. Src mapped to dst (these will be passed to action_copytree).
 
187
JAIL_COPYTREES = {
 
188
    '/usr/lib/python%s' % PYTHON_VERSION:
 
189
        'jail/usr/lib/python%s' % PYTHON_VERSION,
 
190
    '/var/lib/python-support/python%s' % PYTHON_VERSION:
 
191
        'jail/var/lib/python-support/python%s' %PYTHON_VERSION,
 
192
    '/usr/share/matplotlib': 'jail/usr/share/matplotlib',
 
193
    '/etc/ld.so.conf.d': 'jail/etc/ld.so.conf.d',
 
194
    '/usr/share/pycentral': 'jail/usr/share/pycentral',
 
195
    '/usr/share/pycentral-data': 'jail/usr/share/pycentral-data',
 
196
    '/usr/share/nltk': 'jail/usr/share/nltk',
 
197
}
 
198
 
 
199
class ConfigOption:
 
200
    """A configuration option; one of the things written to conf.py."""
 
201
    def __init__(self, option_name, default, prompt, comment):
 
202
        """Creates a configuration option.
 
203
        option_name: Name of the variable in conf.py. Also name of the
 
204
            command-line argument to setup.py conf.
 
205
        default: Default value for this variable.
 
206
        prompt: (Short) string presented during the interactive prompt in
 
207
            setup.py conf.
 
208
        comment: (Long) comment string stored in conf.py. Each line of this
 
209
            string should begin with a '#'.
 
210
        """
 
211
        self.option_name = option_name
 
212
        self.default = default
 
213
        self.prompt = prompt
 
214
        self.comment = comment
 
215
 
 
216
# Configuration options, defaults and descriptions
 
217
config_options = []
 
218
config_options.append(ConfigOption("root_dir", "/",
 
219
    """Root directory where IVLE is located (in URL space):""",
 
220
    """
 
221
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
 
222
# with this).
 
223
# eg. "/" or "/ivle"."""))
 
224
config_options.append(ConfigOption("ivle_install_dir", "/opt/ivle",
 
225
    'Root directory where IVLE will be installed (on the local file '
 
226
    'system):',
 
227
    """
 
228
# In the local file system, where IVLE is actually installed.
 
229
# This directory should contain the "www" and "bin" directories."""))
 
230
config_options.append(ConfigOption("jail_base", "/home/informatics/jails",
 
231
    """Location of Directories
 
232
=======================
 
233
Root directory where the jails (containing user files) are stored
 
234
(on the local file system):""",
 
235
    """
 
236
# In the local file system, where are the student/user file spaces located.
 
237
# The user jails are expected to be located immediately in subdirectories of
 
238
# this location."""))
 
239
config_options.append(ConfigOption("subjects_base",
 
240
    "/home/informatics/subjects",
 
241
    """Root directory where the subject directories (containing worksheets
 
242
and other per-subject files) are stored (on the local file system):""",
 
243
    """
 
244
# In the local file system, where are the per-subject file spaces located.
 
245
# The individual subject directories are expected to be located immediately
 
246
# in subdirectories of this location."""))
 
247
config_options.append(ConfigOption("exercises_base",
 
248
    "/home/informatics/exercises",
 
249
    """Root directory where the exercise directories (containing
 
250
subject-independent exercise sheets) are stored (on the local file
 
251
system):""",
 
252
    """
 
253
# In the local file system, where are the subject-independent exercise sheet
 
254
# file spaces located."""))
 
255
config_options.append(ConfigOption("tos_path",
 
256
    "/home/informatics/tos.html",
 
257
    """Location where the Terms of Service document is stored (on the local
 
258
    file system):""",
 
259
    """
 
260
# In the local file system, where is the Terms of Service document located."""))
 
261
config_options.append(ConfigOption("motd_path",
 
262
    "/home/informatics/motd.html",
 
263
    """Location where the Message of the Day document is stored (on the local
 
264
    file system):""",
 
265
    """
 
266
# In the local file system, where is the Message of the Day document
 
267
# located. This is an HTML file (just the body fragment), which will
 
268
# be displayed on the login page. It is optional."""))
 
269
config_options.append(ConfigOption("public_host", "public.localhost",
 
270
    """Hostname which will cause the server to go into "public mode",
 
271
providing login-free access to student's published work:""",
 
272
    """
 
273
# The server goes into "public mode" if the browser sends a request with this
 
274
# host. This is for security reasons - we only serve public student files on a
 
275
# separate domain to the main IVLE site.
 
276
# Public mode does not use cookies, and serves only public content.
 
277
# Private mode (normal mode) requires login, and only serves files relevant to
 
278
# the logged-in user."""))
 
279
config_options.append(ConfigOption("allowed_uids", "33",
 
280
    """UID of the web server process which will run IVLE.
 
281
Only this user may execute the trampoline. May specify multiple users as
 
282
a comma-separated list.
 
283
    (eg. "1002,78")""",
 
284
    """
 
285
# The User-ID of the web server process which will run IVLE, and any other
 
286
# users who are allowed to run the trampoline. This is stores as a string of
 
287
# comma-separated integers, simply because it is not used within Python, only
 
288
# used by the setup program to write to conf.h (see setup.py config)."""))
 
289
config_options.append(ConfigOption("db_host", "localhost",
 
290
    """PostgreSQL Database config
 
291
==========================
 
292
Hostname of the DB server:""",
 
293
    """
 
294
### PostgreSQL Database config ###
 
295
# Database server hostname"""))
 
296
config_options.append(ConfigOption("db_port", "5432",
 
297
    """Port of the DB server:""",
 
298
    """
 
299
# Database server port"""))
 
300
config_options.append(ConfigOption("db_dbname", "ivle",
 
301
    """Database name:""",
 
302
    """
 
303
# Database name"""))
 
304
config_options.append(ConfigOption("db_forumdbname", "ivle_forum",
 
305
    """Forum Database name:""",
 
306
    """
 
307
# Forum Database name"""))
 
308
config_options.append(ConfigOption("db_user", "postgres",
 
309
    """Username for DB server login:""",
 
310
    """
 
311
# Database username"""))
 
312
config_options.append(ConfigOption("db_password", "",
 
313
    """Password for DB server login:
 
314
    (Caution: This password is stored in plaintext in lib/conf/conf.py)""",
 
315
    """
 
316
# Database password"""))
 
317
config_options.append(ConfigOption("auth_modules", "ldap_auth",
 
318
    """Authentication config
 
319
=====================
 
320
Comma-separated list of authentication modules. Only "ldap" is available
 
321
by default.""",
 
322
    """
 
323
# Comma-separated list of authentication modules.
 
324
# These refer to importable Python modules in the www/auth directory.
 
325
# Modules "ldap" and "guest" are available in the source tree, but
 
326
# other modules may be plugged in to auth against organisation-specific
 
327
# auth backends."""))
 
328
config_options.append(ConfigOption("ldap_url", "ldaps://www.example.com",
 
329
    """(LDAP options are only relevant if "ldap" is included in the list of
 
330
auth modules).
 
331
URL for LDAP authentication server:""",
 
332
    """
 
333
# URL for LDAP authentication server"""))
 
334
config_options.append(ConfigOption("ldap_format_string",
 
335
    "uid=%s,ou=users,o=example",
 
336
    """Format string for LDAP auth request:
 
337
    (Must contain a single "%s" for the user's login name)""",
 
338
    """
 
339
# Format string for LDAP auth request
 
340
# (Must contain a single "%s" for the user's login name)"""))
 
341
config_options.append(ConfigOption("svn_addr", "http://svn.localhost/",
 
342
    """Subversion config
 
343
=================
 
344
The base url for accessing subversion repositories:""",
 
345
    """
 
346
# The base url for accessing subversion repositories."""))
 
347
config_options.append(ConfigOption("svn_conf", "/opt/ivle/svn/svn.conf",
 
348
    """The location of the subversion configuration file used by apache
 
349
to host the user repositories:""",
 
350
    """
 
351
# The location of the subversion configuration file used by
 
352
# apache to host the user repositories."""))
 
353
config_options.append(ConfigOption("svn_repo_path", "/home/informatics/repositories",
 
354
    """The root directory for the subversion repositories:""",
 
355
    """
 
356
# The root directory for the subversion repositories."""))
 
357
config_options.append(ConfigOption("svn_auth_ivle", "/opt/ivle/svn/ivle.auth",
 
358
    """The location of the password file used to authenticate users
 
359
of the subversion repository from the ivle server:""",
 
360
    """
 
361
# The location of the password file used to authenticate users
 
362
# of the subversion repository from the ivle server."""))
 
363
config_options.append(ConfigOption("svn_auth_local", "/opt/ivle/svn/local.auth",
 
364
    """The location of the password file used to authenticate local users
 
365
of the subversion repository:""",
 
366
    """
 
367
# The location of the password file used to authenticate local users
 
368
# of the subversion repository."""))
 
369
config_options.append(ConfigOption("usrmgt_host", "localhost",
 
370
    """User Management Server config
 
371
============================
 
372
The hostname where the usrmgt-server runs:""",
 
373
    """
 
374
# The hostname where the usrmgt-server runs."""))
 
375
config_options.append(ConfigOption("usrmgt_port", "2178",
 
376
    """The port where the usrmgt-server runs:""",
 
377
    """
 
378
# The port where the usrmgt-server runs."""))
 
379
config_options.append(ConfigOption("usrmgt_magic", "",
 
380
    """The password for the usrmgt-server:""",
 
381
    """
 
382
# The password for the usrmgt-server."""))
 
383
 
 
384
# Try importing existing conf, but if we can't just set up defaults
 
385
# The reason for this is that these settings are used by other phases
 
386
# of setup besides conf, so we need to know them.
 
387
# Also this allows you to hit Return to accept the existing value.
 
388
try:
 
389
    confmodule = __import__("lib/conf/conf")
 
390
    for opt in config_options:
 
391
        try:
 
392
            globals()[opt.option_name] = confmodule.__dict__[opt.option_name]
 
393
        except:
 
394
            globals()[opt.option_name] = opt.default
 
395
except ImportError:
 
396
    # Just set reasonable defaults
 
397
    for opt in config_options:
 
398
        globals()[opt.option_name] = opt.default
 
399
 
 
400
# Try importing install_list, but don't fail if we can't, because listmake can
 
401
# function without it.
 
402
try:
 
403
    import install_list
 
404
except:
 
405
    pass
 
406
 
 
407
# Mime types which will automatically be placed in the list by listmake.
 
408
# Note that listmake is not intended to be run by the final user (the system
 
409
# administrator who installs this), so the developers can customize the list
 
410
# as necessary, and include it in the distribution.
 
411
listmake_mimetypes = ['text/x-python', 'text/html',
 
412
    'application/x-javascript', 'application/javascript',
 
413
    'text/css', 'image/png', 'image/gif', 'application/xml']
 
414
 
 
415
# Main function skeleton from Guido van Rossum
 
416
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
35
417
 
36
418
def main(argv=None):
37
419
    if argv is None:
56
438
        help([])
57
439
        return 1
58
440
 
59
 
    oper_func = call_operator(operation)
 
441
    # Disallow run as root unless installing
 
442
    if (operation != 'install' and operation != 'updatejails'
 
443
        and os.geteuid() == 0):
 
444
        print >>sys.stderr, "I do not want to run this stage as root."
 
445
        print >>sys.stderr, "Please run as a normal user."
 
446
        return 1
 
447
    # Call the requested operation's function
 
448
    try:
 
449
        oper_func = {
 
450
            'help' : help,
 
451
            'config' : conf,
 
452
            'build' : build,
 
453
            'listmake' : listmake,
 
454
            'install' : install,
 
455
            'updatejails' : updatejails,
 
456
        }[operation]
 
457
    except KeyError:
 
458
        print >>sys.stderr, (
 
459
            """Invalid operation '%s'. Try python setup.py help."""
 
460
            % operation)
 
461
        return 1
60
462
    return oper_func(argv[2:])
61
463
 
 
464
# Operation functions
 
465
 
62
466
def help(args):
63
 
    if len(args)!=1:
64
 
        print """Usage: python setup.py operation [options]
65
 
Operation can be:
 
467
    if args == []:
 
468
        print """Usage: python setup.py operation [args]
 
469
Operation (and args) can be:
66
470
    help [operation]
67
471
    listmake (developer use only)
68
 
    config
 
472
    config [args]
69
473
    build
70
 
    install
71
 
 
72
 
    For help and options for a specific operation use 'help [operation]'."""
73
 
    else:
74
 
        operator = args[0]
75
 
        oper_func = call_operator(operator)
76
 
        oper_func(['operator','--help'])
77
 
 
78
 
def call_operator(operation):
79
 
    # Call the requested operation's function
80
 
    try:
81
 
        oper_func = {
82
 
            'help' : help,
83
 
            'config' : setup.configure.configure,
84
 
            'build' : setup.build.build,
85
 
            'listmake' : setup.listmake.listmake,
86
 
            'install' : setup.install.install,
87
 
            #'updatejails' : None,
88
 
        }[operation]
89
 
    except KeyError:
 
474
    install [--nojail] [--nosubjects] [-n|--dry]
 
475
"""
 
476
        return 1
 
477
    elif len(args) != 1:
 
478
        print """Usage: python setup.py help [operation]"""
 
479
        return 2
 
480
    else:
 
481
        operation = args[0]
 
482
 
 
483
    if operation == 'help':
 
484
        print """python setup.py help [operation]
 
485
Prints the usage message or detailed help on an operation, then exits."""
 
486
    elif operation == 'listmake':
 
487
        print """python setup.py listmake
 
488
(For developer use only)
 
489
Recurses through the source tree and builds a list of all files which should
 
490
be copied upon installation. This should be run by the developer before
 
491
cutting a distribution, and the listfile it generates should be included in
 
492
the distribution, avoiding the administrator having to run it."""
 
493
    elif operation == 'config':
 
494
        print """python setup.py config [args]
 
495
Configures IVLE with machine-specific details, most notably, various paths.
 
496
Either prompts the administrator for these details or accepts them as
 
497
command-line args. Will be interactive only if there are no arguments given.
 
498
Takes defaults from existing conf file if it exists.
 
499
 
 
500
To run IVLE out of the source directory (allowing development without having
 
501
to rebuild/install), just provide ivle_install_dir as the IVLE trunk
 
502
directory, and run build/install one time.
 
503
 
 
504
Creates lib/conf/conf.py and trampoline/conf.h.
 
505
 
 
506
Args are:"""
 
507
        for opt in config_options:
 
508
            print "    --" + opt.option_name
 
509
        print """As explained in the interactive prompt or conf.py.
 
510
"""
 
511
    elif operation == 'build':
 
512
        print """python -O setup.py build [--dry|-n]
 
513
Compiles all files and sets up a jail template in the source directory.
 
514
-O is recommended to cause compilation to be optimised.
 
515
Details:
 
516
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
 
517
Creates jail/.
 
518
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
 
519
Copies console/ to a location within the jail.
 
520
Copies OS programs and files to corresponding locations within the jail
 
521
  (eg. python and Python libs, ld.so, etc).
 
522
Generates .pyc or .pyo files for all the IVLE .py files.
 
523
 
 
524
--dry | -n  Print out the actions but don't do anything."""
 
525
    elif operation == 'install':
 
526
        print """sudo python setup.py install [--nojail] [--nosubjects][--dry|-n]
 
527
(Requires root)
 
528
Create target install directory ($target).
 
529
Create $target/bin.
 
530
Copy trampoline/trampoline to $target/bin.
 
531
chown and chmod the installed trampoline.
 
532
Copy www/ to $target.
 
533
Copy jail/ to jails template directory (unless --nojail specified).
 
534
Copy subjects/ to subjects directory (unless --nosubjects specified).
 
535
 
 
536
--nojail        Do not copy the jail.
 
537
--nosubjects    Do not copy the subjects and exercises directories.
 
538
--dry | -n  Print out the actions but don't do anything."""
 
539
    elif operation == 'updatejails':
 
540
        print """sudo python setup.py updatejails [--dry|-n]
 
541
(Requires root)
 
542
Copy jail/ to each subdirectory in jails directory.
 
543
 
 
544
--dry | -n  Print out the actions but don't do anything."""
 
545
    else:
90
546
        print >>sys.stderr, (
91
547
            """Invalid operation '%s'. Try python setup.py help."""
92
548
            % operation)
93
 
        sys.exit(1)
94
 
    return oper_func
 
549
    return 1
 
550
 
 
551
def listmake(args):
 
552
    # We build two separate lists, by walking www and console
 
553
    list_www = build_list_py_files('www')
 
554
    list_lib = build_list_py_files('lib')
 
555
    list_subjects = build_list_py_files('subjects', no_top_level=True)
 
556
    list_exercises = build_list_py_files('exercises', no_top_level=True)
 
557
    list_scripts = [
 
558
        "scripts/python-console",
 
559
        "scripts/fileservice",
 
560
        "scripts/serveservice",
 
561
        "scripts/usrmgt-server",
 
562
        "scripts/diffservice",
 
563
    ]
 
564
    # Make sure that the files generated by conf are in the list
 
565
    # (since listmake is typically run before conf)
 
566
    if "lib/conf/conf.py" not in list_lib:
 
567
        list_lib.append("lib/conf/conf.py")
 
568
    # Write these out to a file
 
569
    cwd = os.getcwd()
 
570
    # the files that will be created/overwritten
 
571
    listfile = os.path.join(cwd, "install_list.py")
 
572
 
 
573
    try:
 
574
        file = open(listfile, "w")
 
575
 
 
576
        file.write("""# IVLE Configuration File
 
577
# install_list.py
 
578
# Provides lists of all files to be installed by `setup.py install' from
 
579
# certain directories.
 
580
# Note that any files with the given filename plus 'c' or 'o' (that is,
 
581
# compiled .pyc or .pyo files) will be copied as well.
 
582
 
 
583
# List of all installable files in www directory.
 
584
list_www = """)
 
585
        writelist_pretty(file, list_www)
 
586
        file.write("""
 
587
# List of all installable files in lib directory.
 
588
list_lib = """)
 
589
        writelist_pretty(file, list_lib)
 
590
        file.write("""
 
591
# List of all installable files in scripts directory.
 
592
list_scripts = """)
 
593
        writelist_pretty(file, list_scripts)
 
594
        file.write("""
 
595
# List of all installable files in subjects directory.
 
596
# This is to install sample subjects and material.
 
597
list_subjects = """)
 
598
        writelist_pretty(file, list_subjects)
 
599
        file.write("""
 
600
# List of all installable files in exercises directory.
 
601
# This is to install sample exercise material.
 
602
list_exercises = """)
 
603
        writelist_pretty(file, list_exercises)
 
604
 
 
605
        file.close()
 
606
    except IOError, (errno, strerror):
 
607
        print "IO error(%s): %s" % (errno, strerror)
 
608
        sys.exit(1)
 
609
 
 
610
    print "Successfully wrote install_list.py"
 
611
 
 
612
    print
 
613
    print ("You may modify the set of installable files before cutting the "
 
614
            "distribution:")
 
615
    print listfile
 
616
    print
 
617
 
 
618
    return 0
 
619
 
 
620
def build_list_py_files(dir, no_top_level=False):
 
621
    """Builds a list of all py files found in a directory and its
 
622
    subdirectories. Returns this as a list of strings.
 
623
    no_top_level=True means the file paths will not include the top-level
 
624
    directory.
 
625
    """
 
626
    pylist = []
 
627
    for (dirpath, dirnames, filenames) in os.walk(dir):
 
628
        # Exclude directories beginning with a '.' (such as '.svn')
 
629
        filter_mutate(lambda x: x[0] != '.', dirnames)
 
630
        # All *.py files are added to the list
 
631
        pylist += [os.path.join(dirpath, item) for item in filenames
 
632
            if mimetypes.guess_type(item)[0] in listmake_mimetypes]
 
633
    if no_top_level:
 
634
        for i in range(0, len(pylist)):
 
635
            _, pylist[i] = pylist[i].split(os.sep, 1)
 
636
    return pylist
 
637
 
 
638
def writelist_pretty(file, list):
 
639
    """Writes a list one element per line, to a file."""
 
640
    if list == []:
 
641
        file.write("[]\n")
 
642
    else:
 
643
        file.write('[\n')
 
644
        for elem in list:
 
645
            file.write('    %s,\n' % repr(elem))
 
646
        file.write(']\n')
 
647
 
 
648
def conf(args):
 
649
    global db_port, usrmgt_port
 
650
    # Set up some variables
 
651
 
 
652
    cwd = os.getcwd()
 
653
    # the files that will be created/overwritten
 
654
    conffile = os.path.join(cwd, "lib/conf/conf.py")
 
655
    jailconffile = os.path.join(cwd, "lib/conf/jailconf.py")
 
656
    conf_hfile = os.path.join(cwd, "trampoline/conf.h")
 
657
    phpBBconffile = os.path.join(cwd, "www/php/phpBB3/config.php")
 
658
    usrmgtserver_initdfile = os.path.join(cwd, "doc/setup/usrmgt-server.init")
 
659
 
 
660
    # Get command-line arguments to avoid asking questions.
 
661
 
 
662
    optnames = []
 
663
    for opt in config_options:
 
664
        optnames.append(opt.option_name + "=")
 
665
    (opts, args) = getopt.gnu_getopt(args, "", optnames)
 
666
 
 
667
    if args != []:
 
668
        print >>sys.stderr, "Invalid arguments:", string.join(args, ' ')
 
669
        return 2
 
670
 
 
671
    if opts == []:
 
672
        # Interactive mode. Prompt the user for all the values.
 
673
 
 
674
        print """This tool will create the following files:
 
675
    %s
 
676
    %s
 
677
    %s
 
678
    %s
 
679
    %s
 
680
prompting you for details about your configuration. The file will be
 
681
overwritten if it already exists. It will *not* install or deploy IVLE.
 
682
 
 
683
Please hit Ctrl+C now if you do not wish to do this.
 
684
""" % (conffile, jailconffile, conf_hfile, phpBBconffile, usrmgtserver_initdfile)
 
685
 
 
686
        # Get information from the administrator
 
687
        # If EOF is encountered at any time during the questioning, just exit
 
688
        # silently
 
689
 
 
690
        for opt in config_options:
 
691
            globals()[opt.option_name] = \
 
692
                query_user(globals()[opt.option_name], opt.prompt)
 
693
    else:
 
694
        opts = dict(opts)
 
695
        # Non-interactive mode. Parse the options.
 
696
        for opt in config_options:
 
697
            if '--' + opt.option_name in opts:
 
698
                globals()[opt.option_name] = opts['--' + opt.option_name]
 
699
 
 
700
    # Error handling on input values
 
701
    try:
 
702
        allowed_uids_list = map(int, allowed_uids.split(','))
 
703
    except ValueError:
 
704
        print >>sys.stderr, (
 
705
        "Invalid UID list (%s).\n"
 
706
        "Must be a comma-separated list of integers." % allowed_uids)
 
707
        return 1
 
708
    try:
 
709
        db_port = int(db_port)
 
710
        if db_port < 0 or db_port >= 65536: raise ValueError()
 
711
    except ValueError:
 
712
        print >>sys.stderr, (
 
713
        "Invalid DB port (%s).\n"
 
714
        "Must be an integer between 0 and 65535." % repr(db_port))
 
715
        return 1
 
716
    try:
 
717
        usrmgt_port = int(usrmgt_port)
 
718
        if usrmgt_port < 0 or usrmgt_port >= 65536: raise ValueError()
 
719
    except ValueError:
 
720
        print >>sys.stderr, (
 
721
        "Invalid user management port (%s).\n"
 
722
        "Must be an integer between 0 and 65535." % repr(usrmgt_port))
 
723
        return 1
 
724
 
 
725
    # Generate the forum secret
 
726
    forum_secret = hashlib.md5(uuid.uuid4().bytes).hexdigest()
 
727
 
 
728
    # Write lib/conf/conf.py
 
729
 
 
730
    try:
 
731
        conf = open(conffile, "w")
 
732
 
 
733
        conf.write("""# IVLE Configuration File
 
734
# conf.py
 
735
# Miscellaneous application settings
 
736
 
 
737
""")
 
738
        for opt in config_options:
 
739
            conf.write('%s\n%s = %s\n' % (opt.comment, opt.option_name,
 
740
                repr(globals()[opt.option_name])))
 
741
 
 
742
        # Add the forum secret to the config file (regenerated each config)
 
743
        conf.write('forum_secret = "%s"\n' % (forum_secret))
 
744
 
 
745
        conf.close()
 
746
    except IOError, (errno, strerror):
 
747
        print "IO error(%s): %s" % (errno, strerror)
 
748
        sys.exit(1)
 
749
 
 
750
    print "Successfully wrote lib/conf/conf.py"
 
751
 
 
752
    # Write conf/jailconf.py
 
753
 
 
754
    try:
 
755
        conf = open(jailconffile, "w")
 
756
 
 
757
        # In the "in-jail" version of conf, we don't need MOST of the details
 
758
        # (it would be a security risk to have them here).
 
759
        # So we just write root_dir, and jail_base is "/".
 
760
        # (jail_base being "/" means "jail-relative" paths are relative to "/"
 
761
        # when inside the jail.)
 
762
        conf.write("""# IVLE Configuration File
 
763
# conf.py
 
764
# Miscellaneous application settings
 
765
# (User jail version)
 
766
 
 
767
 
 
768
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
 
769
# with this).
 
770
# eg. "/" or "/ivle".
 
771
root_dir = %s
 
772
 
 
773
# In the local file system, where are the student/user file spaces located.
 
774
# The user jails are expected to be located immediately in subdirectories of
 
775
# this location.
 
776
jail_base = '/'
 
777
 
 
778
# The hostname for serving publicly accessible pages
 
779
public_host = %s
 
780
""" % (repr(root_dir),repr(public_host)))
 
781
 
 
782
        conf.close()
 
783
    except IOError, (errno, strerror):
 
784
        print "IO error(%s): %s" % (errno, strerror)
 
785
        sys.exit(1)
 
786
 
 
787
    print "Successfully wrote lib/conf/jailconf.py"
 
788
 
 
789
    # Write trampoline/conf.h
 
790
 
 
791
    try:
 
792
        conf = open(conf_hfile, "w")
 
793
 
 
794
        conf.write("""/* IVLE Configuration File
 
795
 * conf.h
 
796
 * Administrator settings required by trampoline.
 
797
 * Note: trampoline will have to be rebuilt in order for changes to this file
 
798
 * to take effect.
 
799
 */
 
800
 
 
801
/* In the local file system, where are the jails located.
 
802
 * The trampoline does not allow the creation of a jail anywhere besides
 
803
 * jail_base or a subdirectory of jail_base.
 
804
 */
 
805
static const char* jail_base = "%s";
 
806
 
 
807
/* Which user IDs are allowed to run the trampoline.
 
808
 * This list should be limited to the web server user.
 
809
 * (Note that root is an implicit member of this list).
 
810
 */
 
811
static const int allowed_uids[] = { %s };
 
812
""" % (repr(jail_base)[1:-1], repr(allowed_uids_list)[1:-1]))
 
813
    # Note: The above uses PYTHON reprs, not C reprs
 
814
    # However they should be the same with the exception of the outer
 
815
    # characters, which are stripped off and replaced
 
816
 
 
817
        conf.close()
 
818
    except IOError, (errno, strerror):
 
819
        print "IO error(%s): %s" % (errno, strerror)
 
820
        sys.exit(1)
 
821
 
 
822
    print "Successfully wrote trampoline/conf.h"
 
823
 
 
824
    # Write www/php/phpBB3/config.php
 
825
 
 
826
    try:
 
827
        conf = open(phpBBconffile, "w")
 
828
        
 
829
        # php-pg work around
 
830
        if db_host == 'localhost':
 
831
            forumdb_host = '127.0.0.1'
 
832
        else:
 
833
            forumdb_host = db_host
 
834
 
 
835
        conf.write( """<?php
 
836
// phpBB 3.0.x auto-generated configuration file
 
837
// Do not change anything in this file!
 
838
$dbms = 'postgres';
 
839
$dbhost = '""" + forumdb_host + """';
 
840
$dbport = '""" + str(db_port) + """';
 
841
$dbname = '""" + db_forumdbname + """';
 
842
$dbuser = '""" + db_user + """';
 
843
$dbpasswd = '""" + db_password + """';
 
844
 
 
845
$table_prefix = 'phpbb_';
 
846
$acm_type = 'file';
 
847
$load_extensions = '';
 
848
@define('PHPBB_INSTALLED', true);
 
849
// @define('DEBUG', true);
 
850
//@define('DEBUG_EXTRA', true);
 
851
 
 
852
$forum_secret = '""" + forum_secret +"""';
 
853
?>"""   )
 
854
    
 
855
        conf.close()
 
856
    except IOError, (errno, strerror):
 
857
        print "IO error(%s): %s" % (errno, strerror)
 
858
        sys.exit(1)
 
859
 
 
860
    print "Successfully wrote www/php/phpBB3/config.php"
 
861
 
 
862
    # Write lib/conf/usrmgt-server.init
 
863
 
 
864
    try:
 
865
        conf = open(usrmgtserver_initdfile, "w")
 
866
 
 
867
        conf.write( '''#! /bin/sh
 
868
 
 
869
# Works for Ubuntu. Check before using on other distributions
 
870
 
 
871
### BEGIN INIT INFO
 
872
# Provides:          usrmgt-server
 
873
# Required-Start:    $syslog $networking $urandom
 
874
# Required-Stop:     $syslog
 
875
# Default-Start:     2 3 4 5
 
876
# Default-Stop:      1
 
877
# Short-Description: IVLE user management server
 
878
# Description:       Daemon connecting to the IVLE user management database.
 
879
### END INIT INFO
 
880
 
 
881
PATH=/sbin:/bin:/usr/sbin:/usr/bin
 
882
DESC="IVLE user management server"
 
883
NAME=usrmgt-server
 
884
DAEMON=/opt/ivle/scripts/$NAME
 
885
DAEMON_ARGS="''' + str(usrmgt_port) + ''' ''' + usrmgt_magic + '''"
 
886
PIDFILE=/var/run/$NAME.pid
 
887
SCRIPTNAME=/etc/init.d/usrmgt-server
 
888
 
 
889
# Exit if the daemon does not exist 
 
890
test -f $DAEMON || exit 0
 
891
 
 
892
# Load the VERBOSE setting and other rcS variables
 
893
[ -f /etc/default/rcS ] && . /etc/default/rcS
 
894
 
 
895
# Define LSB log_* functions.
 
896
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
 
897
. /lib/lsb/init-functions
 
898
 
 
899
#
 
900
# Function that starts the daemon/service
 
901
#
 
902
do_start()
 
903
{
 
904
        # Return
 
905
        #   0 if daemon has been started
 
906
        #   1 if daemon was already running
 
907
        #   2 if daemon could not be started
 
908
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
 
909
                || return 1
 
910
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
 
911
                $DAEMON_ARGS \
 
912
                || return 2
 
913
        # Add code here, if necessary, that waits for the process to be ready
 
914
        # to handle requests from services started subsequently which depend
 
915
        # on this one.  As a last resort, sleep for some time.
 
916
}
 
917
 
 
918
#
 
919
# Function that stops the daemon/service
 
920
#
 
921
do_stop()
 
922
{
 
923
        # Return
 
924
        #   0 if daemon has been stopped
 
925
        #   1 if daemon was already stopped
 
926
        #   2 if daemon could not be stopped
 
927
        #   other if a failure occurred
 
928
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
 
929
        RETVAL="$?"
 
930
        [ "$RETVAL" = 2 ] && return 2
 
931
        # Wait for children to finish too if this is a daemon that forks
 
932
        # and if the daemon is only ever run from this initscript.
 
933
        # If the above conditions are not satisfied then add some other code
 
934
        # that waits for the process to drop all resources that could be
 
935
        # needed by services started subsequently.  A last resort is to
 
936
        # sleep for some time.
 
937
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
 
938
        [ "$?" = 2 ] && return 2
 
939
        # Many daemons don't delete their pidfiles when they exit.
 
940
        rm -f $PIDFILE
 
941
        return "$RETVAL"
 
942
}
 
943
 
 
944
#
 
945
# Function that sends a SIGHUP to the daemon/service
 
946
#
 
947
do_reload() {
 
948
        #
 
949
        # If the daemon can reload its configuration without
 
950
        # restarting (for example, when it is sent a SIGHUP),
 
951
        # then implement that here.
 
952
        #
 
953
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
 
954
        return 0
 
955
}
 
956
 
 
957
case "$1" in
 
958
  start)
 
959
    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
 
960
        do_start
 
961
        case "$?" in
 
962
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
 
963
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
 
964
        esac
 
965
        ;;
 
966
  stop)
 
967
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
 
968
        do_stop
 
969
        case "$?" in
 
970
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
 
971
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
 
972
        esac
 
973
        ;;
 
974
  #reload|force-reload)
 
975
        #
 
976
        # If do_reload() is not implemented then leave this commented out
 
977
        # and leave 'force-reload' as an alias for 'restart'.
 
978
        #
 
979
        #log_daemon_msg "Reloading $DESC" "$NAME"
 
980
        #do_reload
 
981
        #log_end_msg $?
 
982
        #;;
 
983
  restart|force-reload)
 
984
        #
 
985
        # If the "reload" option is implemented then remove the
 
986
        # 'force-reload' alias
 
987
        #
 
988
        log_daemon_msg "Restarting $DESC" "$NAME"
 
989
        do_stop
 
990
        case "$?" in
 
991
          0|1)
 
992
                do_start
 
993
                case "$?" in
 
994
                        0) log_end_msg 0 ;;
 
995
                        1) log_end_msg 1 ;; # Old process is still running
 
996
                        *) log_end_msg 1 ;; # Failed to start
 
997
                esac
 
998
                ;;
 
999
          *)
 
1000
                # Failed to stop
 
1001
                log_end_msg 1
 
1002
                ;;
 
1003
        esac
 
1004
        ;;
 
1005
  *)
 
1006
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
 
1007
        echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
 
1008
        exit 3
 
1009
        ;;
 
1010
esac
 
1011
 
 
1012
:
 
1013
''')
 
1014
        
 
1015
        conf.close()
 
1016
    except IOError, (errno, strerror):
 
1017
        print "IO error(%s): %s" % (errno, strerror)
 
1018
        sys.exit(1)
 
1019
 
 
1020
    # fix permissions as the file contains the database password
 
1021
    try:
 
1022
        os.chmod('doc/setup/usrmgt-server.init', 0600)
 
1023
    except OSError, (errno, strerror):
 
1024
        print "WARNING: Couldn't chmod doc/setup/usrmgt-server.init:"
 
1025
        print "OS error(%s): %s" % (errno, strerror)
 
1026
 
 
1027
    print "Successfully wrote lib/conf/usrmgt-server.init"
 
1028
 
 
1029
    print
 
1030
    print "You may modify the configuration at any time by editing"
 
1031
    print conffile
 
1032
    print jailconffile
 
1033
    print conf_hfile
 
1034
    print phpBBconffile
 
1035
    print usrmgtserver_initdfile
 
1036
    print
 
1037
    return 0
 
1038
 
 
1039
def build(args):
 
1040
    # Get "dry" variable from command line
 
1041
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
1042
    opts = dict(opts)
 
1043
    dry = '-n' in opts or '--dry' in opts
 
1044
 
 
1045
    if dry:
 
1046
        print "Dry run (no actions will be executed\n"
 
1047
    
 
1048
    # Find out the revison number
 
1049
    revnum = get_svn_revision()
 
1050
    print "Building Revision %s"%str(revnum)
 
1051
    if not dry:
 
1052
        vfile = open('BUILD-VERSION','w')
 
1053
        vfile.write(str(revnum) + '\n')
 
1054
        vfile.close()
 
1055
 
 
1056
    # Compile the trampoline
 
1057
    curdir = os.getcwd()
 
1058
    os.chdir('trampoline')
 
1059
    action_runprog('make', [], dry)
 
1060
    os.chdir(curdir)
 
1061
 
 
1062
    # Create the jail and its subdirectories
 
1063
    # Note: Other subdirs will be made by copying files
 
1064
    action_mkdir('jail', dry)
 
1065
    action_mkdir('jail/home', dry)
 
1066
    action_mkdir('jail/tmp', dry)
 
1067
 
 
1068
    # Chmod the tmp directory to world writable
 
1069
    action_chmod_w('jail/tmp', dry)
 
1070
 
 
1071
    # Copy all console and operating system files into the jail
 
1072
    action_copylist(install_list.list_scripts, 'jail/opt/ivle', dry)
 
1073
    copy_os_files_jail(dry)
 
1074
    # Chmod the python console
 
1075
    action_chmod_x('jail/opt/ivle/scripts/python-console', dry)
 
1076
    action_chmod_x('jail/opt/ivle/scripts/fileservice', dry)
 
1077
    action_chmod_x('jail/opt/ivle/scripts/serveservice', dry)
 
1078
    
 
1079
    # Also copy the IVLE lib directory into the jail
 
1080
    # This is necessary for running certain scripts
 
1081
    action_copylist(install_list.list_lib, 'jail/opt/ivle', dry)
 
1082
    # IMPORTANT: The file jail/opt/ivle/lib/conf/conf.py contains details
 
1083
    # which could compromise security if left in the jail (such as the DB
 
1084
    # password).
 
1085
    # The "safe" version is in jailconf.py. Delete conf.py and replace it with
 
1086
    # jailconf.py.
 
1087
    action_copyfile('lib/conf/jailconf.py',
 
1088
        'jail/opt/ivle/lib/conf/conf.py', dry)
 
1089
 
 
1090
    # Compile .py files into .pyc or .pyo files
 
1091
    compileall.compile_dir('www', quiet=True)
 
1092
    compileall.compile_dir('lib', quiet=True)
 
1093
    compileall.compile_dir('scripts', quiet=True)
 
1094
    compileall.compile_dir('jail/opt/ivle/lib', quiet=True)
 
1095
 
 
1096
    # Set up ivle.pth inside the jail
 
1097
    # Need to set /opt/ivle/lib to be on the import path
 
1098
    ivle_pth = \
 
1099
        "jail/usr/lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION
 
1100
    f = open(ivle_pth, 'w')
 
1101
    f.write('/opt/ivle/lib\n')
 
1102
    f.close()
 
1103
 
 
1104
    return 0
 
1105
 
 
1106
def copy_os_files_jail(dry):
 
1107
    """Copies necessary Operating System files from their usual locations
 
1108
    into the jail/ directory of the cwd."""
 
1109
    # Currently source paths are configured for Ubuntu.
 
1110
    for filename in JAIL_FILES:
 
1111
        copy_file_to_jail(filename, dry)
 
1112
    for src, dst in JAIL_LINKS.items():
 
1113
        action_symlink(src, dst, dry)
 
1114
    for src, dst in JAIL_COPYTREES.items():
 
1115
        action_copytree(src, dst, dry)
 
1116
 
 
1117
def copy_file_to_jail(src, dry):
 
1118
    """Copies a single file from an absolute location into the same location
 
1119
    within the jail. src must begin with a '/'. The jail will be located
 
1120
    in a 'jail' subdirectory of the current path."""
 
1121
    action_copyfile(src, 'jail' + src, dry)
 
1122
 
 
1123
def install(args):
 
1124
    # Get "dry" and "nojail" variables from command line
 
1125
    (opts, args) = getopt.gnu_getopt(args, "n",
 
1126
        ['dry', 'nojail', 'nosubjects'])
 
1127
    opts = dict(opts)
 
1128
    dry = '-n' in opts or '--dry' in opts
 
1129
    nojail = '--nojail' in opts
 
1130
    nosubjects = '--nosubjects' in opts
 
1131
 
 
1132
    if dry:
 
1133
        print "Dry run (no actions will be executed\n"
 
1134
 
 
1135
    if not dry and os.geteuid() != 0:
 
1136
        print >>sys.stderr, "Must be root to run install"
 
1137
        print >>sys.stderr, "(I need to chown some files)."
 
1138
        return 1
 
1139
 
 
1140
    # Create the target (install) directory
 
1141
    action_mkdir(ivle_install_dir, dry)
 
1142
 
 
1143
    # Create bin and copy the compiled files there
 
1144
    action_mkdir(os.path.join(ivle_install_dir, 'bin'), dry)
 
1145
    tramppath = os.path.join(ivle_install_dir, 'bin/trampoline')
 
1146
    action_copyfile('trampoline/trampoline', tramppath, dry)
 
1147
    # chown trampoline to root and set setuid bit
 
1148
    action_chown_setuid(tramppath, dry)
 
1149
 
 
1150
    # Create a scripts directory to put the usrmgt-server in.
 
1151
    action_mkdir(os.path.join(ivle_install_dir, 'scripts'), dry)
 
1152
    usrmgtpath = os.path.join(ivle_install_dir, 'scripts/usrmgt-server')
 
1153
    action_copyfile('scripts/usrmgt-server', usrmgtpath, dry)
 
1154
    action_chmod_x(usrmgtpath, dry)
 
1155
 
 
1156
    # Copy the www and lib directories using the list
 
1157
    action_copylist(install_list.list_www, ivle_install_dir, dry)
 
1158
    action_copylist(install_list.list_lib, ivle_install_dir, dry)
 
1159
    
 
1160
    # Copy the php directory
 
1161
    forum_dir = "www/php/phpBB3"
 
1162
    forum_path = os.path.join(ivle_install_dir, forum_dir)
 
1163
    action_copytree(forum_dir, forum_path, dry)
 
1164
    print "chown -R www-data:www-data %s" % forum_path
 
1165
    if not dry:
 
1166
        os.system("chown -R www-data:www-data %s" % forum_path)
 
1167
 
 
1168
    if not nojail:
 
1169
        # Copy the local jail directory built by the build action
 
1170
        # to the jails template directory (it will be used as a template
 
1171
        # for all the students' jails).
 
1172
        action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
1173
    if not nosubjects:
 
1174
        # Copy the subjects and exercises directories across
 
1175
        action_copylist(install_list.list_subjects, subjects_base, dry,
 
1176
            srcdir="./subjects")
 
1177
        action_copylist(install_list.list_exercises, exercises_base, dry,
 
1178
            srcdir="./exercises")
 
1179
 
 
1180
    # Append IVLE path to ivle.pth in python site packages
 
1181
    # (Unless it's already there)
 
1182
    ivle_pth = os.path.join(sys.prefix,
 
1183
        "lib/python%s/site-packages/ivle.pth" % PYTHON_VERSION)
 
1184
    ivle_www = os.path.join(ivle_install_dir, "www")
 
1185
    ivle_lib = os.path.join(ivle_install_dir, "lib")
 
1186
    write_ivle_pth = True
 
1187
    write_ivle_lib_pth = True
 
1188
    try:
 
1189
        file = open(ivle_pth, 'r')
 
1190
        for line in file:
 
1191
            if line.strip() == ivle_www:
 
1192
                write_ivle_pth = False
 
1193
            elif line.strip() == ivle_lib:
 
1194
                write_ivle_lib_pth = False
 
1195
        file.close()
 
1196
    except (IOError, OSError):
 
1197
        pass
 
1198
    if write_ivle_pth:
 
1199
        action_append(ivle_pth, ivle_www)
 
1200
    if write_ivle_lib_pth:
 
1201
        action_append(ivle_pth, ivle_lib)
 
1202
 
 
1203
    return 0
 
1204
 
 
1205
def updatejails(args):
 
1206
    # Get "dry" variable from command line
 
1207
    (opts, args) = getopt.gnu_getopt(args, "n", ['dry'])
 
1208
    opts = dict(opts)
 
1209
    dry = '-n' in opts or '--dry' in opts
 
1210
 
 
1211
    if dry:
 
1212
        print "Dry run (no actions will be executed\n"
 
1213
 
 
1214
    if not dry and os.geteuid() != 0:
 
1215
        print >>sys.stderr, "Must be root to run install"
 
1216
        print >>sys.stderr, "(I need to chown some files)."
 
1217
        return 1
 
1218
 
 
1219
    # Update the template jail directory in case it hasn't been installed
 
1220
    # recently.
 
1221
    action_copytree('jail', os.path.join(jail_base, 'template'), dry)
 
1222
 
 
1223
    # Re-link all the files in all students jails.
 
1224
    for dir in os.listdir(jail_base):
 
1225
        if dir == 'template': continue
 
1226
        # First back up the student's home directory
 
1227
        temp_home = os.tmpnam()
 
1228
        action_rename(os.path.join(jail_base, dir, 'home'), temp_home, dry)
 
1229
        # Delete the student's jail and relink the jail files
 
1230
        action_linktree(os.path.join(jail_base, 'template'),
 
1231
            os.path.join(jail_base, dir), dry)
 
1232
        # Restore the student's home directory
 
1233
        action_rename(temp_home, os.path.join(jail_base, dir, 'home'), dry)
 
1234
        # Set up the user's home directory just in case they don't have a
 
1235
        # directory for this yet
 
1236
        action_mkdir(os.path.join(jail_base, dir, 'home', dir), dry)
 
1237
 
 
1238
    return 0
 
1239
 
 
1240
# The actions call Python os functions but print actions and handle dryness.
 
1241
# May still throw os exceptions if errors occur.
 
1242
 
 
1243
class RunError:
 
1244
    """Represents an error when running a program (nonzero return)."""
 
1245
    def __init__(self, prog, retcode):
 
1246
        self.prog = prog
 
1247
        self.retcode = retcode
 
1248
    def __str__(self):
 
1249
        return str(self.prog) + " returned " + repr(self.retcode)
 
1250
 
 
1251
def action_runprog(prog, args, dry):
 
1252
    """Runs a unix program. Searches in $PATH. Synchronous (waits for the
 
1253
    program to return). Runs in the current environment. First prints the
 
1254
    action as a "bash" line.
 
1255
 
 
1256
    Throws a RunError with a retcode of the return value of the program,
 
1257
    if the program did not return 0.
 
1258
 
 
1259
    prog: String. Name of the program. (No path required, if in $PATH).
 
1260
    args: [String]. Arguments to the program.
 
1261
    dry: Bool. If True, prints but does not execute.
 
1262
    """
 
1263
    print prog, string.join(args, ' ')
 
1264
    if dry: return
 
1265
    ret = os.spawnvp(os.P_WAIT, prog, args)
 
1266
    if ret != 0:
 
1267
        raise RunError(prog, ret)
 
1268
 
 
1269
def action_remove(path, dry):
 
1270
    """Calls rmtree, deleting the target file if it exists."""
 
1271
    try:
 
1272
        print "rm -r", path
 
1273
        if not dry:
 
1274
            shutil.rmtree(path, True)
 
1275
    except OSError, (err, msg):
 
1276
        if err != errno.EEXIST:
 
1277
            raise
 
1278
        # Otherwise, didn't exist, so we don't care
 
1279
 
 
1280
def action_rename(src, dst, dry):
 
1281
    """Calls rename. Deletes the target if it already exists."""
 
1282
    action_remove(dst, dry)
 
1283
    print "mv ", src, dst
 
1284
    if dry: return
 
1285
    try:
 
1286
        os.rename(src, dst)
 
1287
    except OSError, (err, msg):
 
1288
        if err != errno.EEXIST:
 
1289
            raise
 
1290
 
 
1291
def action_mkdir(path, dry):
 
1292
    """Calls mkdir. Silently ignored if the directory already exists.
 
1293
    Creates all parent directories as necessary."""
 
1294
    print "mkdir -p", path
 
1295
    if dry: return
 
1296
    try:
 
1297
        os.makedirs(path)
 
1298
    except OSError, (err, msg):
 
1299
        if err != errno.EEXIST:
 
1300
            raise
 
1301
 
 
1302
def action_copytree(src, dst, dry):
 
1303
    """Copies an entire directory tree. Symlinks are seen as normal files and
 
1304
    copies of the entire file (not the link) are made. Creates all parent
 
1305
    directories as necessary.
 
1306
 
 
1307
    See shutil.copytree."""
 
1308
    # Allow copying over itself
 
1309
    if (os.path.normpath(os.path.join(os.getcwd(),src)) ==
 
1310
        os.path.normpath(os.path.join(os.getcwd(),dst))):
 
1311
        return
 
1312
    action_remove(dst, dry)
 
1313
    print "cp -r", src, dst
 
1314
    if dry: return
 
1315
    shutil.copytree(src, dst, True)
 
1316
 
 
1317
def action_linktree(src, dst, dry):
 
1318
    """Hard-links an entire directory tree. Same as copytree but the created
 
1319
    files are hard-links not actual copies. Removes the existing destination.
 
1320
    """
 
1321
    action_remove(dst, dry)
 
1322
    print "<cp with hardlinks> -r", src, dst
 
1323
    if dry: return
 
1324
    common.makeuser.linktree(src, dst)
 
1325
 
 
1326
def action_copylist(srclist, dst, dry, srcdir="."):
 
1327
    """Copies all files in a list to a new location. The files in the list
 
1328
    are read relative to the current directory, and their destinations are the
 
1329
    same paths relative to dst. Creates all parent directories as necessary.
 
1330
    srcdir is "." by default, can be overridden.
 
1331
    """
 
1332
    for srcfile in srclist:
 
1333
        dstfile = os.path.join(dst, srcfile)
 
1334
        srcfile = os.path.join(srcdir, srcfile)
 
1335
        dstdir = os.path.split(dstfile)[0]
 
1336
        if not os.path.isdir(dstdir):
 
1337
            action_mkdir(dstdir, dry)
 
1338
        print "cp -f", srcfile, dstfile
 
1339
        if not dry:
 
1340
            try:
 
1341
                shutil.copyfile(srcfile, dstfile)
 
1342
                shutil.copymode(srcfile, dstfile)
 
1343
            except shutil.Error:
 
1344
                pass
 
1345
 
 
1346
def action_copyfile(src, dst, dry):
 
1347
    """Copies one file to a new location. Creates all parent directories
 
1348
    as necessary.
 
1349
    Warn if file not found.
 
1350
    """
 
1351
    dstdir = os.path.split(dst)[0]
 
1352
    if not os.path.isdir(dstdir):
 
1353
        action_mkdir(dstdir, dry)
 
1354
    print "cp -f", src, dst
 
1355
    if not dry:
 
1356
        try:
 
1357
            shutil.copyfile(src, dst)
 
1358
            shutil.copymode(src, dst)
 
1359
        except (shutil.Error, IOError), e:
 
1360
            print "Warning: " + str(e)
 
1361
 
 
1362
def action_symlink(src, dst, dry):
 
1363
    """Creates a symlink in a given location. Creates all parent directories
 
1364
    as necessary.
 
1365
    """
 
1366
    dstdir = os.path.split(dst)[0]
 
1367
    if not os.path.isdir(dstdir):
 
1368
        action_mkdir(dstdir, dry)
 
1369
    # Delete existing file
 
1370
    if os.path.exists(dst):
 
1371
        os.remove(dst)
 
1372
    print "ln -fs", src, dst
 
1373
    if not dry:
 
1374
        os.symlink(src, dst)
 
1375
 
 
1376
def action_append(ivle_pth, ivle_www):
 
1377
    file = open(ivle_pth, 'a+')
 
1378
    file.write(ivle_www + '\n')
 
1379
    file.close()
 
1380
 
 
1381
def action_chown_setuid(file, dry):
 
1382
    """Chowns a file to root, and sets the setuid bit on the file.
 
1383
    Calling this function requires the euid to be root.
 
1384
    The actual mode of path is set to: rws--s--s
 
1385
    """
 
1386
    print "chown root:root", file
 
1387
    if not dry:
 
1388
        os.chown(file, 0, 0)
 
1389
    print "chmod a+xs", file
 
1390
    print "chmod u+rw", file
 
1391
    if not dry:
 
1392
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
 
1393
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
 
1394
 
 
1395
def action_chmod_x(file, dry):
 
1396
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
1397
    print "chmod 755", file
 
1398
    if not dry:
 
1399
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
1400
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
1401
 
 
1402
 
 
1403
def action_chmod_w(file, dry):
 
1404
    """Chmod 777 a file (sets permissions to rwxrwxrwx)."""
 
1405
    print "chmod 777", file
 
1406
    if not dry:
 
1407
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
1408
            | stat.S_IXGRP | stat.S_IWGRP | stat.S_IRGRP | stat.S_IXOTH
 
1409
            | stat.S_IWOTH | stat.S_IROTH)
 
1410
 
 
1411
def query_user(default, prompt):
 
1412
    """Prompts the user for a string, which is read from a line of stdin.
 
1413
    Exits silently if EOF is encountered. Returns the string, with spaces
 
1414
    removed from the beginning and end.
 
1415
 
 
1416
    Returns default if a 0-length line (after spaces removed) was read.
 
1417
    """
 
1418
    sys.stdout.write('%s\n    (default: "%s")\n>' % (prompt, default))
 
1419
    try:
 
1420
        val = sys.stdin.readline()
 
1421
    except KeyboardInterrupt:
 
1422
        # Ctrl+C
 
1423
        sys.stdout.write("\n")
 
1424
        sys.exit(1)
 
1425
    sys.stdout.write("\n")
 
1426
    # If EOF, exit
 
1427
    if val == '': sys.exit(1)
 
1428
    # If empty line, return default
 
1429
    val = val.strip()
 
1430
    if val == '': return default
 
1431
    return val
 
1432
 
 
1433
def filter_mutate(function, list):
 
1434
    """Like built-in filter, but mutates the given list instead of returning a
 
1435
    new one. Returns None."""
 
1436
    i = len(list)-1
 
1437
    while i >= 0:
 
1438
        # Delete elements which do not match
 
1439
        if not function(list[i]):
 
1440
            del list[i]
 
1441
        i -= 1
 
1442
 
 
1443
def get_svn_revision():
 
1444
    """Returns either the current SVN revision of this build, or None"""
 
1445
    try:
 
1446
        svn = pysvn.Client()
 
1447
        entry = svn.info('.')
 
1448
        revnum = entry.revision.number
 
1449
    except pysvn.ClientError, e:
 
1450
        revnum = None
 
1451
    return revnum
95
1452
 
96
1453
if __name__ == "__main__":
97
1454
    sys.exit(main())