Simple Git-over-SSH shared repositories

At this point in the I.T. world most people are aware of Revision Control Systems such as Git, Subversion and CVS. If you are not currently using such tools there is a good chance that you know somebody who is using them. That person can help you start to benefit from them.

Git is one of the more popular of the bunch. One of the many great features of Git is that you can setup a simple shared repository very quickly on any conveniently available Linux system. Git is able to do this easily because it knows how to access files over SFTP. By simply creating a shell account on a system with an OpenSSH server we can copy repositories to that account and share them.

Documentation on all aspects of Git is readily available from many sources. One excellent reference, a book called Pro Git, is online and downloadable via this URL:

http://git-scm.com/book/en/

Getting Started

Let's say that you are sitting in a coffee shop in front of your notebook computer. You create a new project, myproject in your /projects folder. Next you setup your local git repository, add your project files and commit your work to the local repository:


[demo@notebook projects]$ mkdir myproject
[demo@notebook projects]$ cd myproject
[demo@notebook myproject]$ echo -e "<html><body><p>Hello, World</body></html>\n" >hello.html
[demo@notebook myproject]$ echo -e "*~\n" >.gitignore
[demo@notebook myproject]$ git init
Initialized empty Git repository in /projects/myproject/.git/
[demo@notebook myproject]$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .gitignore
        hello.html

nothing added to commit but untracked files present (use "git add" to track)
[demo@notebook myproject]$ git add .gitignore hello.html 
[demo@notebook myproject]$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   .gitignore
        new file:   hello.html

[demo@notebook myproject]$ git commit -a -m "initial commit of new project files"
[master (root-commit) e6b3470] initial commit of new project files
 2 files changed, 4 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 hello.html
[demo@notebook myproject]$ git status
On branch master
nothing to commit, working directory clean
[demo@notebook myproject]$ ls -aF
./  ../  .git/  .gitignore  hello.html
[demo@notebook myproject]$ ls -aF .git
./  ../  branches/  COMMIT_EDITMSG  config  description  HEAD  hooks/  index  info/  logs/  objects/  refs/

A quick review of the above commands for those who haven't yet worked with Git very much:

  • The echo command in line 4 above creates an ignore file for Git. Each line in the file contains a regular expression to match against file names. In this example I only put one line that tells git to ignore all files with a tilde at the end of their file name. These are generally backup files created by Emacs and other editors.
  • The git init command in line 5 creates the local repository. It's a collection of files and folders in a new .git folder that you see in lines 41 & 42.
  • The git add command in line 19 tells git to track the listed files. In practice you would add project files as they are being created so that Git knows you want to keep track of them.
  • The git commit command in line 31, with the -a (all files) option and a commit message, commits the recent changes into the local repository. If you don't use the -m option and provide a message string the git command with launch a text editor so that you can type a message to go along with the commit. This message can be used to help you keep track of the changes that you've made - so it's a good idea to put bug numbers and other reference information in these messages for future reference. If you don't use the -a option you need to specify each filename separately on the command line.
  • The git status commands in lines 7, 20 and 36 check all the files in the current project folder against the contents of the local repository and provide some useful information.

Note that you can do all the above using any one of many graphical Git tools. Git is also supported, via plugins, in most IDEs (Eclipse, QT, etc.,) and in advanced editors like Emacs. I'll use the command line tool for this article because I'm most comfortable with it and because it always works as you see here. Unfortunately each graphical tool is very different.

Now let's say you get back to the office and you want to continue your work on your desktop PC. Normally you would simply copy all the files to that PC but, with Git, there's an easier way.

The "bare" Repo

First: prepare a copy of your project in "bare" format. This copy will be sent to the server in the next step:

[demo@notebook projects]$ cd /projects
[demo@notebook projects]$ ls -aF myproject/
./  ../  .git/  .gitignore  hello.html
[demo@notebook projects]$ git clone --bare myproject myproject.git
Cloning into bare repository 'myproject.git'...
done.
[demo@notebook projects]$ ls -aF myproject.git/
./  ../  branches/  config  description  HEAD  hooks/  info/  objects/  packed-refs  refs/

The above step created a folder, /projects/myproject.git, which contains the files and folders of the "bare" copy of the local git repository. It's almost the same as the contents of the local repository that git keeps in the /projects/myproject/.git folder.

Bare repositories can be shared directly. For example you can copy your bare repository to a network file share and clone it from there to your desktop computer. You can also push your changes back to the repo from your desktop computer and pull/merge them into your project folder on your notebook computer. If you are working alone or if your team is very small this may, in fact, be a good solution for you.

