45
def make_svn_repo(login):
49
def chown_to_webserver(filename):
51
Chowns a file so the web server user owns it.
52
(This is useful in setting up Subversion conf files).
56
os.system("chown -R www-data:www-data %s" % filename)
60
def make_svn_repo(login, throw_on_error=True):
46
61
"""Create a repository for the given user.
48
63
path = os.path.join(conf.svn_repo_path, login)
49
res = os.system("svnadmin create '%s'" % path)
51
raise Exception("Cannot create repository for %s" % login)
65
res = os.system("svnadmin create '%s'" % path)
66
if res != 0 and throw_on_error:
67
raise Exception("Cannot create repository for %s" % login)
68
except Exception, exc:
73
chown_to_webserver(path)
53
75
def rebuild_svn_config():
54
76
"""Build the complete SVN configuration file.
74
96
f.write("[%s:/]\n" % login)
75
97
f.write("%s = rw\n" % login)
76
f.write("@tutor = r\n")
77
f.write("@lecturer = rw\n")
78
f.write("@admin = rw\n")
98
#f.write("@tutor = r\n")
99
#f.write("@lecturer = rw\n")
100
#f.write("@admin = rw\n")
81
103
os.rename(conf.svn_conf + ".new", conf.svn_conf)
104
chown_to_webserver(conf.svn_conf)
83
def make_svn_config(login):
106
def make_svn_config(login, throw_on_error=True):
84
107
"""Add an entry to the apache-svn config file for the given user.
85
108
Assumes the given user is either a guest or a student.
87
110
f = open(conf.svn_conf, "a")
88
111
f.write("[%s:/]\n" % login)
89
112
f.write("%s = rw\n" % login)
90
f.write("@tutor = r\n")
91
f.write("@lecturer = rw\n")
92
f.write("@admin = rw\n")
113
#f.write("@tutor = r\n")
114
#f.write("@lecturer = rw\n")
115
#f.write("@admin = rw\n")
118
chown_to_webserver(conf.svn_conf)
96
def make_svn_auth(login):
120
def make_svn_auth(login, throw_on_error=True):
97
121
"""Setup svn authentication for the given user.
98
122
FIXME: create local.auth entry
106
db.DB().update_user({'svn_pass':passwd})
130
db.DB().update_user(login, svn_pass=passwd)
108
res = os.system("htpasswd -%smb %s %s" % (create,
132
res = os.system("htpasswd -%smb %s %s %s" % (create,
109
133
conf.svn_auth_ivle,
135
if res != 0 and throw_on_error:
112
136
raise Exception("Unable to create ivle-auth for %s" % login)
114
def make_jail(username, uid, force=True):
138
# Make sure the file is owned by the web server
140
chown_to_webserver(conf.svn_auth_ivle)
144
def generate_manifest(basedir, targetdir, parent=''):
145
""" From a basedir and a targetdir work out which files are missing or out
146
of date and need to be added/updated and which files are redundant and need
149
parent: This is used for the recursive call to track the relative paths
150
that we have decended.
153
cmp = filecmp.dircmp(basedir, targetdir)
155
# Add all new files and files that have changed
156
to_add = [os.path.join(parent,x) for x in (cmp.left_only + cmp.diff_files)]
158
# Remove files that are redundant
159
to_remove = [os.path.join(parent,x) for x in cmp.right_only]
162
for d in cmp.common_dirs:
163
newbasedir = os.path.join(basedir, d)
164
newtargetdir = os.path.join(targetdir, d)
165
newparent = os.path.join(parent, d)
166
(sadd,sremove) = generate_manifest(newbasedir, newtargetdir, newparent)
170
return (to_add, to_remove)
173
def make_jail(username, uid, force=True, manifest=None, svn_pass=None):
115
174
"""Creates a new user's jail space, in the jail directory as configured in
118
This expects there to be a "template" directory within the jail root which
177
This expects there to be a "staging" directory within the jail root which
119
178
contains all the files for a sample student jail. It creates the student's
120
179
directory in the jail root, by making a hard-link copy of every file in the
121
template directory, recursively.
180
staging directory, recursively.
123
182
Returns the path to the user's home directory.
132
191
force: If false, exception if jail already exists for this user.
133
192
If true (default), overwrites it, but preserves home directory.
194
manifest: If provided this will be a pair (to_add, to_remove) of files or
195
directories to add or remove from the jail.
197
svn_pass: If provided this will be a string, the randomly-generated
198
Subversion password for this user (if you happen to already have it).
199
If not provided, it will be read from the database.
135
201
# MUST run as root or some of this may fail
136
202
if os.getuid() != 0:
137
203
raise Exception("Must run make_jail as root")
139
templatedir = os.path.join(conf.jail_base, 'template')
140
if not os.path.isdir(templatedir):
141
raise Exception("Template jail directory does not exist: " +
205
stagingdir = os.path.join(conf.jail_base, '__staging__')
206
if not os.path.isdir(stagingdir):
207
raise Exception("Staging jail directory does not exist: " +
143
209
# tempdir is for putting backup homes in
144
tempdir = os.path.join(conf.jail_base, 'temp')
210
tempdir = os.path.join(conf.jail_base, '__temp__')
145
211
if not os.path.exists(tempdir):
146
212
os.makedirs(tempdir)
147
213
elif not os.path.isdir(tempdir):
168
234
# the backup will be un-made.
169
235
# XXX This will still leave the user's jail in an unusable state,
170
236
# but at least they won't lose their files.
171
shutil.rmtree(userdir)
173
# Hard-link (copy aliasing) the entire tree over
174
linktree(templatedir, userdir)
238
(to_add, to_remove) = manifest
239
# Remove redundant files and directories
241
dst = os.path.join(userdir, d)
242
src = os.path.join(stagingdir, d)
243
if os.path.isdir(dst):
245
elif os.path.isfile(dst):
249
dst = os.path.join(userdir, d)
250
src = os.path.join(stagingdir, d)
251
# Clear the previous file/dir
252
if os.path.isdir(dst):
254
elif os.path.isfile(dst):
257
if os.path.isdir(src):
259
elif os.path.isfile(src):
263
# No manifest, do a full rebuild
264
shutil.rmtree(userdir)
265
# Hard-link (copy aliasing) the entire tree over
266
linktree(stagingdir, userdir)
176
268
# Set up the user's home directory (restore backup)
177
269
# First make sure the directory is empty and its parent exists
183
275
# directory). But it shouldn't fail as homedir should not exist.
184
276
os.makedirs(homedir)
185
277
shutil.move(homebackup, homedir)
186
return os.path.join(homedir, username)
278
userhomedir = os.path.join(homedir, username) # Return value
188
280
# No user jail exists
189
281
# Hard-link (copy aliasing) the entire tree over
190
linktree(templatedir, userdir)
282
linktree(stagingdir, userdir)
192
284
# Set up the user's home directory
193
285
userhomedir = os.path.join(homedir, username)
196
288
os.chown(userhomedir, uid, uid)
197
289
# Chmod to rwxr-xr-x (755)
198
290
os.chmod(userhomedir, 0755)
292
# There is 1 special file which should not be hard-linked, but instead
293
# generated specific to this user: /opt/ivle/lib/conf/conf.py.
294
# "__" username "__" users are exempt (special)
295
if not (username.startswith("__") and username.endswith("__")):
296
make_conf_py(username, userdir, stagingdir, svn_pass)
300
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
302
Creates (overwriting any existing file) a file /opt/ivle/lib/conf/conf.py
303
in a given user's jail.
305
user_jail_dir: User's jail dir, ie. conf.jail_base + username
306
staging_dir: The dir with the staging copy of the jail. (With the
307
template conf.py file).
308
svn_pass: As with make_jail. User's SVN password, but if not supplied,
309
will look up in the DB.
311
# Note: It is important to delete this file and recreate it (somewhat
312
# ironically beginning by pasting the same contents in again), rather than
314
# Note that all files initially are aliased, so appending would result
315
# in a massive aliasing problem. Deleting and recreating ensures that
316
# the conf.py files are unique to each jail.
317
template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
318
conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
320
# If svn_pass isn't supplied, grab it from the DB
323
svn_pass = dbconn.get_user(username).svn_pass
326
# Read the contents of the template conf file
328
template_conf_file = open(template_conf_path, "r")
329
template_conf_data = template_conf_file.read()
330
template_conf_file.close()
332
# Couldn't open template conf.py for some reason
333
# Just treat it as empty file
334
template_conf_data = ("# Warning: Problem building config script.\n"
335
"# Could not find template conf.py file.\n")
337
# Remove the target conf file if it exists
342
conf_file = open(conf_path, "w")
343
conf_file.write(template_conf_data)
344
conf_file.write("\n# The login name for the owner of the jail\n")
345
conf_file.write("login = %s\n" % repr(username))
346
conf_file.write("\n")
347
conf_file.write("# The subversion-only password for the owner of "
349
conf_file.write("svn_pass = %s\n" % repr(svn_pass))
352
# Make this file world-readable
353
# (chmod 644 conf_path)
354
os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
201
357
def linktree(src, dst):
202
358
"""Recursively hard-link a directory tree using os.link().
238
394
raise Exception, errors
240
def make_user_db(**kwargs):
396
def make_user_db(throw_on_error = True, **kwargs):
241
397
"""Creates a user's entry in the database, filling in all the fields.
242
398
All arguments must be keyword args. They are the fields in the table.
243
399
However, instead of supplying a "passhash", you must supply a
250
406
dbconn.create_user(**kwargs)
409
if kwargs['password']:
410
if os.path.exists(conf.svn_auth_local):
414
res = os.system("htpasswd -%smb %s %s %s" % (create,
418
if res != 0 and throw_on_error:
419
raise Exception("Unable to create local-auth for %s" % kwargs['login'])
421
# Make sure the file is owned by the web server
423
chown_to_webserver(conf.svn_auth_local)