diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..364fdec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +public/ diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..4bc7577 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,10 @@ +pipeline: + build: + image: python + when: + event: [ push, pull_request, tag, deployment ] + commands: + - make env + - make + - make ci-deploy + secrets: [ GITEA_WRITE_DEPLOY_KEY, LIBREPAGES_DEPLOY_SECRET ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c49add9 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +default: ## Build the website + ./scripts/spellcheck.sh --check + ./scripts/zola.sh build + +clean: ## Clean build assets + ./scripts/zola.sh clean + +ci-deploy: ## Deploy from CI/CD. Only call from within CI + @if [ "${CI}" != "woodpecker" ]; \ + then echo "Only call from within CI. Will re-write your local Git configuration. To override, set export CI=woodpecker"; \ + exit 1; \ + fi + git config --global user.email "${CI_COMMIT_AUTHOR_EMAIL}" + git config --global user.name "${CI_COMMIT_AUTHOR}" + ./scripts/zola.sh deploy librepages public "${CI_COMMIT_AUTHOR} <${CI_COMMIT_AUTHOR_EMAIL}>" + ./scripts/ci.sh --init "$$GITEA_WRITE_DEPLOY_KEY" + ./scripts/ci.sh --deploy ${LIBREPAGES_DEPLOY_SECRET} librepages + ./scripts/ci.sh --clean + +env: ## Download build dependencies and setup dev environment + ./scripts/zola.sh install + +help: ## Prints help for targets with comments + @cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +serve: ## Serve website during development + ./scripts/zola.sh zola -- serve diff --git a/scripts/ci.sh b/scripts/ci.sh index c52b35d..6127a95 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Used in CI workflow: install Zola binary from GitHub -# Copyright © 2021 Aravinth Manivannan +# ci.sh: Helper script to automate deployment operations on CI/CD +# Copyright © 2022 Aravinth Manivannan # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -15,111 +15,106 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -set -euo pipefail +set -xEeuo pipefail +#source $(pwd)/scripts/lib.sh -readonly TARBALL=zola.tar.gz -readonly SOURCE="https://github.com/getzola/zola/releases/download/v0.14.0/zola-v0.14.0-x86_64-unknown-linux-gnu.tar.gz" +readonly SSH_ID_FILE=/tmp/ci-ssh-id +readonly SSH_REMOTE_NAME=origin-ssh -readonly BIN_PATH=bin -readonly BIN=$BIN_PATH/zola - -readonly DIST=public +match_arg() { + if [ $1 == $2 ] || [ $1 == $3 ] + then + return 0 + else + return 1 + fi +} help() { cat << EOF -ci.sh: CI build script -USAGE: - ci.sh -OPTIONS: - b build build website - c clean clean dependencies and build artifacts - h help print this help menu - i install install build dependencies - u url make urls relative - z zola invoke zola +USAGE: ci.sh [SUBCOMMAND] +Helper script to automate deployment operations on CI/CD + +Subcommands + + -c --clean cleanup secrets, SSH key and other runtime data + -i --init initialize environment, write SSH private to file + -d --deploy push branch to Gitea and call Pages server + -h --help print this help menu EOF } -check_arg(){ - if [ -z $1 ] - then - help - exit 1 - fi +# $1: SSH private key +write_ssh(){ + truncate --size 0 $SSH_ID_FILE + echo "$1" > $SSH_ID_FILE + chmod 600 $SSH_ID_FILE } -match_arg() { - if [ $1 == $2 ] || [ $1 == $3 ] - then - return 0 - else - return 1 - fi -} - -download() { - echo "Downloading Zola" - wget --quiet --output-document=$TARBALL $SOURCE - tar -xvzf $TARBALL > /dev/null - rm $TARBALL - echo "Downloaded zola into $BIN" -} - -init() { - if [ ! -d $BIN_PATH ] - then - mkdir $BIN_PATH - fi - - if [ ! -f $BIN ] - then - cd $BIN_PATH - download - fi -} - -run() { - $BIN "${@:1}" -} - -build() { - run build -} - -no_absolute_url() { - sed -i 's/https:\/\/batsense.net//g' $(find public -type f | grep html) +set_ssh_remote() { + http_remote_url=$(git remote get-url origin) + remote_hostname=$(echo $http_remote_url | cut -d '/' -f 3) + repository_owner=$(echo $http_remote_url | cut -d '/' -f 4) + repository_name=$(echo $http_remote_url | cut -d '/' -f 5) + ssh_remote="git@$remote_hostname:$repository_owner/$repository_name" + ssh_remote="git@git.batsense.net:ForgeFlux/docs.git" + git remote add $SSH_REMOTE_NAME $ssh_remote } clean() { - rm -rf $BIN_PATH || true - rm -rf $DIST || true - echo "Workspace cleaned" + if [ -f $SSH_ID_FILE ] + then + shred $SSH_ID_FILE + rm $SSH_ID_FILE + fi } -check_arg $1 +# $1: Pages API secret +# $2: Deployment target branch +deploy() { + if (( "$#" < 2 )) + then + help + else + git -c core.sshCommand="/usr/bin/ssh -oStrictHostKeyChecking=no -i $SSH_ID_FILE"\ + push --force $SSH_REMOTE_NAME $2 + curl -vv --location --request \ + POST "https://deploy.batsense.net/api/v1/update"\ + --header 'Content-Type: application/json' \ + --data-raw "{ \"secret\": \"$1\", \"branch\": \"$2\" }" + fi +} -if match_arg $1 'i' 'install' -then - init -elif match_arg $1 'c' 'clean' -then - clean -elif match_arg $1 'b' 'build' -then - build -elif match_arg $1 'h' 'help' +if (( "$#" < 1 )) then help -elif match_arg $1 'u' 'url' -then - no_absolute_url -elif match_arg $1 'z' 'zola' -then - $BIN "${@:3}" -else - echo "Error: $1 is not an option" - help - exit 1 + exit -1 fi -exit 0 + +if match_arg $1 '-i' '--init' +then + if (( "$#" < 2 )) + then + help + exit -1 + fi + set_ssh_remote + write_ssh "$2" +elif match_arg $1 '-c' '--clean' +then + clean +elif match_arg $1 '-d' '--deploy' +then + if (( "$#" < 3 )) + then + help + exit -1 + fi + deploy $2 $3 +elif match_arg $1 '-h' '--help' +then + help +else + help +fi diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100755 index 0000000..da0b409 --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright © 2021 Aravinth Manivannan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +check_arg(){ + if [ -z $1 ] + then + help + exit 1 + fi +} + +match_arg() { + if [ $1 == $2 ] || [ $1 == $3 ] + then + return 0 + else + return 1 + fi +} diff --git a/scripts/spellcheck.sh b/scripts/spellcheck.sh new file mode 100755 index 0000000..06674e4 --- /dev/null +++ b/scripts/spellcheck.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Used in CI workflow: install and check for spelling errors +# Copyright © 2021 Aravinth Manivannan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +readonly MISSPELL_DOWNLOAD="https://github.com/client9/misspell/releases/download/v0.3.4/misspell_0.3.4_linux_64bit.tar.gz" +readonly TMP_DIR=$(pwd)/tmp +readonly PROJECT_ROOT=$(pwd) +readonly MISSPELL_TARBALL="$TMP_DIR/misspell.tar.bz2" +readonly MISSPELL="$TMP_DIR/misspell" + +set -Eeuo pipefail + +source $(pwd)/scripts/lib.sh + +FLAGS="" + +help() { + cat << EOF +spellcheck.sh: Check for spelling errors +USAGE: + spellcheck.sh +OPTIONS: + c --check check for spelling erros + h --help print this help menu + w --write check and fix spelling errors +EOF +} + + + +download() { + if [ ! -e $MISSPELL ]; + then + echo "[*] Downloading misspell" + wget --quiet --output-doc=$MISSPELL_TARBALL $MISSPELL_DOWNLOAD; + cd $TMP_DIR + tar -xf $MISSPELL_TARBALL; + cd $PROJECT_ROOT + pip install codespell + else + echo "[*] Found misspell" + fi +} + +spell_check_codespell() { + _check(){ + codespell $FLAGS $PROJECT_ROOT/$1 #|| true + } + _check README.md + _check contents +} + +spell_check_misspell() { + mkdir $TMP_DIR || true + download + + _check(){ + $MISSPELL $FLAGS $PROJECT_ROOT/$1 + } + + _check contents + _check README.md +} + +check_arg $1 + +if match_arg $1 'w' '--write' +then + echo "[*] checking and correcting spellings" + FLAGS="-w" + spell_check_misspell + spell_check_codespell +elif match_arg $1 'c' '--check' +then + echo "[*] checking spellings" + spell_check_misspell + spell_check_codespell +elif match_arg $1 'h' '--help' +then + help +else + echo "undefined option" + help + exit 1 +fi diff --git a/scripts/zola.sh b/scripts/zola.sh new file mode 100755 index 0000000..d69ace0 --- /dev/null +++ b/scripts/zola.sh @@ -0,0 +1,158 @@ +#!/bin/bash +# Used in CI workflow: install Zola binary from GitHub +# Copyright © 2021 Aravinth Manivannan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +set -euo pipefail + +readonly PROJECT_ROOT=$(pwd) +readonly TARBALL=zola.tar.gz +readonly SOURCE="https://github.com/getzola/zola/releases/download/v0.15.3/zola-v0.15.3-x86_64-unknown-linux-gnu.tar.gz" + +readonly BIN_PATH=tmp/bin +readonly BIN=$BIN_PATH/zola + +readonly DIST=public + +source $(pwd)/scripts/lib.sh + +help() { + cat << EOF +zola.sh: Zola build script +USAGE: + zola.sh +OPTIONS: + b build build website + c clean clean dependencies and build artifacts + d deploy deploy build to branch + h help print this help menu + i install install build dependencies + u url make urls relative + z zola invoke zola +EOF +} + +download() { + if [ ! -e $BIN_PATH ]; + then + mkdir -p $BIN_PATH + cd $BIN_PATH + echo "[*] Downloading Zola" + wget --quiet --output-document=$TARBALL $SOURCE + tar -xvzf $TARBALL > /dev/null + rm $TARBALL + echo "[*] Downloaded zola into $BIN" + cd $PROJECT_ROOT + fi +} + +init() { + if [ ! -d $BIN_PATH ] + then + mkdir $BIN_PATH + fi + + if [ ! -f $BIN ] + then + cd $BIN_PATH + download + fi +} + +run() { + $BIN "${@:1}" +} + +build() { + run build +} + +no_absolute_url() { + sed -i 's/https:\/\/hostea.org//g' $(find public -type f | grep html) +} + +clean() { + rm -rf $BIN_PATH || true + rm -rf $DIST || true + echo "[*] Workspace cleaned" +} + +# $1: branch name +# $2: directory containing build assets +# $3: Author in format +deploy() { + cd $PROJECT_ROOT + original_branch=$(git branch --show-current) + tmp_dir=$(mktemp -d) + cp -r $2/* $tmp_dir + + if [[ -z $(git ls-remote --heads origin ${1}) ]] + then + echo "[*] Creating deployment branch $1" + git checkout --orphan $1 + else + echo "[*] Deployment branch $1 exists, pulling changes from remote" + git fetch origin $1 + git switch $1 + fi + + git rm -rf . + /bin/rm -rf * + cp -r $tmp_dir/* . + git add --all + if [ $(git status --porcelain | xargs | sed '/^$/d' | wc -l) -gt 0 ]; + then + echo "[*] Repository has changed, committing changes" + git commit \ + --author="$3" \ + --message="new deploy: $(date --iso-8601=seconds)" + fi + git checkout $original_branch +} + +check_arg $1 +download + +if match_arg $1 'i' 'install' +then + init +elif match_arg $1 'c' 'clean' +then + clean +elif match_arg $1 'd' 'deploy' +then + check_arg $2 + check_arg $3 + check_arg $4 + deploy $2 $3 $4 +elif match_arg $1 'b' 'build' +then + build +elif match_arg $1 'h' 'help' +then + help +elif match_arg $1 'u' 'url' +then + no_absolute_url +elif match_arg $1 'z' 'zola' +then + $BIN "${@:3}" +else + echo "Error: $1 is not an option" + help + exit 1 +fi + +exit 0