• Stars
    star
    260
  • Rank 153,945 (Top 4 %)
  • Language
    Go
  • License
    MIT License
  • Created about 8 years ago
  • Updated 11 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Git toolkit for Go: Smart HTTP server, SSH server, hook receiver

gitkit

Toolkit to build Git push workflows with Go

Build GoDoc

Install

go get github.com/sosedoff/gitkit

Smart HTTP Server

package main

import (
  "log"
  "net/http"
  "github.com/sosedoff/gitkit"
)

func main() {
  // Configure git hooks
  hooks := &gitkit.HookScripts{
    PreReceive: `echo "Hello World!"`,
  }

  // Configure git service
  service := gitkit.New(gitkit.Config{
    Dir:        "/path/to/repos",
    AutoCreate: true,
    AutoHooks:  true,
    Hooks:      hooks,
  })

  // Configure git server. Will create git repos path if it does not exist.
  // If hooks are set, it will also update all repos with new version of hook scripts.
  if err := service.Setup(); err != nil {
    log.Fatal(err)
  }

  http.Handle("/", service)

  // Start HTTP server
  if err := http.ListenAndServe(":5000", nil); err != nil {
    log.Fatal(err)
  }
}

Run example:

go run example.go

Then try to clone a test repository:

$ git clone http://localhost:5000/test.git /tmp/test
# Cloning into '/tmp/test'...
# warning: You appear to have cloned an empty repository.
# Checking connectivity... done.

$ cd /tmp/test
$ touch sample

$ git add sample
$ git commit -am "First commit"
# [master (root-commit) fe40c98] First commit
# 1 file changed, 0 insertions(+), 0 deletions(-)
# create mode 100644 sample

$ git push origin master
# Counting objects: 3, done.
# Writing objects: 100% (3/3), 213 bytes | 0 bytes/s, done.
# Total 3 (delta 0), reused 0 (delta 0)
# remote: Hello World! <----------------- pre-receive hook
# To http://localhost:5000/test.git
# * [new branch]      master -> master

In the example's console you'll see something like this:

2016/05/20 20:01:42 request: GET localhost:5000/test.git/info/refs?service=git-upload-pack
2016/05/20 20:01:42 repo-init: creating pre-receive hook for test.git
2016/05/20 20:03:34 request: GET localhost:5000/test.git/info/refs?service=git-receive-pack
2016/05/20 20:03:34 request: POST localhost:5000/test.git/git-receive-pack

Authentication

package main

import (
  "log"
  "net/http"

  "github.com/sosedoff/gitkit"
)

func main() {
  service := gitkit.New(gitkit.Config{
    Dir:        "/path/to/repos",
    AutoCreate: true,
    Auth:       true, // Turned off by default
  })

  // Here's the user-defined authentication function.
  // If return value is false or error is set, user's request will be rejected.
  // You can hook up your database/redis/cache for authentication purposes.
  service.AuthFunc = func(cred gitkit.Credential, req *gitkit.Request) (bool, error) {
    log.Println("user auth request for repo:", cred.Username, cred.Password, req.RepoName)
    return cred.Username == "hello", nil
  }

  http.Handle("/", service)
  http.ListenAndServe(":5000", nil)
}

When you start the server and try to clone repo, you'll see password prompt. Two examples below illustrate both failed and succesful authentication based on the auth code above.

$ git clone http://localhost:5000/awesome-sauce.git
# Cloning into 'awesome-sauce'...
# Username for 'http://localhost:5000': foo
# Password for 'http://foo@localhost:5000':
# fatal: Authentication failed for 'http://localhost:5000/awesome-sauce.git/'

$ git clone http://localhost:5000/awesome-sauce.git
# Cloning into 'awesome-sauce'...
# Username for 'http://localhost:5000': hello
# Password for 'http://hello@localhost:5000':
# warning: You appear to have cloned an empty repository.
# Checking connectivity... done.

Git also allows using .netrc files for authentication purposes. Open your ~/.netrc file and add the following line:

machine localhost
  login hello
  password world

Next time you try clone the same localhost git repo, git wont show password promt. Keep in mind that the best practice is to use auth tokens instead of plaintext passwords for authentication. See Heroku's docs for more information.

SSH server

package main

import (
  "log"
  "github.com/sosedoff/gitkit"
)

