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>
219 lines
8.4 KiB
Bash
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
|