Initial commit

master
Lukas Bestle 11 years ago
commit 7a3fe21f3d

@ -0,0 +1,21 @@
# The MIT License (MIT)
Copyright (c) 2014 Lukas Bestle <http://lu-x.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,95 @@
# Project management tools
> Tools for deployments and project management
## What this does
[Uberspace](https://uberspace.de) has a great routing setup for Apache sites: You place a directory or symbolic link with the name of the domain (like `www.example.com`) in `/var/www/virtual/<user>/` and every request to this domain is routed to the directory.
As a user with a [pretty complex routing setup](https://git.lukasbestle.com/groups/sites), I wanted to automate setting up new sites and updating them automatically from a Git repository. This toolset provides some CLI tools to make that possible.
## Features
- Create and delete projects (general) and sites (projects for the webserver)
- Set an origin Git repository and branch to get new versions from
- Create and delete links from one or multiple domains to a site
- Get a specific new revision from the Git repository, run a setup script (`.postdeploy.sh` in the repository root) and point a link to the new version
- Reverse a deployable project to the last version
### Directory structure of a project
Every project/site follows this directory structure:
.branch # Branch to allow deployments for (see "Deployment setup")
.domains # Site-specific: List of linked domains for this site
.origin # URL of the origin repository
.project # Empty file determining that this is a project
current # Symlink to the currently active version in versions/
├── <project files>
data # Never directly accessible by user agents and never overwritten
├── <persistent non-VCS application data and configuration>
last # Symlink to the last active version in versions/
├── <project files>
logs # Logs of all deployments
├── <commit-hash>.log
├── <commit-hash>.log
versions # Fully separate Git repositories of the project
├── <id>-<commit-hash>
│ └── <project files>
└── <id>-<commit-hash>
└── <project files>
### Usage without any kind of deployment
If you don't want to keep old versions of your code and use automatic deployment, simply leave out the `<origin>` parameter and you end up with a structure like this:
.domains # Site-specific: List of linked domains for this site
.project # Empty file determining that this is a project
current # Directory for your project files
├── <project files>
data # Never directly accessible by user agents and never overwritten
└── <persistent non-VCS application data and configuration>
### Deployment setup
The tool `project_deploy` takes the full path to the project and the Git revision to install. Obviously, this is not very useful, but easy to use in deployment hook scripts:
1. Write a script that receives web-hooks from GitHub, GitLab or similar and get the repository URL, commit SHA-1 and branch name of the event from the transmitted data.
2. Read the file `~/.projects`, which contains the paths to all known projects and sites, and iterate through it.
3. Open the projects `.origin` and `.branch` files. If they match the web-hook, run `project_deploy <path> <commit-sha1>` and you are done.
You can find an example PHP script for GitLab webhooks in `webhook.gitlab.php`. Another one for GitHub might follow, but it should be easy to do.
## Setup
1. Put this project wherever you want on the destination system and add the `bin` directory to your `PATH`.
2. Create a backup and clean your `DocumentRoot` (`/var/www/virtual/<user>/` on Uberspace), as you probably want to manage everything with this toolset.
3. Create a symlink to your `DocumentRoot` in `~/web` (this is what the `site_*` tools use):
`ln -s /var/www/virtual/$USER/ ~/web`
You can also link to a subdirectory of your `DocumentRoot` if you want to manage the sites of this tool separately from your other sites. Please note that the `site_link` functionality does not work without manually linking the resulting links to your `DocumentRoot` when using non-standard paths for `~/web`.
4. Have fun with the tools in `bin`.
## Configuration
If you want to customize specific settings, you can create a Bash file at `~/.project.cnf` overriding the default values at every run of the tools. These are the possible settings and also the format of the file:
# Default branch to set if no one is given to `project_origin`
CONFIG_DEFAULT_BRANCH="master"
# Allowed length of the <revision> parameter of `project_deploy`
# Used to make deployments consistent (doesn't allow different hash lengths and therefore duplicated deployments)
# Default is a full-length SHA-1 hash, use 7 as value when using short hashes.
CONFIG_HASH_LENGTH=40
# Number of deployed versions to preserve (0 for infinite (be careful, that might use loads of storage space!))
# Versions older than the latest n versions get deleted automatically, logs are always preserved
CONFIG_PRESERVE_VERSIONS=5
## Author
- Lukas Bestle <mail@lukasbestle.com>
## License
This project was published under the terms of the MIT license. You can find a copy [over at the repository](https://git.lukasbestle.com/tools/misc/blob/master/LICENSE.md).

@ -0,0 +1,58 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Creates a project directory, bootstraps it with a basic directory
# structure and optionally prepares it for deployments.
#
# Usage: project_add <project> [<origin>]
#
# <project> Path to the project
# <origin> Repository URL and branch name (<url>[#<branch>])
########################################################################
project="$1"
origin="$2"
if [[ -z "$project" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34mproject_add\033[0m <project> [<origin>]"
exit 1
fi
# Check if the project already exists
if [[ -e "$project" ]]; then
echo -e "\033[31mThe destination \033[34m$project\033[31m already exists.\033[0m" >&2
exit 1
fi
# Create project directory
echo -e "\033[1mCreating project at \033[34m$project\033[0;1m...\033[0m"
if ! (mkdir "$project" && mkdir "$project/data" && touch "$project/.project"); then
echo -e "\033[31mCould not create directory structure at \033[34m$project\033[31m.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Add to ~/.projects
echo -e "\033[1mAdding project to \033[34m~/.projects\033[0;1m...\033[0m"
if ! echo "$(readlink -f "$project")" >> "$HOME/.projects"; then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Add origin if given
if [[ -n "$origin" ]]; then
project_origin "$project" "$origin"
exit $?
else
echo -e "\033[1mInitializing local project directory at \033[34m$project/current\033[0;1m...\033[0m"
if ! mkdir "$project/current"; then
echo -e "\033[31mCould not create local project directory.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
exit 0
fi

@ -0,0 +1,198 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Clones the repository of a project from the server, runs the script
# .postdeploy.sh in the repository (if existing) and links the version
# as current.
#
# Usage: project_deploy <project> <revision>
#
# <project> Path to the project
# <revision> SHA-1 commit hash of the revision to checkout
########################################################################
# Functions
# Reverses changes
function project_deploy::reverse() {
local project="$1"
local destination="$2"
local shiftLinks=$3
echo -e "\033[1mReversing changes...\033[0m"
# Remove destination directory
rm -Rf "$destination"
# Shift links back to their old state
if [[ $shiftLinks == true ]]; then
rm -f "$project/current"
if [[ -e "$project/last" ]]; then
mv "$project/last" "$project/current"
fi
fi
}
# Builds the paths to a version
declare directoryCount versionNumber versionName destinationRelative destination
function buildVersionPath() {
destinationRelative="versions/$versionName"
destination="$(cd "$project" && echo -n "$(pwd)/$destinationRelative")"
}
########################################################################
project="$1"
revision="$2"
# Load configuration
CONFIG_PRESERVE_VERSIONS=5
CONFIG_HASH_LENGTH=40
if [[ -f "$HOME/.project.cnf" ]]; then
source "$HOME/.project.cnf"
fi
if [[ $CONFIG_PRESERVE_VERSIONS == 1 ]]; then
echo -e "\033[31mThe value of \033[34m\$CONFIG_PRESERVE_VERSIONS\033[31m is too low (\033[34m1\033[31m), setting it to \033[34m2\033[31m.\033[0m" >&2
CONFIG_PRESERVE_VERSIONS=2
fi
if [[ -z "$revision" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34mproject_deploy\033[0m <project> <revision>"
exit 1
fi
# Check if the project exists
if [[ ! -f "$project/.project" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
# Check if the project can be updated
if [[ ! -f "$project/.origin" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m is not deployable.\033[0m" >&2
exit 1
fi
# Check if the commit hash is valid
if [[ ${#revision} != $CONFIG_HASH_LENGTH ]]; then
echo -e "\033[31mThe length of the revision name \033[34m$revision\033[31m is not equal to the configured \033[34m$CONFIG_HASH_LENGTH\033[31m characters.\033[0m" >&2
exit 1
fi
# Add header to log
log="$project/logs/$revision.log"
if [[ -f "$log" ]]; then
# Separator between multiple logs
echo -e "\033[1;35m======\033[0m" >> "$log"
fi
echo -e "\033[1;35mRun on \033[34m$(date)\033[35m by \033[34m$USER\033[35m:\n------\033[0m" >> "$log"
# Log everything beginning here
{
# Check if we already installed this version
if ls "$project/versions"/*-$revision &> /dev/null; then
# Yes, use that
# Build the pathname of the destination directory
versionName=$(basename $(echo "$project/versions"/*-$revision))
buildVersionPath
echo -e "\033[1mThis revision has already been installed, using previous installation \033[34m$versionName\033[0;1m.\033[0m"
else
# No, fetch and install
# Build the pathname of the destination directory
# Get all version names, get the last one, split it by "-" and get the first part, remove leading zeros
latestVersion=$(echo "$(ls "$project/versions" | tail -1)" | cut -f1 -d- | sed 's/^0*//')
versionNumber=$(($latestVersion + 1)) # Next is +1
versionName="$(printf "%05d" "$versionNumber")-$revision"
buildVersionPath
# Clone the project
url=$(cat "$project/.origin")
echo -e "\033[1mCloning from \033[34m$url\033[0;1m...\033[0m"
if ! git clone --recursive "$url" "$destination"; then
echo -e " => \033[31mCould not clone project \033[34m$project\033[31m from \033[34m$url\033[31m.\033[0m" >&2
project_deploy::reverse "$project" "$destination"
exit
fi
echo -e " => \033[32mSuccess.\033[0m"
# Checkout correct revision
echo -e "\033[1mChecking out revision \033[34m$revision\033[0;1m...\033[0m"
if ! $(cd "$destination" && git checkout "$revision"); then
echo -e " => \033[31mCould not checkout revision \033[34m$revision\033[31m of project \033[34m$project\033[31m.\033[0m" >&2
project_deploy::reverse "$project" "$destination"
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Run post-deploy script if existing
if [[ -f "$destination/.postdeploy.sh" ]]; then
# Set the working directory to the destination
oldpwd="$(pwd)"
cd "$destination"
echo -e "\033[1mRunning post-deploy script...\033[0m"
if ! $destination/.postdeploy.sh; then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
project_deploy::reverse "$project" "$destination"
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
cd "$oldpwd"
fi
# Remove .git directory
echo -e "\033[1mDeleting \033[34m.git\033[0;1m directory...\033[0m"
if ! rm -Rf "$destination/.git"; then
echo -e " => \033[31mCould not delete \033[34m.git\033[31m directory.\033[0m" >&2
project_deploy::reverse "$project" "$destination"
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Clean up old versions
if [[ $CONFIG_PRESERVE_VERSIONS -gt 0 ]]; then
echo -e "\033[1mCleaning up old versions...\033[0m"
# Delete the first version until there are $CONFIG_PRESERVE_VERSIONS versions
while [[ $(ls -q "$project/versions" | wc -l) -gt $CONFIG_PRESERVE_VERSIONS ]]; do
item="$(ls -d "$project/versions"/* | head -1)"
echo -e " - Deleting \033[34m$item\033[0m..."
rm -Rf "$item"
done
echo -e " => \033[32mSuccess.\033[0m"
else
echo -e "\033[1;31mNot\033[0;1m cleaning up old versions (disabled in configuration).\033[0m"
fi
fi
# Create symlinks to current version
echo -e "\033[1mSymlinking new version...\033[0m"
# Remove old symlink
if ! rm -f "$project/last"; then
echo -e " => \033[31mCould not delete old link.\033[0m" >&2
project_deploy::reverse "$project" "$destination"
exit 1
fi
# Move current symlink to old symlink
if [[ -e "$project/current" ]]; then
if ! mv "$project/current" "$project/last"; then
echo -e " => \033[31mCould not shift current link to old link.\033[0m" >&2
project_deploy::reverse "$project" "$destination"
exit 1
fi
fi
# Create new symlink
if ! ln -s "$destinationRelative" "$project/current"; then
echo -e " => \033[31mCould not create new link.\033[0m" >&2
project_deploy::reverse "$project" "$destination" true
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
} 2>&1 | tee -a "$log" # Duplicate output to log file
exit 0

@ -0,0 +1,17 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Lists all known project directories.
#
# Usage: project_list
########################################################################
# Check if the ~/.project file exists
if [[ ! -f "$HOME/.projects" ]]; then
echo -e "\033[31mThere is no \033[34m~/.projects\033[31m file yet.\033[0m" >&2
exit 1
fi
cat "$HOME/.projects"
exit $?

@ -0,0 +1,99 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Prepares a project for deployment and sets the origin repository and
# branch the project is connected to.
#
# Usage: project_origin <project> [<origin>]
#
# <project> Path to the project
# <origin> Repository URL and branch name (<url>[#<branch>])
# If omitted, the origin gets deleted.
# If the string does not contain a "#", the branch is
# set to "master" ($CONFIG_DEFAULT_BRANCH).
########################################################################
project="$1"
origin="$2"
# Load configuration
CONFIG_DEFAULT_BRANCH="master"
if [[ -f "$HOME/.project.cnf" ]]; then
source "$HOME/.project.cnf"
fi
if [[ -z "$project" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34mproject_origin\033[0m <project> [<origin>]"
exit 1
fi
# Check if the project exists
if [[ ! -f "$project/.project" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
if [[ -n "$origin" ]]; then
# Origin was defined, initialize
# Check if there are project files already
files=$(shopt -s nullglob; shopt -s dotglob; echo "$project/current"/*)
if [[ ${#files} -gt 0 ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m already has a non-empty \033[34mcurrent\033[31m directory. Please backup and delete it first.\033[0m" >&2
exit 1
fi
# Delete the symlink or directory "current"
if ! rm -Rf "$project/current"; then
echo -e "\033[31mCould not delete old \033[34mcurrent\033[31m link/directory.\033[0m" >&2
exit 1
fi
# Split origin URL and branch by "#" character
if [[ "$origin" == *#* ]]; then
url="$(echo "$origin" | cut -f1 -d#)"
branch="$(echo "$origin" | cut -f2 -d#)"
else
# No branch given, set to master
url="$origin"
branch=$CONFIG_DEFAULT_BRANCH
fi
# Check if the required directory structure already exists
if [[ ! -d "$project/versions" || ! -d "$project/logs" ]]; then
# Create structure for deployable projects
echo -e "\033[1mInitializing deployment directory structure for project \033[34m$project\033[0;1m...\033[0m"
if ! (mkdir -p "$project/versions" && mkdir -p "$project/logs"); then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
fi
# Set origin and branch files
echo -e "\033[1mSetting URL to \033[34m$url\033[0;1m and branch to \033[34m$branch\033[0;1m...\033[0m"
if ! (echo -n "$url" > "$project/.origin" && echo -n "$branch" > "$project/.branch"); then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
else
# Origin was not defined, unset
# Check if the project has an origin
if [[ ! -f "$project/.origin" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m does not currently have an origin.\033[0m" >&2
exit 1
fi
echo -e "\033[1mDeleting origin configuration files for project \033[34m$project\033[0;1m...\033[0m"
if ! rm -f "$project/.origin" "$project/.branch"; then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
fi
exit 0

@ -0,0 +1,43 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Removes a project directory.
#
# Usage: project_remove <project>
#
# <project> Path to the project
########################################################################
project="$1"
if [[ -z "$project" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34mproject_remove\033[0m <project>"
exit 1
fi
# Check if the project exists
if [[ ! -f "$project/.project" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
# Delete project
echo -e "\033[1mDeleting project directory \033[34m$project\033[0;1m...\033[0m"
if ! rm -Rf $project; then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Remove from ~/.projects
echo -e "\033[1mRemoving project from \033[34m~/.projects\033[0;1m...\033[0m"
realpath="$(readlink -f "$project")"
if ! sed -i "/${realpath//\//\/}/d" "$HOME/.projects"; then # Replace / with \/ because of the sed separators
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
exit 0

@ -0,0 +1,46 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Reverses a deployable project to the last version.
#
# Usage: project_rollback <project>
#
# <project> Path to the project
########################################################################
project="$1"
if [[ -z "$project" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34mproject_rollback\033[0m <project>"
exit 1
fi
# Check if the project exists
if [[ ! -f "$project/.project" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
# Check if the project is deployable
if [[ ! -f "$project/.origin" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m is not deployable and therefore can't be rolled back.\033[0m" >&2
exit 1
fi
# Check if the project currently has a last version
if [[ ! -e "$project/last" ]]; then
echo -e "\033[31mThe project \033[34m$project\033[31m currently does not have a last version.\033[0m" >&2
echo -e "\033[31mProbably, it has already been rolled back since the latest deployment.\033[0m" >&2
exit 1
fi
echo -e "\033[1mRolling project \033[34m$project\033[0;1m back to last version...\033[0m"
if rm -f "$project/current" && mv "$project/last" "$project/current"; then
echo -e " => \033[32mSuccess.\033[0m"
exit 0
else
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi

@ -0,0 +1,46 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Creates a site directory in ~/web/sites/, bootstraps it with a basic
# directory structure and optionally prepares it for deployments.
#
# Usage: site_add <site> [<origin>]
#
# <site> Name of the site (directory in ~/web/sites/)
# <origin> Repository URL and branch name (<url>[#<branch>])
########################################################################
site="$1"
origin="$2"
if [[ -z "$site" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_add\033[0m <site> [<origin>]"
exit 1
fi
# If there is no ~/web/sites directory yet, create it
if [[ ! -d "$HOME/web/sites" ]]; then
echo -e "\033[1mCreating directory \033[34m~/web/sites\033[0;1m for first site...\033[0m"
if ! mkdir "$HOME/web/sites"; then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
fi
# Create project
if ! project_add "$HOME/web/sites/$site" "$origin"; then
exit $?
fi
# Create .domains file
echo -e "\033[1mInitializing empty \033[34m.domains\033[0;1m file for site...\033[0m"
if ! touch "$HOME/web/sites/$site/.domains"; then
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
exit 0

@ -0,0 +1,26 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Clones the repository of a site from the server, runs the script
# .postdeploy.sh in the repository (if existing) and links the version
# as current.
#
# Usage: site_deploy <site> <revision>
#
# <site> Name of the site (directory in ~/web/sites/)
# <revision> SHA-1 commit hash of the revision to checkout
########################################################################
site="$1"
revision="$2"
if [[ -z "$revision" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_deploy\033[0m <site> <revision>"
exit 1
fi
# Delegate to project_deploy
project_deploy "$HOME/web/sites/$site" "$revision"
exit $?

@ -0,0 +1,57 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Creates a link for the webserver pointing a domain to a site.
#
# Usage: site_link <site> <domain>
#
# <site> Name of the site (directory in ~/web/sites/)
# <domain> FQDN of the domain (like "example.com")
########################################################################
site="$1"
domain="$2"
if [[ -z "$domain" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_link\033[0m <site> <domain>"
exit 1
fi
# Check if the site exists
if [[ ! -f "$HOME/web/sites/$site/.project" ]]; then
echo -e "\033[31mThe site \033[34m$site\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
# Check if the site currently has a current version
if [[ ! -e "$HOME/web/sites/$site/current" ]]; then
echo -e "\033[31mThe site \033[34m$site\033[31m currently does not have a current version, please deploy first.\033[0m" >&2
exit 1
fi
# Check if there is already a link with this domain
if [[ -e "$HOME/web/$domain" ]]; then
echo -e "\033[31mA link for the domain \033[34m$domain\033[31m already exists.\033[0m" >&2
exit 1
fi
# Create link
echo -e "\033[1mCreating link from site \033[34m$site\033[0;1m to domain \033[34m$domain\033[0;1m...\033[0m"
if ! ln -s "sites/$site/current" "$HOME/web/$domain"; then
echo -e "\033[31mCould not create link at \033[34m$HOME/web/$domain\033[31m.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Add to site's domains
echo -e "\033[1mAdding domain to site's domains (\033[34m.domains\033[0;1m file)...\033[0m"
echo "$domain" >> "$HOME/web/sites/$site/.domains"
if [[ $? == 0 ]]; then
echo -e " => \033[32mSuccess.\033[0m"
exit 0
else
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi

@ -0,0 +1,17 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Lists all known sites.
#
# Usage: project_list
########################################################################
# Check if the sites directory exists
if [[ ! -d "$HOME/web/sites" ]]; then
echo -e "\033[31mThere is no \033[34m~/web/sites\033[31m directory yet.\033[0m" >&2
exit 1
fi
ls "$HOME/web/sites" | cat # Make sure every item is printed on a new line
exit $?

@ -0,0 +1,28 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Prepares a site for deployment and sets the origin repository and
# branch the site is connected to.
#
# Usage: site_origin <site> [<origin>]
#
# <site> Name of the site (directory in ~/web/sites/)
# <origin> Repository URL and branch name (<url>[#<branch>])
# If omitted, the origin gets deleted.
# If the string does not contain a "#", the branch is
# set to "master".
########################################################################
site="$1"
origin="$2"
if [[ -z "$site" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_origin\033[0m <site> [<origin>]"
exit 1
fi
# Delegate to project_origin
project_origin "$HOME/web/sites/$site" "$origin"
exit $?

@ -0,0 +1,34 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Unlinks and removes a site directory in ~/web/sites/.
#
# Usage: site_remove <site>
#
# <site> Name of the site (directory in ~/web/sites/)
########################################################################
site="$1"
if [[ -z "$site" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_remove\033[0m <site>"
exit 1
fi
# Check if the site exists
if [[ ! -f "$HOME/web/sites/$site/.project" ]]; then
echo -e "\033[31mThe site \033[34m$site\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
# Unlink site
if ! site_unlink "$site"; then
echo -e " => \033[31mCould not unlink domains of site \033[34m$site\033[31m.\033[0m" >&2
exit 1
fi
# Delegate to project_remove
project_remove "$HOME/web/sites/$site"
exit $?

@ -0,0 +1,22 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Reverses a deployable site to the last version.
#
# Usage: site_rollback <site>
#
# <site> Name of the site (directory in ~/web/sites/)
########################################################################
site="$1"
if [[ -z "$site" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_rollback\033[0m <site>"
exit 1
fi
# Delegate to project_rollback
project_rollback "$HOME/web/sites/$site"
exit $?

@ -0,0 +1,82 @@
#!/bin/bash
########################################################################
# 2014-09-07 Lukas Bestle mail@lukasbestle.com
########################################################################
# Removes a link for the webserver pointing a domain to a site.
#
# Usage: site_unlink <site> [<domain>]
#
# <site> Name of the site (directory in ~/web/sites/)
# <domain> FQDN of the domain (like "example.com")
# If not given, the site is completely unlinked
########################################################################
site="$1"
domain="$2"
if [[ -z "$site" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34msite_unlink\033[0m <site> [<domain>]"
exit 1
fi
# Check if the site exists
if [[ ! -f "$HOME/web/sites/$site/.project" ]]; then
echo -e "\033[31mThe site \033[34m$site\033[31m does not exist or is invalid.\033[0m" >&2
exit 1
fi
# Check if the site has domains
if [[ ! -f "$HOME/web/sites/$site/.domains" ]]; then
echo -e "\033[31mThe site \033[34m$site\033[31m does not have any domain links.\033[0m" >&2
exit 1
fi
if [[ -z "$domain" ]]; then
# Unlink all domains
code=0
while read domain; do
# Run the script itself with a specific domain
site_unlink "$site" "$domain"
tempCode=$?
# Error handling
if [[ $tempCode != 0 ]]; then
code=$tempCode
fi
done < "$HOME/web/sites/$site/.domains"
exit $code
fi
# Check if there is a link for this domain
if [[ ! -e "$HOME/web/$domain" ]]; then
echo -e "\033[31mThere is no link for the domain \033[34m$domain\033[31m.\033[0m" >&2
exit 1
fi
# Check if the domain belongs to the site
if ! grep -Fxq -- "$domain" "$HOME/web/sites/$site/.domains"; then
echo -e "\033[31mThe domain \033[34m$domain\033[31m does not belong to the site \033[34m$site\033[31m.\033[0m" >&2
exit 1
fi
# Remove link
echo -e "\033[1mRemoving link from site \033[34m$site\033[0;1m to domain \033[34m$domain\033[0;1m...\033[0m"
if ! rm -f "$HOME/web/$domain"; then
echo -e "\033[31mCould not remove link at \033[34m$HOME/web/$domain\033[31m.\033[0m" >&2
exit 1
fi
echo -e " => \033[32mSuccess.\033[0m"
# Remove from site's domains
echo -e "\033[1mRemoving domain from site's domains (\033[34m.domains\033[0;1m file)...\033[0m"
sed -i "/$domain/d" "$HOME/web/sites/$site/.domains"
if [[ $? == 0 ]]; then
echo -e " => \033[32mSuccess.\033[0m"
exit 0
else
echo -e " => \033[31mSomething went wrong.\033[0m" >&2
exit 1
fi

@ -0,0 +1,125 @@
<?php
/**
* Project management tools
* Tools for deployments and project management
*
* GitLab frontend for deployments
*
* @author Lukas Bestle <mail@lukasbestle.com>
* @copyright Copyright 2014 Lukas Bestle
* @file webhook.gitlab.php
*/
// 1. Configuration
// ====================
// Long token used as authentication (appended as GET parameter: https://hooks.example.com/webhook.gitlab.php?token=<AUTH_TOKEN>)
// ATTENTION: Make sure this is long and secure, as anyone could checkout any commit of your projects otherwise
define('AUTH_TOKEN', '<long token>');
// Path where you installed the scripts from the "bin" directory of this repository
// This is required, as PHP doesn't automatically use the environment and therefore your $PATH from your shell
define('TOOLKIT_PATH', '/home/<user>/bin');
// 2. Setup
// ====================
/**
* This is an example webhook for GitLab servers.
* It's fairly simple to set this script up on your server/Uberspace, here's how:
*
* 1. Setup the toolkit, so you can access the tools from your SSH shell (more about that in README.md)
* 2. Take a look at the settings above.
* 2.1. Seriously, have you set an auth token? Yes? Alright.
* 3. Place this script in a directory in ~/web/ (something like "~/web/hooks.example.com")
* 4. You should now be able to access https://hooks.example.com/webhook.gitlab.php?token=<AUTH_TOKEN>
* This should output "Authenticated, but no POST body sent.".
* 5. Generate an SSH key by using `ssh-keygen`. The default values are alright for this purpose.
* 6. Copy the contents of ~/.ssh/id_rsa.pub to your clipboard and add it as "Deploy Key" in your project's GitLab settings interface.
*
* Then, you can easily create projects and let this script deploy them for you:
*
* 7. Create the project on the server by running either `project_add <path> <exact SSH url from GitLab>#<branch to deploy>` or
* `site_add <name> <exact SSH url from GitLab>#<branch to deploy>`.
* It's important to use the exact SSH url (not the HTTPS one), so this script can find the project!
* 8. Add the URL (the one you tested in 4.) of the script to your project's web hooks.
* 9. Hit "Test Hook" in GitLab's settings interface. The project should be deployed a few seconds later.
*
* Troubleshooting:
* - Use `cat <path to project>/logs/*.log`. This should tell you what went wrong.
* - Check if the TOOLKIT_PATH you set above is correct.
* - Check if you set the origin of your project to the correct URL.
* - Open the web hook URL you entered in GitLab in your browser and check if the output is "Authenticated, but no POST body sent.".
* - If there is no log file and you can't find the reason yourself, it is complicated. ;)
* Debugging this is not easy (GitLab does not seem to store logs of its requests), but you can try to craft a JSON body like GitLab,
* send it to this script by using cURLor GUI tools like Rested and it will tell you exactly what went wrong.
*/
// 3. Done
// The code starts here
// ====================
// We are always returning plain text
header('Content-Type: text/plain');
// Check if the authentication is valid
if(!isset($_GET['token']) || $_GET['token'] !== AUTH_TOKEN) {
http_response_code(401);
die('Invalid authentication.');
}
// Get the request body
$inputPointer = fopen('php://input', 'r');
$input = stream_get_contents($inputPointer);
if(!$input) {
http_response_code(405);
die('Authenticated, but no POST body sent.');
}
// Parse payload
$payload = json_decode($input, true);
if(!is_array($payload)) {
http_response_code(400);
die('Invalid payload (no JSON?).');
}
// Get some interesting information from the payload
$url = $payload['repository']['url'];
$commit = $payload['after'];
$ref = $payload['ref'];
if(!preg_match('{(?:.*/){2}(.*)}', $ref, $matches)) {
http_response_code(400);
die('Invalid ref field (does not match regular expression "(?:.*/){2}(.*)").');
}
$branch = $matches[1];
// Debug
echo "Received commit hash \"$commit\" for repository URL \"$url\" (branch \"$branch\").\n";
// Open ~/.projects and iterate through every project
$listPointer = fopen($_SERVER['HOME'] . '/.projects', 'r');
$exitCode = 0;
while(($project = fgets($listPointer)) !== false) {
// Trim whitespace
$project = trim($project);
// Only deployable projects are interesting for us
if(!is_file($project . '/.origin') || !is_file($project . '/.branch')) continue;
// If there is a .origin and .branch file, check if they match
if(file_get_contents($project . '/.origin') == $url && file_get_contents($project . '/.branch') == $branch) {
// Found the right project
echo "Found project at $project, running deploy script.\n";
// Run deploy script
passthru('export PATH=' . escapeshellarg(TOOLKIT_PATH) . ':$PATH; project_deploy ' . escapeshellarg($project) . ' ' . escapeshellarg($commit), $exitCode);
// If it didn't work, add debug statement
if($exitCode !== 0) echo "Something didn't work.\n";
}
}
// Iterated through every project, exit
http_response_code(($exitCode === 0)? 200 : 500);
die('All done.');
Loading…
Cancel
Save