Talk:Gitosis

From DreamHost
Jump to: navigation, search

Do not edit your own .ssh/authorized_keys

Just to note gitosis generates it's own .ssh/authorized_keys file. So if you edit this when you first create the SSH account things will fail. Suki 13:08, 30 March 2009 (UTC)

Allowing http based repository access

Suki 10:09, 31 March 2009 (UTC)

WARNING: The following in NOT an ideal solution. A better one is in the works. Take it as is and without any implied warranty or usefulness.

Because it is not allowed to run git-daemon on a shared Dreamhost I still wanted a way for public repositories to be available to clone publicly. Gitosis offers great access control for the read write operations via SSH.

This is what I did to get http repository support for the Gitosis install described here. I had to patch the Gitosis code to allow public repositories access. But first let me describe to you my directory setup.

As per the default install Gitosis creates a repositories directory in your $HOME folder. By default all git repositories that are created here have the directory permissions of 750 (or Owner read,write,execute; Group read,write ; Other nothing) what this means is that if you attempt to symlink to this directory the webserver (and thus a git clone opperation) will fail with access denied.

So first mission is to set all public repositories permissions to 755 allowing the Other group read access to the public repositories. The patch mentioned will do this automatically for all repositories that have gitweb = ok set (or all if globally set)

The second hurtle is that the repositories directory sits outside the website document root. Meaning that you have to access these public repositories via symlinks.

Why not place the repositories directory inside the web document root? Because this would expose private repositories including gitosis-admin!

The patch mentioned will automatically add a symlink to the proper public directory within the document root.

So this is how it works. Apply the patch at the bottom of this page. (You can upload this via ftp or use copy / paste to a running editor via ssh.)

cd
cd src/gitosis
patch -p1 < $HOME/0001-patched-for-dreamhost-custom-config.patch

Now continue the instructions to install gitosis

python setup.py install --prefix=$HOME

Next make the appropriate public directory. For me I created a sub-domain for this user using dreamhost's web panel (For this I will use example.com)

cd
cd git.example.com
mkdir repos

Once complete follow the instructions to edit your gitosis-admin and add the following to the [gitosis] section.

public_html_path = /home/gituser/git.example.com/repos

In your gitweb.conf be sure to put in

@git_base_url_list = ('http://git.example.com/repos');

0001-Added-Apache-htaccess-patch-for-public-http-access.patch

From 6ad425d93f2be765105d56af03e273cf84969d4c Mon Sep 17 00:00:00 2001
From: Devin Weaver <suki@tritarget.org>
Date: Fri, 12 Jun 2009 00:59:11 -0400
Subject: [PATCH] Added Apache htaccess patch for public http access.

Dreamhost and some other hosting services do not allow public access to
a git repository via git-daemon. This is a patch that will enable a set
of Apache htaccess files that disallow access to non public repositories
while allowing public anonymous access via the http protocal. The
repositories directory must be within the DocumentRoot. Use
``public_http = YES`` in a repo section to enable this feature. This
also includes the following patch:

    Fixed permissions problem.

    It seems that when the setup.py script builds the package it does not
    honor the executable permissions for the admin/hooks/post-update. This
    means that when the gitosis-admin.git is updated the changes are not
    realized until someone attepts gitosis-serve or a second commit is done.
    Added a hack to set the permissions when the initial gitosis-admin.git
    repository is created.
---
 gitosis/gitweb.py     |  104 +++++++++++++++++++++++++++++++++++++++++++++++++
 gitosis/init.py       |    3 +
 gitosis/repository.py |    3 +-
 gitosis/run_hook.py   |    1 +
 gitosis/serve.py      |    4 +-
 5 files changed, 113 insertions(+), 2 deletions(-)

diff --git a/gitosis/gitweb.py b/gitosis/gitweb.py
index b4b538b..63e5b5d 100644
--- a/gitosis/gitweb.py
+++ b/gitosis/gitweb.py
@@ -163,3 +163,107 @@ def set_descriptions(config):
         finally:
             f.close()
         os.rename(tmp, path)
