GOAL: A simple to setup and run, multi-tenant Git Server written in NodeJS.

This was initially created to be used as a multi-tenant git server with powerful event triggers.

Require the modules needed:

pushover  = require 'pushover'
http    = require 'http'
https   = require 'https'
async   = require 'async'
fs      = require 'fs'

class GitServer

Constructor function for each instance of GitServer

repos Array List of repositories
repoLocation String Location where the repo's are/will be stored
port Int Port on which to run this server.
certs Object Object of 'key' and 'cert' with the location of the certs (only used for HTTPS)
  constructor: ( @repos = [], @logging = false, @repoLocation = '/tmp/repos', @port = 7000, @certs )->

Create the pushover git object:

    @git    = pushover @repoLocation, { autoCreate:false, write_messages:true }
    @permMap  = fetch:'R', push:'W'

Setup the repo listeners:


Go through all the @repo's and create them if they dont exist:

    @makeReposIfNull =>

Route requests to pushover:

      if @certs?
        @server = https.createServer @certs, @git.handle.bind(@git)
        red   = `'\033[31m'`
        reset = `'\033[0m'`
        message = """
          WARNING: No SSL certs passed in. Running as HTTP and not HTTPS.
          Be careful, without HTTPS your user/pass will not be encrypted"""
        console.log red + message + reset
        @server = http.createServer @git.handle.bind(@git)

Open up the desired port ( 80 requires sudo )

      @server.listen @port, =>

Just let the console know we have started.

        @log 'Server listening on ', @port, '\r'

Create a repo on the fly

repoName Object Name of the repo we are creating.
  createRepo: ( repo, callback )=>
    if ! or !repo.anonRead?
      @log 'Not enough details, need atleast .name and .anonRead'

make sure it doesnt already exist:

    if !@getRepo
      @log 'Creating repo',
      @repos.push repo
      @git.create, callback
      @log 'This repo already exists'

Log all arguments passed into this function IF @logging = true

  log: ()=>
    args = for key,value of arguments
    if @logging then console.log "LOG: ", args.join(' ')

Process the request and check for basic authentication.

gitObject Object Git object from the pushover module
method String Method we are getting security for ['fetch','push']
repo Object Repo object that we are doing this method on
  processSecurity: ( gitObject, method, repo )=>

Try to get the auth header

    req   = gitObject.request
    res   = gitObject.response
    auth  = req.headers['authorization']
    if auth is undefined

if they didnt send a auth header, tell them we need one:

      res.statusCode = 401
      res.setHeader 'WWW-Authenticate', 'Basic realm="Secure Area"'
      res.end '<html><body>Need some creds son</body></html>'

now they should have responded with the auth headers:


Decode the auth string

      plain_auth  = ( new Buffer( auth.split(' ')[1], 'base64' ) ).toString()

split the string to get username:password

      creds = plain_auth.split ':'

Send off this user info and authorize it:

      @permissableMethod creds[0], creds[1], method, repo, gitObject

Check to see if: return Username and password match return This user has permission to do this method on this repo

username String Username of the requesting user
password String Password of the requesting user
method String Method we are checking against ['fetch','push']
gitObject Object Git object from pushover module
  permissableMethod: ( username, password, method, repo, gitObject )=>

Just let the console know someone is trying to do something that requires a password:

    @log username,'is trying to', method,'on repo:',,'...'

Find the user object:

    user = @getUser username, password, repo

check if the user exists:

    if user is false

This user isnt in this repo's .users array:

      @log username,'was rejected as this user doesnt exist, or password is wrong'
      gitObject.reject(500,'Wrong username or password')
      if @permMap[ method ] in user.permissions
        @log username,'Successfully did a', method,'on',
        @checkTriggers method, repo, gitObject, gitObject.accept.bind(gitObject)
        @log username,'was rejected, no permission to',method,'on',
        gitObject.reject(500,"You dont have these permissions")

Setup the listeners for git events:

  gitListeners: ()=>

On each git push request

    @git.on 'push', @onPush

On each git fetch request

    @git.on 'fetch', @onFetch

On each git info request

    @git.on 'info', @onFetch

Checks all the passed in repo's to make sure they all have a real .git directory.

  makeReposIfNull: ( callback )=>
    @log 'Making repos if they dont exist';

Get all the repo names in an Array

    repoNames = []
    for repo in @repos

Make sure this repo has the require fields, if so, add to array:

      if and repo.anonRead? and repo.users?

This repo was missing some field we require

        console.log 'Bad Repo',, 'is missing an attribute..'

Call .exists on each repo name

    async.reject repoNames, @git.exists.bind(@git), ( results )=>

If we have repo's that need to be created:

      if results.length > 0

Create each repo that doesn not exist:

        console.log('Creating repo directory: ', repo ) for repo in results

call .create on each repo: results, @git.create.bind(@git), callback
      else callback() # Otherwise, open up the server.

When the git fetch command is triggered, this is fired.

fetch Object Git object from pushover module.
  onFetch: ( fetch )=>
    @log 'Got a FETCH call for', fetch.repo
    repo = @getRepo fetch.repo
    if repo isnt false # if this repo actually exists:

This repo allows anyone to fetch it, so accept the request:

      if repo.anonRead is true
        @checkTriggers 'fetch', repo, fetch, fetch.accept.bind(fetch)

this repo has no anon access, so we need to check the user/pass

        @processSecurity fetch, 'fetch', repo
    else # otherwise we need to reject this
      @log 'Rejected - Repo',fetch.repo,'doesnt exist'
      fetch.reject(500,'This repo doesnt exist')

When the git push command is triggered, this is fired.

push Object Git object from pushover module.
  onPush: ( push, res )=>
    console.log 'res', res
    push.response.write 'ZasdfasdfasdfasfasfasfasfASFASFD'
    @log 'Got a PUSH call for', push.repo
    repo = @getRepo push.repo
    if repo isnt false # if this repo actually exists:
      @processSecurity push, 'push', repo
      @log 'Rejected - Repo',push.repo,'doesnt exist'
      push.reject(500,'This repo doesnt exist')

Check if this repo has onSuccessful triggers

method String fetch|push
repo Object Repo object we are checking
gitObject Object Object from the git req. includes .branch etc.
  checkTriggers: ( method, repo, gitObject, callback )=>

If .onSuccessful exists:

    if repo.onSuccessful?

If this method exists in it:

      if repo.onSuccessful[method]?

log it, and call it

        @log 'On successful triggered: ', method, 'on',
        repo.onSuccessful[method]?( repo, method, gitObject, callback )

Get the user object, check user/pass is correct and it exists in this repo.

username String Username to find
password String Password of the Username
repo Object Repo object this user should be in.
  getUser: ( username, password, repo )=>
    for userObject in repo.users

If we found this user, return it

      return userObject if userObject.user.username is username and userObject.user.password is password
    false # Otherwise, return a false

Get the repo from the array of repos

repoName String Name of the repo we are trying to find
  getRepo: ( repoName )=>
    for repo in @repos

If the repo exists, return it.

      return repo if'.git' is repoName
    false # Otherwise, return a false

Export this as a module:

module.exports = GitServer