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 clone
doesn'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+w
etc, 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 clone
to $TMP_DIR
, followed by a git 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?
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-user
to 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 the git archive
command which facilitates this step.
As soon as the cron-job runs and a deploy is triggered, the site-user
extracts the contents of the archive into /var/www/site
and removes the archive.
This way, the git-user
has no access to the webroot. At the same time, the site-user
does not even need read-access to the repository.
It is also possible to specify an alternative index-file with the environment variable GIT_INDEX_FILE
to circumvent the default location ($GIT_DIR/index
). But I don't know if Git needs write access to other files/folders as well.