GIT – how to setup your own server

Let’s focus on the two most important scripts for our git server, the create and the delete script:

create

#! /bin/bash

# usage:        create <PROJECT> - create a git bare repository named PROJECT.git
#               this command will setup the repo and send a mail for confirmation.

GITDIR="/var/git"
MULTIMAIL="/usr/doc/git-2.14.4/contrib/hooks/multimail/git_multimail.py"
GITUSER="git"
GITGRP="git"

function is_bare() {
        repodir=$1
        if "$(git --git-dir="$repodir" rev-parse --is-bare-repository)" = true
        then
                true
        else
                false
        fi
}

function git_init() {
        PROJECT=$1
        echo "creating project \"${PROJECT}.git\""
        if [ ! -d ${GITDIR}/${PROJECT}.git ]; then
                mkdir ${GITDIR}/${PROJECT}.git
        fi
        cd ${GITDIR}/${PROJECT}.git
        git init --bare
        mkdir custom-hooks
        ln -s $MULTIMAIL custom-hooks/
        touch hooks/post-receive
        cat > hooks/post-receive <<EOPR
#!/bin/sh
/usr/bin/pee ${GITDIR}/${PROJECT}.git/custom-hooks/deploy.sh \
        ${GITDIR}/${PROJECT}.git/custom-hooks/git_multimail.py

EOPR
       cat >> config <<EOT

[multimailhook]
        mailer = "sendmail"
        refchangeShowGraph = true
        mailingList = "receiver@someemail.tld"
        commitEmailFormat = "html"
        htmlInIntro = true
        htmlInFooter = true
        from = "sender@someemail.tld"
        administrator = "admin@someemail.tld"
        quiet = true
        logFile = "/var/log/multimail.log"
        errorLogFile = "/var/log/multimail_err.log"
EOT
        touch custom-hooks/deploy.sh
        cat > custom-hooks/deploy.sh <<EODP
#!/bin/bash
# Directory where to deploy files from repository
DPTARGET=""
# Directory containing repository
DPGIT_DIR="${GITDIR}/${PROJECT}.git"
# Branch that is going to be deployed to server
DPBRANCH="master"

while read oldrev newrev ref
do
        # if DPTARGET is empty we don't want deploy for this project
        if [[ ! "" == \$DPTARGET ]]; then
                # let's check that we are deploying to the correct branch
                if [[ \$ref = refs/heads/\${DPBRANCH} ]]; then
                        echo "Ref \$ref received. Deploying \${DPBRANCH} branch to production..."
                        git --work-tree=\$DPTARGET --git-dir=\$DPGIT_DIR checkout -f $DPBRANCH
                        NOW=\$(date +"%d%m%Y-%H%M")
                        git tag release_\$NOW \$DPBRANCH
                        echo "   /==============================="
                        echo "   | DEPLOYMENT COMPLETED"
                        echo "   | Target branch: \$DPTARGET"
                        echo "   | Target folder: \$DPGIT_DIR"
                        echo "   | Tag name     : release_\$NOW"
                        echo "   \=============================="
                else
                        echo "Ref \$ref received. Doing nothing: only the \${DPBRANCH} branch may be deployed on this server."
                fi
        else
                echo "Target directory not declared. Skipping deploy to server."
        fi
done

EODP
        chmod 0755 hooks/post-receive custom-hooks/deploy.sh
        cd ${GITDIR}/
        chown -R ${GITUSER}:${GITGRP} ${GITDIR}/${PROJECT}.git
        echo "All done, you can now work on \"${PROJECT}.git\""
        exit 0
}

if [ ! -z $1 ]; then
        PROJECT=$1
else
        read -p 'Project name: ' PROJECT
fi

if [ ! -d ${GITDIR}/${PROJECT}.git ]; then
        git_init $PROJECT
else
        echo "Project directory ${PROJECT}.git already exists."
        if [ $(ls -A ${GITDIR}/${PROJECT}.git) ]; then
                if is_bare ${GITDIR}/${PROJECT}.git
                then
                        echo "looks like \"${PROJECT}.git\" is an existing git project directory, choose another name."
                        exit 171
                else
                        echo "\"${PROJECT}.git\" is not empty, I can't create a Git Project in it. Choose another name."
                        exit 172
                fi
        else
                echo "\"${PROJECT}.git\" is an empty directory. Do you want to initialize a Git Project here? [y/N]"
                read answer
                case $answer in
                        Y|y)
                                git_init $PROJECT       
                                ;;
                        N|n)
                                echo "Aborting due to user request."
                                exit 173
                                ;;
                        *)
                                # we assume no as default answer.
                                echo "you said \"$answer\" which I don't understand, so to me is no. Aborting."
                                exit 177
                                ;;
                esac
        fi
