Initial commit
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…
Reference in New Issue