Deploy with git push
- Introduction
- Configuration
- One-time Server Setup
- Authorized developers
- Create a new repository on the server
- Push-to-publish
Introduction
Wouldn’t it be nice to publish a website with a simple git push?
Here I’ll explain you how I usually do this.
Configuration
| Remote server available at the address | |
|---|---|
| Name of the repository | |
| Branch to be deployed | |
| User impersonated by webserver | |
| Group of the user impersonated by webserver | |
| ConcreteCMS languages to update/install | |
| Options |
|
One-time Server Setup
Install Required Packages
First of all, you need to install git on the server.
You can do this by running the following command (on Debian/Ubuntu & family):
sudo apt-get update -q
sudo apt-get install -qy git
If the above command fails with an error like Unable to locate package git, you can try this:
sudo apt-get install -qy git-core
Install and Configure Composer
# Go to your home directory
cd
# Download and install Composer
curl -sSLf https://getcomposer.org/installer | php
# Move Composer to the default binary folder
sudo mv composer.phar /usr/local/bin/composer
# Check that Composer works
composer --version
Private GitHub Repositories
If you need to access GitHub (private) repositories, you have to create a new Personal access token.
Assuming that the newly generated token is , you then have to run these commands:
mkdir -p /home/git/.composer/
cat <<'EOF' | sudo tee /home/git/.composer/auth.json >/dev/null
{
"github-oauth": {
"github.com": "{{ gitHubPAT }}"
}
}
EOF
chown -R git:www-data /home/git/.composer
chmod -R ug+rw /home/git/.composer
Script to Update ConcreteCMS
Updating ConcreteCMS is as simple as running the c5:update CLI command.
By the way, it’s better to:
- turn on maintenance mode before doing it (and turning it back off when done)
- run
c5:updateto update the ConcreteCMS core - run
c5:package:update --allto update any ConcreteCMS package that may have been udated by composer - update the installed language files
- refresh the Doctrine Entities
- clear the ConcreteCMS cache
So, what about creating a script to make it easier to perform all those operations?
I did it for you ;)
Download this gists, save it as /usr/local/bin/update-concrete, and make it executable:
sudo curl -sSLf \
-o/usr/local/bin/update-concrete \
https://gist.githubusercontent.com/mlocati/5db7bb36b4c3ac7676a4ace97b69ab46/raw/update-concrete
sudo chmod 755 /usr/local/bin/update-concrete
Create the git user
We need to create a user account on the server. This account will be the one used by the publish process.
With the following command we create that account:
sudo adduser \
--gecos Git \
--disabled-login \
--disabled-password \
--shell /usr/bin/git-shell \
--home /home/git \
--ingroup "{{ webUserGroup }}" \
git
Here’s the explanation of the above options:
--gecos Git: set the full name of the account toGit(this essentially in order to avoid asking useless data like the account room number and work/home phone)--disabled-login: the user won’t be able to use the account until the password is set--disabled-password: disable the login using passwords (we’ll access the system with SSH keys)--shell /usr/bin/git-shell: the git user is only allowed to run git commands--home /home/git: set the home directory for the user to/home/git--ingroup {{ webUserGroup }}: add the new user to the user group used by the web server instead of the default onegit: the username of the new account
We then need to configure the git shell.
In order to improve the security of unwanted logins and abort shell sessions, let’s create a file that is executed when the git user logs in the shell and that will abort the session.
sudo mkdir -p /home/git/git-shell-commands
cat <<'EOF' | sudo tee /home/git/git-shell-commands/no-interactive-login >/dev/null
#!/bin/sh
printf "Hi %s!\nYou've successfully authenticated, but I do not provide interactive shell access.\n" "$USER"
exit 128
EOF
sudo chmod +x /home/git/git-shell-commands/no-interactive-login
Allow {{ webUser }} Impersonation
The git user needs to be able to publish files acting like {{ webUser }}.
In order to allow this, run this command:
sudo visudo
Go to the end of the editor contents and add these lines:
# The user git, on any host (ALL), can run as {{ webUser }} (without password) these commands
git ALL=({{ webUser }}) NOPASSWD: {{ visudoCommands }}
Script launched by the hook
When you’ll push to the git repository, you’ll need to perform some operations.
In order to simplify these operations I’ve created this gist, save it as /usr/local/bin/git-post-receive-hook, and make it executable:
sudo curl -sSLf \
-o/usr/local/bin/git-post-receive-hook \
https://gist.githubusercontent.com/mlocati/5db7bb36b4c3ac7676a4ace97b69ab46/raw/git-post-receive-hook
sudo chmod 755 /usr/local/bin/git-post-receive-hook
Authorized developers
Every developer that should be able to publish needs an Ed25519 (or RSA) key pair.
It’s possible (and recommended) to use a different key for every developer.
Create the keys under Windows
I order to create the key pair, you can use PuTTYgen.
If you already installed TortoiseGit you should already have this command, otherwise you can download it.
So, open PuTTYgen and:
- in the
Parameterspage be sure to select the type of key to generateEdDSA(curve:Ed25519)- if your server is old:
RSAwith at least4096number of bits in a generated key
- Hit the
Generatebutton and move randomly your mouse over the PuTTYgen window - In the
Key commentfield enter the name of the developer (egJohn Doe) - In the
Key passphraseandConfirm passphrasefields enter a password of your choice - Hit the
Save private keybutton to save the private key to file - Copy the contents of the
Public key for pasting into OpenSSH authorized_keys fileand save it: this is the public key that we’ll need.
Create the keys under *nix
Simply run this command to create an Ed25519 key pair
ssh-keygen -f key-for-git -C 'Name of the developer' -t ed25519
If your server is old, you can generate an RSA keypair with at least 4096 bits:
ssh-keygen -f key-for-git -C 'Name of the developer' -t rsa -b 4096
Where:
-f key-for-git: usekey-for-gitas the name of the files that will contain the keys-C 'Name of the developer': this is the comment to associate to the key (use your developer name)-t ed25519: generate an Ed25519 key pair-t rsa -b 4096: generate an RSA key pair of 4096 bits
Once you run the ssh-keygen command and specified a password of your choice, you’ll have these two files:
key-for-git: contains the private keykey-for-git.pub: contains the public key
Allow the developer to publish to the server
Login to the server and run this command:
sudo mkdir -p /home/git/.ssh
sudo nano /home/git/.ssh/authorized_keys
Go to the end of the editor contents and add a new line containing the previously generated public key.
The public key is a single line that starts with ssh-ed25519 (for Ed25519 keys) or ssh-rsa (for RSA keys), followed by a quite long list of characters and ending with the developer name you specified in the comments during the creation of the key.
For improved security, it’s better to prepend the public key with no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty .
So, the full line to be added to the authorized_keys will be something like
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-...
Create a new repository on the server
We’ll end up with:
- Directory to be published by the web server (apache, nginx, whatever):
/var/www/{{ repositoryName }} - Repository with the repository:
/var/git/{{ repositoryName }}.git
First of all, we create the directory that will contain web site (it will be owned by the {{ webUser }} user):
sudo mkdir -p '/var/www/{{ repositoryName }}'
sudo chown -R '{{ webUser }}:{{ webUserGroup }}' '/var/www/{{ repositoryName }}'
sudo chmod u+rw -R '/var/www/{{ repositoryName }}'
Then we create the directory that will contain the bare repository data:
sudo mkdir -p '/var/git/{{ repositoryName }}.git'
cd '/var/git/{{ repositoryName }}.git'
sudo git init --bare
sudo git config core.sharedRepository group
The core.sharedRepository group option of the git repository is needed in order to grant write access to both the git and {{ webUser }} users (they both belong to the same user group - {{ webUserGroup }}).
And now the key concept of this whole approach: when someone pushes to this repository, we checkout the repository to the publish folder and run some fancy stuff with our git-post-receive-hook:
cat <<'EOF' | sudo tee /var/git/{{ repositoryName }}.git/hooks/post-receive >/dev/null
#!/bin/sh
/usr/local/bin/git-post-receive-hook \
{{ hookOptions }} \
'/var/git/{{ repositoryName }}.git' \
'/var/www/{{ repositoryName }}'
EOF
sudo chown -R 'git:{{ webUserGroup }}' '/var/git/{{ repositoryName }}.git'
sudo chmod -R ug+rwX '/var/git/{{ repositoryName }}.git'
sudo chmod -R ug+x '/var/git/{{ repositoryName }}.git/hooks/post-receive'
Push-to-publish
Everything is almost ready!
The only step required to deploy with a simple git push is to add the remote to your repository.
For instance, here’s how to add a remote named deploy to your local repository:
git remote add deploy 'git@{{ serverAddress }}:/var/git/{{ repositoryName }}.git'
When you push to the deploy remote, the published directory willbe updated automatically.
Nice, isn’t it?
Questions or suggestions? Let’s discuss about it!