aboutsummaryrefslogtreecommitdiff
path: root/gitosis/init.py
diff options
context:
space:
mode:
Diffstat (limited to 'gitosis/init.py')
-rw-r--r--gitosis/init.py178
1 files changed, 178 insertions, 0 deletions
diff --git a/gitosis/init.py b/gitosis/init.py
new file mode 100644
index 0000000..bf0eb9b
--- /dev/null
+++ b/gitosis/init.py
@@ -0,0 +1,178 @@
+"""
+Initialize a user account for use with gitosis.
+"""
+
+import errno
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+
+from pkg_resources import resource_filename
+from cStringIO import StringIO
+from ConfigParser import RawConfigParser
+
+from gitosis import repository
+from gitosis import util
+
+log = logging.getLogger('gitosis.init')
+
+def die(msg):
+ log.error(msg)
+ sys.exit(1)
+
+def read_ssh_pubkey(fp=None):
+ if fp is None:
+ fp = sys.stdin
+ line = fp.readline()
+ return line
+
+_ACCEPTABLE_USER_RE = re.compile(r'^[a-z][a-z0-9]*@[a-z][a-z0-9]*$')
+
+class InsecureSSHKeyUsername(Exception):
+ """Username contains not allowed characters"""
+
+ def __str__(self):
+ return '%s: %s' % (self.__doc__, ': '.join(self.args))
+
+def ssh_extract_user(pubkey):
+ _, user = pubkey.rsplit(None, 1)
+ if _ACCEPTABLE_USER_RE.match(user):
+ return user
+ else:
+ raise InsecureSSHKeyUsername(repr(user))
+
+def initial_commit(git_dir, cfg, pubkey, user):
+ repository.fast_import(
+ git_dir=git_dir,
+ commit_msg='Automatic creation of gitosis repository.',
+ committer='Gitosis Admin <%s>' % user,
+ files=[
+ ('keydir/%s.pub' % user, pubkey),
+ ('gitosis.conf', cfg),
+ ],
+ )
+
+def run_post_update(git_dir):
+ args = [os.path.join(git_dir, 'hooks', 'post-update')]
+ returncode = subprocess.call(
+ args=args,
+ cwd=git_dir,
+ close_fds=True,
+ env=dict(GIT_DIR='.'),
+ )
+ if returncode != 0:
+ die(
+ ("post-update returned non-zero exit status %d"
+ % returncode),
+ )
+
+def symlink_config(git_dir):
+ dst = os.path.expanduser('~/.gitosis.conf')
+ tmp = '%s.%d.tmp' % (dst, os.getpid())
+ try:
+ os.unlink(tmp)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ pass
+ else:
+ raise
+ os.symlink(
+ os.path.join(git_dir, 'gitosis.conf'),
+ tmp,
+ )
+ os.rename(tmp, dst)
+
+def getParser():
+ parser = optparse.OptionParser(
+ usage='%prog',
+ description='Initialize a user account for use with gitosis',
+ )
+ parser.set_defaults(
+ config=os.path.expanduser('~/.gitosis.conf'),
+ )
+ parser.add_option('--config',
+ metavar='FILE',
+ help='read config from FILE',
+ )
+ return parser
+
+def init_admin_repository(
+ git_dir,
+ pubkey,
+ user,
+ ):
+ repository.init(
+ path=git_dir,
+ template=resource_filename('gitosis.templates', 'admin')
+ )
+ repository.init(
+ path=git_dir,
+ )
+ if not repository.has_initial_commit(git_dir):
+ log.info('Making initial commit...')
+ # ConfigParser does not guarantee order, so jump through hoops
+ # to make sure [gitosis] is first
+ cfg_file = StringIO()
+ print >>cfg_file, '[gitosis]'
+ print >>cfg_file
+ cfg = RawConfigParser()
+ cfg.add_section('group gitosis-admin')
+ cfg.set('group gitosis-admin', 'members', user)
+ cfg.set('group gitosis-admin', 'writable', 'gitosis-admin')
+ cfg.write(cfg_file)
+ initial_commit(
+ git_dir=git_dir,
+ cfg=cfg_file.getvalue(),
+ pubkey=pubkey,
+ user=user,
+ )
+
+def main():
+ logging.basicConfig(level=logging.INFO)
+ os.umask(0022)
+
+ parser = getParser()
+ (options, args) = parser.parse_args()
+ if args:
+ parser.error('Did not expect arguments.')
+
+ cfg = RawConfigParser()
+ try:
+ conffile = file(options.config)
+ except (IOError, OSError), e:
+ if e.errno == errno.ENOENT:
+ # not existing is ok
+ pass
+ else:
+ # I trust the exception has the path.
+ die("Unable to read config file: %s." % e)
+ else:
+ try:
+ cfg.readfp(conffile)
+ finally:
+ conffile.close()
+
+
+ log.info('Reading SSH public key...')
+ pubkey = read_ssh_pubkey()
+ user = ssh_extract_user(pubkey)
+ if user is None:
+ die('Cannot parse user from SSH public key.')
+ log.info('Admin user is %r', user)
+ log.info('Creating repository structure...')
+ repositories = util.getRepositoryDir(cfg)
+ util.mkdir(repositories)
+ admin_repository = os.path.join(repositories, 'gitosis-admin.git')
+ init_admin_repository(
+ git_dir=admin_repository,
+ pubkey=pubkey,
+ user=user,
+ )
+ log.info('Running post-update hook...')
+ run_post_update(git_dir=admin_repository)
+ log.info('Symlinking ~/.gitosis.conf to repository...')
+ symlink_config(git_dir=admin_repository)
+ log.info('Done.')