Start av-club: what it is, and a verified installer
Media processing pulled out of the website that first needed it. ffmpeg, ImageMagick, libvips and exiftool are a real installation rather than an npm install, and encoder settings get revisited as browser support moves — neither of which belongs on the same clock as page templates and database schemas. No processing code yet. What is here is the tooling requirement, an installer, and docs/PIPELINE.md writing down what has NOT been decided — how a consumer talks to this, whether processing blocks an upload, where output goes — so those get chosen deliberately instead of by whatever gets written first. verify.sh checks by running: it encodes a video and probes the result against what ffmpeg was told to make, loads sharp under Bun specifically, and asserts a re-encode still strips GPS. That last one guards a real leak — if a future sharp changes its default, this fails a check rather than quietly publishing the coordinates of a client's living room. Confirmed non-vacuous by hiding ffmpeg and watching it exit 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bring a machine to the state av-club needs, then prove it by running verify.sh.
|
||||
#
|
||||
# Idempotent: apt-get install on an already-installed package is a no-op, so re-running this is safe
|
||||
# and is the intended way to check a machine is still correct.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Everything the pipeline shells out to. Kept as one list so the README, the docs and this script
|
||||
# cannot disagree about what an install consists of.
|
||||
PACKAGES=(
|
||||
ffmpeg # ffmpeg + ffprobe: transcode, poster frames, probe duration/codec/size
|
||||
libimage-exiftool-perl # exiftool: read and strip metadata — client photos carry GPS
|
||||
imagemagick # convert + identify: odd formats, and inspecting a file by hand
|
||||
libvips-tools # vipsthumbnail: the CLI side of the library sharp uses
|
||||
webp # cwebp + dwebp
|
||||
libavif-bin # avifenc + avifdec
|
||||
)
|
||||
|
||||
say() { printf '\n\033[1m%s\033[0m\n' "$1"; }
|
||||
warn() { printf '\033[33m%s\033[0m\n' "$1"; }
|
||||
die() { printf '\033[31m%s\033[0m\n' "$1" >&2; exit 1; }
|
||||
|
||||
say "Checking the machine"
|
||||
|
||||
[ -f /etc/os-release ] || die "No /etc/os-release — cannot tell what this machine is."
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
|
||||
echo " ${PRETTY_NAME:-unknown}"
|
||||
|
||||
# Say so rather than guessing. The package names below are Debian/Ubuntu ones and would simply not
|
||||
# exist elsewhere; failing here with a sentence beats failing later with "E: Unable to locate package".
|
||||
if [ "${ID:-}" != "ubuntu" ] && [ "${ID_LIKE:-}" != *debian* ]; then
|
||||
die "This installer only knows apt-based systems. Install these by hand: ${PACKAGES[*]}"
|
||||
fi
|
||||
if [ "${VERSION_ID:-}" != "24.04" ]; then
|
||||
warn " Verified on Ubuntu 24.04; this is ${VERSION_ID:-unknown}. Continuing — check the output."
|
||||
fi
|
||||
|
||||
command -v bun >/dev/null 2>&1 || die "bun is not on PATH. See https://bun.sh"
|
||||
echo " bun $(bun --version)"
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
command -v sudo >/dev/null 2>&1 || die "Not root and no sudo available."
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
say "Installing system packages"
|
||||
echo " ${PACKAGES[*]}"
|
||||
echo
|
||||
|
||||
# noninteractive so a package with a debconf prompt cannot hang an unattended run.
|
||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "${PACKAGES[@]}"
|
||||
|
||||
say "Installing bun dependencies"
|
||||
(cd "$REPO_ROOT" && bun install)
|
||||
|
||||
say "Verifying"
|
||||
"$REPO_ROOT/scripts/verify.sh"
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check the media tooling by RUNNING it, not by asking the package manager whether it is present.
|
||||
#
|
||||
# A package can be installed and still not work: sharp is a native module that can resolve to a
|
||||
# broken wasm build, and a codec can be missing from an ffmpeg that reports a version happily. Every
|
||||
# check here does real work and compares the result against what was asked for.
|
||||
#
|
||||
# Exits non-zero if anything fails, so it is usable in CI or as the last step of install.sh.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
FAILED=0
|
||||
|
||||
pass() { printf ' \033[32mok\033[0m %s\n' "$1"; }
|
||||
fail() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; FAILED=1; }
|
||||
note() { printf ' \033[33m--\033[0m %s\n' "$1"; }
|
||||
|
||||
echo
|
||||
echo "Binaries on PATH"
|
||||
|
||||
# `magick` is deliberately absent from this list: Ubuntu 24.04 ships ImageMagick 6, which provides
|
||||
# convert/identify and no `magick`. See docs/TOOLING.md.
|
||||
for cmd in ffmpeg ffprobe convert identify exiftool vipsthumbnail cwebp dwebp avifenc avifdec; do
|
||||
if command -v "$cmd" >/dev/null 2>&1; then
|
||||
pass "$cmd"
|
||||
else
|
||||
fail "$cmd is not on PATH"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "ffmpeg encodes and ffprobe reads back what was asked for"
|
||||
|
||||
if ffmpeg -hide_banner -loglevel error \
|
||||
-f lavfi -i testsrc=duration=3:size=1280x720:rate=30 \
|
||||
-c:v libx264 -pix_fmt yuv420p "$WORK/test.mp4" 2>"$WORK/ffmpeg.err"; then
|
||||
|
||||
probe=$(ffprobe -v error \
|
||||
-show_entries stream=codec_name,width,height \
|
||||
-show_entries format=duration \
|
||||
-of default=noprint_wrappers=1 "$WORK/test.mp4" 2>/dev/null)
|
||||
|
||||
# Compared against what ffmpeg was TOLD to produce. A version string proves nothing; this proves
|
||||
# the h264 encoder is actually present and the container is readable.
|
||||
for expect in "codec_name=h264" "width=1280" "height=720" "duration=3.000000"; do
|
||||
if grep -qx "$expect" <<<"$probe"; then pass "$expect"; else fail "expected $expect"; fi
|
||||
done
|
||||
else
|
||||
fail "ffmpeg could not encode h264: $(head -1 "$WORK/ffmpeg.err")"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "ffmpeg extracts a poster frame"
|
||||
|
||||
if ffmpeg -hide_banner -loglevel error -i "$WORK/test.mp4" -ss 1 -frames:v 1 "$WORK/poster.jpg" 2>/dev/null \
|
||||
&& [ -s "$WORK/poster.jpg" ]; then
|
||||
pass "poster.jpg $(identify -format '%wx%h' "$WORK/poster.jpg" 2>/dev/null)"
|
||||
else
|
||||
fail "could not extract a poster frame"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "sharp runs under Bun"
|
||||
|
||||
# Run with the REPO as cwd, deliberately. Resolved from anywhere else, Bun finds sharp in its global
|
||||
# install cache and lands on the wasm32 build, which is missing @emnapi/runtime and dies with
|
||||
# "sharp.libvipsVersion is not a function" — a broken-looking failure that is not a broken install.
|
||||
if [ -d "$REPO_ROOT/node_modules/sharp" ]; then
|
||||
if out=$(cd "$REPO_ROOT" && bun -e '
|
||||
import sharp from "sharp";
|
||||
const px = 64;
|
||||
const src = await sharp({ create: { width: 300, height: 200, channels: 3, background: "#888" } })
|
||||
.jpeg().toBuffer();
|
||||
for (const fmt of ["webp", "avif", "jpeg"]) {
|
||||
const buf = await sharp(src).resize(px)[fmt]().toBuffer();
|
||||
const m = await sharp(buf).metadata();
|
||||
if (m.width !== px) throw new Error(`${fmt}: expected ${px}px, got ${m.width}`);
|
||||
}
|
||||
console.log(`${sharp.versions.sharp} libvips ${sharp.versions.vips}`);
|
||||
' 2>&1); then
|
||||
pass "sharp $out"
|
||||
else
|
||||
fail "sharp failed under Bun: $(tail -3 <<<"$out" | tr '\n' ' ')"
|
||||
fi
|
||||
else
|
||||
note "sharp not installed — run 'bun install' in $REPO_ROOT"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "exiftool reads metadata, and a sharp re-encode strips it"
|
||||
|
||||
if [ -s "$WORK/poster.jpg" ]; then
|
||||
exiftool -q -overwrite_original \
|
||||
-GPSLatitude=38.7223 -GPSLatitudeRef=N \
|
||||
-GPSLongitude=-9.1393 -GPSLongitudeRef=W \
|
||||
-Make="Apple" -Model="iPhone 15 Pro" "$WORK/poster.jpg" 2>/dev/null
|
||||
|
||||
if exiftool -s3 -GPSLatitude "$WORK/poster.jpg" 2>/dev/null | grep -q .; then
|
||||
pass "exiftool wrote and read back GPS tags"
|
||||
|
||||
if [ -d "$REPO_ROOT/node_modules/sharp" ] \
|
||||
&& (cd "$REPO_ROOT" && bun -e "
|
||||
import sharp from 'sharp';
|
||||
await sharp('$WORK/poster.jpg').resize(300).jpeg().toFile('$WORK/stripped.jpg');
|
||||
" 2>/dev/null); then
|
||||
|
||||
# The point of the check: sharp drops EXIF unless .withMetadata() is called. If that ever
|
||||
# stops being true, client GPS coordinates start getting published and nothing else says so.
|
||||
leaked=$(exiftool -s3 -GPSLatitude -GPSLongitude -Make -Model "$WORK/stripped.jpg" 2>/dev/null | grep -c .)
|
||||
if [ "$leaked" -eq 0 ]; then
|
||||
pass "GPS, Make and Model gone after re-encode"
|
||||
else
|
||||
fail "$leaked identifying tag(s) SURVIVED the re-encode"
|
||||
fi
|
||||
else
|
||||
note "skipped strip check — sharp unavailable"
|
||||
fi
|
||||
else
|
||||
fail "exiftool could not write test tags"
|
||||
fi
|
||||
else
|
||||
note "skipped — no poster frame to test with"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Command-line encoders"
|
||||
|
||||
cwebp -quiet "$WORK/poster.jpg" -o "$WORK/t.webp" 2>/dev/null && [ -s "$WORK/t.webp" ] \
|
||||
&& pass "cwebp" || fail "cwebp"
|
||||
avifenc -s 10 "$WORK/poster.jpg" "$WORK/t.avif" >/dev/null 2>&1 && [ -s "$WORK/t.avif" ] \
|
||||
&& pass "avifenc" || fail "avifenc"
|
||||
vipsthumbnail "$WORK/poster.jpg" -s 100 -o "$WORK/thumb-%s.jpg" >/dev/null 2>&1 \
|
||||
&& [ -s "$WORK/thumb-poster.jpg" ] && pass "vipsthumbnail" || fail "vipsthumbnail"
|
||||
|
||||
echo
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
printf '\033[32mAll checks passed.\033[0m\n\n'
|
||||
else
|
||||
printf '\033[31mSomething failed above.\033[0m See docs/TOOLING.md.\n\n'
|
||||
fi
|
||||
exit "$FAILED"
|
||||
Reference in New Issue
Block a user