update.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
# Unattended update to the latest RELEASED state of this template.
#
# Design, in one paragraph: releases are the update channel. A tag is cut
# only after CI has built the pinned images, booted the full stack, and
# passed the smoke tests — so "update to the
# newest tag" means "update to a combination a machine has already run",
# which is the guarantee a floating `latest` can never give. The script
# refuses to cross a MAJOR template version on its own: majors are
# breaking by definition (an application platform jump, a changed variable
# contract) and their release notes exist to be read by a person.
#
# Run it from cron or a systemd timer for unattended minor/patch updates:
# 17 5 * * * /opt/sftp-traefik-letsencrypt-docker-compose/update.sh >> /var/log/sftp-update.log 2>&1
#
# Flags:
# --dry-run show what would happen, change nothing
# --allow-major permit crossing a major version (you read the notes)
set -euo pipefail
cd "$(dirname "$0")"
COMPOSE_FILE="sftp-traefik-letsencrypt-docker-compose.yml"
PROJECT="${COMPOSE_PROJECT_NAME:-sftp}"
DRY_RUN=false
ALLOW_MAJOR=false
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--allow-major) ALLOW_MAJOR=true ;;
*) echo "unknown flag: $arg" >&2; exit 2 ;;
esac
done
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "local changes present — commit or stash them first, nothing updated" >&2
exit 1
fi
# docker compose reads .env last, after the checkout. A .env this user
# cannot read (a copy restored without -p comes back root:root 0600) would
# stop it there, with the tree already on the new tag, and would make every
# required variable below read as "not set". Name the file and stop first.
if [ -e .env ] && [ ! -r .env ]; then
echo ".env is not readable by $(id -un) ($(stat -c '%A %U:%G' .env 2>/dev/null || stat -f '%Sp %Su:%Sg' .env)) — fix its ownership, nothing updated" >&2
exit 1
fi
git fetch --tags --quiet origin
latest="$(git tag -l 'v*' --sort=-v:refname | head -1)"
if [ -z "$latest" ]; then
echo "no release tags found — nothing to update to" >&2
exit 1
fi
current="$(git describe --tags --abbrev=0 2>/dev/null || echo 'v0.0.0')"
if [ "$(git rev-parse HEAD)" = "$(git rev-parse "$latest^{commit}")" ]; then
echo "already on $latest"
exit 0
fi
cur_major="${current#v}"; cur_major="${cur_major%%.*}"
new_major="${latest#v}"; new_major="${new_major%%.*}"
if [ "$new_major" != "$cur_major" ] && [ "$ALLOW_MAJOR" != "true" ]; then
echo "refusing to cross a major version unattended: $current -> $latest" >&2
echo "read the release notes, then re-run with --allow-major:" >&2
echo " https://github.com/heyvaldemar/sftp-traefik-letsencrypt-docker-compose/releases/tag/$latest" >&2
exit 3
fi
# NEW VARIABLES SINCE YOUR VERSION. An update can add a required variable,
# and `docker compose up` would then stop with a message naming it - after
# the checkout, with the tree already on the new tag. Better to say so here,
# before anything has moved. Names only, never values.
if [ -n "${COMPOSE_FILES+x}" ]; then _files=("${COMPOSE_FILES[@]}"); else _files=("$COMPOSE_FILE"); fi
_new="$(comm -13 <(git show "HEAD:.env.example" 2>/dev/null | grep -oE '^[A-Z0-9_]+=' | tr -d '=' | sort -u) \
<(git show "$latest:.env.example" 2>/dev/null | grep -oE '^[A-Z0-9_]+=' | tr -d '=' | sort -u))"
if [ -n "$_new" ]; then
echo "new variables in .env.example since $current:"
while IFS= read -r _k; do echo " $_k"; done <<<"$_new"
_required=""
for _f in "${_files[@]}"; do
_required="$_required $(git show "$latest:$_f" 2>/dev/null | grep -oE '\$\{[A-Z0-9_]+:\?' | sed -E 's/^\$\{//; s/:\?$//' | tr '\n' ' ' || true)"
done
_missing=""
for _k in $_new; do
case " $_required " in *" $_k "*) grep -qE "^${_k}=." .env 2>/dev/null || _missing="$_missing $_k" ;; esac
done
if [ -n "$_missing" ]; then
echo "required in $latest and not set in .env:$_missing" >&2
echo "see .env.example at $latest for what each one is; nothing was changed" >&2
[ "$DRY_RUN" = "true" ] || exit 4
fi
fi
echo "updating $current -> $latest"
if [ "$DRY_RUN" = "true" ]; then
git log --oneline "HEAD..$latest^{commit}" | sed 's/^/ would apply: /'
echo "dry run — nothing changed"
exit 0
fi
git checkout -q "$latest"
docker compose -f "$COMPOSE_FILE" -p "$PROJECT" up -d --remove-orphans
echo "now on $latest"