*** Backup Mirror *** The web interface and JSON api for the ConnectedHumber Air Quality Monitoring Project.
https://github.com/ConnectedHumber/Air-Quality-Web
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
13 KiB
412 lines
13 KiB
#!/usr/bin/env bash |
|
# Make sure the current directory is the location of this script to simplify matters |
|
cd "$(dirname "$(readlink -f "$0")")" || exit 1; |
|
|
|
if [ ! -d "${PWD}/.git" ]; then |
|
# shellcheck disable=SC1117 |
|
echo -e "\033[1m\033[31mError: The .git folder does not appear to exist. Please ensure you clone this repository with git, like this:\n\n\tgit clone https://github.com/ConnectedHumber/Air-Quality-Web.git\n\033[0m"; |
|
exit 1; |
|
fi |
|
################ |
|
### Settings ### |
|
################ |
|
|
|
# The name of this project |
|
project_name="Air Quality Mapper"; |
|
|
|
# The path to the lantern build engine git submodule |
|
lantern_path="./lantern-build-engine"; |
|
|
|
### |
|
# Custom Settings |
|
### |
|
|
|
# Put any custom settings here. |
|
|
|
# Client-side build output |
|
cache_dir="./.cache"; |
|
build_output_folder="./app"; |
|
|
|
# Database settings for ssh port forwarding task |
|
database_host="db.connectedhumber.org"; |
|
database_name="aq_db"; |
|
database_user="www-data"; |
|
|
|
# Minimum major version of npm |
|
min_npm_version_major="6"; |
|
|
|
# Deployment settings |
|
deploy_ssh_user="ci"; |
|
deploy_ssh_host="aq.connectedhumber.org"; |
|
deploy_ssh_port="22"; |
|
|
|
deploy_root_dir="Air-Quality-Web"; |
|
deploy_root_dir_beta="Air-Quality-Web-Beta"; |
|
|
|
############################################################################### |
|
|
|
# Check out the lantern git submodule if needed |
|
if [ ! -f "${lantern_path}/lantern.sh" ]; then git submodule update --init "${lantern_path}"; fi |
|
|
|
# shellcheck disable=SC1090 |
|
source "${lantern_path}/lantern.sh"; |
|
|
|
if [[ "$#" -lt 1 ]]; then |
|
echo -e "${FBLE}${project_name}${RS} build script"; |
|
echo -e " by Starbeamrainbowlabs"; |
|
# shellcheck disable=SC2154 |
|
echo -e "${LC}Powered by the lantern build engine, v${version}${RS}"; |
|
echo -e ""; |
|
echo -e "${CSECTION}Usage${RS}"; |
|
echo -e " ./build ${CTOKEN}{action}${RS} ${CTOKEN}{action}${RS} ${CTOKEN}{action}${RS} ..."; |
|
echo -e ""; |
|
echo -e "${CSECTION}Available actions${RS}"; |
|
echo -e " ${CACTION}setup${RS} - Perform initial setup"; |
|
echo -e " ${CACTION}setup-dev${RS} - Perform additional setup for development environments. Run after ${CACTION}setup${RS}."; |
|
echo -e " ${CACTION}database${RS} - Connect to the database via SSH & open MariaDB CLI connection, prompting for a password"; |
|
echo -e " ${CACTION}dev-server${RS} - Start a development server"; |
|
echo -e " ${CACTION}dev-server-stop${RS} - Stop the currently running development server"; |
|
echo -e " ${CACTION}client${RS} - Build the client web app"; |
|
echo -e " ${CACTION}client-watch${RS} - Watch for changes to the client code & rebuild automatically"; |
|
echo -e " ${CACTION}docs${RS} - Render the documentation"; |
|
echo -e " ${CACTION}ci${RS} - Perform CI tasks"; |
|
echo -e " ${LC}${CACTION}archive${RS} - CI: Create release archive"; |
|
echo -e ""; |
|
|
|
exit 1; |
|
fi |
|
|
|
############################################################################### |
|
|
|
|
|
# ███████ ███████ ████████ ██ ██ ██████ |
|
# ██ ██ ██ ██ ██ ██ ██ |
|
# ███████ █████ ██ ██ ██ ██████ |
|
# ██ ██ ██ ██ ██ ██ |
|
# ███████ ███████ ██ ██████ ██ |
|
|
|
task_download-composer() { |
|
if [ -f "${cache_dir}/composer" ]; then |
|
return 0; |
|
fi |
|
[ ! -d "${cache_dir}" ] && mkdir "${cache_dir}"; |
|
|
|
|
|
task_begin "Downloading composer"; |
|
execute curl -sS "https://getcomposer.org/composer.phar" -o "${cache_dir}/composer"; exit_code=$?; |
|
chmod +x "${cache_dir}/composer"; |
|
task_end ${exit_code}; |
|
} |
|
|
|
check_php_module() { |
|
module_name="$1"; |
|
subtask_begin "Checking for ${module_name} PHP module"; |
|
if [[ "$(php -m | grep -ic pdo_mysql)" -eq "0" ]]; then |
|
subtask_end 1 "Error: The php_mysql PHP module is not installed."; |
|
fi |
|
subtask_end 0; |
|
} |
|
|
|
task_setup() { |
|
stage_begin "Setting up"; |
|
|
|
task_begin "Checking environment"; |
|
check_command git true; |
|
check_command php true; |
|
check_command node true; |
|
check_command npm true; |
|
check_command curl true; |
|
|
|
check_php_module pdo_mysql; |
|
check_php_module mbstring; |
|
|
|
if [ ! -w "${PWD}" ]; then |
|
task_end 1 "${HC}${FRED}Error: Can't write to the repository directory! This usually you have a permission error of some kind. Try using sudo to run this build command as the user that owns this cloned repository."; |
|
fi |
|
|
|
npm_version_major="$(npm --version | head -c 1)"; |
|
if [[ "${npm_version_major}" -lt "${min_npm_version_major}" ]]; then |
|
echo "${FRED}${HC}Error: Your version of npm is too far out of date. You're running version $(npm --version), but version 6+ is required."; |
|
fi |
|
task_end $?; |
|
|
|
task_begin "Initialising submodules"; |
|
execute git submodule update --init; |
|
task_end $?; |
|
|
|
task_begin "Installing client dependencies"; |
|
echo -e "${HC}Not including development dependencies. To complete setup for development, execute the ${CACTION}setup-dev${RS} ${HC}build task.${RS}"; |
|
execute npm install --production; |
|
task_end $?; |
|
|
|
tasks_run download-composer; |
|
|
|
task_begin "Installing server dependencies"; |
|
execute "${cache_dir}/composer" install --no-dev; |
|
task_end $?; |
|
|
|
if [ ! -d "./data" ]; then |
|
task_begin "Setting up initial data folder"; |
|
mkdir "./data"; chmod 0700 "./data"; |
|
|
|
echo -e "# -------[ Custom Settings File - Last updated $(date) ]-------" >"./data/settings.toml"; |
|
echo -e '[database]\nusername = "INSERT_DATABASE_USERNAME_HERE"\npassword = "INSERT_DATABASE_PASSWORD_HERE"' >>"./data/settings.toml"; |
|
chmod 0600 "./data/settings.toml"; |
|
|
|
echo -e "${HC}Don't forget to edit './data/settings.toml' to specify the database username and password${RS}"; |
|
echo -e ""; |
|
|
|
task_end $?; |
|
fi |
|
|
|
stage_end $?; |
|
} |
|
|
|
task_setup-dev() { |
|
task_begin "Checking environment"; |
|
check_command mysql true optional; |
|
task_end $?; |
|
|
|
task_begin "Installing client development dependencies"; |
|
execute npm install; |
|
task_end $?; |
|
|
|
task_begin "Installing server development dependencies"; |
|
execute "${cache_dir}/composer" install; |
|
task_end $?; |
|
} |
|
|
|
|
|
# ██████ ███████ ██ ██ |
|
# ██ ██ ██ ██ ██ |
|
# ██ ██ █████ ██ ██ |
|
# ██ ██ ██ ██ ██ |
|
# ██████ ███████ ████ |
|
|
|
task_database() { |
|
task_begin "Connecting to the database"; |
|
set_title "Database"; |
|
ssh -TN "${database_host}" -L 3306:localhost:3306 & |
|
ssh_pid=$!; |
|
sleep 1; |
|
mysql --host 127.0.0.1 --port 3306 --database "${database_name}" --user "${database_user}" --password; |
|
|
|
kill "${ssh_pid}"; wait; sleep 0.5; |
|
task_end $?; |
|
} |
|
|
|
task_dev-server() { |
|
task_begin "Starting development server"; |
|
php -S "[::1]:40482" & |
|
exit_code=$?; |
|
[[ "${exit_code}" -eq "0" ]] && echo $! >/tmp/micro-lantern-dev-server.pid; |
|
task_end $?; # Should be 0 unless php died for some reason |
|
sleep 1; |
|
} |
|
|
|
task_dev-server-stop() { |
|
task_begin "Stopping development server"; |
|
|
|
kill "$(cat /tmp/micro-lantern-dev-server.pid)"; |
|
rm /tmp/micro-lantern-dev-server.pid; |
|
|
|
task_end $?; |
|
} |
|
|
|
|
|
# ██████ ██ ██ ███████ ███ ██ ████████ |
|
# ██ ██ ██ ██ ████ ██ ██ |
|
# ██ ██ ██ █████ ██ ██ ██ ██ |
|
# ██ ██ ██ ██ ██ ██ ██ ██ |
|
# ██████ ███████ ██ ███████ ██ ████ ██ |
|
|
|
task_client() { |
|
task_begin "Packaging Javascript"; |
|
execute node_modules/.bin/rollup --sourcemap --config rollup.config.js; |
|
task_end $? "Error: rollup packing failed!"; |
|
|
|
task_begin "Copying html"; |
|
execute cp client_src/index.html "${build_output_folder}"; |
|
task_end $?; |
|
|
|
# task_begin "Copying css"; |
|
# # FUTURE: Package this up too? |
|
# cp -r client_src/css/ "${build_output_folder}"; |
|
# task_end $?; |
|
} |
|
|
|
task_client-watch() { |
|
set_title "Client Watcher"; |
|
|
|
echo -e "Watching for changes."; |
|
while :; do # : = infinite loop |
|
# Wait for an update |
|
# inotifywait's non-0 exit code forces an exit for some reason :-/ |
|
inotifywait -qr --event modify --format '%:e %f' client_src; |
|
|
|
# Rebuild the client code - spawn a sub-process to avoid the hard exit |
|
# This still doesn't work though, which is *really* annoying |
|
stage_begin "Rebuilding client code"; |
|
./build client; |
|
stage_end $?; |
|
done |
|
} |
|
|
|
|
|
# ██████ ██████ ██████ ███████ |
|
# ██ ██ ██ ██ ██ ██ |
|
# ██ ██ ██ ██ ██ ███████ |
|
# ██ ██ ██ ██ ██ ██ |
|
# ██████ ██████ ██████ ███████ |
|
|
|
task_docs() { |
|
task_begin "Rendering docs"; |
|
execute node_modules/.bin/nightdocs --config nightdocs.toml; |
|
task_end $?; |
|
|
|
task_begin "Copying images"; |
|
cp -r docs/images/ __nightdocs/; |
|
task_end $?; |
|
} |
|
|
|
|
|
# ██████ ██ |
|
# ██ ██ |
|
# ██ ██ |
|
# ██ ██ |
|
# ██████ ██ |
|
task_ci() { |
|
tasks_run setup setup-dev |
|
NODE_ENV="production" tasks_run client docs archive; |
|
|
|
latest_commit="$(git log -n 1 --pretty=format:"%H")"; |
|
current_commit="$(git rev-parse HEAD)"; |
|
|
|
if [ "${latest_commit}" != "${current_commit}" ]; then |
|
echo "Not deploying, as this isn't the latest commit on the branch."; |
|
echo "It's likely that this commit is actually one of several in a queue to be processed by the CI server at once."; |
|
return 0; |
|
fi |
|
|
|
if [ "${GIT_REF_NAME}" == "refs/heads/master" ]; then |
|
tasks_run deploy; |
|
elif [ "${GIT_REF_NAME}" == "refs/heads/dev" ]; then |
|
echo "Deploying as beta release, as we're on the dev branch"; |
|
deploy_root_dir="${deploy_root_dir_beta}"; |
|
tasks_run deploy; |
|
else |
|
echo "Not deploying, as we're not on either the master or dev branches."; |
|
fi |
|
} |
|
|
|
task_archive() { |
|
task_begin "Preparing to archive"; |
|
execute mv vendor vendor.bak; |
|
execute "${cache_dir}/composer" install --no-dev; |
|
task_end $?; |
|
|
|
task_begin "Packing archive"; |
|
# We include the data directory here because we assume that this task is ONLY run in a CI environment, so it should only contain the default setup generated by an earlier task. |
|
execute tar cafv "${ARCHIVE}/Air-Quality-Web.tar.gz" app/ __nightdocs/ lib/ logic/ vendor/ data/ ./*.php ./*.md LICENSE version settings.default.toml; |
|
task_end $?; |
|
|
|
task_begin "Cleaning up"; |
|
execute rm -rf vendor; |
|
execute mv vendor.bak vendor; |
|
task_end $?; |
|
} |
|
|
|
|
|
task_deploy() { |
|
stage_begin "Deploying to ${deploy_ssh_host}...."; |
|
if [ "${SSH_KEY_PATH}" == "" ]; then |
|
echo "${FRED}${HC}Error: Can't find the SSH key as the environment variable SSH_KEY_PATH isn't set.${RS}" >&2; |
|
stage_end 1; |
|
fi |
|
|
|
|
|
task_begin "Preparing upload"; |
|
|
|
subtask_begin "Creating temporary directory"; |
|
temp_dir="$(mktemp -d --suffix "-air-quality-upload")"; |
|
subtask_end $? "Error: Failed to create temporary directory"; |
|
|
|
subtask_begin "Unpacking release files"; |
|
execute tar -xf "${ARCHIVE}/Air-Quality-Web.tar.gz" -C "${temp_dir}"; |
|
subtask_end $? "Failed to unpack release files"; |
|
|
|
subtask_begin "Removing data directory"; |
|
rm -r "${temp_dir}/data"; # Delete the default data directory - there's one on the server already |
|
subtask_end $?; |
|
|
|
# subtask_begin "Unwinding symlinks"; |
|
# find "${temp_dir}" -D exec -type l -not -path "./beta" -exec bash -c 'ln -f "$(readlink -m "$0")" "$0"' {} \; |
|
# subtask_end $?; |
|
|
|
task_end $?; |
|
|
|
# Define the directory whose contents we want to upload |
|
source_upload_dir="${temp_dir}/"; |
|
|
|
task_begin "Acquiring upload lock"; |
|
# Acquire an exclusive project-wide lock so that we only upload stuff one-at-a-time |
|
exec 9<"${WORKSPACE}"; |
|
flock --exclusive 9; |
|
task_end $? "Failed to acquire lock!"; |
|
|
|
task_begin "Uploading release"; |
|
sftp -i "${SSH_KEY_PATH}" -P "${deploy_ssh_port}" -o PasswordAuthentication=no "${deploy_ssh_user}@${deploy_ssh_host}" << SFTPCOMMANDS |
|
mkdir ${deploy_root_dir}/www-new |
|
put -r ${source_upload_dir}/* ${deploy_root_dir}/www-new |
|
bye |
|
SFTPCOMMANDS |
|
task_end $?; |
|
|
|
task_begin "Making release live"; |
|
# Actions: |
|
# 1. Connect to remote server |
|
# 2. Upload new files |
|
# 3. Create data dir symlink |
|
# 4. Swap in new directory |
|
# 5. Delete old directory |
|
lftp_commands_filename="$(mktemp --suffix "-commands.lftp")"; |
|
|
|
( |
|
echo "set sftp:connect-program 'ssh -x -i ${SSH_KEY_PATH}'"; |
|
# We have an extra : before the @ here to avoid the password prompt |
|
echo "connect sftp://${deploy_ssh_user}:@${deploy_ssh_host}:${deploy_ssh_port}"; |
|
|
|
echo "ln -s \"../data\" \"${deploy_root_dir}/www-new/data\""; |
|
echo "ln -s \"../../Air-Quality-Web-Beta/www/\" \"${deploy_root_dir}/www-new/beta\""; |
|
|
|
echo "mv \"${deploy_root_dir}/www\" \"${deploy_root_dir}/www-old\""; |
|
echo "mv \"${deploy_root_dir}/www-new\" \"${deploy_root_dir}/www\""; |
|
echo "rm -r \"${deploy_root_dir}/data/cache\""; |
|
echo "rm -r \"${deploy_root_dir}/www-old\""; |
|
echo "bye"; |
|
) >"${lftp_commands_filename}"; |
|
|
|
|
|
execute lftp --version; |
|
execute cat "${lftp_commands_filename}"; |
|
execute lftp -f "${lftp_commands_filename}"; |
|
exit_code=$? |
|
task_end "${exit_code}" "Failed to make release live"; |
|
|
|
task_begin "Releasing lock"; |
|
exec 9>&- # Close file descriptor 9 and release the lock |
|
task_end $?; |
|
|
|
|
|
task_begin "Cleaning up"; |
|
execute rm -r "${temp_dir}"; |
|
task_end $?; |
|
|
|
|
|
stage_end $? "Failed to deploy to ${deploy_ssh_host}."; |
|
} |
|
|
|
|
|
######################################################################### |
|
|
|
tasks_run "$@";
|
|
|