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
# build and install IVLE in 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
28
# setup.py conf [args]
29
# Configures IVLE with machine-specific details, most notably, various paths.
30
# Either prompts the administrator for these details or accepts them as
32
# Creates www/conf/conf.py and trampoline/conf.h.
35
# Compiles all files and sets up a jail template in the source directory.
37
# Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
39
# Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
40
# Copies console/ to a location within the jail.
41
# Copies OS programs and files to corresponding locations within the jail
42
# (eg. python and Python libs, ld.so, etc).
43
# Generates .pyc files for all the IVLE .py files.
45
# setup.py listmake (for developer use only)
46
# Recurses through the source tree and builds a list of all files which should
47
# be copied upon installation. This should be run by the developer before
48
# cutting a distribution, and the listfile it generates should be included in
49
# the distribution, avoiding the administrator having to run it.
51
# setup.py install [--nojail] [--dry|n]
53
# Create target install directory ($target).
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).
60
# TODO: List in help, and handle, args for the conf operation
68
# Try importing existing conf, but if we can't just set up defaults
69
# The reason for this is that these settings are used by other phases
70
# of setup besides conf, so we need to know them.
71
# Also this allows you to hit Return to accept the existing value.
73
confmodule = __import__("www/conf/conf")
74
root_dir = confmodule.root_dir
75
ivle_install_dir = confmodule.ivle_install_dir
76
jail_base = confmodule.jail_base
78
# Just set reasonable defaults
80
ivle_install_dir = "/opt/ivle"
81
jail_base = "/home/informatics/jails"
85
# Main function skeleton from Guido van Rossum
86
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
88
class Usage(Exception):
89
def __init__(self, msg):
33
92
def main(argv=None):
56
oper_func = call_operator(operation)
57
return oper_func(argv[2:])
115
# Call the requested operation's function
121
'listmake' : listmake,
123
}[operation](argv[2:])
125
print >>sys.stderr, (
126
"""Invalid operation '%s'. Try python setup.py help."""
131
opts, args = getopt.getopt(argv[1:], "h", ["help"])
132
except getopt.error, msg:
134
# more code, unchanged
136
print >>sys.stderr, err.msg
137
print >>sys.stderr, "for help use --help"
140
# Operation functions
61
print """Usage: python setup.py operation [options]
144
print """Usage: python setup.py operation [args]
145
Operation (and args) can be:
67
For help and options for a specific operation use 'help [operation]'."""
70
oper_func = call_operator(operator)
71
oper_func(['operator','--help'])
73
def call_operator(operation):
74
# Call the requested operation's function
78
'build' : setup.build.build,
79
'install' : setup.install.install,
149
install [--nojail] [-n|--dry]
153
print """Usage: python setup.py help [operation]"""
158
if operation == 'help':
159
print """python setup.py help [operation]
160
Prints the usage message or detailed help on an operation, then exits."""
161
elif operation == 'conf':
162
print """python setup.py conf [args]
163
Configures IVLE with machine-specific details, most notably, various paths.
164
Either prompts the administrator for these details or accepts them as
166
Creates www/conf/conf.py and trampoline/conf.h.
169
elif operation == 'build':
170
print """python setup.py build
171
Compiles all files and sets up a jail template in the source directory.
173
Compiles (GCC) trampoline/trampoline.c to trampoline/trampoline.
175
Creates standard subdirs inside the jail, eg bin, opt, home, tmp.
176
Copies console/ to a location within the jail.
177
Copies OS programs and files to corresponding locations within the jail
178
(eg. python and Python libs, ld.so, etc).
179
Generates .pyc files for all the IVLE .py files."""
180
elif operation == 'listmake':
181
print """python setup.py listmake
182
(For developer use only)
183
Recurses through the source tree and builds a list of all files which should
184
be copied upon installation. This should be run by the developer before
185
cutting a distribution, and the listfile it generates should be included in
186
the distribution, avoiding the administrator having to run it."""
187
elif operation == 'install':
188
print """sudo python setup.py install [--nojail] [--dry|-n]
190
Create target install directory ($target).
192
Copy trampoline/trampoline to $target/bin.
193
chown and chmod the installed trampoline.
194
Copy www/ to $target.
195
Copy jail/ to jails template directory (unless --nojail specified).
197
--nojail Do not copy the jail.
198
--dry | -n Print out the actions but don't do anything."""
82
200
print >>sys.stderr, (
83
201
"""Invalid operation '%s'. Try python setup.py help."""
206
# We build two separate lists, by walking www and console
207
list_www = build_list_py_files('www')
208
list_console = build_list_py_files('console')
209
# Make sure that the files generated by conf are in the list
210
# (since listmake is typically run before conf)
211
if "www/conf/conf.py" not in list_www:
212
list_www.append("www/conf/conf.py")
213
# Write these out to a file
215
# the files that will be created/overwritten
216
listfile = os.path.join(cwd, "install_list.py")
219
file = open(listfile, "w")
221
file.write("""# IVLE Configuration File
223
# Provides lists of all Python files to be installed by `setup.py install'.
225
# List of all installable Python files in www directory.
227
writelist_pretty(file, list_www)
229
# List of all installable Python files in console directory.
231
writelist_pretty(file, list_console)
234
except IOError, (errno, strerror):
235
print "IO error(%s): %s" % (errno, strerror)
238
print "Successfully wrote install_list.py"
241
print ("You may modify the set of installable files before cutting the "
248
def build_list_py_files(dir):
249
"""Builds a list of all py files found in a directory and its
250
subdirectories. Returns this as a list of strings."""
252
for (dirpath, dirnames, filenames) in os.walk(dir):
253
# Exclude directories beginning with a '.' (such as '.svn')
254
filter_mutate(lambda x: x[0] != '.', dirnames)
255
# All *.py files are added to the list
256
pylist += [os.path.join(dirpath, item) for item in filenames
257
if item.endswith('.py')]
260
def writelist_pretty(file, list):
261
"""Writes a list one element per line, to a file."""
267
file.write(' %s,\n' % repr(elem))
271
global root_dir, ivle_install_dir, jail_base, allowed_uids
272
# Set up some variables
275
# the files that will be created/overwritten
276
conffile = os.path.join(cwd, "www/conf/conf.py")
277
conf_hfile = os.path.join(cwd, "trampoline/conf.h")
279
# Fixed config options that we don't ask the admin
281
default_app = "dummy"
283
print """This tool will create the following files:
286
prompting you for details about your configuration. The file will be
287
overwritten if it already exists. It will *not* install or deploy IVLE.
289
Please hit Ctrl+C now if you do not wish to do this.
290
""" % (conffile, conf_hfile)
292
# Get information from the administrator
293
# If EOF is encountered at any time during the questioning, just exit
296
root_dir = query_user(root_dir,
297
"""Root directory where IVLE is located (in URL space):""")
298
ivle_install_dir = query_user(ivle_install_dir,
299
'Root directory where IVLE will be installed (on the local file '
301
jail_base = query_user(jail_base,
302
"""Root directory where the jails (containing user files) are stored
303
(on the local file system):""")
304
allowed_uids = query_user(allowed_uids,
305
"""UID of the web server process which will run IVLE.
306
Only this user may execute the trampoline. May specify multiple users as
307
a comma-separated list.
310
# Error handling on input values
313
allowed_uids = map(int, allowed_uids.split(','))
315
print >>sys.stderr, (
316
"Invalid UID list (%s).\n"
317
"Must be a comma-separated list of integers." % allowed_uids)
320
# Write www/conf/conf.py
323
conf = open(conffile, "w")
325
conf.write("""# IVLE Configuration File
327
# Miscellaneous application settings
330
# In URL space, where in the site is IVLE located. (All URLs will be prefixed
332
# eg. "/" or "/ivle".
335
# In the local file system, where IVLE is actually installed.
336
# This directory should contain the "www" and "bin" directories.
337
ivle_install_dir = "%s"
339
# In the local file system, where are the student/user file spaces located.
340
# The user jails are expected to be located immediately in subdirectories of
344
# Which application to load by default (if the user navigates to the top level
345
# of the site). This is the app's URL name.
346
# Note that if this app requires authentication, the user will first be
347
# presented with the login screen.
349
""" % (root_dir, ivle_install_dir, jail_base, default_app))
352
except IOError, (errno, strerror):
353
print "IO error(%s): %s" % (errno, strerror)
356
print "Successfully wrote www/conf/conf.py"
358
# Write trampoline/conf.h
361
conf = open(conf_hfile, "w")
363
conf.write("""/* IVLE Configuration File
365
* Administrator settings required by trampoline.
366
* Note: trampoline will have to be rebuilt in order for changes to this file
370
/* In the local file system, where are the jails located.
371
* The trampoline does not allow the creation of a jail anywhere besides
372
* jail_base or a subdirectory of jail_base.
374
static const char* jail_base = "%s";
376
/* Which user IDs are allowed to run the trampoline.
377
* This list should be limited to the web server user.
378
* (Note that root is an implicit member of this list).
380
static const int allowed_uids[] = { %s };
381
""" % (jail_base, repr(allowed_uids)[1:-1]))
384
except IOError, (errno, strerror):
385
print "IO error(%s): %s" % (errno, strerror)
388
print "Successfully wrote trampoline/conf.h"
391
print "You may modify the configuration at any time by editing"
398
dry = False # Set to True later if --dry
400
# Compile the trampoline
401
action_runprog('gcc', ['-Wall', '-o', 'trampoline/trampoline',
402
'trampoline/trampoline.c'], dry)
404
# Create the jail and its subdirectories
406
action_mkdir('jail/bin')
407
action_mkdir('jail/lib')
408
action_mkdir('jail/usr/bin')
409
action_mkdir('jail/usr/lib')
410
action_mkdir('jail/opt/ivle')
411
action_mkdir('jail/home')
412
action_mkdir('jail/tmp')
414
# TODO: Copy console into the jail
415
# TODO: Copy operating system files into the jail
416
# TODO: Compile .py files into .pyc files
424
# The actions call Python os functions but print actions and handle dryness.
425
# May still throw os exceptions if errors occur.
428
"""Represents an error when running a program (nonzero return)."""
429
def __init__(self, prog, retcode):
431
self.retcode = retcode
433
return str(self.prog) + " returned " + repr(self.retcode)
435
def action_runprog(prog, args, dry):
436
"""Runs a unix program. Searches in $PATH. Synchronous (waits for the
437
program to return). Runs in the current environment. First prints the
438
action as a "bash" line.
440
Throws a RunError with a retcode of the return value of the program,
441
if the program did not return 0.
443
prog: String. Name of the program. (No path required, if in $PATH).
444
args: [String]. Arguments to the program.
445
dry: Bool. If True, prints but does not execute.
447
print prog, string.join(args, ' ')
449
ret = os.spawnvp(os.P_WAIT, prog, args)
451
raise RunError(prog, ret)
453
def action_mkdir(path):
454
"""Calls mkdir. Silently ignored if the directory already exists.
455
Creates all parent directories as necessary."""
456
print "mkdir -p", path
460
except OSError, (err, msg):
461
if err != errno.EEXIST:
464
def query_user(default, prompt):
465
"""Prompts the user for a string, which is read from a line of stdin.
466
Exits silently if EOF is encountered. Returns the string, with spaces
467
removed from the beginning and end.
469
Returns default if a 0-length line (after spaces removed) was read.
471
sys.stdout.write('%s\n (default: "%s")\n>' % (prompt, default))
473
val = sys.stdin.readline()
474
except KeyboardInterrupt:
476
sys.stdout.write("\n")
478
sys.stdout.write("\n")
480
if val == '': sys.exit(1)
481
# If empty line, return default
483
if val == '': return default
486
def filter_mutate(function, list):
487
"""Like built-in filter, but mutates the given list instead of returning a
488
new one. Returns None."""
491
# Delete elements which do not match
492
if not function(list[i]):
88
496
if __name__ == "__main__":