Still, it's really easy to implement Git-over-SSH - just a few quick commands on any Linux server - so let's continue: Next, we'll create a git account on a Linux server and copy this bare repository to that account so that it can be shared, from that account, over SSH.

Server Setup

Now we will shell into any convenient Linux system that we can use as a Git server.

Remember that you will use your Git server for a very long time and, therefore, you will start to rely on it as a backup of all your work. For this reason it's a good idea to use a computer with a RAID-backed filesystem for extra security.

Here are the two steps involved in the setup: basically just a shell account and a folder in which to store the project repos:

  • Shell into the system and create an account. Let's call it user git:

    [demo@notebook projects]$ ssh myserver.somewhere
    demo@myserver.somewhere's password: 
    Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic x86_64)
    ...
    demo@myserver:~$ sudo useradd -m git
    [sudo] password for demo: 
    demo@myserver:~$ sudo passwd git
    Enter new UNIX password: 
    Retype new UNIX password: 
    passwd: password updated successfully
    demo@myserver:~$ exit
    logout
    Connection closed.
    
  • Down the road you may need to move your repository files around or make some other changes. If that happens you'll find it handy if they are all within a convenient folder within the git account. In any case you will need to log into the new git account to ensure that everything is working properly:

    [demo@notebook projects]$ ssh git@myserver.somewhere
    git@myserver.somewhere's password: 
    Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic x86_64)
    ...
    $ ls -aF
    ./  ../  .bash_logout  .bashrc  .cache/  .profile  .Xauthority
    $ mkdir project
    $ exit
    Connection closed.
    

And we're done! Yes, it's that simple. You now have an account on a server with a reliable filesystem - which is all you need to share repositories using Git over SSH.

Sharing myproject.git

Now you can copy the bare myproject.git repo to the server for sharing:

  • Use scp to send the folder to the server:

    [demo@notebook projects]$ cd /projects
    [demo@notebook projects]$ scp -r myproject.git git@myserver.somewhere:project/
    git@myserver.somewhere's password: 
    description                            100%   73     0.1KB/s   00:00    
    packed-refs                            100%   98     0.1KB/s   00:00    
    pre-applypatch.sample                  100%  398     0.4KB/s   00:00    
    pre-rebase.sample                      100% 4898     4.8KB/s   00:00    
    pre-commit.sample                      100% 1642     1.6KB/s   00:00    
    prepare-commit-msg.sample              100% 1239     1.2KB/s   00:00    
    update.sample                          100% 3611     3.5KB/s   00:00    
    commit-msg.sample                      100%  896     0.9KB/s   00:00    
    applypatch-msg.sample                  100%  452     0.4KB/s   00:00    
    pre-push.sample                        100% 1352     1.3KB/s   00:00    
    post-update.sample                     100%  189     0.2KB/s   00:00    
    36e1e4621a7a79cad1c2b069faaa994d1bf73b 100%   19     0.0KB/s   00:00    
    3ac2a48902c8b00af4d1e4ab5ad89d539875df 100%   52     0.1KB/s   00:00    
    b3470d96c4b8ccd9b29840524b81b78eefe69c 100%  142     0.1KB/s   00:00    
    777bdcf0ca1bd8a758cace055d234bf127e6ee 100%   90     0.1KB/s   00:00    
    config                                 100%  111     0.1KB/s   00:00    
    HEAD                                   100%   23     0.0KB/s   00:00    
    exclude                                100%  240     0.2KB/s   00:00    
    

    The bare repo is now at /home/git/project/myproject.git on the server.

  • At this point, since you are probably still sitting in front of your notebook PC, it's a good idea to connect your original project repository to the new shared repository (also called a remote repository in the Git documentation.) Here's how:

    [demo@notebook projects]$ cd /projects/myproject
    [demo@notebook myproject]$ git remote add myserver git@myserver.somewhere:project/myproject.git
    [demo@notebook myproject]$ git push -u myserver master
    git@myserver.somewhere's password: 
    Branch master set up to track remote branch master from myserver.
    Everything up-to-date
    [demo@notebook myproject]$ git pull
    git@myserver.somewhere's password: 
    Already up-to-date.
    [demo@notebook myproject]$ git pull -u myserver master
    git@myserver.somewhere's password: 
    From myserver.somewhere:project/myproject
     * branch            master     -> FETCH_HEAD
    Already up-to-date.
    [demo@notebook myproject]$ git remote -v
    origin  git@myserver.somewhere:project/myproject.git (fetch)
    origin  git@myserver.somewhere:project/myproject.git (push)
    

    The git remote add command in line 2 adds a remote repository which we're calling myserver for short. The URL for the repo is git@myserver.somewhere:project/myproject.git. You can see the current list of remotes using git remote -v as in line 15 above.

    Note the use of the -u option in the git push and git pull commands above. This sets myserver as the default remote to use for the master branch. The next time you make changes to the code you can optionally push or pull without having to specify again the remote or the name of the branch - the two will default to the ones you used with the -u option.

  • Finally: go to your desktop computer and clone the project there:

    [demo@desktop desktop-projects]$ git clone git@myserver.somewhere:project/myproject.git
    Cloning into 'myproject'...
    git@myserver.somewhere's password: 
    remote: Counting objects: 4, done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 4 (delta 0), reused 0 (delta 0)
    Receiving objects: 100% (4/4), done.
    Checking connectivity... done.
    [demo@desktop desktop-projects]$ cd myproject/
    [demo@desktop myproject]$ ls -aF
    ./  ../  .git/  .gitignore  hello.html
    