// User-defined key lookup function. You can make a call to a database or
// some sort of cache storage (redis/memcached) to speed things up.
// Content is a string containing ssh public key of a user.
func lookupKey(content string) (*gitkit.PublicKey, error) {
  return &gitkit.PublicKey{Id: "12345"}, nil
}

func main() {
  // In the example below you need to specify a full path to a directory that
  // contains all git repositories, and also a directory that has a gitkit specific
  // ssh private and public key pair that used to run ssh server.
  server := gitkit.NewSSH(gitkit.Config{
    Dir:    "/path/to/git/repos",
    KeyDir: "/path/to/gitkit",
  })

  // User-defined key lookup function. All requests will be rejected if this function
  // is not provider. SSH server only accepts key-based authentication.
  server.PublicKeyLookupFunc = lookupKey

  // Specify host and port to run the server on.
  err := server.ListenAndServe(":2222")
  if err != nil {
    log.Fatal(err)
  }
}

Example above uses non-standard SSH port 2222, which can't be used for local testing by default. To make it work you must modify you ssh client configuration file with the following snippet:

$ nano ~/.ssh/config

Paste the following:

Host localhost
  Port 2222

Now that the server is configured, we can fire it up:

$ go run ssh_server.go

First thing you'll need to make sure you have tested the ssh host verification:

$ ssh git@localhost -p 2222
# The authenticity of host '[localhost]:2222 ([::1]:2222)' can't be established.
# RSA key fingerprint is SHA256:eZwC9VSbVnoHFRY9QKGK3aBSUqkShRF0HxFmQyLmBJs.
# Are you sure you want to continue connecting (yes/no)? yes
# Warning: Permanently added '[localhost]:2222' (RSA) to the list of known hosts.
# Unsupported request type.
# Connection to localhost closed.

All good now. Unsupported request type. is a succes output since gitkit does not allow running shell sessions. Assuming you have configured the directory for git repositories, clone the test repo:

$ git clone git@localhost:test.git
# Cloning into 'test'...
# remote: Counting objects: 3, done.
# remote: Total 3 (delta 0), reused 0 (delta 0)
# Receiving objects: 100% (3/3), done.
# Checking connectivity... done.

Done, you have now ability to run git push/pull. The important stuff in all examples above is lookupKey function. It controls whether user is allowd to authenticate with ssh or not.

Receiver

In Git, The first script to run when handling a push from a client is pre-receive. It takes a list of references that are being pushed from stdin; if it exits non-zero, none of them are accepted. More on hooks.

package main

import (
  "log"
  "os"
  "fmt"

  "github.com/sosedoff/gitkit"
)

// HookInfo contains information about branch, before and after revisions.
// tmpPath is a temporary directory with checked out git tree for the commit.
func receive(hook *gitkit.HookInfo, tmpPath string) error {
  log.Println("Action:", hook.Action)
  log.Println("Ref:", hook.Ref)
  log.Println("Ref name:", hook.RefName)
  log.Println("Old revision:", hook.OldRev)
  log.Println("New revision:", hook.NewRev)

  // Check if push is non fast-forward (force)
  force, err := gitkit.IsForcePush(hook)
  if err != nil {
    return err
  }

  // Reject force push
  if force {
    return fmt.Errorf("non fast-forward pushed are not allowed")
  }

  // Check if branch is being deleted
  if hook.Action == gitkit.BranchDeleteAction {
    fmt.Println("Deleting branch!")
    return nil
  }

  // Getting a commit message is built-in
  message, err := gitkit.ReadCommitMessage(hook.NewRev)
  if err != nil {
    return err
  }
  log.Println("Commit message:", message)

  return nil
}

func main() {
  receiver := gitkit.Receiver{
    MasterOnly:  false,         // if set to true, only pushes to master branch will be allowed
    TmpDir:      "/tmp/gitkit", // directory for temporary git checkouts
    HandlerFunc: receive,       // your handler function
  }

  // Git hook data is provided via STDIN
  if err := receiver.Handle(os.Stdin); err != nil {
    log.Println("Error:", err)
    os.Exit(1) // terminating with non-zero status will cancel push
  }
}

To test if receiver works, you will need to add a sample pre-receive hook to any git repo. With go run its easier to debug but final script should be compiled and will run very fast.

