96 lines
3.6 KiB
Bash
Executable File
96 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
|
export PATH
|
|
umask 077
|
|
|
|
# Manual updater for operators without using the web control. The same
|
|
# verified TypeScript updater used by the systemd queue performs download,
|
|
# extraction and atomic release switching.
|
|
PREFIX=${TALLYNOTE_INSTALL_PREFIX:-${TALLYNOTE_PREFIX:-/opt/tallynote}}
|
|
DATA_DIR=${TALLYNOTE_DATA_DIR:-/var/lib/tallynote}
|
|
REQUEST_FILE=${TALLYNOTE_UPDATE_REQUEST_FILE:-$DATA_DIR/update-request.json}
|
|
NODE=${TALLYNOTE_NODE:-}
|
|
|
|
die() { printf 'tallynote update: %s\n' "$*" >&2; exit 1; }
|
|
version_sort_desc() {
|
|
if sort -V </dev/null >/dev/null 2>&1; then
|
|
sort -V -r
|
|
return
|
|
fi
|
|
awk -F'[.-]' '{ printf "%020d.%020d.%020d.%s\t%s\n", $1, $2, $3, ($4 == "" ? "~" : $4), $0 }' \
|
|
| sort -r | cut -f2-
|
|
}
|
|
[[ ${EUID:-$(id -u)} -eq 0 ]] || die 'must run as root'
|
|
|
|
if [[ "${1:-}" == "--rollback" ]]; then
|
|
current="$PREFIX/current"
|
|
[[ -L "$current" ]] || die 'current release is not a symlink'
|
|
current_target=$(readlink -f -- "$current")
|
|
current_name=$(basename -- "$current_target")
|
|
[[ "$current_name" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]] || die 'current release version is invalid'
|
|
mapfile -t releases < <(
|
|
find "$PREFIX/releases" -mindepth 1 -maxdepth 1 -type d -printf '%p\n' \
|
|
| awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$/' \
|
|
| version_sort_desc
|
|
)
|
|
previous=''
|
|
found_current=0
|
|
for release in "${releases[@]}"; do
|
|
release_target=$(readlink -f -- "$release")
|
|
if [[ "$release_target" == "$current_target" ]]; then
|
|
found_current=1
|
|
continue
|
|
fi
|
|
if (( found_current )); then
|
|
previous=$release
|
|
break
|
|
fi
|
|
done
|
|
[[ -n "$previous" && -d "$previous" ]] || die 'no previous release available'
|
|
was_active=0
|
|
if systemctl is-active --quiet tallynote.service; then was_active=1; fi
|
|
if (( was_active )); then systemctl stop tallynote.service; fi
|
|
tmp="$PREFIX/.current-rollback-$$-${RANDOM}"
|
|
[[ ! -e "$tmp" && ! -L "$tmp" ]] || die 'rollback temporary path already exists'
|
|
ln -s -- "$previous" "$tmp"
|
|
mv -Tf -- "$tmp" "$current"
|
|
if (( was_active )); then systemctl start tallynote.service; fi
|
|
printf 'rolled back to %s\n' "$(basename -- "$previous")"
|
|
exit 0
|
|
fi
|
|
|
|
[[ -f "$REQUEST_FILE" ]] || die "no queued update request at $REQUEST_FILE"
|
|
[[ -L "$PREFIX/current" ]] || die 'current release is not a symlink'
|
|
|
|
# Prefer the systemd runner, which performs the post-switch health check and
|
|
# rollback. The fallback remains useful in development installations where the
|
|
# privileged helper has not been installed yet.
|
|
if [[ -x /usr/local/libexec/tallynote-update-runner ]]; then
|
|
exec /usr/local/libexec/tallynote-update-runner
|
|
fi
|
|
if [[ -z "$NODE" ]]; then
|
|
NODE="$PREFIX/current/runtime/bin/node"
|
|
[[ -x "$NODE" ]] || NODE=$(command -v node || true)
|
|
fi
|
|
[[ -n "$NODE" ]] || die 'node runtime not found'
|
|
CLI="$PREFIX/current/dist/server/cli/update.js"
|
|
[[ -f "$CLI" ]] || die 'update CLI not found'
|
|
|
|
was_active=0
|
|
if systemctl is-active --quiet tallynote.service; then was_active=1; fi
|
|
if (( was_active )); then systemctl stop tallynote.service; fi
|
|
set +e
|
|
"$NODE" "$CLI" --request-file "$REQUEST_FILE"
|
|
result=$?
|
|
set -e
|
|
if (( result != 0 )); then
|
|
rm -f -- "$REQUEST_FILE"
|
|
if (( was_active )); then systemctl start tallynote.service || true; fi
|
|
die 'update failed; the previous release remains active'
|
|
fi
|
|
if (( was_active )); then systemctl start tallynote.service || { rm -f -- "$REQUEST_FILE"; die 'updated service failed to start'; }; fi
|
|
rm -f -- "$REQUEST_FILE"
|
|
printf 'update completed; inspect the update page for details\n'
|