diff --git a/README.md b/README.md index a4f37f3..aa16477 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Git webhook deploy agent +Git webhook deploy agent with sudo support ========== For sysadmins simply setup a post hook agent on server to deploy git projects like PaaS from your using third-party git service. @@ -36,13 +36,13 @@ Additional things about git you should make sure: Set a git post hook in the admin panel of your repository like this: - [POST]:http://user:password@deploy.yourserver.com:6060/project/id@remote/branch + [POST]:http://user:password@deploy.yourserver.com:6060/project/id@remote/branch/extraArg * `user:password` is reqired part in post URL. The agent will check the request with HTTP basic authentication to avoid mistake request. * `6060` as port is set in the config, you can change it as you wish. -* `/project/:id` is the router, `@remote/branch` is optional default to `origin/master`. If branch (or with remote) is set in hook URL, the part after `@` should be exactly match the config (see below). +* `/project/:id` is the router, `@remote/branch` is optional default to `origin/master`. If branch (or with remote) is set in hook URL, the part after `@` should be exactly match the config (see below). `extraArg` can be passed to router ### Configuration ### @@ -63,12 +63,14 @@ Here is a sample of configuration structure: "projects": { "sample": { // branch - "master": { + "origin/master/extraArg": { // Project path "path": "/var/www/sample", // Task to be run after git pull, such as build etc. // "shell": "./build.sh", + // run scirpt with sudo -H -u $user -g $group + // "sudo": true // Users in list allow to trigger deploy "users": ["abc"] @@ -84,7 +86,10 @@ Here is a sample of configuration structure: // Each user ID should match server user name. "users": { "abc": "ba1f2511fc30423bdbb183fe33f3dd0f" - } + }, + //Group name for deploy tasks + "group": ["www-data"] + } ``` diff --git a/bin/cli.sh b/bin/cli.sh index d73244a..59dd73a 100755 --- a/bin/cli.sh +++ b/bin/cli.sh @@ -37,7 +37,7 @@ case $1 in start ) if [[ $CONFIGURED = 1 ]]; then echo "Starting deploy agent server..." - pm2 start $SERVER_SCRIPT --name $APP_NAME + pm2 start $SERVER_SCRIPT --name $APP_NAME --log $LOG_PATH/agent.log else check_config fi diff --git a/bin/deploy.sh b/bin/deploy.sh index 453bdad..8465fb4 100755 --- a/bin/deploy.sh +++ b/bin/deploy.sh @@ -5,17 +5,29 @@ PROJECT=$1 remote=$2 branch=$3 -task=$4 +path=$4 +task=$5 +sudo=$6 +user=$7 +group=$8 +if [[ $sudo == true ]] ; then + +echo "Sudo On: sudo -H -u$user -g$group ${BASH_SOURCE[0]} $PROJECT $remote $branch $path $task false $user $group" +sudo -H -u$user -g$group ${BASH_SOURCE[0]} $PROJECT $remote $branch $path $task false $user $group + +else + +date echo "start deploying $remote/$branch" TEMP_FLAG=$LOG_PATH/.$PROJECT.$remote-$branch if [[ ! -f $TEMP_FLAG ]]; then - touch $TEMP_FLAG + touch $TEMP_FLAG else - echo "still deploying..." - exit 0 + echo "still deploying..." + exit 0 fi @@ -30,27 +42,31 @@ git fetch $remote for br in $(git branch | sed 's/^[\* ]*//') do - if [[ $br == $branch ]]; then - echo "found branch: $branch" - found=1 - fi + if [[ $br == $branch ]]; then + echo "found branch: $branch" + found=1 + fi done if [[ $found == 1 ]]; then - echo "git checkout -q $branch" - git checkout -q $branch - echo "git merge $remote/$branch" - git merge $remote/$branch + echo "git checkout -q $branch" + git checkout -q $branch + echo "git merge $remote/$branch" + git merge $remote/$branch else - echo "git checkout $remote/$branch -b $branch" - git checkout $remote/$branch -b $branch + echo "git checkout $remote/$branch -b $branch" + git checkout $remote/$branch -b $branch fi -git submodule update --init --recursive +#git submodule update --init --recursive if [[ $task != '' ]]; then - echo "start to run shell script: $task" - sh $task + echo "start to run shell script: sh $task $path $branch" + sh $task $path $branch fi rm $TEMP_FLAG + +fi + +date diff --git a/config.json b/config.json index 7728909..cef3dd5 100644 --- a/config.json +++ b/config.json @@ -6,12 +6,14 @@ "sample": { "origin/master": { "path": "/var/www/sample", - "users": ["abc"] + "users": ["abc"], + "sudo": true } } }, "users": { "abc": "ba1f2511fc30423bdbb183fe33f3dd0f" - } + }, + "group": ["www-data"] } diff --git a/server.js b/server.js index fe1e0ed..beb7098 100644 --- a/server.js +++ b/server.js @@ -8,78 +8,88 @@ var basicAuth = require('basic-auth'); function hook(req, res, next) { - console.log('Deployment request received: ' + JSON.stringify(req.params)); - - var id = req.params[0]; - if (!id) { - console.log('[400] Bad request. Without project id.'); - return res.status(400).end(); - } - - // find project in config - var project = config.projects[id]; - if (!project) { - console.log('[404] Not found. No project named as "' + id + '" found.'); - return res.status(404).end(); - } - - // find branch options in config - var branch = req.params[1] || config.defaultBranch || 'master'; - var options = project[branch]; - if (!options) { - console.log('[404] No options of branch "' + branch + '" found. Please check config.'); - return res.status(404).end(); - } - - var branchParam = branch.split('/'); - var remote = branchParam[0]; - if (branchParam.length == 1) { - remote = config.defaultRemote || 'origin'; - } else { - branch = branchParam[1]; - } - - // check auth - var auth = basicAuth(req); - if (!auth || - !auth.pass || - options.users.indexOf(auth.name) < 0 || - config.users[auth.name] != auth.pass) { - console.log('[403] Forbidden.'); - return res.status(403).end(); - } - - console.log('Authentication passed.'); - - if (!fs.existsSync(options.path)) { - console.log('[404] No path found for project: "' + id + '"'); - return res.status(404).end(); - } - - // need nodejs >= 0.12 - var uid = parseInt(child_process.execSync('id -u ' + auth.name).toString().trim(), 10); - var home = child_process.execSync('echo ~' + auth.name).toString().trim(); - - if (!uid || !home) { - return res.status(400).send('not found'); - } - - res.status(200).send('ok'); - - child_process.execFile(path.join(__dirname, 'bin/deploy.sh'), [id, remote, branch, options.shell || ''], { - cwd: options.path, - uid: uid, - env: process.env - }, function (error, stdout, stderr) { - console.log(stdout); - if (error) { - console.log(error); - } else { - console.log('Deployment done.'); - } - }); - - console.log('[200] Deployment started.'); + console.log('Deployment request received: ' + JSON.stringify(req.params)); + + var id = req.params[0]; + if (!id) { + console.log('[400] Bad request. Without project id.'); + return res.status(400).end(); + } + + // find project in config + var project = config.projects[id]; + if (!project) { + console.log('[404] Not found. No project named as "' + id + '" found.'); + return res.status(404).end(); + } + + // find branch options in config + var branch = req.params[1] || config.defaultBranch || 'master'; + var options = project[branch]; + if (!options) { + console.log('[404] No options of branch "' + branch + '" found. Please check config.'); + return res.status(404).end(); + } + + var branchParam = branch.split('/'); + var remote = branchParam[0]; + if (branchParam.length == 1) { + remote = config.defaultRemote || 'origin'; + } else { + branch = branchParam[1]; + } + + // check auth + var auth = basicAuth(req); + if (!auth || + !auth.pass || + options.users.indexOf(auth.name) < 0 || + config.users[auth.name] != auth.pass) { + console.log('[403] Forbidden.'); + return res.status(403).end(); + } + + console.log('Authentication passed.'); + + if (!fs.existsSync(options.path)) { + console.log('[404] No path found for project: "' + id + '"'); + return res.status(404).end(); + } + + if (!config.group){ + config.group=auth.name; + } + // need nodejs >= 0.12 + var uid = parseInt(child_process.execSync('id -u ' + auth.name).toString().trim(), 10); + var home = child_process.execSync('echo ~' + auth.name).toString().trim(); + var gid = parseInt(child_process.execSync('id -g ' + config.group).toString().trim(), 10); + + if (!uid || !home) { + return res.status(400).send('not found'); + } + + res.status(200).send('ok'); + + if ( options.sudo == true) { + uid = 0; + gid = 0; + } + + child_process.execFile(path.join(__dirname, 'bin/deploy.sh'), [id, remote, branch, options.path, options.shell || '', options.sudo, auth.name, config.group ], { + cwd: options.path, + uid: uid, + gid: gid, + env: process.env + }, function (error, stdout, stderr) { + console.log(stdout); + if (error) { + console.log(error); + } else { + console.log('Deployment done.'); + } + }); + + console.log('[200] Deployment started.'); } var config = require('/etc/hookagent.json'); @@ -87,13 +97,13 @@ var config = require('/etc/hookagent.json'); var agent = express(); agent.get('/', function (req, res, next) { - // indicate process is running - res.status(200).send('ok'); + // indicate process is running + res.status(200).send('ok'); }); // [POST]:/project/project-name[@[remote/]branch-name] agent.post(/\/project\/([\w\.\-]+)(?:@([\w\/\.\-]+))?/i, hook); agent.listen(config.port, function() { - console.log("Hook agent started at %s. Listening on %d", new Date(), config.port); + console.log("Hook agent started at %s. Listening on %d", new Date(), config.port); });