Files
Warp/Docker-AIO/entrypoint.sh
T
tajniak81andClaude Opus 5.5 332916e9db Docker-AIO: PocketBase image with media in an external S3 bucket
Dockerfile.PocketBase.s3 is the PocketBase all-in-one image with S3
turned on: media goes to an existing bucket on WARP_PB_S3_ENDPOINT
(AWS, MinIO, or a SeaweedFS elsewhere), and the database stays in
/data/pb_data. The compose files require the endpoint and key pair,
default the bucket to warp, the region to us-east-1 and path style
to true, and add host.docker.internal so the endpoint can be on the
Docker host. Modelled on DriverVault's external-S3 compose files.

The entrypoint now waits up to 30 seconds for an external S3 endpoint
to answer and stops if it doesn't, instead of starting a homeserver
that would fail on its first upload. WARP_PB_S3_FORCE_PATH_STYLE is
left empty in .env.example so the S3 file's default applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:45:01 +02:00

219 lines
8.4 KiB
Bash

#!/bin/sh
# warp-aio: runs the homeserver, the admin panel and the web client in one
# container, with whatever the image adds: PostgreSQL, or SeaweedFS as the
# media store.
#
# If any process exits, the others are stopped and the container exits, so
# the restart policy brings the whole set back. SIGTERM stops them in the
# reverse of the order they started: the clients, the homeserver, then
# SeaweedFS and PostgreSQL underneath it.
#
# With arguments, runs them instead: `docker run warp-aio warp version`.
#
# WARP_AIO_ADMIN=false no admin panel
# WARP_AIO_WEB=false no web client
# WARP_AIO_EMBEDDED_POSTGRES=true PostgreSQL in the container, data in
# $PGDATA (/data/postgres); the default
# in warp-aio-postgresql, the only image
# that has PostgreSQL
# WARP_AIO_SEAWEEDFS=server SeaweedFS as one `weed server -s3`
# WARP_AIO_SEAWEEDFS=split SeaweedFS as master, volume, filer, S3
# gateway and admin UI, one process each;
# set by the warp-aio-pocketbase-seaweedfs
# images, the only ones with SeaweedFS
set -eu
if [ "$#" -gt 0 ]; then
exec "$@"
fi
log() { echo "warp-aio: $*" >&2; }
# "name:pid" of every process started, newest first, so shutdown walks it in
# reverse start order.
procs=""
# run NAME COMMAND...: start COMMAND in the background and track it.
run() {
name=$1
shift
"$@" &
procs="$name:$! $procs"
}
shutdown() {
trap '' TERM INT
for entry in $procs; do
name=${entry%%:*}
pid=${entry#*:}
# SIGINT is PostgreSQL's fast shutdown: it does not wait for
# clients, and everything that used it is gone by now.
if [ "$name" = postgres ]; then sig=INT; else sig=TERM; fi
kill -"$sig" "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
done
exit "${1:-0}"
}
trap 'log "stopping"; shutdown 0' TERM INT
# check: stop everything if any process has exited.
check() {
for entry in $procs; do
if ! kill -0 "${entry#*:}" 2>/dev/null; then
log "${entry%%:*} exited; stopping the rest"
shutdown 1
fi
done
}
# wait_http NAME URL: wait until URL answers, as long as nothing has died.
wait_http() {
until curl -fsS -o /dev/null "$2" 2>/dev/null; do
check
log "waiting for $1"
sleep 1
done
}
if [ "${WARP_AIO_EMBEDDED_POSTGRES:-false}" = true ]; then
if ! command -v postgres >/dev/null; then
log "WARP_AIO_EMBEDDED_POSTGRES=true, but this image has no PostgreSQL; use warp-aio-postgresql"
exit 1
fi
export PGDATA="${PGDATA:-/data/postgres}"
if [ ! -s "$PGDATA/PG_VERSION" ]; then
log "initialising PostgreSQL in $PGDATA"
# Trust is safe here: PostgreSQL listens on no TCP address, only on
# a socket inside this container.
initdb -D "$PGDATA" -U warp --auth=trust --encoding=UTF8 --locale=C >/dev/null
fi
run postgres postgres -D "$PGDATA" -c listen_addresses='' -c unix_socket_directories=/run/postgresql
until pg_isready -q -h /run/postgresql -U warp; do
check
sleep 1
done
if [ "$(psql -h /run/postgresql -U warp -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = 'warp'")" != 1 ]; then
createdb -h /run/postgresql -U warp warp
fi
fi
# SeaweedFS keeps everything under /seaweed/data, in the layout `weed server
# -dir` writes, which the split roles share as well: switching between the
# two images needs no migration. Every role advertises 127.0.0.1; the volume
# server serves file content by id with no authentication at all, so only
# the S3 gateway and the admin UI are meant to be published. The volume
# server is on 8081 because 8080 is the web client.
seaweedfs="${WARP_AIO_SEAWEEDFS:-}"
if [ -n "$seaweedfs" ]; then
if ! command -v weed >/dev/null; then
log "WARP_AIO_SEAWEEDFS=$seaweedfs, but this image has no SeaweedFS; use warp-aio-pocketbase-seaweedfs"
exit 1
fi
# One pair of credentials: the gateway's identity and what the
# homeserver signs its requests with. An empty pair would leave the
# gateway in its allow-anyone default, so refuse instead.
if [ -z "${WARP_PB_S3_ACCESS_KEY:-}" ] || [ -z "${WARP_PB_S3_SECRET:-}" ]; then
log "WARP_PB_S3_ACCESS_KEY and WARP_PB_S3_SECRET are required; refusing to start an unauthenticated S3 gateway"
exit 1
fi
bucket="${WARP_PB_S3_BUCKET:-warp}"
mkdir -p /seaweed/data
fi
case "$seaweedfs" in
"") ;;
server)
# Master, volume, filer and gateway as goroutines of one process. With
# no -s3.config, the gateway takes its single identity from
# AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, given to it alone.
run seaweedfs env AWS_ACCESS_KEY_ID="$WARP_PB_S3_ACCESS_KEY" AWS_SECRET_ACCESS_KEY="$WARP_PB_S3_SECRET" \
weed server -dir=/seaweed/data -ip=127.0.0.1 -ip.bind=0.0.0.0 \
-volume.port=8081 -volume.max=0 -master.volumeSizeLimitMB=1024 -s3 -s3.port=8333
wait_http "SeaweedFS S3 gateway" http://127.0.0.1:8333/healthz
# The homeserver never creates its bucket. Creating one that exists is
# a no-op.
echo "s3.bucket.create -name $bucket" | weed shell -master=127.0.0.1:9333 >/dev/null || true
;;
split)
# The admin UI can mint credentials for the bucket, and weed serves it
# with no authentication when the password is empty.
if [ -z "${WEED_ADMIN_PASSWORD:-}" ]; then
log "WEED_ADMIN_PASSWORD is empty; refusing to serve an unauthenticated SeaweedFS admin UI"
exit 1
fi
mkdir -p /seaweed/admin
run seaweedfs-master weed master -ip=127.0.0.1 -ip.bind=127.0.0.1 -port=9333 \
-mdir=/seaweed/data -volumeSizeLimitMB=1024 -metricsPort=9324
wait_http "SeaweedFS master" http://127.0.0.1:9333/cluster/status
run seaweedfs-volume weed volume -master=127.0.0.1:9333 -ip=127.0.0.1 -ip.bind=127.0.0.1 \
-port=8081 -dir=/seaweed/data -max=0 -metricsPort=9325
wait_http "SeaweedFS volume server" http://127.0.0.1:8081/healthz
run seaweedfs-filer weed filer -master=127.0.0.1:9333 -ip=127.0.0.1 -ip.bind=127.0.0.1 \
-port=8888 -defaultStoreDir=/seaweed/data -metricsPort=9326
wait_http "SeaweedFS filer" http://127.0.0.1:8888/healthz
# The bucket, and the homeserver's identity in the filer's IAM store,
# both re-applied on every boot; that is how a rotated secret lands.
# The store is the one the admin UI writes, so the gateway runs with no
# -config and no AWS_* variables: those are lower-priority sources that
# the first identity added in the admin UI would silently displace.
shell() { weed shell -master=127.0.0.1:9333 -filer=127.0.0.1:8888; }
printf '%s\n' \
"s3.bucket.create -name $bucket" \
"s3.configure -user warp -access_key $WARP_PB_S3_ACCESS_KEY -secret_key $WARP_PB_S3_SECRET -actions Admin -apply" |
shell >/dev/null || true
# An empty IAM store would leave the gateway open to anyone.
if ! echo "s3.configure" | shell 2>/dev/null | grep -q "$WARP_PB_S3_ACCESS_KEY"; then
log "the homeserver's S3 identity is not in the filer's IAM store; refusing to start the gateway"
shutdown 1
fi
run seaweedfs-s3 weed s3 -filer=127.0.0.1:8888 -ip.bind=0.0.0.0 -port=8333 -metricsPort=9327
run seaweedfs-admin weed admin -port=23646 -master=127.0.0.1:9333 -dataDir=/seaweed/admin -metricsPort=9328
wait_http "SeaweedFS S3 gateway" http://127.0.0.1:8333/healthz
;;
*)
log "WARP_AIO_SEAWEEDFS must be server, split or empty, not $seaweedfs"
exit 1
;;
esac
# An S3 bucket elsewhere: the homeserver starts without checking it, and
# would only fail on the first upload. Wait for the endpoint to answer at
# all (any HTTP status will do: without credentials it may well refuse).
if [ "${WARP_PB_S3_ENABLED:-false}" = true ] && [ -z "$seaweedfs" ] && [ -n "${WARP_PB_S3_ENDPOINT:-}" ]; then
tries=0
until curl -s -o /dev/null --max-time 5 "$WARP_PB_S3_ENDPOINT"; do
tries=$((tries + 1))
if [ "$tries" -ge 30 ]; then
log "S3 endpoint $WARP_PB_S3_ENDPOINT does not answer; stopping"
shutdown 1
fi
log "waiting for S3 endpoint $WARP_PB_S3_ENDPOINT"
sleep 1
done
fi
# The signing key is created on first start and kept in the volume: losing
# it means other servers no longer recognise this one.
if [ ! -f "$WARP_PRIVATE_KEY" ]; then
warp generate-keys --output "$WARP_PRIVATE_KEY"
fi
run warp warp serve
if [ "${WARP_AIO_ADMIN:-true}" = true ]; then
run warp-admin warp-admin
fi
if [ "${WARP_AIO_WEB:-true}" = true ]; then
run warp-web warp-web
fi
# Busybox sh has no `wait -n`, so check on the processes every second. The
# sleep runs in the background so a signal is handled at once.
while :; do
check
sleep 1 &
wait $! || true
done