diff --git a/conf/example.conf b/conf/example.conf index cf52e46..b0776b7 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -6,9 +6,13 @@ # the description string for gitweb) # - comments in the normal shell-ish style; no surprises there # - there are NO continuation lines of any kind -# - user/repo names as simple as possible -# (usernames: only alphanumerics, ".", "_", "-"; -# reponames: same, plus "/", but not at the start) +# - user/repo names as simple as possible; they must start with an +# alphanumeric, but after that they can also contain ".", "_", "-". +# - usernames can optionally be followed by an "@" and a domainname +# containing at least one "." (this allows you to use an email +# address as someone's username) +# - reponames can contain "/" characters (this allows you to +# put your repos in a tree-structure for convenience) # objectives, over and above gitosis: # - simpler syntax @@ -87,7 +91,8 @@ repo gitolite-admin RW+ = @admins - # "@all" is a special, predefined, group name + # "@all" is a special, predefined, group name of all users + # (everyone who has a pubkey in keydir) repo testing RW+ = @all @@ -181,3 +186,26 @@ repo linux perl # give gitweb access as described above if you're specifying a description gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment" + +# REPO SPECIFIC GITCONFIG +# ----------------------- + +# (Thanks to teemu dot matilainen at iki dot fi) + +# this should be specified within a "repo" stanza + +# syntax: +# config sectionname.keyname = [optional value_string] + +# example usage: if you placed a hook in src/hooks that requires configuration +# information that is specific to each repo, you could do this: + +repo gitolite + config hooks.mailinglist = gitolite-commits@example.tld + config hooks.emailprefix = "[gitolite] " + config foo.bar = "" + config foo.baz = + +# This does either a plain "git config section.key value" (for the first 3 +# examples above) or "git config --unset-all section.key" (for the last +# example). Other forms (--add, the value_regex, etc) are not supported. diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index 106e415..af4022f 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -37,13 +37,16 @@ Assumptions/pre-requisites: * git is installed on that server (and so is perl) * you have a userid on that server * you have ssh-pubkey (**password-less**) login to that userid - * (if you have only password access, run `ssh-keygen -t rsa` to create a - new keypair if needed, then run `ssh-copy-id user@host`) + * if you have only password access, run `ssh-keygen -t rsa` to create a + new keypair if needed, then run `ssh-copy-id user@host`. If you do + not have `ssh-copy-id`, read doc/3-faq-tips-etc.mkd and look for + `ssh-copy-id` in that file for instructions * you have a clone or an archive of gitolite somewhere on your workstation + * if you don't have one, just run `git clone git://github.com/sitaramc/gitolite` -If so, just `cd` to that clone and run `src/gl-easy-install` and follow the -prompts! (Running it without any arguments shows you usage plus other useful -info). +Once you have all this, just `cd` to that clone and run `src/gl-easy-install` +and follow the prompts! (Running it without any arguments shows you usage +plus other useful info). #### typical example run diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index d0460ec..a93df4b 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -9,6 +9,7 @@ In this document: * adding users and repos * specifying gitweb and daemon access * custom hooks + * custom git config ### administer @@ -90,3 +91,25 @@ just run easy install once again; it'll do it to existing repos also. implements all the branch-level permissions in gitolite. If you fiddle with the hooks directory, please make sure you do not mess with this file accidentally, or all your fancy per-branch permissions will stop working.** + +#### custom git config + +The custom hooks feature is a blunt instrument -- all repos get the hook you +specified and will run it. In order to make it a little more fine-grained, +you could set your hooks to only work if a certain "gitconfig" variable was +set. Which means we now need a way to specify "git config" settings on a per +repository basis. + +Thanks to Teemu (teemu dot matilainen at iki dot fi), gitolite now does this +very easily. For security reasons, this can only be done from the master +config file (i.e., if you're using delegation, the delegated admins cannot +specify git config settings). + +Please see `conf/example.conf` for syntax. Note that this only supports the +basic forms of the "git config" command: + + git config section.key value # value may be an empty string + git config --unset-all section.key + +It does not (currently) support other options like `--add`, the `value_regex`, +etc. diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index c5e85e8..8f5a5a5 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -19,22 +19,32 @@ In this document: * what repos do I have access to? * "exclude" (or "deny") rules * "personal" branches + * custom hooks and custom git config * repos named with wildcards * design choices * keeping the parser and the access control separate ### common errors and mistakes - * forgetting to suffix `.git` to the end of the reponame in the `git clone`. - This suffix is *not* used in the gitolite config file for the sake of - clarity and cleaner syntax, but don't let that fool you. It's a - convention in the git world that **bare repos** end with `.git`. - * adding `repositories/` at the start of the repo name in the `git clone`. This error is typically made by the *admin* himself -- because he knows what `$REPO_BASE` is set to and thinks he has to provide that prefix on - the client side also :-) In fact gitolite prepends `$REPO_BASE` when it - is required anyway, so you shouldn't do the same thing! + the client side also :-) In fact gitolite prepends `$REPO_BASE` + internally, so you shouldn't also do the same thing! + + * being able to clone but getting errors on push. Most likely caused by a + combination of: + + * you already have shell access to the server, not just "gitolite" + access, *and* + + * you cloned using `git clone git@server:repositories/repo.git` (notice + there's an extra "repositories/" in there?) + + In other words, you used a key that completely bypassed gitolite and went + straight to the shell to do the clone. + + Please see doc/6-ssh-troubleshooting.mkd for what all this means. ### git version dependency @@ -67,6 +77,27 @@ normal way, since it's not empty anymore. ### other errors, warnings, notes... + * don't have `ssh-copy-id`? This is broadly what that command does, if you + want to replicate it manually. The input is your pubkey, typically + `~/.ssh/id_rsa.pub` from your client/workstation. + + * it copies it to the server as some file + + * it appends that file to `~/.ssh/authorized_keys` on the server + (creating it if it doesn't already exist) + + * it then makes sure that all these files/directories have go-w perms + set (assuming user is "git"): + + /home/git/.ssh/authorized_keys + /home/git/.ssh + /home/git + + [Actually, sshd requires that even directories *above* ~ (/, /home, + typically) also must be `go-w`, but that needs root. And typically + they're already set that way anyway. (Or if they're not, you've got + bigger problems than gitolite install not working!)] + * cloning an empty repo is only possible with clients greater than 1.6.2. So at least one of your clients needs to have a recent git. Once at least one commit has been made, older clients can also use it @@ -345,12 +376,36 @@ gitolite knows these two keys belong to the same person. Note that you don't say "sitaram@laptop" and so on in the **config** file -- as far as the config file is concerned there's just **one** user called -"sitaram" -- so you only say "sitaram" there. Only the **pubkey files** have -the extra "@" stuff. +"sitaram" -- so you only say "sitaram" there. I think this is easier to maintain if you have to delete or change one of those keys. +However, now that `sitaramc@gmail.com` is also a valid username, we need to +distinguish between `sitaramc@gmail.com.pub` and `sitaramc@desktop.pub`. We +do that by requiring that the multi-key suffix you use (like "desktop" and +"laptop") should not have a `"."` in it. If it does, it looks like an email +address. The following table lists sample pubkey filenames and the +corresponding derived usernames (which is what goes into the +`conf/gitolite.conf` file): + + * old style multikeys; not mistaken for emails because there is no "." in + hostname part + + sitaramc.pub sitaramc + sitaramc@laptop.pub sitaramc + sitaramc@desktop.pub sitaramc + + * new style, email keys; there is a "." in hostname part; so it's an email + address + + sitaramc@gmail.com.pub sitaramc@gmail.com + + * multikeys *with* email address + + sitaramc@gmail.com@laptop.pub sitaramc@gmail.com + sitaramc@gmail.com@desktop.pub sitaramc@gmail.com + #### support for git installed outside default PATH The normal solution is to add to the system default PATH somehow, either by @@ -507,6 +562,12 @@ first check: Just don't *show* the user this config file; it might sound insulting :-) +#### custom hooks and custom git config + +You can specify hooks that you want to propagate to all repos, as well as +per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and +`conf/example.conf` for details. + #### repos named with wildcards **This feature only exists in the "wildrepos" branch!** Please see diff --git a/src/gitolite.pm b/src/gitolite.pm index 0534a87..1ce0480 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -24,9 +24,9 @@ $WARN = "\n\t\t***** WARNING *****\n "; $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; $W_COMMANDS=qr/^git[ -]receive-pack$/; -# note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't -$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern -$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +# note that REPONAME_PATT allows "/", while USERNAME_PATT allows "@" +$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern +$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@-]*$); # very simple pattern # same as REPONAME, plus some common regex metas $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._/-]*$); diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 618dcae..50dedba 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -36,11 +36,11 @@ $Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }; # - anytime a pubkey is added/deleted # - anytime gitolite.conf is changed # input: -# - GL_CONF (default: ~/.gitolite/gitolite.conf) +# - GL_CONF (default: ~/.gitolite/conf/gitolite.conf) # - GL_KEYDIR (default: ~/.gitolite/keydir) # output: # - ~/.ssh/authorized_keys (dictated by sshd) -# - GL_CONF_COMPILED (default: ~/.gitolite/gitolite.conf-compiled.pm) +# - GL_CONF_COMPILED (default: ~/.gitolite/conf/gitolite.conf-compiled.pm) # security: # - touches a very critical system file that manages the restrictions on # incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see @@ -111,6 +111,9 @@ my %rurp_seen = (); # catch usernames<->pubkeys mismatches; search for "lint" below my %user_list = (); +# repo configurations +my %repo_config = (); + # gitweb descriptions and owners; plain text, keyed by "$repo.git" my %desc = (); my %owner = (); @@ -129,7 +132,7 @@ sub expand_list for my $item (@list) { - die "$ABRT bad user or repo name $item\n" unless $item =~ $REPOPATT_PATT; + die "$ABRT bad user or repo name $item\n" unless $item =~ $REPOPATT_PATT or $item =~ $USERNAME_PATT; if ($item =~ /^@/) # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; @@ -182,7 +185,6 @@ sub parse_conf_file # store the members of each group as hash key. Keep track of when # the group was *first* created by using $fragment as the *value* do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) ); - # again, we take the more "relaxed" pattern die "$ABRT bad group $1\n" unless $1 =~ $REPONAME_PATT; } # repo(s) @@ -278,6 +280,16 @@ sub parse_conf_file } } } + # configuration + elsif (/^config (.+) = ?(.*)/) + { + my ($key, $value) = ($1, $2); + die "$WARN $fragment attempting to set repo configuration\n" if $fragment ne 'master'; + for my $repo (@repos) # each repo in the current stanza + { + $repo_config{$repo}{$key} = $value; + } + } # very simple syntax for the gitweb description of repo; one of: # reponame = "some description string" # reponame "owner name" = "some description string" @@ -379,6 +391,22 @@ warn "\n\t\t***** WARNING *****\n" . "\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n" if $git_version < 10602; # that's 1.6.2 to you +# ---------------------------------------------------------------------------- +# update repo configurations +# ---------------------------------------------------------------------------- + +for my $repo (keys %repo_config) { + wrap_chdir("$repo_base_abs/$repo.git"); + while ( my ($key, $value) = each(%{ $repo_config{$repo} }) ) { + if ($value) { + $value =~ s/^"(.*)"$/$1/; + system("git", "config", $key, $value); + } else { + system("git", "config", "--unset-all", $key); + } + } +} + # ---------------------------------------------------------------------------- # handle gitweb and daemon # ---------------------------------------------------------------------------- @@ -450,7 +478,7 @@ for my $pubkey (glob("*")) print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n"; next; } - my $user = $pubkey; $user =~ s/(\@.+)?\.pub$//; + my $user = $pubkey; $user =~ s/(\@[^.]+)?\.pub$//; # lint check 2 print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n" unless $user_list{$user};