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>
79 lines
4.1 KiB
Bash
79 lines
4.1 KiB
Bash
#!/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
|