Lint / Shell (shellcheck) (push) Successful in 14s
Lint / JS (eslint) (push) Successful in 11s
Lint / No secrets in webhook configs (push) Successful in 5s
Lint / Landing page is rendered (matrix (push) Successful in 4s
Lint / Python (ruff) (push) Successful in 6s
Lint / Python deps (pip-audit) (push) Successful in 32s
Lint / Secret scan (gitleaks) (push) Successful in 4s
Lint / Shell (shellcheck) (pull_request) Successful in 7s
Lint / JS (eslint) (pull_request) Successful in 5s
Lint / No secrets in webhook configs (pull_request) Successful in 4s
Lint / Landing page is rendered (matrix (pull_request) Successful in 3s
Lint / Python (ruff) (pull_request) Successful in 4s
Lint / Python deps (pip-audit) (pull_request) Successful in 30s
Lint / Secret scan (gitleaks) (pull_request) Successful in 4s
Decorations came through Nextcloud's public DAV: a new PHP session (four cookies) per image and no cache headers, on every decorated avatar. The set (629 decorations + thumbnails, 573 MB) is now a versioned Gitea generic package (avatar-decorations 1.0.0, 4 tar parts), pinned by sha256 in cinny/decorations.sha256. LXC 106's deploy installs it to /var/www/decorations-<version> (verified against the pin and the in-package manifest; previous version kept; failures leave the live set alone), and nginx serves /decorations/ with a one-week public cache. deploy/publish-decorations.sh publishes a new version and writes the pin. Fixes #22 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
191 lines
8.4 KiB
Bash
191 lines
8.4 KiB
Bash
#!/bin/bash
|
|
# Auto-deploy script for LXC 106 (cinny)
|
|
# Handles: cinny/config.json, cinny/nginx.conf, cinny/upstream-check.sh,
|
|
# cinny/lotus-build.sh, cinny/lotus_deploy.sh,
|
|
# deploy/hooks-lxc106.json, systemd/cinny-upstream-check.cron
|
|
# Triggered by: Gitea webhook on push to main → http://10.10.10.6:9001/hooks/matrix-deploy
|
|
# (webhook-lotus; the :9000 listener is bound to 127.0.0.1 and unreachable).
|
|
set -euo pipefail
|
|
|
|
REPO_DIR="/opt/matrix-config"
|
|
LOG="/var/log/matrix-deploy.log"
|
|
CLONE_URL="https://code.lotusguild.org/LotusGuild/matrix.git"
|
|
|
|
exec >> "$LOG" 2>&1
|
|
echo "=== $(date) === LXC106 deploy triggered ==="
|
|
|
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
|
git clone "$CLONE_URL" "$REPO_DIR"
|
|
CHANGED="cinny/config.json cinny/decorations.sha256 cinny/nginx.conf cinny/upstream-check.sh cinny/lotus-build.sh cinny/lotus_deploy.sh deploy/hooks-lxc106.json systemd/cinny-upstream-check.cron"
|
|
else
|
|
cd "$REPO_DIR"
|
|
git fetch --all
|
|
PREV=$(git rev-parse HEAD)
|
|
git reset --hard origin/main
|
|
NEW=$(git rev-parse HEAD)
|
|
CHANGED=$(git diff --name-only "$PREV" "$NEW")
|
|
echo "Changed files: $CHANGED"
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^cinny/config.json'; then
|
|
echo "Deploying cinny config.json (merged: keeps server-only values)..."
|
|
# The live file carries values injected on the server that are empty in git
|
|
# (gifApiKey, set by lotus_deploy.sh from GIPHY_API_KEY). A plain copy would
|
|
# blank them, so repo keys win except where the repo value is empty and the
|
|
# live one isn't. Backup outside the web root; on any error the live file
|
|
# is left untouched.
|
|
mkdir -p /root/config-backups
|
|
cp -p /var/www/html/config.json "/root/config-backups/config.json.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
|
|
if python3 - "$REPO_DIR/cinny/config.json" /var/www/html/config.json <<'PY'
|
|
import json, os, sys, tempfile
|
|
repo_path, live_path = sys.argv[1], sys.argv[2]
|
|
repo = json.load(open(repo_path))
|
|
live = json.load(open(live_path)) if os.path.exists(live_path) else {}
|
|
merged = dict(repo)
|
|
kept = []
|
|
for key, value in live.items():
|
|
if key in repo and repo[key] in ("", None) and value not in ("", None):
|
|
merged[key] = value
|
|
kept.append(key)
|
|
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(live_path))
|
|
with os.fdopen(fd, "w") as f:
|
|
json.dump(merged, f, indent=2)
|
|
f.write("\n")
|
|
json.load(open(tmp))
|
|
os.chmod(tmp, 0o644)
|
|
os.replace(tmp, live_path)
|
|
print("kept server values for: " + (", ".join(kept) or "none"))
|
|
PY
|
|
then
|
|
echo "✓ config.json deployed"
|
|
else
|
|
echo "✗ config.json merge FAILED — live file left unchanged"
|
|
fi
|
|
fi
|
|
|
|
# [matrix #22] Avatar decorations: a versioned Gitea generic package, pinned
|
|
# (version + sha256 of each part) in cinny/decorations.sha256, installed to
|
|
# /var/www/decorations-<version> and served by nginx at /decorations/. Runs on a
|
|
# pin change or when nothing is installed yet. A failed download or checksum
|
|
# leaves the live set untouched; the previous version is kept for rollback.
|
|
install_decorations() {
|
|
local pin="$REPO_DIR/cinny/decorations.sha256"
|
|
local ver base tmp dest
|
|
ver=$(grep -oE 'avatar-decorations-[0-9][^ ]*\.part1\.tar' "$pin" | sed -E 's/^avatar-decorations-(.*)\.part1\.tar$/\1/')
|
|
[ -n "$ver" ] || { echo "✗ decorations: no version in $pin"; return 1; }
|
|
dest="/var/www/decorations-$ver"
|
|
if [ -d "$dest/decorations" ] && [ "$(readlink -f /var/www/decorations)" = "$dest/decorations" ]; then
|
|
echo "decorations $ver already live"; return 0
|
|
fi
|
|
base="https://code.lotusguild.org/api/packages/LotusGuild/generic/avatar-decorations/$ver"
|
|
tmp=$(mktemp -d /var/www/.decorations-XXXXXX)
|
|
# Every step checks its own exit status: `set -e` does not apply inside a
|
|
# subshell on the left of `||`, so a failed checksum would be ignored.
|
|
if ! (
|
|
cd "$tmp" || exit 1
|
|
cp "$pin" SHA256SUMS || exit 1
|
|
while read -r _ f; do
|
|
curl -fsSL --retry 3 -o "$f" "$base/$f" || exit 1
|
|
done < SHA256SUMS
|
|
sha256sum -c --quiet SHA256SUMS || exit 1
|
|
while read -r _ f; do
|
|
tar -xf "$f" || exit 1
|
|
rm -f "$f"
|
|
done < SHA256SUMS
|
|
cd decorations || exit 1
|
|
sha256sum -c --quiet MANIFEST.sha256 || exit 1
|
|
); then
|
|
echo "✗ decorations $ver: download/verify failed — live set unchanged"
|
|
rm -rf "$tmp"
|
|
return 1
|
|
fi
|
|
rm -rf "$dest"; mkdir -p "$dest"; mv "$tmp/decorations" "$dest/decorations"; rm -rf "$tmp"
|
|
chmod -R a+rX "$dest"
|
|
ln -sfn "$dest/decorations" /var/www/decorations.new && mv -T /var/www/decorations.new /var/www/decorations
|
|
# Keep the live version and the one before it; drop older ones.
|
|
find /var/www -maxdepth 1 -name 'decorations-*' -type d -printf '%T@ %p\n' | sort -rn \
|
|
| tail -n +3 | cut -d' ' -f2- | xargs -r rm -rf
|
|
echo "✓ decorations $ver live at /var/www/decorations ($(find "$dest/decorations" -maxdepth 1 -name '*.png' | wc -l) images)"
|
|
}
|
|
if echo "$CHANGED" | grep -q '^cinny/decorations.sha256' || [ ! -e /var/www/decorations ]; then
|
|
echo "Deploying avatar decorations..."
|
|
install_decorations || true
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^cinny/nginx.conf'; then
|
|
echo "Deploying cinny nginx site config..."
|
|
# Back up the live config, swap in the repo copy, and validate before
|
|
# reloading. If `nginx -t` fails, restore the backup and skip the reload so
|
|
# a bad config can never take the site down.
|
|
BACKUP="/etc/nginx/sites-available/cinny.bak-$(date +%Y%m%d%H%M%S)"
|
|
cp /etc/nginx/sites-available/cinny "$BACKUP"
|
|
cp "$REPO_DIR/cinny/nginx.conf" /etc/nginx/sites-available/cinny
|
|
if nginx -t; then
|
|
systemctl reload nginx
|
|
echo "✓ nginx site config deployed + reloaded"
|
|
else
|
|
echo "✗ nginx -t FAILED — restoring previous config, skipping reload"
|
|
cp "$BACKUP" /etc/nginx/sites-available/cinny
|
|
fi
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^cinny/upstream-check.sh'; then
|
|
echo "Deploying upstream-check.sh..."
|
|
cp "$REPO_DIR/cinny/upstream-check.sh" /usr/local/bin/cinny-upstream-check.sh
|
|
chmod +x /usr/local/bin/cinny-upstream-check.sh
|
|
echo "✓ upstream-check.sh deployed"
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^cinny/lotus-build.sh'; then
|
|
echo "Deploying lotus-build.sh..."
|
|
cp "$REPO_DIR/cinny/lotus-build.sh" /usr/local/bin/cinny-build.sh
|
|
chmod +x /usr/local/bin/cinny-build.sh
|
|
echo "✓ lotus-build.sh deployed"
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^cinny/lotus_deploy.sh'; then
|
|
echo "Deploying lotus_deploy.sh (webhook CI-gated web deploy)..."
|
|
# The `lotus-deploy` webhook hook executes /usr/local/bin/lotus_deploy.sh.
|
|
# Validate syntax before swapping so a broken script can never wedge deploys.
|
|
if bash -n "$REPO_DIR/cinny/lotus_deploy.sh"; then
|
|
cp "$REPO_DIR/cinny/lotus_deploy.sh" /usr/local/bin/lotus_deploy.sh
|
|
chmod +x /usr/local/bin/lotus_deploy.sh
|
|
echo "✓ lotus_deploy.sh deployed"
|
|
else
|
|
echo "✗ bash -n FAILED on lotus_deploy.sh — skipping install"
|
|
fi
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^deploy/hooks-lxc106.json'; then
|
|
echo "Deploying hooks-lxc106.json..."
|
|
cp "$REPO_DIR/deploy/hooks-lxc106.json" /etc/webhook/hooks.json
|
|
systemctl restart webhook
|
|
# webhook-lotus (:9001) serves the same hooks and is the one running THIS
|
|
# script (Gitea posts matrix-deploy there), so restarting it now would kill
|
|
# this deploy mid-run: restart it shortly after we exit instead.
|
|
systemd-run --on-active=15s --unit="webhook-lotus-reload-$(date +%s)" systemctl restart webhook-lotus
|
|
echo "✓ hooks.json deployed, webhook restarted (webhook-lotus in 15s)"
|
|
fi
|
|
|
|
if echo "$CHANGED" | grep -q '^systemd/cinny-upstream-check.cron'; then
|
|
echo "Deploying cinny-upstream-check.cron..."
|
|
cp "$REPO_DIR/systemd/cinny-upstream-check.cron" /etc/cron.d/cinny-upstream-check
|
|
chmod 644 /etc/cron.d/cinny-upstream-check
|
|
echo "✓ cron deployed"
|
|
fi
|
|
|
|
# Keep the installed copy of this script in step with the repo (the webhook
|
|
# runs /usr/local/bin/matrix-deploy.sh, not the repo file). Checked with bash -n
|
|
# first; takes effect from the next deploy.
|
|
if echo "$CHANGED" | grep -q '^deploy/lxc106-cinny.sh'; then
|
|
if bash -n "$REPO_DIR/deploy/lxc106-cinny.sh"; then
|
|
cp "$REPO_DIR/deploy/lxc106-cinny.sh" /usr/local/bin/matrix-deploy.sh
|
|
chmod +x /usr/local/bin/matrix-deploy.sh
|
|
echo "✓ matrix-deploy.sh updated"
|
|
else
|
|
echo "✗ bash -n FAILED on lxc106-cinny.sh — keeping the installed copy"
|
|
fi
|
|
fi
|
|
|
|
echo "=== $(date) === LXC106 deploy complete ==="
|