+
+
+def generate_public_htaccess(config):
+    """
+    Generate Apache htaccess files that will allow only ``public_http``
+    enabled repos access via http.
+
+    :param config: configuration to read projects from
+    :type config: RawConfigParser
+    """
+    log = logging.getLogger('gitosis.gitweb.generate_public_htaccess')
+
+    log.info('Generating public http htaccess files if needed.')
+    path = os.path.join(util.getGeneratedFilesDir(config), 'htaccess')
+    tmp_file = '%s.%d.tmp' % (path, os.getpid())
+
+    repositories = util.getRepositoryDir(config)
+
+    is_htaccess_used = False
+
+    try:
+        global_enable = config.getboolean('gitosis', 'public_http')
+    except (NoSectionError, NoOptionError):
+        global_enable = False
+
+    for section in config.sections():
+        l = section.split(None, 1)
+        type_ = l.pop(0)
+        if type_ != 'repo':
+            continue
+        if not l:
+            continue
+
+        try:
+            enable = config.getboolean(section, 'public_http')
+        except (NoSectionError, NoOptionError):
+            enable = global_enable
+
+        name, = l
+
+        if enable:
+            is_htaccess_used = True
+
+        if not os.path.exists(os.path.join(repositories, name)):
+            namedotgit = '%s.git' % name
+            if os.path.exists(os.path.join(repositories, namedotgit)):
+                name = namedotgit
+            else:
+                log.warning(
+                    'Cannot find %(name)r in %(repositories)r'
+                    % dict(name=name, repositories=repositories))
+                continue
+
+        if not enable:
+            disabled_htaccess = os.path.join(repositories, name, '.htaccess')
+            if os.path.exists(disabled_htaccess):
+                log.debug('Removing .htaccess from repository %s' % name)
+                os.unlink(disabled_htaccess)
+            continue
+
+# Create .htaccess file for public repo.
+        log.debug('Generating .htaccess for repository %s' % name)
+        fp = file(tmp_file, 'w')
+        try:
+            print >>fp, "# This is a generated document by gitosis."
+            print >>fp, "# DO NOT EDIT. Changes will be lost."
+            print >>fp, "Order Allow,Deny"
+            print >>fp, "Allow from all"
+        finally:
+            fp.close()
+        os.rename(tmp_file, os.path.join(repositories, name, '.htaccess'))
+
+# Create post-update hook for public repo.
+        log.debug('Generating post-update hook for repository %s' % name)
+        fp = file(tmp_file, 'w')
+        try:
+            print >>fp, "#!/bin/sh"
+            print >>fp, "# This is a generated document by gitosis."
+            print >>fp, "# DO NOT EDIT. Changes will be lost."
+            print >>fp
+            print >>fp, "exec git-update-server-info"
+        finally:
+            fp.close()
+        hook = os.path.join(repositories, name, 'hooks', 'post-update')
+        os.chmod(tmp_file, 0755)
+        os.rename(tmp_file, hook)
+
+# Force Apache to refuse all connections in the repository. Public access is
+# overridden by the existance of the above htaccess per repository.
+    log.debug('Generating .htaccess for main repository directory')
+    repo_htaccess = os.path.join(util.getRepositoryDir(config), '.htaccess')
+    if is_htaccess_used:
+        fp = file(tmp_file, 'w')
+        try:
+            print >>fp, "# This is a generated document by gitosis."
+            print >>fp, "# DO NOT EDIT. Changes will be lost."
+            print >>fp, "Order Deny,Allow"
+            print >>fp, "Deny from all"
+        finally:
+            fp.close()
+        os.rename(tmp_file, repo_htaccess)
+    elif os.path.exists(repo_htaccess):
+        log.debug('Removing .htaccess for main repository directory')
+        os.unlink(repo_htaccess)
diff --git a/gitosis/init.py b/gitosis/init.py
index 87ad9a7..c77a130 100644
--- a/gitosis/init.py
+++ b/gitosis/init.py
@@ -77,6 +77,9 @@ def init_admin_repository(
     repository.init(
         path=git_dir,
         )
+    # The setup.py does not appear to honor the execute permissions of the
+    # template files when packaged. Force executable here.
+    os.chmod(os.path.join(git_dir, 'hooks', 'post-update'), 0755)
     if not repository.has_initial_commit(git_dir):
         log.info('Making initial commit...')
         # ConfigParser does not guarantee order, so jump through hoops
diff --git a/gitosis/repository.py b/gitosis/repository.py
index 092e41d..7b8d8c0 100644
--- a/gitosis/repository.py
+++ b/gitosis/repository.py
@@ -36,7 +36,8 @@ def init(
     if _git is None:
         _git = 'git'
 
-    util.mkdir(path, 0750)
+    #util.mkdir(path, 0750)
+    util.mkdir(path, 0755) # Needed for HTTP access. Less secure.
     args = [
         _git,
         '--git-dir=.',
diff --git a/gitosis/run_hook.py b/gitosis/run_hook.py
index e535e6a..4379d09 100644
--- a/gitosis/run_hook.py
+++ b/gitosis/run_hook.py
@@ -39,6 +39,7 @@ def post_update(cfg, git_dir):
         config=cfg,
         path=os.path.join(generated, 'projects.list'),
         )
+    gitweb.generate_public_htaccess(config=cfg)
     gitdaemon.set_export_ok(
         config=cfg,
         )
diff --git a/gitosis/serve.py b/gitosis/serve.py
index 867249e..2cfc756 100644
--- a/gitosis/serve.py
+++ b/gitosis/serve.py
@@ -140,7 +140,8 @@ def serve(
         p = topdir
         for segment in repopath.split(os.sep)[:-1]:
             p = os.path.join(p, segment)
-            util.mkdir(p, 0750)
+            #util.mkdir(p, 0750)
+            util.mkdir(p, 0755) # Needed for HTTP access. Less secure.
 
         repository.init(path=fullpath)
         gitweb.set_descriptions(
@@ -151,6 +152,7 @@ def serve(
             config=cfg,
             path=os.path.join(generated, 'projects.list'),
             )
+        gitweb.generate_public_htaccess(config=cfg)
         gitdaemon.set_export_ok(
             config=cfg,
             )
-- 
1.6.3.1

Sample gitosis.conf

[gitosis]
loglevel = ERROR
daemon = no
gitweb = no
public_http = no

[group gitosis-admin]
writable = gitosis-admin
members = gituser@remote.host

[group wwwdocs]
writable = wwwdocs
members = @gitosis-admin

[repo testrepo]
gitweb = yes
public_http = yes
owner = gituser
description = A test repository

[group testrepo]
writable = testrepo
members = @gitosis-admin


Where to copy the public key

The instructions suggested that you should copy the SSH public key to ~/tmp using scp. This won't work because most users don't have a ~/tmp directory. Also there was a typo in the command so that the key would be copied to /tmp instead. I have now changed the instructions so that the key is copied to the home directory (~/) and deleted after use. --Marzol3 03:21, 4 June 2011 (PDT)