aboutsummaryrefslogtreecommitdiff
path: root/v2/aux
diff options
context:
space:
mode:
Diffstat (limited to 'v2/aux')
-rwxr-xr-xv2/aux/ci/git-post-receive.sh186
-rwxr-xr-xv2/aux/ci/git-pre-receive.sh14
-rwxr-xr-xv2/aux/ci/report.sh250
-rw-r--r--v2/aux/domain.txt1
-rw-r--r--v2/aux/lib.sh37
-rwxr-xr-xv2/aux/po4a-cfg.sh109
l---------v2/aux/workflow/TODOs.sh1
-rw-r--r--v2/aux/workflow/favicon.html1
l---------v2/aux/workflow/preamble.md1
l---------v2/aux/workflow/style.css1
10 files changed, 597 insertions, 4 deletions
diff --git a/v2/aux/ci/git-post-receive.sh b/v2/aux/ci/git-post-receive.sh
new file mode 100755
index 0000000..76adccf
--- /dev/null
+++ b/v2/aux/ci/git-post-receive.sh
@@ -0,0 +1,186 @@
+#!/bin/sh
+# shellcheck source=/dev/null disable=2317
+. /etc/rc
+set -eu
+
+
+# shellcheck disable=2034
+read -r _oldrev SHA REFNAME
+
+if [ "$SHA" = '0000000000000000000000000000000000000000' ]; then
+ exit
+fi
+
+
+SKIP_DEPLOY=false
+for n in $(seq 0 $((GIT_PUSH_OPTION_COUNT - 1))); do
+ opt="$(eval "printf '%s' \"\$GIT_PUSH_OPTION_$n\"")"
+ case "$opt" in
+ ci.skip)
+ cat <<-EOF
+
+ "$opt" option detected, not running CI.
+
+ EOF
+ exit
+ ;;
+ deploy.skip)
+ SKIP_DEPLOY=true
+ ;;
+ *)
+ ;;
+ esac
+done
+
+
+epoch() {
+ awk 'BEGIN { srand(); print(srand()); }'
+}
+
+now() {
+ date '+%Y-%m-%dT%H:%M:%S%:z'
+}
+
+NAME="$(basename "$PWD" .git)"
+LOGS_DIR=/var/log/ci/"$NAME"/
+HTML_OUTDIR="/srv/www/s/$NAME"
+TIMESTAMP="$(now)"
+FILENAME="$TIMESTAMP-$SHA.log"
+LOGFILE="$LOGS_DIR/$FILENAME"
+mkdir -p "$LOGS_DIR"
+
+
+END_MARKER='\033[0m'
+LIGHT_BLUE_B='\033[1;36m'
+YELLOW='\033[1;33m'
+
+blue() {
+ printf "${LIGHT_BLUE_B}%s${END_MARKER}" "$1"
+}
+
+yellow() {
+ printf "${YELLOW}%s${END_MARKER}" "$1"
+}
+
+info() {
+ sed "s|^\(.\)|$(blue 'CI'): \1|"
+}
+
+
+uuid() {
+ od -xN20 /dev/urandom |
+ head -n1 |
+ awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
+}
+
+tmpname() {
+ printf '%s/uuid-tmpname with spaces.%s' "${TMPDIR:-/tmp}" "$(uuid)"
+}
+
+mkdtemp() {
+ name="$(tmpname)"
+ mkdir -- "$name"
+ printf '%s' "$name"
+}
+
+
+{
+ cat <<-EOF | info
+ Starting CI job at: $(now)
+ EOF
+ START="$(epoch)"
+
+ duration() {
+ if [ "$RUN_DURATION" -gt 60 ]; then
+ cat <<-EOF
+ $(yellow 'WARNING'): run took more than 1 minute! ($RUN_DURATION seconds)
+ EOF
+ else
+ cat <<-EOF
+ Run took $RUN_DURATION seconds.
+ EOF
+ fi
+ }
+
+ finish() {
+ STATUS="$?"
+ END="$(epoch)"
+ RUN_DURATION=$((END - START))
+ cat <<-EOF | info
+ Finishing CI job at: $(now)
+ Exit status was $STATUS
+ Re-run with:
+ \$ $CMD
+ $(duration)
+ EOF
+
+ NOTE="$(
+ cat <<-EOF
+ See CI logs with:
+ git notes --ref=refs/notes/ci-logs show $SHA
+ git notes --ref=refs/notes/ci-data show $SHA
+
+ Exit status: $STATUS
+ Duration: $RUN_DURATION
+ EOF
+ )"
+ git notes --ref=refs/notes/ci-data add -f -m "$(
+ cat <<-EOF
+ status $STATUS
+ sha $SHA
+ filename $FILENAME
+ duration $RUN_DURATION
+ timestamp $TIMESTAMP
+ to-prod $TO_PROD
+ refname $REFNAME
+ EOF
+ )" "$SHA"
+ git notes --ref=refs/notes/ci-logs add -f -F "$LOGFILE" "$SHA"
+ git notes add -f -m "$NOTE" "$SHA"
+
+ {
+ printf 'Git CI HTML report for %s (%s) started.\n' "$NAME" "$SHA" >&2
+ DIR="$(mkdtemp)"
+ REMOTE="$PWD"
+ cd "$DIR"
+ {
+ git clone "$REMOTE" .
+ git fetch origin 'refs/notes/*:refs/notes/*'
+ } 1>/dev/null 2>&1
+ sh aux/ci/report.sh -n "$NAME" -o public-ci
+ sudo -u deployer mkdir -p "$HTML_OUTDIR"/ci/
+ sudo -u deployer rsync \
+ --chmod=D775,F664 \
+ --chown=deployer:deployer \
+ --delete \
+ -a \
+ public-ci/ "$HTML_OUTDIR"/ci/
+ rm -rf "$DIR"
+ printf 'Git CI HTML report for %s (%s) finished.\n' "$NAME" "$SHA" >&2
+ } 2>&1 | logger -i -p local0.warn -t git-ci 1>/dev/null 2>&1 &
+ }
+ trap finish EXIT
+
+ unset GIT_DIR
+
+ if [ "$REFNAME" = 'refs/heads/main' ] && [ "$SKIP_DEPLOY" = false ]; then
+ cat <<-EOF | info
+ In branch "main", running deploy for $SHA.
+ EOF
+ TO_PROD=true
+ CMD="sudo cicd $NAME $SHA"
+ else
+ if [ "$SKIP_DEPLOY" = true ]; then
+ cat <<-EOF | info
+ "deploy.skip" option detected, skipping deploy for $SHA.
+ EOF
+ else
+ cat <<-EOF | info
+ Not on branch "main", skipping deploy for $SHA.
+ EOF
+ fi
+ TO_PROD=false
+ CMD="sudo cicd -n $NAME $SHA"
+ fi
+ $CMD
+} 2>&1 | ts -s '%.s' | tee "$LOGFILE"
diff --git a/v2/aux/ci/git-pre-receive.sh b/v2/aux/ci/git-pre-receive.sh
new file mode 100755
index 0000000..199d06e
--- /dev/null
+++ b/v2/aux/ci/git-pre-receive.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -eu
+
+read -r _oldrev SHA _refname
+unset GIT_DIR
+
+if [ "$SHA" = '0000000000000000000000000000000000000000' ]; then
+ exit
+fi
+
+printf 'Upgrading post-receive hook...' >&2
+git show "$SHA":aux/ci/git-post-receive.sh > hooks/post-receive
+chmod +x hooks/post-receive
+printf 'done.\n' >&2
diff --git a/v2/aux/ci/report.sh b/v2/aux/ci/report.sh
new file mode 100755
index 0000000..0a0a0ae
--- /dev/null
+++ b/v2/aux/ci/report.sh
@@ -0,0 +1,250 @@
+#!/bin/sh
+set -eu
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ aux/ci/report.sh -o OUTDIR
+ aux/ci/report.sh -h
+ EOF
+}
+
+help() {
+ cat <<-'EOF'
+
+
+ Options:
+ -o OUTDIR the directory where to place the generated files
+ -h, --help show this message
+
+
+ Gather data from Git Notes, and generate an HTML report on CI runs.
+
+ Two refs with notes are expected:
+ 1. refs/notes/ci-data: contains metadata abount the CI runs,
+ with timestamps, filenames and exit status;
+ 2. refs/notes/ci-logs: contains the content of the log.
+
+ When reconstructing the CI run, the $FILENAME present in
+ the refs/notes/ci-data ref names the file, and its content comes
+ from refs/notes/ci-logs.
+
+ On a CI run that generated the numbers from 1 to 10, for a file named
+ 'my-ci-run-2020-01-01-deadbeef.log' that exited successfully, ran for
+ 15 seconds and was deployed to production, the expected output on the
+ target directory "public" is:
+
+ $ tree public/
+ public/
+ index.html
+ data/
+ 2020-01-01T01:00:00-deadbeef.log
+ ...
+ logs/
+ 2020-01-01T01:00:00-deadbeef.log
+ ...
+
+ $ cat public/data/2020-01-01T01:00:00-deadbeef.log
+ status 0
+ sha deadbeef
+ filename deadbeef 2020-01-01T01:00:00-deadbeef.log
+ duration 15
+ timestamp 2020-01-01T01:00:00
+ to-prod true
+ refname refs/heads/main
+
+ $ cat public/logs/2020-01-01T01:00:00-deadbeef.log
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+
+ The generated 'index.html' is a webpage with the list of all known
+ CI runs, their status, a link to the commit and a link to the
+ log and data files.
+
+ To enable fetching these refs by default, do so in the git config:
+
+ $ git config --add remote.origin.fetch '+refs/notes/*:refs/notes/*'
+
+
+ Examples:
+
+ Generate the report on the 'www' directory:
+
+ $ sh aux/ci/report.sh -o www
+ EOF
+}
+
+
+for flag in "$@"; do
+ case "$flag" in
+ --)
+ break
+ ;;
+ --help)
+ usage
+ help
+ exit
+ ;;
+ *)
+ ;;
+ esac
+done
+
+while getopts 'o:h' flag; do
+ case "$flag" in
+ o)
+ OUTDIR="$OPTARG"
+ ;;
+ h)
+ usage
+ help
+ exit
+ ;;
+ *)
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+. aux/lib.sh
+
+eval "$(assert_arg "${OUTDIR:-}" '-o OUTDIR')"
+
+
+esc() {
+ sed \
+ -e 's|&|\&amp;|g' \
+ -e 's|<|\&lt;|g' \
+ -e 's|>|\&gt;|g' \
+ -e 's|"|\&quot;|g' \
+ -e "s|'|\&#39;|g"
+}
+
+mkdir -p "$OUTDIR"
+cd "$OUTDIR"
+mkdir -p logs data
+
+for c in $(git notes list | cut -d' ' -f2); do
+ git notes --ref=refs/notes/ci-data show "$c" > data/FILENAME-tmp
+ FILENAME="$(grep '^filename ' data/FILENAME-tmp | cut -d' ' -f2-)"
+ mv data/FILENAME-tmp data/"$FILENAME"
+ git notes --ref=refs/notes/ci-logs show "$c" > logs/"$FILENAME"
+done
+
+{
+ cat <<-EOF
+ <!DOCTYPE html>
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="CI logs for $NAME" />
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+ <title>$NAME - CI logs</title>
+ <style>
+ body {
+ max-width: 800px;
+ margin: 0 auto;
+ }
+
+ code {
+ display: block;
+ margin: 1em 0em 3em 3em;
+ overflow: auto;
+ }
+
+ pre {
+ display: inline;
+ }
+
+ ol {
+ list-style-type: disc;
+ }
+
+ pre, code {
+ background-color: #ddd;
+ }
+
+ @media(prefers-color-scheme: dark) {
+ :root {
+ color: white;
+ background-color: black;
+ }
+
+ a {
+ color: hsl(211, 100%, 60%);
+ }
+
+ a:visited {
+ color: hsl(242, 100%, 80%);
+ }
+
+ pre, code {
+ background-color: #222;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <main>
+ <h1>
+ CI logs for
+ <a href="https://$DOMAIN/s/$NAME/">$NAME</a>
+ </h1>
+ <ol>
+ EOF
+
+
+ PASS='&#x2705;' # ✅
+ WARN='&#x1F40C;' # 🐌
+ FAIL='&#x274C;' # ❌
+ find data/ -type f | LANG=C.UTF-8 sort -r | while read -r f; do
+ STATUS="$( grep '^status ' "$f" | cut -d' ' -f2- | esc)"
+ SHA="$( grep '^sha ' "$f" | cut -d' ' -f2- | esc)"
+ FILENAME="$(grep '^filename ' "$f" | cut -d' ' -f2- | esc)"
+ DURATION="$(grep '^duration ' "$f" | cut -d' ' -f2- | cut -d'"' -f1 | esc)"
+ MESSAGE="$({
+ git log -1 --format=%B "$SHA" || {
+ git fetch origin "$SHA"
+ git log -1 --format=%B "$SHA"
+ }
+ } | esc)"
+
+ if [ "$STATUS" = 0 ]; then
+ if [ "$DURATION" -le 60 ]; then
+ STATUS_MARKER="$PASS"
+ else
+ STATUS_MARKER="$WARN"
+ fi
+ else
+ STATUS_MARKER="$FAIL"
+ fi
+
+ cat <<-EOF
+ <li id="$FILENAME">
+ <a href="#$FILENAME"><pre>#</pre></a>
+ $STATUS_MARKER - <pre>${DURATION:-?}s</pre>
+ <pre>(<a href="https://$DOMAIN/git/$NAME/commit/?id=$SHA">commit</a>)</pre>
+ <a href="logs/$FILENAME"><pre>$FILENAME</pre></a>
+ <pre>(<a href="data/$FILENAME">data</a>)</pre>
+ <br />
+ <code><pre>$MESSAGE</pre></code>
+ </li>
+ EOF
+ done
+
+ cat <<-EOF
+ </ol>
+ </main>
+ </body>
+ </html>
+ EOF
+} > index.html
diff --git a/v2/aux/domain.txt b/v2/aux/domain.txt
new file mode 100644
index 0000000..fd7ea0f
--- /dev/null
+++ b/v2/aux/domain.txt
@@ -0,0 +1 @@
+euandre.org
diff --git a/v2/aux/lib.sh b/v2/aux/lib.sh
new file mode 100644
index 0000000..93a37eb
--- /dev/null
+++ b/v2/aux/lib.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+DOMAIN="$(cat aux/domain.txt)"
+NAME="$(basename "$PWD")"
+export DOMAIN NAME
+
+assert_arg() {
+ if [ -z "$1" ]; then
+ printf 'Missing %s.\n\n' "$2" >&2
+ cat <<-'EOF'
+ usage >&2
+ exit 2
+ EOF
+ fi
+}
+
+uuid() {
+ od -xN20 /dev/urandom |
+ head -n1 |
+ awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
+}
+
+tmpname() {
+ printf '%s/uuid-tmpname-without-spaces.%s' "${TMPDIR:-/tmp}" "$(uuid)"
+}
+
+mkstemp() {
+ name="$(tmpname)"
+ touch -- "$name"
+ printf '%s' "$name"
+}
+
+mkdtemp() {
+ name="$(tmpname)"
+ mkdir -p -- "$name"
+ printf '%s' "$name"
+}
diff --git a/v2/aux/po4a-cfg.sh b/v2/aux/po4a-cfg.sh
new file mode 100755
index 0000000..b20e303
--- /dev/null
+++ b/v2/aux/po4a-cfg.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+set -eu
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ aux/po4a-cfg.sh > po/po4a.cfg
+ aux/po4a-cfg.sh -h
+ EOF
+}
+
+help() {
+ cat <<-'EOF'
+
+
+ Options:
+ -h, --help show this message
+
+
+ Discover translatable files in the repository (via
+ git-ls-files(1)) that have '.en.' or '/en/' in their name and
+ emit the configuration file to be used with po4a(1).
+
+
+ Examples:
+
+ Setup i18n on a new repository:
+
+ $ mkdir po
+ $ touch po/pt.po
+ $ touch po/"$(basename "$PWD")".pot
+ $ aux/po4a-cfg.sh > po/po4a.cfg
+ $ po4a po/po4a.cfg
+
+
+ Conditionally update the configuration in a Makefile:
+
+ po/po4a.cfg: ALWAYS
+ @sh aux/po4a-cfg.sh | ifnew $@
+ po4a $@
+ EOF
+}
+
+
+for flag in "$@"; do
+ case "$flag" in
+ --)
+ break
+ ;;
+ --help)
+ usage
+ help
+ exit
+ ;;
+ *)
+ ;;
+ esac
+done
+
+while getopts 'h' flag; do
+ case "$flag" in
+ h)
+ usage
+ help
+ exit
+ ;;
+ *)
+ usage >&2
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+. aux/lib.sh
+
+
+guess_type() {
+ case "$1" in
+ *.md)
+ echo text
+ ;;
+ *.[1-9]*.in)
+ echo man
+ ;;
+ *.html)
+ echo xhtml
+ ;;
+ *)
+ echo text
+ ;;
+ esac
+}
+
+
+cat <<-'EOF'
+ [options] --keep 0 --master-charset UTF-8 --localized-charset UTF-8
+
+ [po_directory] po
+
+EOF
+
+git ls-files | grep -F '.en.' | while read -r file; do
+ TYPE="$(guess_type "$file")"
+ # shellcheck disable=2016
+ VAR_FILE="$(printf '%s' "$file" | sed 's|\.en\.|.$lang.|')"
+ # shellcheck disable=2016
+ printf '[type: %s] %s $lang:%s\n' "$TYPE" "$file" "$VAR_FILE"
+done
diff --git a/v2/aux/workflow/TODOs.sh b/v2/aux/workflow/TODOs.sh
deleted file mode 120000
index aaa6c50..0000000
--- a/v2/aux/workflow/TODOs.sh
+++ /dev/null
@@ -1 +0,0 @@
-../../../aux/workflow/TODOs.sh \ No newline at end of file
diff --git a/v2/aux/workflow/favicon.html b/v2/aux/workflow/favicon.html
deleted file mode 100644
index 6f1bac8..0000000
--- a/v2/aux/workflow/favicon.html
+++ /dev/null
@@ -1 +0,0 @@
-<link rel="icon" type="image/svg+xml" href="static/favicon.svg" />
diff --git a/v2/aux/workflow/preamble.md b/v2/aux/workflow/preamble.md
deleted file mode 120000
index 739f3ea..0000000
--- a/v2/aux/workflow/preamble.md
+++ /dev/null
@@ -1 +0,0 @@
-../../../aux/workflow/preamble.md \ No newline at end of file
diff --git a/v2/aux/workflow/style.css b/v2/aux/workflow/style.css
deleted file mode 120000
index ffff132..0000000
--- a/v2/aux/workflow/style.css
+++ /dev/null
@@ -1 +0,0 @@
-../../../aux/workflow/style.css \ No newline at end of file