fi

This is the create script, as you can see it’s a bit complex because it will do a few things for me:

  • create the bare repository using the argument provided on the command line or asking for a project name
  • check before creating the repo, if another repo with the same name exists or if a directory with files in it exists. That way I’ll make sure to only create a repo inside an empty directory.
  • add a “custom-hooks” directory that will hold a link to the multimail.py script as well as my deploy.sh script
  • create a post-receive script that uses pee from the moreutils project to run multiple scripts at the same hook.
  • ensure sane permissions on the whole project directory.
  • always assume no (the safest option) when asking the user about the action to take.

So after saving this script as “create” inside the git-shell-commands directory, we’ll give it executable permissions and we can move to the delete script.

# cat create-bare-repo.sh &gt; /var/git/git-shell-commands/create
# chown git:git -R /var/git/git-shell-commands
# chmod 0755 /var/git/git-shell-commands/create

delete

Let’s see the delete script:

#! /bin/bash

# usage:        delete <REPOSITORY> - PERMANENTLY delete a repository if existing.
#               CAREFUL, this action cannot be undone. This command will ask for confirmation.

GITDIR="/var/git"

function is_bare() {
        repodir=$1
        if "$(git --git-dir="$repodir" rev-parse --is-bare-repository)" = true
        then
                true
        else
                false
        fi
}

if [ ! -z $1 ]; then
        PROJECT=$1
else
        read -p 'Project to delete: ' PROJECT
fi

if [ -d ${GITDIR}/${PROJECT}.git ]; then
        if [[ $(ls -A ${GITDIR}/${PROJECT}.git) ]]; then
                if is_bare ${GITDIR}/${PROJECT}.git
                then
                        echo "You are going to delete the git repository \"${PROJECT}.git\" Do you really want to continue? Note, this action cannot be reverted. [y/N]"
                        read delAnswer
                        case $delAnswer in
                                Y|y)
                                        rm -rf ${PROJECT}.git
                                        ;;
                                N|n)
                                        echo "Aborting due to user request."
                                        exit 173
                                        ;;
                                *)
                                        echo "you said \"$delAnswer\" which I don't understand. Assuming No. Aborting."
                                        exit 177
                                        ;;
                        esac
                else
                        echo "\"${PROJECT}.git\" doesn't look like a git repository. Check with your System Administrator."
                        exit 177
                fi
        else
                echo "\"${PROJECT}.git\" is an empty directory, Skipping. Check with your System Administrator."
                exit 177
        fi
fi

This script is much simpler than the previous one, it’ll accept the name of the project as argument on the command line or will ask for it and will only delete it if it is a proper git repository, otherwise it will just exit with an error code.

I improved the way those scripts recognise a git repository from simply relying on the fact that there’s a HEAD file inside the directory they’re checking, which wasn’t the best option, to using git itself to check if the directory is a bare repository. Much better!!

Since we are here let’s modify the help command to make it show a short description of every available command.

#!/bin/sh

# usage:        help - Lists all the available commands
#               help <command> - Detailled explanation of how "command" works

if tty -s
then
        HELPTEXT="Hi $USER, Run 'help' for help, 'help <command>' for specific help on a command, run 'exit' to exit. Available commands:"
else
        HELPTEXT="Hi $USER, Run 'help' for help, 'help <command>' for specific help on a command. Available commands:"
fi

cd "$(dirname "$0")"

if [[ ! -z $1 ]]; then
        cmd=$1
        if [[ -f $cmd && -x $cmd ]]; then
                awk 'NR>=3&&NR<=4' $cmd | cut -c 3-
        else
                echo "command \"$cmd\" doesn't exists"
        fi
else
        echo $HELPTEXT
        for cmd in *
        do
                case "$cmd" in
                help) ;;
                *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
                esac
        done
fi

The main thing I added is the support for a command line argument, now I’m able to run it by itself and display the usual output with a list of available commands, or followed by a command name to give a brief explanation like this:

git> help
Hi git, Run 'help' for help, 'help <command>' for specific help on a command, run 'exit' to exit. Available commands:
clear
create
delete
list
git> help create
usage:  create  - create a git bare repository named PROJECT.git
        this command will setup the repo and send a mail for confirmation.
git>

Pretty nice isn’t it?! Now it’s much more user friendly, and to show the description I used awk and cut to parse the comment at the top of every script I have in the git-shell-commands directory.

In the next page we’ll see the usual routine I follow when working with this new setup.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

  

This site uses Akismet to reduce spam. Learn how your comment data is processed.