#!/bin/bash
cat | go run /path/to/your-receiver.go

Modify something in the repo, commit the change and push:

$ git push
# Counting objects: 3, done.
# Delta compression using up to 8 threads.
# Compressing objects: 100% (3/3), done.
# Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
# Total 3 (delta 2), reused 0 (delta 0)
# -------------------------- out receiver output is here ----------------
# remote: 2016/05/24 17:21:37 Ref: refs/heads/master
# remote: 2016/05/24 17:21:37 Old revision: 5ee8d0891d1e5574e427dc16e0908cb9d28551b9
# remote: 2016/05/24 17:21:37 New revision: e13d6b3a27403029fe674e7b911efd468b035a33
# remote: 2016/05/24 17:21:37 Message: Remove stuff
# To git@localhost:dummy-app.git
#    5ee8d08..e13d6b3  master -> master

Extras

Remove remote: prefix

If your pre-receive script logs anything to STDOUT, the output might look like this:

# Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
# Total 3 (delta 2), reused 0 (delta 0)
remote: Sample script output <---- YOUR SCRIPT

There's a simple hack to remove this nasty remote: prefix:

#!/bin/bash
/my/receiver-script | sed -u "s/^/"$'\e[1G\e[K'"/"

If you're running on OSX, use gsed instead: brew install gnu-sed.

Result:

# Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
# Total 3 (delta 2), reused 0 (delta 0)
Sample script output

References

License

The MIT License

Copyright (c) 2016-2023 Dan Sosedoff, [email protected]

More Repositories

1

pgweb

Cross-platform client for PostgreSQL databases
Go
8,042
star
2

capistrano-unicorn

Capistrano integration for Unicorn! - NEEDS MAINTAINER
Ruby
403
star
3

goodreads

Goodreads API wrapper
Ruby
240
star
4

lunchy-go

OSX Launch Manager
Go
180
star
5

opentable

Unofficial OpenTable API
Ruby
147
star
6

grooveshark

Grooveshark.com unofficial API library
Ruby
123
star
7

slack-notify

Slack.com notifier
Ruby
94
star
8

musicbot

Play music from Slack: Raspberry Pi + Mopidy + Spotify
Go
93
star
9

lxc-ruby

Linux containers (LXC) ruby wrapper
Ruby
89
star
10

xml-sitemap

Easy XML sitemap generation for Ruby/Rails/Merb/Sinatra applications
Ruby
56
star
11

munin-ruby

Munin node client for Ruby
Ruby
41
star
12

omxremote

Web frontend and API for Raspberry Pi omxplayer media player
Go
38
star
13

ansible-vault-go

Go package to interact with Ansible Vault files
Go
37
star
14

wireguard-aws-gateway

Wireguard VPN gateway or AWS
HCL
29
star
15

mail_extract

Strip email message body from quotes and signatures
Ruby
25
star
16

net-ssh-session

Shell session for Net::SSH connections
Ruby
25
star
17

travis-github

Travis CI builds tab in Github
JavaScript
24
star
18

cloudwatchlogs

Web interface for AWS CloudWatch Logs
Go
23
star
19

rackspace-clouddns

Rackspace CloudDNS Ruby Wrapper
Ruby
20
star
20

github-events

Listen to Github repository events in real time
Go
18
star
21

shuttle

Minimalistic deployment tool
Ruby
17
star
22

heroku-vegeta

Using Heroku as a distributed stress-testing platform
Go
16
star
23

nginx2influxdb

Stream Nginx logs directly into InfluxDB
Go
14
star
24

rack-revision

Rack middleware for add code revision header X-REVISION
Ruby
14
star
25

pastie

Simplified API to Pastie.org
Ruby
12
star
26

markup-editor

Web-based markup editor with on-the-fly preview
JavaScript
12
star
27

app-config

Flexible and simple in-application settings for Rails/Sinatra applications
Ruby
11
star
28

docker-router

Reverse proxy for docker containers with automatic SSL
Go
11
star
29

envd

Serve application environment variables over HTTP
Go
11
star
30

bundle_cache

Cache bundle to Amazon S3
Go
10
star
31

docify

Render your favorite markup (RDoc, Markdown, Textile) into nice-looking html.
Ruby
10
star
32

apple_push

Sinatra-based API to send Apple Push Notifications
Ruby
10
star
33

