2016-03-26 12:52:54 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
import os, sys, stat, re, socket, subprocess
|
|
|
|
from click import argument, command, group, option
|
|
|
|
|
|
|
|
APP_ROOT = os.environ.get('APP_ROOT', os.path.join(os.environ['HOME'],'.piku'))
|
|
|
|
|
|
|
|
# http://off-the-stack.moorman.nu/2013-11-23-how-dokku-works.html
|
|
|
|
|
2016-03-26 17:14:13 +00:00
|
|
|
def app_name_and_path(app):
|
|
|
|
"""Sanitize the app name and build matching path"""
|
|
|
|
app = "".join(c for c in app if c.isalnum() or c in ('.','_')).rstrip()
|
|
|
|
return app, os.path.abspath(os.path.join(APP_ROOT, app))
|
|
|
|
|
|
|
|
|
2016-03-26 12:52:54 +00:00
|
|
|
def get_free_port(address=""):
|
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
s.bind((address,0))
|
|
|
|
port = s.getsockname()[1]
|
|
|
|
s.close()
|
|
|
|
return port
|
2016-03-26 17:28:01 +00:00
|
|
|
|
|
|
|
|
2016-03-26 12:52:54 +00:00
|
|
|
def setup_authorized_keys(ssh_fingerprint, script_path, pubkey):
|
2016-03-26 17:39:23 +00:00
|
|
|
"""Sets up an authorized_keys file to redirect SSH commands"""
|
2016-03-26 12:52:54 +00:00
|
|
|
authorized_keys = os.path.join(os.environ['HOME'],'.ssh','authorized_keys')
|
|
|
|
if not os.path.exists(os.dirname(authorized_keys)):
|
|
|
|
os.makedirs(os.dirname(authorized_keys))
|
2016-03-26 17:39:23 +00:00
|
|
|
# Restrict features and force all SSH commands to go through our script
|
2016-03-26 12:52:54 +00:00
|
|
|
h = open(authorized_keys, 'a')
|
2016-03-26 17:28:01 +00:00
|
|
|
h.write("""command="FINGERPRINT=%(ssh_fingerprint)s NAME=default %(script_path)s $SSH_ORIGINAL_COMMAND",no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding %(pubkey)s\n""" % locals())
|
2016-03-26 12:52:54 +00:00
|
|
|
h.close()
|
|
|
|
|
|
|
|
|
|
|
|
@group(invoke_without_command=True)
|
2016-03-26 17:28:01 +00:00
|
|
|
def piku():
|
2016-03-26 12:52:54 +00:00
|
|
|
pass
|
|
|
|
|
2016-03-26 17:28:01 +00:00
|
|
|
@piku.resultcallback()
|
2016-03-26 12:52:54 +00:00
|
|
|
def cleanup(ctx):
|
2016-03-26 17:33:14 +00:00
|
|
|
"""Callback from command execution -- currently used for debugging"""
|
2016-03-26 12:52:54 +00:00
|
|
|
print sys.argv[1:]
|
|
|
|
print os.environ
|
|
|
|
|
|
|
|
# https://github.com/dokku/dokku/blob/master/plugins/git/commands#L103
|
2016-03-26 17:28:01 +00:00
|
|
|
@piku.command("git-receive-pack")
|
2016-03-26 12:52:54 +00:00
|
|
|
@argument('app')
|
|
|
|
def receive(app):
|
|
|
|
"""Handle git pushes for an app, initializing the local repo if necessary"""
|
2016-03-26 17:14:13 +00:00
|
|
|
app, app_path = app_name_and_path(app)
|
2016-03-26 12:52:54 +00:00
|
|
|
hook_path = os.path.join(app_path, 'hooks', 'pre-receive')
|
|
|
|
if not os.path.exists(app_path):
|
|
|
|
os.makedirs(os.path.dirname(hook_path))
|
|
|
|
os.chdir(os.path.dirname(app_path))
|
|
|
|
# Initialize the repository with a hook to this script
|
|
|
|
subprocess.call("git init --bare " + app + " > /dev/null", shell=True)
|
|
|
|
h = open(hook_path,'w')
|
|
|
|
h.write("""#!/usr/bin/env bash
|
|
|
|
set -e; set -o pipefail;
|
2016-03-26 17:33:14 +00:00
|
|
|
cat | PIKU_ROOT="$PIKU_ROOT" $HOME/piku.py git-hook """ + app)
|
2016-03-26 12:52:54 +00:00
|
|
|
h.close()
|
2016-03-26 17:34:51 +00:00
|
|
|
# Make the hook executable by our user
|
|
|
|
os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IXUSR)
|
2016-03-26 12:52:54 +00:00
|
|
|
# Handle the actual receive. We'll be called with 'git-hook' while it happens
|
|
|
|
os.chdir(os.path.dirname(app_path))
|
|
|
|
subprocess.call('git-shell -c "%s"' % " ".join(sys.argv[1:]) , shell=True)
|
|
|
|
print "receive", app
|
|
|
|
|
|
|
|
|
2016-03-26 17:28:01 +00:00
|
|
|
@piku.command("git-hook")
|
2016-03-26 12:52:54 +00:00
|
|
|
@argument('app')
|
|
|
|
def git_hook(app):
|
2016-03-26 17:23:29 +00:00
|
|
|
"""Pre-receive git hook"""
|
2016-03-26 17:14:13 +00:00
|
|
|
app, app_path = app_name_and_path(app)
|
|
|
|
for line in sys.stdin:
|
2016-03-26 17:15:19 +00:00
|
|
|
oldrev, newrev, refname = line.strip().split(" ")
|
2016-03-26 17:14:13 +00:00
|
|
|
if refname == "refs/heads/master":
|
2016-03-26 17:23:29 +00:00
|
|
|
# Handle pushes to master branch
|
2016-03-26 17:14:13 +00:00
|
|
|
print "receive", app, newrev
|
|
|
|
else:
|
2016-03-26 17:23:29 +00:00
|
|
|
# Handle pushes to another branch
|
2016-03-26 17:28:01 +00:00
|
|
|
print "receive-branch", app, newrev, refname
|
2016-03-26 12:52:54 +00:00
|
|
|
print "hook", app
|
|
|
|
|
|
|
|
|
2016-03-26 17:28:01 +00:00
|
|
|
@piku.command("git-upload-pack", help="handle Git receive")
|
2016-03-26 12:52:54 +00:00
|
|
|
@argument('app')
|
|
|
|
def pass_through(app):
|
2016-03-26 17:14:13 +00:00
|
|
|
app, app_path = app_name_and_path(app)
|
|
|
|
os.chdir(os.path.dirname(app_path))
|
2016-03-26 12:52:54 +00:00
|
|
|
subprocess.call('git-shell -c "%s"' % " ".join(sys.argv[1:]) , shell=True)
|
|
|
|
print "upload", app
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2016-03-26 17:28:01 +00:00
|
|
|
piku()
|