Dockerfile.PocketBase.SeaweedFS bakes SeaweedFS into the PocketBase all-in-one image as one `weed server -s3` process; Dockerfile.PocketBase.SeaweedFS.split runs master, volume, filer, S3 gateway and the admin UI as separate processes, with the homeserver's S3 identity seeded into the filer's IAM store on every boot. Media goes to the bucket, the database stays in /data/pb_data, and both layouts share /seaweed/data, so switching between them needs no migration. The entrypoint now tracks every process in one list and stops them in reverse start order, and it refuses to start an open S3 gateway or admin UI. Each image gets a build and a prod compose file, and .env.example and the README cover the new settings. Modelled on DriverVault's Docker-AIO SeaweedFS images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
7.8 KiB
Bash
203 lines
7.8 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
|
|
|
|
# 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
|