I'm setting up a deploy-on-git-push process on a remote Debian server. It is basically the common approach of having a bare repository with a post-receive hook which does a checkout to the web server's docroot, more or less.
I've used slightly simpler variations of this set up successfully for years, but this time around I'm complicating it by trying to separate ownership of the git repo and the site files between 2 different users. I don't want the user who can SSH in (the git push happens over SSH) to have write perms to the site, and vice-versa (the site user shouldn't have write perms to the git repository).
I have 1 user, let's call her
git-user, who owns a bare git repository at/var/gitrepos/my_site.git, and this user is the only user allowed to connect over SSH;I have a 2nd user, say
site-user, who should own the checked-out files at/var/www/site;
I push over SSH to the repo, and this means any post-receive hook runs as the user I SSH in as - git-user in this case. So the post-receive hook can't do the checkout itself, as the site files would end up being owned by git-user, not site-user.
So my post-receive simply touches a trigger file, /var/run/deploy. A separate script is run from site-user's cron frequently, eg every minute, looking for that file. If it sees it, it does a checkout to /var/www/site. The relevant part of that script looks something like:
# Running as site-user
mkdir $NEW \
&& cd $NEW \
&& git --work-tree=. --git-dir=/var/gitrepos/my_site.git checkout -f www
However this fails with:
fatal: Unable to create '/var/gitrepos/my_site.git/index.lock': Permission denied
This is true, site-user - intentionally - does not have write permission to /var/gitrepos/my_site.git. I am not sure why doing a checkout to a different directory needs to create a lockfile in the repo, but apparently it does, and I guess I shouldn't fight that.
So what are the options?
git clonedoesn't need write perms to the repo, so this works, but it means I get the whole.git/directory. I have to remove that, or configure the web server to disallow access, both extra steps I'd rather avoid. Not a big deal obviously but this still feels wrong;I could add both users to a group and set up
g+wetc, but this voids the whole point of this approach (to deny each user write perms to the other's files);I guess I could mess with
sudoers, and allow one user to run a command as the other user, but again this feels like I am just chipping away at the separation I'm trying to enforce?I can do a
git cloneto$TMP_DIR, followed by agit checkout --git-dir=$TMP_DIR/.git/, but this seems super clunky, as well as taking 2x as long;
Any other neat options I am missing?
UPDATE
As suggested by @Matt below I tried setting GIT_INDEX_FILE to a writeable (by site-user) file outside the repository. This does seem to get past the first problem, but still fails with:
error: Unable to create '/var/gitrepos/my_site.git/HEAD.lock': Permission denied
I don't understand why a checkout to a new location needs to modify anything in the repository?
Another option would be for the
git-userto create an archive that contains all the relevant files to be deployed (think of it as an artefact). This can be done in the post-receive hook before touching the trigger file. To create the ZIP/TAR, you can use thegit archivecommand which facilitates this step.As soon as the cron-job runs and a deploy is triggered, the
site-userextracts the contents of the archive into/var/www/siteand removes the archive.This way, the
git-userhas no access to the webroot. At the same time, thesite-userdoes not even need read-access to the repository.It is also possible to specify an alternative index-file with the environment variable
GIT_INDEX_FILEto circumvent the default location ($GIT_DIR/index). But I don't know if Git needs write access to other files/folders as well.