#!/usr/bin/env bash ######################################################################## # Copyright 2014 Lukas Bestle # License MIT ######################################################################## # 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 [] # # Path to the project # SHA-1 commit hash of the revision to checkout # Defaults to the latest commit on the set branch. ######################################################################## # 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 project_deploy::buildVersionPath() { destinationRelative="versions/$versionName" destination="$(cd "$project" && echo -n "$(pwd)/$destinationRelative")" } ######################################################################## project="$1" revision="${2:-latest}" # 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 "$project" ]]; then # Print help echo -e "\033[1mUsage:\033[0m \033[34mproject_deploy\033[0m []" exit 1 fi # Check if the default value for $CONFIG_HASH_LENGTH has been changed if [[ "$revision" == "latest" ]] && [[ $CONFIG_HASH_LENGTH != 40 ]]; then echo -e "\033[31mYou have changed the default value of \033[34m\$CONFIG_HASH_LENGTH\033[31m. To make sure the deployed revisions are unique and never duplicated, deployments of the latest revision aren't supported with a custom value of this option. Please give me the exact revision string in your custom length.\033[0m" >&2 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 # 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 { # Make sure the repository exists and is updated # We only need to do this if the revision was not deployed yet if ! ls "$project/versions"/*-$revision &> /dev/null; then # Check if the repository has already been cloned url="$(cat "$project/.origin")" if ! [[ -d "$project/repo" ]]; then # No, clone it now echo -e "\033[1mCloning from \033[34m$url\033[0;1m for the first time...\033[0m" if ! git clone --recursive "$url" "$project/repo"; then echo -e " => \033[31mCould not clone project \033[34m$project\033[31m from \033[34m$url\033[31m.\033[0m" >&2 exit 1 fi echo -e " => \033[32mSuccess.\033[0m" else # Yes, only fetch from the remote repository echo -e "\033[1mFetching new commits from \033[34m$url\033[0;1m...\033[0m" if ! (git -C "$project/repo" fetch); then echo -e " => \033[31mCould not fetch from \033[34m$url\033[31m.\033[0m" >&2 exit 1 fi echo -e " => \033[32mSuccess.\033[0m" fi fi # Determine the actual revision to use if [[ "$revision" != "latest" ]]; then # Check if the given 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 else # Check if the set branch exists branch="$(cat "$project/.branch")" echo -e "\033[1mDetermining current revision for branch \033[34m$branch\033[0;1m...\033[0m" if ! (git -C "$project/repo" show-branch "$branch" &> /dev/null); then echo -e " => \033[31mThe set branch \033[34m$branch\033[31m does not exist.\033[0m" >&2 exit 1 fi # Get the commit hash from the set branch revision="$(git -C "$project/repo" rev-list --max-count=1 "origin/$branch")" if [[ $? != 0 ]]; then echo -e " => \033[31mCould not determine current revision.\033[0m" >&2 exit 1 fi echo -e " => \033[32mUsing revision \033[34m$revision\033[32m.\033[0m" fi # 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)")" project_deploy::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" project_deploy::buildVersionPath # Copy repository to new destination echo -e "\033[1mCopying repository to deployment destination...\033[0m" if ! cp -R "$project/repo" "$destination"; then echo -e " => \033[31mCould not copy repository.\033[0m" >&2 project_deploy::reverse "$project" "$destination" exit 1 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 ! git -C "$destination" 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" # Update submodules echo -e "\033[1mUpdating submodules...\033[0m" if ! git -C "$destination" submodule update --init --recursive; then echo -e " => \033[31mCould not update submodules 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 [[ -x "$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