2016-03-26 12:52:54 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
2016-03-27 12:19:09 +00:00
|
|
|
import os, sys, stat, re, shutil, socket
|
2016-03-26 23:01:28 +00:00
|
|
|
from click import argument, command, group, option, secho as echo
|
2016-03-27 12:08:30 +00:00
|
|
|
from os.path import abspath, exists, join, dirname
|
2016-03-27 12:19:09 +00:00
|
|
|
from subprocess import call
|
2016-03-29 20:57:51 +00:00
|
|
|
from time import sleep
|
2016-03-31 22:26:52 +00:00
|
|
|
from glob import glob
|
2016-03-26 12:52:54 +00:00
|
|
|
|
2016-03-29 20:18:38 +00:00
|
|
|
# === Globals - all tweakable settings are here ===
|
2016-03-27 12:12:13 +00:00
|
|
|
|
2016-03-27 12:08:30 +00:00
|
|
|
PIKU_ROOT = os.environ.get('PIKU_ROOT', join(os.environ['HOME'],'.piku'))
|
2016-03-28 22:37:36 +00:00
|
|
|
|
2016-03-27 12:08:30 +00:00
|
|
|
APP_ROOT = abspath(join(PIKU_ROOT, "apps"))
|
2016-03-27 12:20:44 +00:00
|
|
|
ENV_ROOT = abspath(join(PIKU_ROOT, "envs"))
|
2016-03-27 12:08:30 +00:00
|
|
|
GIT_ROOT = abspath(join(PIKU_ROOT, "repos"))
|
|
|
|
LOG_ROOT = abspath(join(PIKU_ROOT, "logs"))
|
2016-03-28 22:37:36 +00:00
|
|
|
UWSGI_AVAILABLE = abspath(join(PIKU_ROOT, "uwsgi-available"))
|
|
|
|
UWSGI_ENABLED = abspath(join(PIKU_ROOT, "uwsgi-enabled"))
|
|
|
|
UWSGI_ROOT = abspath(join(PIKU_ROOT, "uwsgi"))
|
2016-03-26 12:52:54 +00:00
|
|
|
|
2016-03-27 12:12:13 +00:00
|
|
|
|
2016-03-29 20:18:38 +00:00
|
|
|
# === Utility functions ===
|
2016-03-26 12:52:54 +00:00
|
|
|
|
2016-03-26 21:33:02 +00:00
|
|
|
def sanitize_app_name(app):
|
2016-03-26 17:14:13 +00:00
|
|
|
"""Sanitize the app name and build matching path"""
|
|
|
|
app = "".join(c for c in app if c.isalnum() or c in ('.','_')).rstrip()
|
2016-03-26 21:33:02 +00:00
|
|
|
return app
|
2016-03-26 17:14:13 +00:00
|
|
|
|
|
|
|
|
2016-03-26 12:52:54 +00:00
|
|
|
def get_free_port(address=""):
|
2016-03-28 18:28:24 +00:00
|
|
|
"""Find a free TCP port (entirely at random)"""
|
2016-03-26 12:52:54 +00:00
|
|
|
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-27 12:08:30 +00:00
|
|
|
authorized_keys = join(os.environ['HOME'],'.ssh','authorized_keys')
|
2016-03-27 12:18:04 +00:00
|
|
|
if not exists(dirname(authorized_keys)):
|
|
|
|
os.makedirs(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-28 17:40:52 +00:00
|
|
|
with open(authorized_keys, 'a') as h:
|
|
|
|
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-29 19:24:17 +00:00
|
|
|
|
2016-03-27 12:12:13 +00:00
|
|
|
|
2016-03-31 22:42:13 +00:00
|
|
|
# TODO: allow for multiple workers
|
2016-03-29 19:24:17 +00:00
|
|
|
def parse_procfile(filename):
|
|
|
|
"""Parses a Procfile and returns the worker types. Only one worker of each type is allowed."""
|
|
|
|
workers = {}
|
|
|
|
if not exists(filename):
|
|
|
|
return None
|
|
|
|
with open(filename, 'r') as procfile:
|
|
|
|
for line in procfile:
|
2016-03-29 19:38:48 +00:00
|
|
|
try:
|
|
|
|
kind, command = map(lambda x: x.strip(), line.split(":", 1))
|
|
|
|
if kind in ['web', 'worker', 'wsgi']:
|
|
|
|
workers[kind] = command
|
|
|
|
except:
|
|
|
|
echo("Warning: unrecognized Procfile declaration '%s'" % line, fg='yellow')
|
|
|
|
if not len(workers):
|
|
|
|
return None
|
2016-03-29 19:24:17 +00:00
|
|
|
# WSGI trumps regular web workers
|
|
|
|
if 'wsgi' in workers:
|
|
|
|
if 'web' in workers:
|
|
|
|
del(workers['web'])
|
|
|
|
return workers
|
2016-03-31 22:39:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
def parse_settings(filename):
|
|
|
|
"""Parses a settings file and returns a dict with environment variables"""
|
|
|
|
env = {}
|
|
|
|
if not exists(filename):
|
|
|
|
return None
|
|
|
|
with open(filename, 'r') as settings:
|
|
|
|
for line in settings:
|
|
|
|
try:
|
|
|
|
k, v = map(lambda x: x.strip(), line.split("=", 1))
|
|
|
|
env[k] = v
|
|
|
|
except:
|
|
|
|
echo("Warning: malformed setting '%s'" % line, fg='yellow')
|
|
|
|
return env
|
2016-03-29 19:24:17 +00:00
|
|
|
|
2016-03-27 12:12:13 +00:00
|
|
|
|
|
|
|
def do_deploy(app):
|
|
|
|
"""Deploy an app by resetting the work directory"""
|
|
|
|
app_path = join(APP_ROOT, app)
|
2016-03-29 19:24:17 +00:00
|
|
|
procfile = join(app_path, 'Procfile')
|
2016-03-27 12:12:13 +00:00
|
|
|
env = {'GIT_WORK_DIR': app_path}
|
|
|
|
if exists(app_path):
|
|
|
|
echo("-----> Deploying app '%s'" % app, fg='green')
|
2016-03-27 12:19:09 +00:00
|
|
|
call('git pull --quiet', cwd=app_path, env=env, shell=True)
|
|
|
|
call('git checkout -f', cwd=app_path, env=env, shell=True)
|
2016-03-29 19:24:17 +00:00
|
|
|
workers = parse_procfile(procfile)
|
|
|
|
if workers:
|
|
|
|
if exists(join(app_path, 'requirements.txt')):
|
|
|
|
echo("-----> Python app detected.", fg='green')
|
|
|
|
deploy_python(app, workers)
|
|
|
|
else:
|
|
|
|
echo("-----> Could not detect runtime!", fg='red')
|
|
|
|
# TODO: detect other runtimes
|
2016-03-28 18:28:24 +00:00
|
|
|
else:
|
2016-03-29 19:24:17 +00:00
|
|
|
echo("Error: Procfile not found for app '%s'." % app, fg='red')
|
2016-03-27 12:12:13 +00:00
|
|
|
else:
|
|
|
|
echo("Error: app '%s' not found." % app, fg='red')
|
2016-03-28 17:40:52 +00:00
|
|
|
|
|
|
|
|
2016-03-29 19:24:17 +00:00
|
|
|
def deploy_python(app, workers):
|
2016-03-28 18:28:24 +00:00
|
|
|
"""Deploy a Python application"""
|
2016-03-29 19:24:17 +00:00
|
|
|
|
2016-03-28 17:40:52 +00:00
|
|
|
if not exists(env_path):
|
|
|
|
echo("-----> Creating virtualenv for '%s'" % app, fg='green')
|
|
|
|
os.makedirs(env_path)
|
|
|
|
call('virtualenv %s' % app, cwd=ENV_ROOT, shell=True)
|
2016-03-28 18:28:24 +00:00
|
|
|
|
|
|
|
# TODO: run pip only if requirements have changed
|
|
|
|
echo("-----> Running pip for '%s'" % app, fg='green')
|
2016-03-28 17:40:52 +00:00
|
|
|
activation_script = join(env_path,'bin','activate_this.py')
|
|
|
|
execfile(activation_script, dict(__file__=activation_script))
|
2016-03-28 22:55:02 +00:00
|
|
|
call('pip install -r %s' % join(APP_ROOT, app, 'requirements.txt'), cwd=env_path, shell=True)
|
2016-03-31 22:26:52 +00:00
|
|
|
create_workers(app, workers)
|
|
|
|
|
|
|
|
|
|
|
|
def create_workers(app, workers):
|
2016-03-31 22:42:13 +00:00
|
|
|
"""Create all workers for an app"""
|
2016-03-31 22:26:52 +00:00
|
|
|
ordinal = 1
|
2016-03-31 22:39:29 +00:00
|
|
|
env_file = join(APP_ROOT, 'ENV')
|
|
|
|
settings = join(ENV_ROOT, app)
|
|
|
|
env = {
|
|
|
|
'PATH': os.environ['PATH'],
|
|
|
|
'VIRTUAL_ENV': env_path,
|
|
|
|
'PORT': str(get_free_port())
|
|
|
|
}
|
|
|
|
# Load environment variables shipped with repo (if any)
|
|
|
|
if exists(env_file):
|
|
|
|
env.update(parse_settings(env_file))
|
|
|
|
# Override with custom settings (if any)
|
|
|
|
if exists(settings):
|
|
|
|
env.update(parse_settings(settings))
|
|
|
|
# Create workers
|
2016-03-31 22:26:52 +00:00
|
|
|
for k, v in workers.iteritems():
|
2016-03-31 22:39:29 +00:00
|
|
|
create_worker(app, k, v, env, ordinal)
|
|
|
|
ordinal += 1
|
2016-03-31 22:26:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def single_worker(app, kind, command, env, ordinal=1):
|
2016-03-31 22:42:13 +00:00
|
|
|
"""Set up and deploy a single worker of a given kind"""
|
2016-03-31 22:26:52 +00:00
|
|
|
env_path = join(ENV_ROOT, app)
|
|
|
|
available = join(UWSGI_AVAILABLE, '%s_%s_%d.ini' % (app, kind, ordinal))
|
|
|
|
enabled = join(UWSGI_ENABLED, '%s_%s_%d.ini' % (app, kind, ordinal))
|
2016-03-28 18:28:24 +00:00
|
|
|
|
2016-03-29 19:24:17 +00:00
|
|
|
settings = [
|
2016-03-31 22:26:52 +00:00
|
|
|
('virtualenv', join(ENV_ROOT, app)),
|
|
|
|
('chdir', join(APP_ROOT, app)),
|
|
|
|
('master', 'true'),
|
|
|
|
('project', app),
|
|
|
|
('max-requests', '1000'),
|
|
|
|
('processes', '1'),
|
|
|
|
('procname-prefix', '%s_%s_%d' % (app, kind, ordinal))
|
|
|
|
('enable-threads', 'true'),
|
|
|
|
('threads', '4'),
|
|
|
|
('log-maxsize', '1048576'),
|
|
|
|
('logto', '%s_%d.log' % (join(LOG_ROOT, app, kind), ordinal)),
|
|
|
|
('log-backupname', '%s_%d.log.old' % (join(LOG_ROOT, app, kind), ordinal)),
|
2016-03-29 19:24:17 +00:00
|
|
|
]
|
2016-03-31 22:26:52 +00:00
|
|
|
for k, v in env.iteritems():
|
|
|
|
settings.append(('env', '%s=%v' % (k,v)))
|
|
|
|
if kind == 'wsgi':
|
|
|
|
settings.extend([
|
|
|
|
('module', command),
|
|
|
|
('http', ':%s' % env['PORT'])
|
|
|
|
]
|
2016-03-29 19:24:17 +00:00
|
|
|
else:
|
2016-03-31 22:26:52 +00:00
|
|
|
settings.append(('attach-daemon', command))
|
2016-03-28 18:11:19 +00:00
|
|
|
with open(available, 'w') as h:
|
2016-03-29 19:24:17 +00:00
|
|
|
h.write('[uwsgi]\n')
|
|
|
|
for k, v in settings:
|
|
|
|
h.write("%s = %s\n" % (k, v))
|
2016-03-31 22:26:52 +00:00
|
|
|
echo("-----> Enabling '%s:%s_%d'" % (app, kind, ordinal), fg='green')
|
2016-03-29 20:57:51 +00:00
|
|
|
os.unlink(enabled)
|
2016-03-29 21:23:57 +00:00
|
|
|
sleep(5) # TODO: replace this with zmq signalling
|
2016-03-28 17:40:52 +00:00
|
|
|
shutil.copyfile(available, enabled)
|
2016-03-31 22:26:52 +00:00
|
|
|
|
2016-03-27 12:12:13 +00:00
|
|
|
|
2016-03-29 20:18:38 +00:00
|
|
|
# === CLI commands ===
|
2016-03-26 12:52:54 +00:00
|
|
|
|
2016-03-27 12:08:30 +00:00
|
|
|
@group()
|
2016-03-26 17:28:01 +00:00
|
|
|
def piku():
|
2016-03-27 12:16:00 +00:00
|
|
|
"""Initialize paths"""
|
2016-03-28 10:26:10 +00:00
|
|
|
for p in [APP_ROOT, GIT_ROOT, ENV_ROOT, UWSGI_ROOT, UWSGI_AVAILABLE, UWSGI_ENABLED, LOG_ROOT]:
|
2016-03-27 12:16:00 +00:00
|
|
|
if not exists(p):
|
|
|
|
os.makedirs(p)
|
2016-03-26 22:47:39 +00:00
|
|
|
|
2016-03-26 12:52:54 +00:00
|
|
|
|
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-28 18:28:24 +00:00
|
|
|
pass
|
|
|
|
#print sys.argv[1:]
|
2016-03-26 21:33:02 +00:00
|
|
|
#print os.environ
|
2016-03-26 12:52:54 +00:00
|
|
|
|
2016-03-26 22:47:39 +00:00
|
|
|
|
2016-03-29 20:18:38 +00:00
|
|
|
# --- User commands ---
|
2016-03-26 12:52:54 +00:00
|
|
|
|
2016-03-26 22:08:10 +00:00
|
|
|
@piku.command("deploy")
|
|
|
|
@argument('app')
|
|
|
|
def deploy_app(app):
|
2016-03-26 23:01:28 +00:00
|
|
|
"""Deploy an application"""
|
2016-03-26 22:08:10 +00:00
|
|
|
app = sanitize_app_name(app)
|
2016-03-26 22:19:30 +00:00
|
|
|
do_deploy(app)
|
2016-03-26 22:47:39 +00:00
|
|
|
|
|
|
|
|
2016-03-29 20:18:38 +00:00
|
|
|
@piku.command("destroy")
|
|
|
|
@argument('app')
|
|
|
|
def destroy_app(app):
|
|
|
|
"""Destroy an application"""
|
|
|
|
app = sanitize_app_name(app)
|
|
|
|
for p in [join(x, app) for x in [APP_ROOT, GIT_ROOT, ENV_ROOT, LOG_ROOT]]:
|
|
|
|
if exists(p):
|
|
|
|
echo("Removing folder '%s'" % p, fg='yellow')
|
|
|
|
shutil.rmtree(p)
|
2016-03-31 22:53:35 +00:00
|
|
|
for p in [join(x, '%s*.ini' % app) for x in [UWSGI_AVAILABLE, UWSGI_ENABLED]]:
|
|
|
|
g = glob(p)
|
|
|
|
if len(g):
|
|
|
|
for f in g:
|
|
|
|
echo("Removing file '%s'" % f, fg='yellow')
|
|
|
|
os.remove(f)
|
2016-03-26 22:47:39 +00:00
|
|
|
|
|
|
|
|
2016-03-27 12:08:30 +00:00
|
|
|
@piku.command("disable")
|
|
|
|
@argument('app')
|
|
|
|
def disable_app(app):
|
|
|
|
"""Disable an application"""
|
|
|
|
app = sanitize_app_name(app)
|
2016-03-31 22:26:52 +00:00
|
|
|
config = glob(join(UWSGI_ENABLED, '%s*.ini' % app))
|
|
|
|
if len(config):
|
2016-03-27 12:08:30 +00:00
|
|
|
echo("Disabling app '%s'..." % app, fg='yellow')
|
2016-03-31 22:26:52 +00:00
|
|
|
for c in config:
|
|
|
|
os.remove(c)
|
2016-03-28 22:35:31 +00:00
|
|
|
else:
|
2016-03-31 22:26:52 +00:00
|
|
|
echo("Error: app '%s' not deployed!" % app, fg='red')
|
2016-03-28 22:35:31 +00:00
|
|
|
|
|
|
|
|
2016-03-27 12:08:30 +00:00
|
|
|
@piku.command("enable")
|
|
|
|
@argument('app')
|
|
|
|
def enable_app(app):
|
|
|
|
"""Enable an application"""
|
|
|
|
app = sanitize_app_name(app)
|
2016-03-31 22:26:52 +00:00
|
|
|
enabled = glob(join(UWSGI_ENABLED, '%s*.ini' % app))
|
|
|
|
available = glob(join(UWSGI_AVAILABLE, '%s*.ini' % app))
|
2016-03-27 12:08:30 +00:00
|
|
|
if exists(join(APP_ROOT, app)):
|
2016-03-31 22:26:52 +00:00
|
|
|
if len(enabled):
|
|
|
|
if len(available):
|
2016-03-27 12:08:30 +00:00
|
|
|
echo("Enabling app '%s'..." % app, fg='yellow')
|
2016-03-31 22:26:52 +00:00
|
|
|
for a in available:
|
|
|
|
shutil.copy(a, join(UWSGI_ENABLED, app))
|
2016-03-27 12:08:30 +00:00
|
|
|
else:
|
|
|
|
echo("Error: app '%s' is not configured.", fg='red')
|
2016-03-27 12:12:43 +00:00
|
|
|
else:
|
2016-03-27 12:08:30 +00:00
|
|
|
echo("Warning: app '%s' is already enabled, skipping.", fg='yellow')
|
|
|
|
else:
|
|
|
|
echo("Error: app '%s' does not exist.", fg='red')
|
|
|
|
|
2016-03-28 18:28:24 +00:00
|
|
|
|
2016-03-29 20:18:38 +00:00
|
|
|
@piku.command("ls")
|
|
|
|
def list_apps():
|
|
|
|
"""List applications"""
|
|
|
|
for a in os.listdir(APP_ROOT):
|
|
|
|
echo(a, fg='green')
|
|
|
|
|
|
|
|
|
2016-03-31 22:53:35 +00:00
|
|
|
# TODO: multitail
|
|
|
|
@piku.command("tail")
|
2016-03-26 22:47:39 +00:00
|
|
|
@argument('app')
|
2016-03-29 20:18:38 +00:00
|
|
|
def tail_logs(app):
|
|
|
|
"""Tail an application log"""
|
2016-03-26 22:47:39 +00:00
|
|
|
app = sanitize_app_name(app)
|
2016-03-31 22:53:35 +00:00
|
|
|
logfile = join(LOG_ROOT, "%s*.log" % app)
|
|
|
|
if len(glob(logfile)):
|
2016-03-29 20:18:38 +00:00
|
|
|
call('tail -F %s' % logfile, cwd=LOG_ROOT, shell=True)
|
|
|
|
else:
|
|
|
|
echo("No logs found for app '%s'." % app, fg='yellow')
|
|
|
|
|
|
|
|
|
|
|
|
@piku.command("restart")
|
|
|
|
@argument('app')
|
|
|
|
def restart_app(app):
|
|
|
|
"""Restart an application"""
|
2016-03-31 22:53:35 +00:00
|
|
|
app = sanitize_app_name(app)
|
|
|
|
enabled = glob(join(UWSGI_ENABLED, '%s*.ini' % app))
|
|
|
|
available = glob(join(UWSGI_AVAILABLE, '%s*.ini' % app))
|
|
|
|
if len(enabled):
|
2016-03-29 20:18:38 +00:00
|
|
|
echo("Restarting app '%s'..." % app, fg='yellow')
|
2016-03-29 20:57:51 +00:00
|
|
|
# Destroying the original file signals uWSGI to kill the vassal instead of reloading it
|
2016-03-31 22:53:35 +00:00
|
|
|
for e in enabled:
|
|
|
|
os.unlink(e)
|
|
|
|
sleep(5) # TODO: replace this with zmq signalling
|
|
|
|
if len(available):
|
|
|
|
for a in available:
|
|
|
|
shutil.copy(a, join(UWSGI_ENABLED, app))
|
2016-03-29 20:18:38 +00:00
|
|
|
else:
|
|
|
|
echo("Error: app '%s' not enabled!" % app, fg='red')
|
|
|
|
|
|
|
|
|
|
|
|
# --- Internal commands ---
|
2016-03-26 22:47:39 +00:00
|
|
|
|
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-28 22:35:31 +00:00
|
|
|
"""INTERNAL: Post-receive git hook"""
|
2016-03-26 21:33:02 +00:00
|
|
|
app = sanitize_app_name(app)
|
2016-03-27 12:08:30 +00:00
|
|
|
repo_path = join(GIT_ROOT, app)
|
|
|
|
app_path = join(APP_ROOT, app)
|
2016-03-26 17:14:13 +00:00
|
|
|
for line in sys.stdin:
|
2016-03-26 17:15:19 +00:00
|
|
|
oldrev, newrev, refname = line.strip().split(" ")
|
2016-03-26 22:08:10 +00:00
|
|
|
#print "refs:", oldrev, newrev, refname
|
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-27 12:08:30 +00:00
|
|
|
if not exists(app_path):
|
|
|
|
echo("-----> Creating app '%s'" % app, fg='green')
|
2016-03-26 21:33:02 +00:00
|
|
|
os.makedirs(app_path)
|
2016-03-27 12:19:09 +00:00
|
|
|
call('git clone --quiet %s %s' % (repo_path, app), cwd=APP_ROOT, shell=True)
|
2016-03-26 22:19:30 +00:00
|
|
|
do_deploy(app)
|
2016-03-26 17:14:13 +00:00
|
|
|
else:
|
2016-03-28 22:45:35 +00:00
|
|
|
# TODO: Handle pushes to another branch
|
|
|
|
echo("receive-branch '%s': %s, %s" % (app, newrev, refname))
|
2016-03-28 18:28:24 +00:00
|
|
|
#print "hook", app, sys.argv[1:]
|
2016-03-29 20:18:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
@piku.command("git-receive-pack")
|
|
|
|
@argument('app')
|
|
|
|
def receive(app):
|
|
|
|
"""INTERNAL: Handle git pushes for an app"""
|
|
|
|
app = sanitize_app_name(app)
|
|
|
|
hook_path = join(GIT_ROOT, app, 'hooks', 'post-receive')
|
|
|
|
if not exists(hook_path):
|
|
|
|
os.makedirs(dirname(hook_path))
|
|
|
|
# Initialize the repository with a hook to this script
|
|
|
|
call("git init --quiet --bare " + app, cwd=GIT_ROOT, shell=True)
|
|
|
|
with open(hook_path,'w') as h:
|
|
|
|
h.write("""#!/usr/bin/env bash
|
|
|
|
set -e; set -o pipefail;
|
|
|
|
cat | PIKU_ROOT="%s" $HOME/piku.py git-hook %s""" % (PIKU_ROOT, app)) # TODO: remove hardcoded script name
|
|
|
|
# Make the hook executable by our user
|
|
|
|
os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IXUSR)
|
|
|
|
# Handle the actual receive. We'll be called with 'git-hook' after it happens
|
|
|
|
call('git-shell -c "%s"' % " ".join(sys.argv[1:]), cwd=GIT_ROOT, shell=True)
|
2016-03-28 18:28:24 +00:00
|
|
|
|
2016-03-26 21:33:02 +00:00
|
|
|
|
2016-03-26 12:52:54 +00:00
|
|
|
if __name__ == '__main__':
|
2016-03-26 17:28:01 +00:00
|
|
|
piku()
|