proxie

HTTP proxy server with sqlite-powered storage and web interface for debugging.
Ruby
9
star
34

lxc-server

Web API for LXC container management
Ruby
9
star
35

cxml

Ruby implementation of cXML communication protocol
Ruby
9
star
36

fakemail

Sendmail replacement to debug email output, layouts, etc.
Ruby
8
star
37

hipache-api

HTTP interface for Hipache
Go
8
star
38

munin-plugins

Some useful munin plugins
Ruby
8
star
39

wg-registry

User and device self-service portal for WireGuard
Go
8
star
40

unfuddle-cli

Console-based tool to manage Unfuddle repositories
Ruby
8
star
41

cron2

Cron2 is cron scheduler on steroids: HCL, Docker, Logs, Notifications, Etc
Go
7
star
42

grit-http

Sinatra based API for Git repositories, powered by Grit
Ruby
7
star
43

zeroconf-beacon

Spawn a simple zeroconf service over the local network
Go
7
star
44

lxc-tools

Set of tools to work with LXC containers
Ruby
6
star
45

xtract

Simple data extraction tool
Go
6
star
46

go-craigslist

Craigslist.org wrapper for Go
HTML
6
star
47

snatch

Remote database downloader based on SSH
Ruby
6
star
48

docker-gateway

Stupid simple reverse proxy for Docker
Go
6
star
49

rack-norris

X-Chuck-Norris header with a joke in your Rack app!
Ruby
5
star
50

reeder

Experimental RSS reader
CSS
5
star
51

actions

Repo for Github Actions
Shell
4
star
52

howdy

Experimental YAML-based service monitoring thingy.
Go
4
star
53

unfuddle-services

Sinatra based hook server for Unfuddle. Similar to github-services.
Ruby
3
star
54

rscale

Image scaling wrapper based on ImageMagick console utils
Ruby
3
star
55

dep-cache

Parallel dependency cache helper for running in CI environments
Go
3
star
56

gitvault

Your personal git hosting
Ruby
3
star
57

arduino-rc-receiver

Script to troubleshoot RC receiver with Arduino
Arduino
3
star
58

dotfiles

Development environment configuration, scripts, etc
Ruby
3
star
59

shelly

Remote bash runner over HTTP
Go
2
star
60

debugserver

Debugging server for any console-less applications
Ruby
2
star
61

munin-dashboard

Sinatra based API and GUI for munin nodes
Ruby
2
star
62

ws

Websocket debugging tool
Go
2
star
63

spellcheck

Text spellcheck/correction based on Redis key-value storage server
Ruby
2
star
64

capistrano-payload

Capistrano plugin that delivers JSON payload to the specified URL
Ruby
2
star
65

dependenci

Client for http://dependenci.com
Ruby
2
star
66

irc2pusher

Send IRC messages to a Pusher channel
2
star
67

sqlembed

Parse out *.sql files and embed the queries as consts
Go
2
star
68

app-detective

Detect application type
Ruby
2
star
69

debugclient-php

DebugServer PHP client library
PHP
2
star
70

terminal_helpers

Various helpers for console-based applications
Ruby
2
star
71

crash_hook

Exception notifications via HTTP
Ruby
2
star
72

fpm-builder

Helpers for building packages with fpm
Shell
1
star
73

git-branches

A small tool to show current status of the git repository
Go
1
star
74

arrow_payments

ArrowPayments gateway for Ruby
Ruby
1
star
75

crash_server

Simple web application for crash_hook
Ruby
1
star
76

docker-sandbox

Docker quickstart and playground
1
star
77

bootstrap_navigation

Navigation helper for bootstap html/css framework
Ruby
1
star
78

git-handler

Control git flow
Ruby
1
star
79

server-configs

Set of various server configs and scripts
1
star
80

reckless

Ruby client to Chicago's records store Reckless.com
Ruby
1
star
81

helm-wireguard

Wireguard Helm Chart
Go
1
star
82

docker-http-proxy

Go
1
star
83

shuttle-go

Shuttle is a minimalisting application deployment tool
Go
1
star
84

heroku-addon

Heroku Addon SDK for Go
Go
1
star
85

dummy-service

This a service dummy for random testing
Ruby
1
star
86

dockerfiles

Collection of Dockerfiles
Shell
1
star