Files
imagesbyolofsson-se/deploy/cleura/portfolio-sync.sh
T
Linus OlofssonandClaude Opus 5.5 24a59c6e93 Portfolio sync: a photo that fails no longer stops the others; sharp accepts pixel-shift and 15000 px squares
Seen on the first run on Cleura 2026-11-06: 13 photos tagged since 2026-09-29 were never synced. portfolio-sync.mjs
sets exit code 1 when one photo fails (DSC00342_PSMS16-square.jpg: "Input image exceeds pixel limit", sharp's default
268 MP) and set -e then stopped before commit/push - on Nurem as well. Now the photos that worked are committed and
built, the run still ends with exit 1 so systemd shows it, and sharp's limit is 1 000 MP (JPEGs shrink while decoding;
the container gets 3 GB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 05:18:14 +01:00

79 lines
4.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# deploy/cleura/portfolio-sync.sh – Immich "portfolio" tag -> this repo -> imagesbyolofsson.se, on the Cleura server.
#
# Replaces /opt/immich-sync/run-sync.sh on Nurem (started by n8n over SSH) when imagesbyolofsson.se moved to Cleura
# (Linus 2026-11-06: everything on the Cleura server). Same steps: fetch main, run scripts/portfolio-sync.mjs,
# commit + push to Gitea main when photos were added or removed, and then rebuild the live site with
# /opt/portal/site/build-live.sh (EXIF check included). Nothing is built when nothing changed.
#
# Installed by deploy/cleura/install-portfolio-sync.sh as /opt/portal/site/portfolio-sync.sh, run by
# portfolio-sync.timer every 20 minutes. Secrets: /etc/portal/portfolio-sync.env (root 600) with
# IMMICH_URL, PORTFOLIO_TAG_ID, IMMICH_API_KEY (read-only Immich key) and GIT_URL (Gitea URL that may push main,
# with user:token@ in it, as on Nurem).
#
# The Gitea token never lands on disk outside that env file: the clone's remote URL is stored without it, git gets
# it from a credential helper at fetch/push time, and the Node container (npm install runs third-party code) gets
# an empty tmpfs over .git and only the Immich variables.
set -euo pipefail
umask 077
ENVF=${PORTFOLIO_SYNC_ENV:-/etc/portal/portfolio-sync.env}
DIR=${PORTFOLIO_SYNC_DIR:-/opt/portal/site/sync/repo}
NODE=node:22.22.0-alpine@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34
exec 9>/run/lock/portfolio-sync.lock
flock -n 9 || { echo "[sync] already running"; exit 0; }
set -a; . "$ENVF"; set +a
export IMMICH_URL="${IMMICH_URL:-https://foto.imagesbyolofsson.se/api}"
export PORTFOLIO_TAG_ID="${PORTFOLIO_TAG_ID:-67d16b6c-0dea-42ef-a289-2c9e57fa5c5a}"
[ -n "${IMMICH_API_KEY:-}" ] || { echo "FATAL: IMMICH_API_KEY missing in $ENVF"; exit 2; }
[ -n "${GIT_URL:-}" ] || { echo "FATAL: GIT_URL missing in $ENVF"; exit 2; }
# Split GIT_URL into the clean URL (stored in .git/config) and user/token (kept in this process only).
read -r CLEAN_URL GIT_USER GIT_TOKEN < <(python3 - "$GIT_URL" <<'PY'
import sys, urllib.parse as u
p = u.urlsplit(sys.argv[1])
host = p.hostname + (":%d" % p.port if p.port else "")
print(u.urlunsplit((p.scheme, host, p.path, "", "")), u.unquote(p.username or "-"), u.unquote(p.password or "-"))
PY
)
unset GIT_URL
export GIT_USER GIT_TOKEN
# shellcheck disable=SC2016 # $GIT_USER/$GIT_TOKEN are expanded by the helper's shell, not here.
G=(git -c credential.helper= -c 'credential.helper=!f() { echo "username=$GIT_USER"; echo "password=$GIT_TOKEN"; }; f')
if [ ! -d "$DIR/.git" ]; then
mkdir -p "$(dirname "$DIR")"
"${G[@]}" clone -q "$CLEAN_URL" "$DIR"
fi
chmod 700 "$(dirname "$DIR")" "$DIR"
git -C "$DIR" remote set-url origin "$CLEAN_URL"
echo "[sync] fetch + reset main"
"${G[@]}" -C "$DIR" fetch -q origin main
git -C "$DIR" checkout -q -B main origin/main
git -C "$DIR" reset -q --hard origin/main
echo "[sync] scripts/portfolio-sync.mjs"
# portfolio-sync.mjs exits 1 when a single photo fails but still writes the others; those are committed below, and the
# run ends with exit 1 so systemd shows that something needs a look (journalctl -u portfolio-sync).
SYNC_RC=0
docker run --rm --memory 3g --cpus 2 --network bridge \
-e IMMICH_URL -e IMMICH_API_KEY -e PORTFOLIO_TAG_ID \
-v "$DIR":/app --mount type=tmpfs,destination=/app/.git -w /app "$NODE" \
sh -c 'npm install --no-audit --no-fund --loglevel=error >/dev/null && node scripts/portfolio-sync.mjs' || SYNC_RC=$?
if git -C "$DIR" status --porcelain -- src/assets/images/portfolio | grep -q .; then
git -C "$DIR" add -A src/assets/images/portfolio
git -C "$DIR" -c user.name="portfolio-sync (Cleura)" -c user.email="Admin@imagesbyolofsson.se" \
commit -q -m "chore(portfolio): sync $(date -u +%Y-%m-%d)"
"${G[@]}" -C "$DIR" push -q origin main
echo "[sync] pushed $(git -C "$DIR" rev-parse --short HEAD), rebuilding the live site"
/opt/portal/site/build-live.sh main
else
echo "[sync] no changes"
fi
if [ "$SYNC_RC" != 0 ]; then
echo "[sync] WARNING: portfolio-sync.mjs exited $SYNC_RC - some photos failed (see the lines with '!' above)"
exit "$SYNC_RC"
fi