Share handed over the raw file address or the item's link: no preview, no time, nothing a person without iPX could do much with. Now, as Overcast does, an episode shares as a short link to its own page, play.ipxreader.app/e/<id>, which anyone can open: the show's art and name, the episode's title, date, length and notes, a player on the publisher's own file starting at ?t= if a time was shared, buttons to subscribe in Apple Podcasts, Overcast, Pocket Casts or Castro, and Open Graph tags for a preview where the link is pasted. ipx-play is a service of its own (crates/play), built like the directory: ipx sends it the episode with its token (POST /v1/shares) and gets the link back. The id comes from the feed's address and the guid, so the same episode is one link whoever shares it. Only episodes of feeds the directory would be sent go there, by the directory's own rule (snapshot_entries), so the two cannot disagree on what is private; ipx-play also refuses an address that looks private. Anything else, and any server without ipx-play set up, shares as before. In the page, Share on an episode opens a menu once the link is back: Share link, Share from 12:34 (the player's clock, or where you left off), Copy link. A menu because the share sheet needs a press of its own, and the request for the link used up the one that opened it. The page runs no script: the start time is the media fragment #t=, and its CSP loads nothing but its stylesheet, the art and the file. Admin, Directory, Sharing episodes sets the address and token. The browser suite starts its own ipx-play; it was not run here (no Caddy on this Mac). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.2 KiB
Bash
51 lines
2.2 KiB
Bash
#!/bin/sh
|
|
# Build and push one service's image, inside limits Tower can afford.
|
|
#
|
|
# sh scripts/build-image.sh ipx|directory|play|web|coverage
|
|
#
|
|
# On 2026-11-04 a plain `docker buildx build` of ipx (its dependencies' cache layer invalidated
|
|
# by a Cargo.toml change) rebuilt everything in release on all twelve threads with 5 GB free and
|
|
# no swap; Tower thrashed for three hours with every core pegged and had to be rebooted. So the
|
|
# build runs in a BuildKit container under a cgroup cap: past the memory cap the build is killed,
|
|
# it does not take production down with it. And it does not start without room for that cap.
|
|
set -eu
|
|
|
|
MEM=6g # the builder's memory cap (Tower has no swap to spill into)
|
|
CPUS=0-5 # six of twelve threads; cargo inside sees six and runs six jobs
|
|
NEED_KB=$((9 * 1024 * 1024)) # the cap plus 3 GB for everything else
|
|
MAX_LOAD=12
|
|
BUILDER=ipx-capped
|
|
|
|
case "${1:-}" in
|
|
ipx) FILE=Dockerfile; IMAGE=ipx ;;
|
|
directory) FILE=Dockerfile.directory; IMAGE=ipx-directory ;;
|
|
play) FILE=Dockerfile.play; IMAGE=ipx-play ;;
|
|
web) FILE=Dockerfile.web; IMAGE=ipx-web ;;
|
|
# Not a service: what scripts/coverage.sh runs the Rust tests in (#390).
|
|
coverage) FILE=Dockerfile.coverage; IMAGE=ipx-coverage ;;
|
|
*) echo "usage: $0 ipx|directory|play|web|coverage" >&2; exit 2 ;;
|
|
esac
|
|
cd "$(dirname "$0")/.."
|
|
|
|
avail=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo)
|
|
load=$(cut -d' ' -f1 /proc/loadavg)
|
|
echo "MemAvailable $((avail / 1024)) MB, load $load"
|
|
if [ "$avail" -lt "$NEED_KB" ]; then
|
|
echo "refusing: under $((NEED_KB / 1024 / 1024)) GB available. Wait, or find what is using it." >&2
|
|
exit 1
|
|
fi
|
|
if awk -v l="$load" -v m="$MAX_LOAD" 'BEGIN { exit !(l > m) }'; then
|
|
echo "refusing: load over $MAX_LOAD. Wait for it to come down." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Made once; buildx starts its container again when needed, after a reboot too.
|
|
if ! docker buildx inspect "$BUILDER" >/dev/null 2>&1; then
|
|
docker buildx create --name "$BUILDER" --driver docker-container \
|
|
--driver-opt "memory=$MEM,cpuset-cpus=$CPUS" \
|
|
--buildkitd-config scripts/buildkitd.toml >/dev/null
|
|
fi
|
|
|
|
exec docker buildx build --builder "$BUILDER" -f "$FILE" \
|
|
--tag "192.168.1.130:5000/$IMAGE:latest" --push .
|