At this point you and your team can go back and forth between various sandboxes on various computers. You can pull the most recent project files from the repo, work on your project and push your work back to the repo. Of course you will probably forget to pull or push from time to time - but that's not a big problem when you have Git handy. Git includes features that are designed to simplify the process of merging your changes back into the repo (well, not for binary files - only for text files - but that's usually what you need most when developing software and/or text-based documentation.)

Don't forget to check out the Pro Git book for more information on how to diff, branch and merge your changes. Learning how to make good use of these tools can help you be more productive in your day-to-day work.

Going further

If you are only working with a small handful of people - two or three people you know well and trust - you will find that this simple repository is very handy and easy to use. Once your team starts to get bigger - especially when there are new team members who come and go from time to time - you will find that you need more control over permissions.

Of course there is more work to be done to setup a more sophisticated Git server. Check the documentation for tools like gitosis and/or gitolite. At some point you will find that you are willing to make the extra effort to implement and maintain a more sophisticated server.

Until then, though, you can leverage your linux system to add some basic controls to your simple repository:

  1. Stop using the git password.

    It's generally a very bad idea for all users to know the password for the git account. A better approach would be to use SSH key files. These files are easy to generate so it's easy to make one for each member of your team. Then, later, when a given user is no longer allowed to access the repository, you can simply remove his key from the list of authorized users. This is much easier than having to change the password and notify everybody of the change.

    If you have administrative access to the system you can edit your /etc/ssh/sshd_config file to disallow password logins for all users or just for the git account or the git user group:

    Match User git
    Match Group git
    PasswordAuthentication no
    

    Adding the above lines to the SSH daemon config file would force the use of SSH Keys for anyone trying to use the git account.

    See the SSH documentation for more information on the Match User and Match group configuration options. There are quite a few tutorials on the internet explaining how to generate keys and add them to the list of authorized keys in the .ssh directory of the git account.

  2. Block shell access via the git account.

    Once you give access to the git account to a new team-member that person will have too much access to the system. To block shell access to the git account you can use the git-shell like this:

    $ ssh me@myserver.somewhere
    $ sudo usermod --shell /usr/bin/git-shell git
    $ exit
    

    Now, when you try to shell into the git account you will see something like this:

    fatal: Interactive git shell is not enabled.
    hint: ~/git-shell-commands should exist and have read and execute access.
    Connection to myserver.somewhere closed.
    

    Note that using the git-shell disables copying bare repositories into the git account using scp. You will need to scp the files to another account, move them to the git account and chown them so that they belong to git. These three quick commands, instead of the one scp command alone, prove that not having access to the git account protects the server from all the small mistakes that people can make during a typical day.

  3. Configure your office router to allow world access to your Git server:

    Because SSH requires only a TCP connection to port 22 on the server it's usually not too difficult to make your server accessible via the internet. To accomplish this you need only access your office router and redirect TCP port 22 to the Git server.

    Check your router documentation for more information on how to do this. If you received a modem from your ISP you will probably find, these days, that the router is inside the modem. Your ISP might be able to direct you to the documentation; otherwise you will need to get the model number of the modem and search for the instructions on the web.

    Note that allowing world-access to a Linux system is a big security risk. Make sure you take, at least, minimal precautions such as

    • Installing a firewall and blocking all networks except the ones you actually use,
    • Disabling all password logins in favor of authentication via ssh-keys,
    • Disabling root login via SSH,
    • and installing DenyHosts to block brute force attacks.
       

    Such minimal precautions don't protect you from all potential threats but they do provide a great deal of security.

  4. Use gitweb to provide read-only access, via a web browser, to the repo. Setup is relatively easy.

Eventually your team will outgrow this simple repository; until then you'll find that it's really handy.

Tags: